You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

81 lines
3.1 KiB

// ============ An example using dependent/constructed types to build route handlers ============
// This is a more advanced example that prevents a hypothetical web route definition library
// from accepting handler functions whose parameters do not match the types of the provided parameters.
// This is a simplification of real-world routing implementation I built in @extollo/lib:
// https://code.garrettmills.dev/extollo/lib/src/branch/master/src/http/routing/Route.ts
// https://code.garrettmills.dev/extollo/lib/src/branch/master/src/util/support/types.ts
// First, we'll define some helper types for constructing/destructing variadic tuple types:
/** Takes a tuple and a type and produces a new tuple suffixed w/ the type. */
type AppendTypeArray<Arr extends unknown[], Item> = [...Arr, Item]
/** Converts a tuple of types and formats it as a function signature. */
type TypeArraySignature<Arr extends unknown[], Return> = (...params: Arr) => Return
type ExampleTypeArray = AppendTypeArray<['test', 3.14], true> // => ['test', 3.14, true]
type ExampleTypeSignature = TypeArraySignature<ExampleTypeArray, void> // => ('test', 3.14, true) => void
// Now, some helper types used by the Route class:
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface Request {} // Stub type for the HTTP request
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface HandledRoute {} // Prevents adding parameters to handled routes
/** Type of functions that provide parameters to the request handler. */
type ParameterProvider<T> = (request: Request) => T
/**
* Stub route definition implementation. The type of the handler function
* accepted by `handleWith(...)` depends on the parameter middleware added
* via `withParameter(...)`.
*/
export class Route<HandlerParams extends unknown[] = []> {
/** Start a net GET route. */
public static get(endpoint: string): Route<[]> {
return new Route(endpoint)
}
constructor(
public readonly endpoint: string,
) {}
/** The collected parameter middleware. */
protected parameters: ParameterProvider<unknown>[] = []
/** Register a new middleware parameter. */
public withParameter<T>(provider: ParameterProvider<T>): Route<AppendTypeArray<HandlerParams, T>> {
this.parameters.push(provider)
return this as unknown as Route<AppendTypeArray<HandlerParams, T>>
}
/** Register an appropriately-typed handler for this route. */
public handleWith(handler: TypeArraySignature<HandlerParams, unknown>): HandledRoute {
return this
}
}
Route.get('/no-param')
.handleWith(() => 'hello!')
Route.get('/one-param')
.withParameter(() => 3.14)
.handleWith((pi: number) => `Pi is ${pi}!`)
Route.get('/two-params')
.withParameter(() => 'Hello, ')
.withParameter(() => 'John Doe')
.handleWith((greeting: string, name: string) => `${greeting}${name}`)
/* Route.get('/invalid-params')
.withParameter(() => 3.14)
.handleWith((pi: number, greeting: string) => `${pi}${greeting}`)*/
/* Route.get('/invalid-method-call')
.handleWith(() => 'Hello!')
.withParameter(() => 3.14)*/