Skip to content

Commit

Permalink
fix(zod-openapi): return Response if response is not text or JSON (#…
Browse files Browse the repository at this point in the history
…853)

* fix(zod-openapi): return `Response` if response is not text or JSON

Co-authored-by: sushichan044 <mail@sushichan.live>

* fixed tests and correct types

* add changeset

---------

Co-authored-by: sushichan044 <mail@sushichan.live>
  • Loading branch information
yusukebe and sushichan044 authored Nov 28, 2024
1 parent a2ffc34 commit a9804af
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 12 deletions.
5 changes: 5 additions & 0 deletions .changeset/fluffy-doors-carry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hono/zod-openapi': patch
---

fix: return `Response` if response is not text or JSON
37 changes: 29 additions & 8 deletions packages/zod-openapi/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import type {
ValidationTargets,
} from 'hono'
import type { MergePath, MergeSchemaPath } from 'hono/types'
import type { JSONParsed, RemoveBlankRecord } from 'hono/utils/types'
import type { JSONParsed, JSONValue, RemoveBlankRecord, SimplifyDeepArray } from 'hono/utils/types'
import type {
ClientErrorStatusCode,
InfoStatusCode,
Expand Down Expand Up @@ -69,6 +69,28 @@ type IsForm<T> = T extends string
: never
: never

type ReturnJsonOrTextOrResponse<
ContentType,
Content,
Status extends keyof StatusCodeRangeDefinitions | StatusCode
> = ContentType extends string
? ContentType extends `application/${infer Start}json${infer _End}`
? Start extends '' | `${string}+` | `vnd.${string}+`
? TypedResponse<
SimplifyDeepArray<Content> extends JSONValue
? JSONValue extends SimplifyDeepArray<Content>
? never
: JSONParsed<Content>
: never,
ExtractStatusCode<Status>,
'json'
>
: never
: ContentType extends `text/plain${infer _Rest}`
? TypedResponse<Content, ExtractStatusCode<Status>, 'text'>
: Response
: never

type RequestPart<R extends RouteConfig, Part extends string> = Part extends keyof R['request']
? R['request'][Part]
: {}
Expand Down Expand Up @@ -173,14 +195,13 @@ type ExtractStatusCode<T extends RouteConfigStatusCode> = T extends keyof Status
? StatusCodeRangeDefinitions[T]
: T
export type RouteConfigToTypedResponse<R extends RouteConfig> = {
[Status in keyof R['responses'] & RouteConfigStatusCode]: IsJson<
keyof R['responses'][Status]['content']
> extends never
[Status in keyof R['responses'] &
RouteConfigStatusCode]: undefined extends R['responses'][Status]['content']
? TypedResponse<{}, ExtractStatusCode<Status>, string>
: TypedResponse<
JSONParsed<ExtractContent<R['responses'][Status]['content']>>,
ExtractStatusCode<Status>,
'json' | 'text'
: ReturnJsonOrTextOrResponse<
keyof R['responses'][Status]['content'],
ExtractContent<R['responses'][Status]['content']>,
Status
>
}[keyof R['responses'] & RouteConfigStatusCode]

Expand Down
24 changes: 24 additions & 0 deletions packages/zod-openapi/test/handler.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,28 @@ describe('supports async handler', () => {
>
>
})

test('Route accepts a response other than the response from c.json() and c.text()', () => {
const route = createRoute({
method: 'get',
path: '/html',
responses: {
200: {
content: {
'text/html': {
schema: z.string(),
},
},
description: 'Return HTML',
},
},
})

const handler: RouteHandler<typeof route> = (c) => {
return c.html('<h1>Hello from html</h1>')
}

const hono = new OpenAPIHono()
hono.openapi(route, handler)
})
})
2 changes: 1 addition & 1 deletion packages/zod-openapi/test/index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ describe('Types', () => {
id: number
message: string
}
outputFormat: 'json' | 'text'
outputFormat: 'json'
status: 200
}
}
Expand Down
6 changes: 3 additions & 3 deletions packages/zod-openapi/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1779,21 +1779,21 @@ describe('RouteConfigToTypedResponse', () => {
age: number
},
200,
'json' | 'text'
'json'
>
| TypedResponse<
{
ok: boolean
},
400,
'json' | 'text'
'json'
>
| TypedResponse<
{
ok: boolean
},
ServerErrorStatusCode,
'json' | 'text'
'json'
>
type verify = Expect<Equal<Expected, Actual>>
})
Expand Down

0 comments on commit a9804af

Please sign in to comment.