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 {} = {}>
= S extends `${infer Segment}/${infer Rest}`
? Segment extends `[[...${infer OptCatchAll extends `${any}${any}`}]]`
? DynamicRoute<Rest, R & Partial<Record<OptCatchAll, string[]>>, never> & Guard
: Segment extends `[...${infer CatchAll extends `${any}${any}`}]`
? DynamicRoute<Rest, R & Record<CatchAll, string[]>, never> & Guard
: Segment extends `[${infer Param extends `${any}${any}`}]`
? DynamicRoute<Rest, R & Record<Param, string>, Guard>
// If not [Param], reset Guard (using & {} has no impact, but & never will cause return to never)
: DynamicRoute<Rest, R, {}>
: S extends '' ? {[P in keyof R]: R[P]} : DynamicRoute<`${S}/`, R, Guard>
type DynamicRoute<S extends string, R = {}>
= S extends `${infer Segment}/${infer Rest}`
? Segment extends `[[...${infer OptCatchAll extends `${any}${any}`}]]` ? StaticRoute<Rest, R & Partial<Record<OptCatchAll, string[]>>>
: Segment extends `[...${infer CatchAll extends `${any}${any}`}]` ? StaticRoute<Rest, R & Record<CatchAll, string[]>>
: Segment extends `[${infer Param extends `${any}${any}`}]` ? DynamicRoute<Rest, R & Record<Param, string>>
: DynamicRoute<Rest, R>
: S extends '' ? {[P in keyof R]: R[P]} : DynamicRoute<`${S}/`, R>
type StaticRoute<S extends string, R = {}>
= S extends `${infer Segment}/${infer Rest}`
? Segment extends `[[...${any}${any}]]` | `[...${any}${any}]` ? never
: Segment extends `[${infer Param extends `${any}${any}`}]` ? StaticRoute<Rest, Omit<R & Record<Param, string>, never>>
: DynamicRoute<Rest, R>
: S extends '' ? {[P in keyof R]: R[P]} : StaticRoute<`${S}/`, R>
Solution by teamchong #33350