fix(zod-openapi): return `Response` if response is not text or JSON (#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>pull/854/head
parent
a2ffc34b31
commit
a9804afe71
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'@hono/zod-openapi': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: return `Response` if response is not text or JSON
|
|
@ -26,7 +26,7 @@ import type {
|
||||||
ValidationTargets,
|
ValidationTargets,
|
||||||
} from 'hono'
|
} from 'hono'
|
||||||
import type { MergePath, MergeSchemaPath } from 'hono/types'
|
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 {
|
import type {
|
||||||
ClientErrorStatusCode,
|
ClientErrorStatusCode,
|
||||||
InfoStatusCode,
|
InfoStatusCode,
|
||||||
|
@ -69,6 +69,28 @@ type IsForm<T> = T extends string
|
||||||
: never
|
: never
|
||||||
: 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']
|
type RequestPart<R extends RouteConfig, Part extends string> = Part extends keyof R['request']
|
||||||
? R['request'][Part]
|
? R['request'][Part]
|
||||||
: {}
|
: {}
|
||||||
|
@ -173,14 +195,13 @@ type ExtractStatusCode<T extends RouteConfigStatusCode> = T extends keyof Status
|
||||||
? StatusCodeRangeDefinitions[T]
|
? StatusCodeRangeDefinitions[T]
|
||||||
: T
|
: T
|
||||||
export type RouteConfigToTypedResponse<R extends RouteConfig> = {
|
export type RouteConfigToTypedResponse<R extends RouteConfig> = {
|
||||||
[Status in keyof R['responses'] & RouteConfigStatusCode]: IsJson<
|
[Status in keyof R['responses'] &
|
||||||
keyof R['responses'][Status]['content']
|
RouteConfigStatusCode]: undefined extends R['responses'][Status]['content']
|
||||||
> extends never
|
|
||||||
? TypedResponse<{}, ExtractStatusCode<Status>, string>
|
? TypedResponse<{}, ExtractStatusCode<Status>, string>
|
||||||
: TypedResponse<
|
: ReturnJsonOrTextOrResponse<
|
||||||
JSONParsed<ExtractContent<R['responses'][Status]['content']>>,
|
keyof R['responses'][Status]['content'],
|
||||||
ExtractStatusCode<Status>,
|
ExtractContent<R['responses'][Status]['content']>,
|
||||||
'json' | 'text'
|
Status
|
||||||
>
|
>
|
||||||
}[keyof R['responses'] & RouteConfigStatusCode]
|
}[keyof R['responses'] & RouteConfigStatusCode]
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -76,7 +76,7 @@ describe('Types', () => {
|
||||||
id: number
|
id: number
|
||||||
message: string
|
message: string
|
||||||
}
|
}
|
||||||
outputFormat: 'json' | 'text'
|
outputFormat: 'json'
|
||||||
status: 200
|
status: 200
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1779,21 +1779,21 @@ describe('RouteConfigToTypedResponse', () => {
|
||||||
age: number
|
age: number
|
||||||
},
|
},
|
||||||
200,
|
200,
|
||||||
'json' | 'text'
|
'json'
|
||||||
>
|
>
|
||||||
| TypedResponse<
|
| TypedResponse<
|
||||||
{
|
{
|
||||||
ok: boolean
|
ok: boolean
|
||||||
},
|
},
|
||||||
400,
|
400,
|
||||||
'json' | 'text'
|
'json'
|
||||||
>
|
>
|
||||||
| TypedResponse<
|
| TypedResponse<
|
||||||
{
|
{
|
||||||
ok: boolean
|
ok: boolean
|
||||||
},
|
},
|
||||||
ServerErrorStatusCode,
|
ServerErrorStatusCode,
|
||||||
'json' | 'text'
|
'json'
|
||||||
>
|
>
|
||||||
type verify = Expect<Equal<Expected, Actual>>
|
type verify = Expect<Equal<Expected, Actual>>
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue