33345-extreme-dynamic-route

Back

type Merge<T> = {
  [P in keyof T]: T[P];
};

type DynamicRoute<
  T extends string,
  Check extends boolean = false,
  Result = {}
> = T extends `${"/" | ""}[[...${infer P}]]${infer Rest}`
  ? Check extends true
    ? never
    : DynamicRoute<Rest, true, Merge<Result & { [K in P]?: string[] }>>
  : T extends `${"/" | ""}[...${infer P}]${infer Rest}`
  ? P extends ""
    ? DynamicRoute<Rest, Check, Merge<Result & { "...": string }>>
    : Check extends true
    ? never
    : DynamicRoute<
        Rest,
        true,
        Merge<Result & { [K in `${P}`]: P extends "" ? string : string[] }>
      >
  : T extends `${"/" | ""}[${infer P1}${infer P2}]${infer Rest}`
  ? DynamicRoute<Rest, Check, Merge<Result & { [K in `${P1}${P2}`]: string }>>
  : T extends `${"/" | ""}${infer _P1}${infer _P2}/${infer Rest}`
  ? DynamicRoute<Rest, false, Result>
  : Result;

Solution by yukicountry #34556

type SlashSplit<T extends string> =
  T extends `` ? [] :
  T extends `/${infer TSingle}` ? [...SlashSplit<TSingle>] :
  T extends `${infer TDouble1}/${infer TDouble2}` ? [TDouble1, ...SlashSplit<TDouble2>] :
  [T];

type PartialRoute<S extends string> = S extends `[[...]]` ? { [K in `...`]?: string } :
                                      S extends `[...]` ? { [K in `...`]: string } :
                                      S extends `[[...${infer OptionalArrayName}]]` ? { [K in OptionalArrayName]?: string[] } :
                                      S extends `[[${infer OptionalStringName}]]` ? { [K in OptionalStringName]?: string } :
                                      S extends `[...${infer ArrayName}]` ? { [K in ArrayName]: string[] } :
                                      S extends `[${infer StringName}]` ? { [K in StringName as StringName extends "" ? never : K]: string } :
                                      {}

type ObjectValue<T> = T[keyof T];

type PathObject<S extends string[], Obj = {}, AddedArrayAndNoString = false> =
  S extends [infer FirstElem extends string, ...infer Rest extends string[]] ?
    string[] extends ObjectValue<PartialRoute<FirstElem>> ?
      // if we have added an array without a string in between then it is invalid
      AddedArrayAndNoString extends true ?
        never :
        PathObject<Rest, PartialRoute<FirstElem> & Obj, true> :
      // no dynamic params case
      ObjectValue<PartialRoute<FirstElem>> extends never ?
        PathObject<Rest, PartialRoute<FirstElem> & Obj, false> :
        PathObject<Rest, PartialRoute<FirstElem> & Obj, AddedArrayAndNoString> :
    Obj

// this type is necessary because apparently { foo: string } & { bar: string }
// is not equivalent to { foo: string, bar: string }, learnt something new
type Mix<Obj> = {
  [K in keyof Obj]: Obj[K]
}

type DynamicRoute<T extends string> = Mix<PathObject<SlashSplit<T>>>;

This is my first of these puzzles, any feedback would be much appreciated!

Solution by M-Kusumgar #33631

type IsArr<T> = T extends `${string}...${string}` ? true : false
type Extract<T> = T extends `[...${infer F}]` | `...${infer F}` ? F : T

type DynamicRoute<T extends string, _Res extends Record<string, string | string[]> = {}> = T extends `${infer A}[...]${infer B}`
  ? DynamicRoute<`${A}${B}`, _Res & { '...': string }>
  : T extends `${string}]/[...${string}`
    ? never
      : T extends `${infer A}[[${ infer F }]]${ infer B }`
      ? DynamicRoute<`${A}${B}`, _Res & ( Extract<F> extends '' ? {} : { [K in Extract<F>]?: IsArr<F> extends true ? string[] : string } ) >
      : T extends `${infer A}[${ infer F }]${ infer B }`
        ? DynamicRoute<`${A}${B}`, _Res & ( Extract<F> extends '' ? {} : { [K in Extract<F>]: IsArr<F> extends true ? string[] : string } ) >
    : { [K in keyof _Res]: _Res[K] }

Solution by lvjiaxuan #33614

// `Guard` prevents consecutive [[...OptCatchAll]] or [...CatchAll] segments;
// returns never if found, even with [Param] in between.
type DynamicRoute<S extends string, R = {}, Guard extends {} = {}>
  = EnsureSlash<S> extends `${infer Segment}/${infer Rest}`
    ? Segment extends `[[...${infer OptCatchAll extends NonEmpty}]]`
      ? DynamicRoute<Rest, AddOpt<R, OptCatchAll, string[]>, never> & Guard
    : Segment extends `[...${infer CatchAll extends NonEmpty}]`
      ? DynamicRoute<Rest, AddReq<R, CatchAll, string[]>, never> & Guard
    : Segment extends `[${infer Param extends NonEmpty}]`
      ? DynamicRoute<Rest, AddReq<R, Param, string>, Guard>
    // If not [Param], reset Guard (using & {} has no impact, but & never will cause return to never)
    : DynamicRoute<Rest, R, {}>
  // Merge all collected parameters into a single object
  : {[K in keyof R]: R[K]}

type EnsureSlash<S extends string> = S extends `${any}/${any}` ? S : S extends NonEmpty ? `${S}/` : ''

type AddOpt<R, Key extends PropertyKey, ValueType> = R & {[K in Key]?: ValueType}

type AddReq<R, Key extends PropertyKey, ValueType> = R & {[K in Key]: ValueType}

type NonEmpty = `${any}${any}`

Playground

Solution by teamchong #33350