Skip to content

Instantly share code, notes, and snippets.

@menduz
Last active January 5, 2021 16:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save menduz/093be07052bd959f1945ab8b58c401af to your computer and use it in GitHub Desktop.
Save menduz/093be07052bd959f1945ab8b58c401af to your computer and use it in GitHub Desktop.
url-parser-types.ts
/**
* Creates object types compliant with https://github.com/pillarjs/path-to-regexp
*
* ParseUrlParams<"/users/:user_id/:test+"> = { user_id: string, test: string | string[] }
*
* @public
*/
export type ParseUrlParams<State extends string, Memo extends Record<string, any> = {}> = string extends State
? ParseUrlParams.ParserError<"ParseUrlParams got generic string type">
: State extends `${infer _}:${infer Rest}`
? ParseUrlParams.AddUrlSection<Rest, Memo>
: Memo
export namespace ParseUrlParams {
export type ParserError<T extends string> = { error: true } & T
export type AddUrlSection<State extends string, Memo extends Record<string, any> = {}> = string extends State
? ParserError<"AddUrlSection got generic string type">
: CleanKey<State> extends `${infer Key}/`
? AddKeyValue<Memo, Key, string>
: CleanKey<State> extends `${infer Key}*${infer Rest}`
? ParseUrlParams<Rest, AddOptionalKeyValue<Memo, Key, string | string[]>>
: CleanKey<State> extends `${infer Key}/${infer Rest}`
? ParseUrlParams<Rest, AddKeyValue<Memo, Key, string>>
: CleanKey<State> extends `${infer Key}+${infer Rest}`
? ParseUrlParams<Rest, AddKeyValue<Memo, Key, string | string[]>>
: CleanKey<State> extends `${infer Key}?${infer Rest}`
? ParseUrlParams<Rest, AddOptionalKeyValue<Memo, Key, string>>
: CleanKey<State> extends `${infer Key}.${infer Rest}`
? ParseUrlParams<Rest, AddKeyValue<Memo, Key, string>>
: CleanKey<State> extends `${infer Key}-${infer Rest}`
? ParseUrlParams<Rest, AddKeyValue<Memo, Key, string>>
: CleanKey<State> extends `${infer Key}`
? AddKeyValue<Memo, Key, string>
: ParseUrlParams<`AddUrlSection returned unexpected value for: ${State}`>
// remove matcher groups
export type CleanKey<State extends string> = string extends State
? ParserError<"CleanKey got generic string type">
: State extends `${infer Key}(${infer _})${infer Rest}`
? `${Key}${Rest}`
: State
export type AddKeyValue<Memo extends Record<string, any>, Key extends string, Value extends any> = Memo &
{ [K in Key]: Value }
export type AddOptionalKeyValue<Memo extends Record<string, any>, Key extends string, Value extends any> = Memo &
{ [K in Key]?: Value }
}
// ------------ tests ----------------
function assert<T extends string = string>(r: ParseUrlParams<T>): asserts r is ParseUrlParams<T> {}
assert<"/:asd/b">({ asd: '' })
assert<"/xxx/:asd/bbb:dsa">({ asd: "", dsa: "" })
assert<"/xxx/:asd/bbb/:dsa">({ asd: "", dsa: "" })
assert<"/xxx/:asd/bbb/:dsa">({ asd: "", dsa: "" })
assert<"/:test*-bar">({ test: [] })
assert<"/:test*-bar">({})
assert<"/:test*-bar">({ test: "asd" })
assert<"/:test*-bar">({ test: ["asd"] })
assert<"/:test+-bar">({ test: [""] })
assert<"/:test*">({ test: [] })
assert<"/:test+">({ test: "" })
assert<"/:test+">({ test: [""] })
assert<"/:test?-bar">({ test: "" })
assert<"/:test?">({ test: "" })
assert<"/:test(\\d+)">({ test: "" })
assert<"/:test(\\d+)+">({ test: "" })
assert<"/:test(\\d+)+">({ test: [""] })
assert<"/route.:ext(json|xml)?">({})
assert<"/route.:ext(json|xml)?">({ ext: "" })
assert<"/route.:ext(json|xml)">({ ext: "" })
assert<"/route.:ext(json|xml)+">({ ext: "" })
assert<"/route.:ext(json|xml)+">({ ext: [""] })
assert<"/route.:ext([a-z]+)*/:asd">({ ext: [""], asd: "" })
assert<"/route.:ext([a-z]+)*/:asd">({ asd: "" })
assert<"/route.:ext([a-z]+)*">({ ext: [""] })
assert<"/route.:ext([a-z]+)/:asd">({ ext: "", asd: "" })
assert<"/:test(.*)">({ test: "" })
assert<"/route\\(\\\\(\\d+\\\\)\\)">({})
assert<"/{apple-}?icon-:res(\\d+).png">({ res: "" })
assert<"/:foo/:bar">({ foo: "", bar: "" })
assert<"/users/:user_id/:asd+">({ user_id: "", asd: [] })
function handleRequest<T extends string>(
url: T,
handler: (req: { params: ParseUrlParams<T> }) => void
) {
// noop
}
handleRequest('/users/:user_id/:asd+', (req) => {
req.params.asd
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment