diff --git a/.changeset/fluffy-doors-carry.md b/.changeset/fluffy-doors-carry.md new file mode 100644 index 000000000..234d0d6b7 --- /dev/null +++ b/.changeset/fluffy-doors-carry.md @@ -0,0 +1,5 @@ +--- +'@hono/zod-openapi': patch +--- + +fix: return `Response` if response is not text or JSON diff --git a/packages/zod-openapi/src/index.ts b/packages/zod-openapi/src/index.ts index 0318ef81f..62cb592f0 100644 --- a/packages/zod-openapi/src/index.ts +++ b/packages/zod-openapi/src/index.ts @@ -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, @@ -69,6 +69,28 @@ type IsForm = 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 extends JSONValue + ? JSONValue extends SimplifyDeepArray + ? never + : JSONParsed + : never, + ExtractStatusCode, + 'json' + > + : never + : ContentType extends `text/plain${infer _Rest}` + ? TypedResponse, 'text'> + : Response + : never + type RequestPart = Part extends keyof R['request'] ? R['request'][Part] : {} @@ -173,14 +195,13 @@ type ExtractStatusCode = T extends keyof Status ? StatusCodeRangeDefinitions[T] : T export type RouteConfigToTypedResponse = { - [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, string> - : TypedResponse< - JSONParsed>, - ExtractStatusCode, - 'json' | 'text' + : ReturnJsonOrTextOrResponse< + keyof R['responses'][Status]['content'], + ExtractContent, + Status > }[keyof R['responses'] & RouteConfigStatusCode] diff --git a/packages/zod-openapi/test/handler.test-d.ts b/packages/zod-openapi/test/handler.test-d.ts index 9e1dea146..f8b561726 100644 --- a/packages/zod-openapi/test/handler.test-d.ts +++ b/packages/zod-openapi/test/handler.test-d.ts @@ -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 = (c) => { + return c.html('

Hello from html

') + } + + const hono = new OpenAPIHono() + hono.openapi(route, handler) + }) }) diff --git a/packages/zod-openapi/test/index.test-d.ts b/packages/zod-openapi/test/index.test-d.ts index fc24b6542..e23ab9de9 100644 --- a/packages/zod-openapi/test/index.test-d.ts +++ b/packages/zod-openapi/test/index.test-d.ts @@ -76,7 +76,7 @@ describe('Types', () => { id: number message: string } - outputFormat: 'json' | 'text' + outputFormat: 'json' status: 200 } } diff --git a/packages/zod-openapi/test/index.test.ts b/packages/zod-openapi/test/index.test.ts index bbb1bf433..375c325e4 100644 --- a/packages/zod-openapi/test/index.test.ts +++ b/packages/zod-openapi/test/index.test.ts @@ -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> })