I am currently developing type guards for an API framework and I aim to link the path parameters (a string) with a validation object.
My Desired Outcome:
The structure of my validation object is as follows:
const params = {
firstArg: someValidationFunction,
secondArg: someValidationFunction,
}
Here are the expected outcomes for the following strings:
-> OK (both arguments are present in the string with correct names)/api/some/path/{firstArg}/{secondArg}
-> OK (arguments' order does not affect validation)/api/some/path/{secondArg}/{firstArg}
/api/some/path/{someOtherArg}
-> Not OK (both arguments missing and unexpected argument found)
-> Not OK (unexpected argument found)/api/some/path/{firstArg}/{secondArg}/{someOtherArg}
-> Not OK (second argument missing as it lacks curly braces)/api/some/path/{firstArg}/secondArg
Progress So Far:
Referring to this blog post, I have implemented this type helper:
type ExtractPathParams<Path> = Path extends `${infer Segment}/${infer Rest}`
? ExtractParam<Segment, ExtractPathParams<Rest>>
: ExtractParam<Path, {}>;
type ExtractParam<Path, NextPart> = Path extends `{${infer Param}}`
? Record<Param, any> & NextPart
: NextPart;
This helper function retrieves the arguments from the path, and I have been attempting to compare the keys from that with the keys from the validation object. Inspired by this stackoverflow post:
function assert<T extends never>() {}
type Equal<A, B> = Exclude<A, B> | Exclude<B, A>;
type ValidationKeys = keyof params;
const one = '/api/some/path/{firstArg}/{secondArg}';
const two = '/api/some/path/{secondArg}/{firstArg}';
const three = '/api/some/path/{someOtherArg}';
const four = '/api/some/path/{firstArg}/{secondArg}/{someOtherArg}';
const five = '/api/some/path/{firstArg}/secondArg';
assert<Equal<keyof ExtractPathParams<typeof one>, ValidationKeys>>();
assert<Equal<keyof ExtractPathParams<typeof two>, ValidationKeys>>();
// @ts-expect-error
assert<Equal<keyof ExtractPathParams<typeof three>, ValidationKeys>>();
// @ts-expect-error
assert<Equal<keyof ExtractPathParams<typeof four>, ValidationKeys>>();
// @ts-expect-error
assert<Equal<keyof ExtractPathParams<typeof five>, ValidationKeys>>();
While this approach works, I would prefer to receive a type error indicating which keys are missing in the path based on the keys appearing in the ValidationKeys
object instead of stating that string
is not assignable to never
.
Is such an outcome achievable?