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}`
Solution by teamchong #33350