Skip to content

Commit

Permalink
Add support for multiple content types in response and defining headers
Browse files Browse the repository at this point in the history
  • Loading branch information
G4brym committed Mar 7, 2024
1 parent 0748192 commit 6c8d721
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 15 deletions.
1 change: 1 addition & 0 deletions docs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ nav:
- Advanced User Guide:
- advanced-user-guide/reusable-schemas.md
- advanced-user-guide/custom-response-formats.md
- advanced-user-guide/custom-response-headers.md
- advanced-user-guide/openai-plugin.md
- advanced-user-guide/ci-cd-pipelines.md
- advanced-user-guide/event-listener-worker.md
Expand Down
31 changes: 31 additions & 0 deletions docs/pages/advanced-user-guide/custom-response-formats.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,34 @@ export class ToDoList extends OpenAPIRoute {
}
}
```

## Describing multiple content types:

```ts
import { OpenAPIRoute, Str } from '@cloudflare/itty-router-openapi'

export class ToDoList extends OpenAPIRoute {
static schema = {
summary: 'My summary of a custom pdf file endpoint.',
responses: {
'200': {
contentType: 'application/xml',
content: {
'application/json': {
schema: {
foo: 'bar'
}
},
'audio/mpeg': {
schema: new Str({ format: 'binary' })
}
},
},
},
}

async handle(request: Request, env: any, context: any, data: any) {
// ...
}
}
```
60 changes: 60 additions & 0 deletions docs/pages/advanced-user-guide/custom-response-headers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
## Describing response headers:

```ts
import { OpenAPIRoute, Str } from '@cloudflare/itty-router-openapi'

export class ToDoList extends OpenAPIRoute {
static schema: OpenAPIRouteSchema = {
responses: {
200: {
description: 'Object with user data.',
schema: {
series: {
timestamps: ['2023-01-01 00:00:00'],
values: [0.56],
},
},
headers: {
'x-bar': 'header-example',
'x-foo': new Str({required: false}),
},
},
},
}


async handle(request: Request, env: any, context: any, data: any) {
// ...
}
}
```

## Describing response headers with zod:

```ts
import { OpenAPIRoute, Str } from '@cloudflare/itty-router-openapi'

export class ToDoList extends OpenAPIRoute {
static schema: OpenAPIRouteSchema = {
responses: {
200: {
description: 'Object with user data.',
schema: {
series: {
timestamps: ['2023-01-01 00:00:00'],
values: [0.56],
},
},
headers: z.object({
'x-bar': z.string()
}),
},
},
}


async handle(request: Request, env: any, context: any, data: any) {
// ...
}
}
```
45 changes: 33 additions & 12 deletions src/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { OpenAPIRouteSchema, RouteOptions, RouteValidated } from './types'
import { extractQueryParameters } from './parameters'
import { z, ZodObject } from 'zod'
import { z, ZodObject, ZodType } from 'zod'
import { isAnyZodType, legacyTypeIntoZod } from './zod/utils'
import { RouteConfig } from '@asteasolutions/zod-to-openapi'
import { jsonResp } from './utils'
Expand Down Expand Up @@ -77,23 +77,44 @@ export class OpenAPIRoute<I = IRequest, A extends any[] = any[]> {
}

for (const [key, value] of Object.entries(schema.responses)) {
let responseSchema: object = (value.schema as object) || {}
if (value.content) {
for (const [contentType, contentObject] of Object.entries(
value.content
)) {
if (!isAnyZodType(contentObject.schema)) {
value.content[contentType].schema = legacyTypeIntoZod(
contentObject.schema
)
}
}

if (!isAnyZodType(responseSchema)) {
responseSchema = legacyTypeIntoZod(responseSchema)
}
if (value.schema) {
// If content is defined, response cannot have schema
delete value.schema
}
} else if (value.schema) {
let responseSchema: object = (value.schema as object) || {}

const contentType = value.contentType || 'application/json'
if (!isAnyZodType(responseSchema)) {
responseSchema = legacyTypeIntoZod(responseSchema)
}

// @ts-ignore
responses[key] = {
description: value.description,
content: {
const contentType = value.contentType || 'application/json'

value.content = {
[contentType]: {
schema: responseSchema,
schema: responseSchema as ZodType,
},
},
}

delete value.schema
}

if (value.headers && !isAnyZodType(value.headers)) {
value.headers = legacyTypeIntoZod(value.headers) as ZodObject<any>
}

responses[key] = value
}

if (schema.parameters) {
Expand Down
30 changes: 27 additions & 3 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { RouteEntry } from 'itty-router'
import { ZodType } from 'zod'
import { ResponseConfig } from '@asteasolutions/zod-to-openapi/dist/openapi-registry'
import { AnyZodObject, ZodType } from 'zod'
import {
ResponseConfig,
ZodMediaTypeObject,
} from '@asteasolutions/zod-to-openapi/dist/openapi-registry'
import { RouteConfig } from '@asteasolutions/zod-to-openapi'
import { OpenAPIObjectConfigV31 } from '@asteasolutions/zod-to-openapi/dist/v3.1/openapi-generator'
import { OpenAPIObjectConfig } from '@asteasolutions/zod-to-openapi/dist/v3.0/openapi-generator'
// @ts-ignore
import { HeadersObject as HeadersObject30 } from 'openapi3-ts/dist/model/openapi30'
// @ts-ignore
import { HeadersObject as HeadersObject31 } from 'openapi3-ts/dist/model/openapi31'

export interface RouterOptions {
base?: string
Expand All @@ -26,9 +33,26 @@ export declare type RouteParameter = {
type: ZodType
}

export declare type RouteResponse = Omit<ResponseConfig, 'content'> & {
export declare type MediaTypeObject = Omit<ZodMediaTypeObject, 'schema'> & {
schema: any // TODO enable type hint when legacy types drop the 'new'
}

export declare type ContentObject = {
[mediaType: string]: MediaTypeObject
}

export declare type RouteResponse = Omit<
ResponseConfig,
'headers' | 'content'
> & {
headers?:
| AnyZodObject
| HeadersObject30
| HeadersObject31
| Record<string, any>
schema?: Record<any, any>
contentType?: string
content?: ContentObject
}

export declare type OpenAPIRouteSchema = Omit<
Expand Down

0 comments on commit 6c8d721

Please sign in to comment.