-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add type inference for query parameters (#33)
- Loading branch information
1 parent
e3d43d6
commit 1f3958d
Showing
10 changed files
with
391 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
--- | ||
"openapi-msw": minor | ||
--- | ||
|
||
Added `query` helper to resolver-info argument. It provides a type-safe wrapper around `URLSearchParams` for reading search parameters. As usual, the information about available parameters is inferred from your OpenAPI spec. | ||
|
||
```typescript | ||
/* | ||
Imagine this endpoint specification for the following example: | ||
/query-example: | ||
get: | ||
summary: Query Example | ||
operationId: getQueryExample | ||
parameters: | ||
- name: filter | ||
in: query | ||
required: true | ||
schema: | ||
type: string | ||
- name: page | ||
in: query | ||
schema: | ||
type: number | ||
- name: sort | ||
in: query | ||
required: false | ||
schema: | ||
type: string | ||
enum: ["asc", "desc"] | ||
- name: sortBy | ||
in: query | ||
schema: | ||
type: array | ||
items: | ||
type: string | ||
*/ | ||
|
||
const handler = http.get("/query-example", ({ query }) => { | ||
const filter = query.get("filter"); // Typed as string | ||
const page = query.get("page"); // Typed as string | null since it is not required | ||
const sort = query.get("sort"); // Typed as "asc" | "desc" | null | ||
const sortBy = query.getAll("sortBy"); // Typed as string[] | ||
|
||
// Supported methods from URLSearchParams: get(), getAll(), has(), size | ||
if (query.has("sort", "asc")) { | ||
/* ... */ | ||
} | ||
|
||
return HttpResponse.json({ | ||
/* ... */ | ||
}); | ||
}); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import type { OptionalKeys } from "./type-utils.js"; | ||
|
||
/** Return values for getting the first value of a query param. */ | ||
type ParamValuesGet<Params extends object> = { | ||
[Name in keyof Params]-?: Name extends OptionalKeys<Params> | ||
? Params[Name] | null | ||
: Params[Name]; | ||
}; | ||
|
||
/** Return values for getting all values of a query param. */ | ||
type ParamValuesGetAll<Params extends object> = { | ||
[Name in keyof Params]-?: Required<Params>[Name][]; | ||
}; | ||
|
||
/** | ||
* Wrapper around the search params of a request that offers methods for | ||
* querying search params with enhanced type-safety from OpenAPI-TS. | ||
*/ | ||
export class QueryParams<Params extends object> { | ||
#searchParams: URLSearchParams; | ||
constructor(request: Request) { | ||
this.#searchParams = new URL(request.url).searchParams; | ||
} | ||
|
||
/** | ||
* Wraps around {@link URLSearchParams.size}. | ||
* | ||
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/size) | ||
*/ | ||
get size(): number { | ||
return this.#searchParams.size; | ||
} | ||
|
||
/** | ||
* Wraps around {@link URLSearchParams.get} with type inference from the | ||
* provided OpenAPI-TS `paths` definition. | ||
* | ||
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/get) | ||
*/ | ||
get<Name extends keyof Params>(name: Name): ParamValuesGet<Params>[Name] { | ||
const value = this.#searchParams.get(name as string); | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
return value as any; | ||
} | ||
|
||
/** | ||
* Wraps around {@link URLSearchParams.getAll} with type inference from the | ||
* provided OpenAPI-TS `paths` definition. | ||
* | ||
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/getAll) | ||
*/ | ||
getAll<Name extends keyof Params>( | ||
name: Name, | ||
): ParamValuesGetAll<Params>[Name] { | ||
const values = this.#searchParams.getAll(name as string); | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
return values as any; | ||
} | ||
|
||
/** | ||
* Wraps around {@link URLSearchParams.has} with type inference from the | ||
* provided OpenAPI-TS `paths` definition. | ||
* | ||
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/has) | ||
*/ | ||
has<Name extends keyof Params>(name: Name, value?: Params[Name]): boolean { | ||
return this.#searchParams.has(name as string, value as string | undefined); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,22 @@ | ||
/** Converts a type to string while preserving string literal types. */ | ||
export type Stringify<Value> = Value extends string ? Value : string; | ||
/** | ||
* Converts a type to string while preserving string literal types. | ||
* {@link Array}s are unboxed to their stringified value. | ||
*/ | ||
export type Stringify<Value> = Value extends (infer Type)[] | ||
? Type extends string | ||
? Type | ||
: string | ||
: Value extends string | ||
? Value | ||
: string; | ||
|
||
/** Converts a object values to their {@link Stringify} value. */ | ||
export type ConvertToStringified<Params> = { | ||
[Name in keyof Params]: Stringify<Required<Params>[Name]>; | ||
}; | ||
|
||
/** Returns a union of all property keys that are optional in the given object. */ | ||
export type OptionalKeys<O extends object> = { | ||
// eslint-disable-next-line @typescript-eslint/ban-types | ||
[K in keyof O]-?: {} extends Pick<O, K> ? K : never; | ||
}[keyof O]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
openapi: 3.0.2 | ||
info: | ||
title: Options API | ||
version: 1.0.0 | ||
servers: | ||
- url: http://localhost:3000 | ||
paths: | ||
/single-query: | ||
get: | ||
summary: Single Query Params | ||
operationId: getSingleQuery | ||
parameters: | ||
- name: query | ||
in: query | ||
required: true | ||
schema: | ||
type: string | ||
- name: page | ||
in: query | ||
schema: | ||
type: number | ||
- name: sort | ||
in: query | ||
required: false | ||
schema: | ||
type: string | ||
enum: ["asc", "desc"] | ||
responses: | ||
200: | ||
description: Success | ||
content: | ||
application/json: | ||
schema: | ||
$ref: "#/components/schemas/Resource" | ||
/multi-query: | ||
get: | ||
summary: Multi Query Params | ||
operationId: getMultiQuery | ||
parameters: | ||
- name: id | ||
in: query | ||
schema: | ||
type: array | ||
items: | ||
type: number | ||
- name: sortBy | ||
in: query | ||
schema: | ||
type: "array" | ||
items: | ||
type: string | ||
enum: ["asc", "desc"] | ||
responses: | ||
200: | ||
description: Success | ||
content: | ||
application/json: | ||
schema: | ||
$ref: "#/components/schemas/Resource" | ||
components: | ||
schemas: | ||
Resource: | ||
type: object | ||
required: | ||
- id | ||
properties: | ||
id: | ||
type: string |
Oops, something went wrong.