Skip to content

Commit

Permalink
Path and query parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
epiphone committed Dec 9, 2019
1 parent 1086675 commit 74a61ee
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 13 deletions.
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,6 @@ const router = express.Router() as OpenAPIRouter<MySchema> & express.Router
You can also select a subset of `express.Router` with [`Pick`/`Omit`](https://www.typescriptlang.org/docs/handbook/utility-types.html#picktk) when allowing additional methods only for a specific HTTP method, for example.

## TODO
- path parameters
- query parameters
- response and request headers
- async handlers
- API client types, axios?
Expand Down
59 changes: 51 additions & 8 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as express from 'express-serve-static-core' // tslint:disable-line:no-implicit-dependencies
import { Schema as JSONSchema } from 'json-schema-type-mapper'
import * as JSONSchemaTypeMapper from 'json-schema-type-mapper'
import * as oa from './OpenAPI'

// TODO: for now assuming schema.components.schemas is populated and doesn't contain `$ref` objects
Expand Down Expand Up @@ -29,6 +29,11 @@ export type SchemasById<S extends OpenAPIObject> = {
>
}

export type JSONSchema<
T,
S extends OpenAPIObject
> = JSONSchemaTypeMapper.Schema<T, SchemasById<S>>

/**
* Force TS to load a type that has not been computed
* https://github.com/pirix-gh/ts-toolbelt
Expand All @@ -54,10 +59,31 @@ export type RequestBody<
Method extends keyof S['paths'][Path],
O extends S['paths'][Path][Method] = S['paths'][Path][Method]
> = Compute<
| JSONSchema<ValueOf<O['requestBody']['content']>['schema'], SchemasById<S>>
| JSONSchema<ValueOf<O['requestBody']['content']>['schema'], S>
| (O['requestBody']['required'] extends true ? never : undefined)
>

export type ParametersIn<
S extends OpenAPIObject,
Path extends keyof S['paths'],
Method extends keyof S['paths'][Path],
In extends oa.ParameterLocation
> = S['paths'][Path][Method]['parameters'] extends Array<infer P>
? Extract<P, { in: In }>
: never

export type Parameters<
S extends OpenAPIObject,
P extends keyof S['paths'],
M extends keyof S['paths'][P],
In extends oa.ParameterLocation,
Ps extends ParametersIn<S, P, M, In> = ParametersIn<S, P, M, In>
> = Compute<
{
[Name in Ps['name']]: JSONSchema<Extract<Ps, { name: Name }>['schema'], S>
}
>

/** Gather OpenAPI operations under a single flat union for easier processing */
export type Operation<S extends OpenAPIObject> = ValueOf<
{
Expand All @@ -67,7 +93,10 @@ export type Operation<S extends OpenAPIObject> = ValueOf<
method: Method
operationId: S['paths'][Path][Method]['operationId']
path: Path
params: {} // TODO dig up params

headers: Parameters<S, Path, Method, 'header'>
params: Parameters<S, Path, Method, 'path'>
query: Parameters<S, Path, Method, 'query'>

// TODO handle different content types and `required`:
requestBody: RequestBody<S, Path, Method>
Expand All @@ -77,7 +106,7 @@ export type Operation<S extends OpenAPIObject> = ValueOf<
ValueOf<
S['paths'][Path][Method]['responses']
>['content']['application/json']['schema'],
SchemasById<S>
S
>
}
}
Expand All @@ -95,17 +124,31 @@ export type PathsByMethod<
}

interface OperationObject {
params: any
params: Record<string, any>
path: string
query: Record<string, any>
responseBody: any
requestBody: any
}

// `express.Request` has no generic parameter for `query` so we have to roll out
// our own `Request` type:
export type Request<O extends OperationObject> = Compute<
{ query: O['query'] } & Omit<
express.Request<O['params'], O['responseBody'], O['requestBody']>,
'query'
>
>

export type RequestHandler<O extends OperationObject> = (
req: Request<O>,
res: express.Response<O['responseBody']>,
next: express.NextFunction
) => any

export type RouterMatcher<O extends OperationObject> = (
path: O['path'],
...handlers: Array<
express.RequestHandler<O['params'], O['responseBody'], O['requestBody']>
>
...handlers: Array<RequestHandler<O>>
) => any

type HTTPMethod =
Expand Down
12 changes: 9 additions & 3 deletions sample.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@ import PetStoreSchema from './PetStoreSchema'

const router = (express.Router() as unknown) as OpenAPIRouter<PetStoreSchema>

router.get('/pets/{id}', (req, res, next) => undefined)
router.get('/pets', (req, res, next) => undefined)
router.get('/pets/{id}', (req, res, next) => {
res.send({ name: 'Pet', id: req.params.id })
})
router.get('/pets', (req, res, next) => {
res.send([{ id: 1, name: 'asd' }])
})

router.delete('/pets/{id}', (req, res, next) => undefined)
router.delete('/pets/{id}', (req, res, next) => {
res.send()
})

router.post('/pets', (req, res, next) => {
// req.body autocompletes to `{ [x: string]: JSONValue; name: string; tag?: string | undefined; }`
Expand Down

0 comments on commit 74a61ee

Please sign in to comment.