From efc8a461c0ed9164e7811cb40b593c7eec6f313e Mon Sep 17 00:00:00 2001 From: Yusuke Wada Date: Sat, 28 Dec 2024 11:04:20 +0900 Subject: [PATCH] feat(context): `ResponseInit` accepts generics `StatusCode` for `status` (#3770) --- src/context.ts | 30 +++++++++++++++++------------- src/types.test.ts | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 13 deletions(-) diff --git a/src/context.ts b/src/context.ts index c5aa68e77..0c86d3dd8 100644 --- a/src/context.ts +++ b/src/context.ts @@ -108,7 +108,7 @@ interface Set { */ interface NewResponse { (data: Data | null, status?: StatusCode, headers?: HeaderRecord): Response - (data: Data | null, init?: ResponseInit): Response + (data: Data | null, init?: ResponseOrInit): Response } /** @@ -118,7 +118,8 @@ interface BodyRespond { // if we return content, only allow the status codes that allow for returning the body (data: Data, status?: ContentfulStatusCode, headers?: HeaderRecord): Response (data: null, status?: StatusCode, headers?: HeaderRecord): Response - (data: Data | null, init?: ResponseInit): Response + (data: Data, init?: ResponseOrInit): Response + (data: null, init?: ResponseOrInit): Response } /** @@ -142,7 +143,7 @@ interface TextRespond { ): Response & TypedResponse ( text: T, - init?: ResponseInit + init?: ResponseOrInit ): Response & TypedResponse } @@ -173,7 +174,7 @@ interface JSONRespond { U extends ContentfulStatusCode = ContentfulStatusCode >( object: T, - init?: ResponseInit + init?: ResponseOrInit ): JSONRespondReturn } @@ -213,9 +214,10 @@ interface HTMLRespond { status?: ContentfulStatusCode, headers?: HeaderRecord ): T extends string ? Response : Promise - >(html: T, init?: ResponseInit): T extends string - ? Response - : Promise + >( + html: T, + init?: ResponseOrInit + ): T extends string ? Response : Promise } /** @@ -257,12 +259,14 @@ type ResponseHeadersInit = | Record | Headers -interface ResponseInit { +interface ResponseInit { headers?: ResponseHeadersInit - status?: number + status?: T statusText?: string } +type ResponseOrInit = ResponseInit | Response + export const TEXT_PLAIN = 'text/plain; charset=UTF-8' /** @@ -632,7 +636,7 @@ export class Context< #newResponse( data: Data | null, - arg?: StatusCode | ResponseInit, + arg?: StatusCode | ResponseOrInit, headers?: HeaderRecord ): Response { // Optimized @@ -739,7 +743,7 @@ export class Context< */ text: TextRespond = ( text: string, - arg?: ContentfulStatusCode | ResponseInit, + arg?: ContentfulStatusCode | ResponseOrInit, headers?: HeaderRecord ): ReturnType => { // If the header is empty, return Response immediately. @@ -777,7 +781,7 @@ export class Context< U extends ContentfulStatusCode = ContentfulStatusCode >( object: T, - arg?: U | ResponseInit, + arg?: U | ResponseOrInit, headers?: HeaderRecord ): JSONRespondReturn => { const body = JSON.stringify(object) @@ -791,7 +795,7 @@ export class Context< html: HTMLRespond = ( html: string | Promise, - arg?: ContentfulStatusCode | ResponseInit, + arg?: ContentfulStatusCode | ResponseOrInit, headers?: HeaderRecord ): Response | Promise => { this.#preparedHeaders ??= {} diff --git a/src/types.test.ts b/src/types.test.ts index 5118d3212..bad8ab2ca 100644 --- a/src/types.test.ts +++ b/src/types.test.ts @@ -2247,6 +2247,7 @@ describe('Returning type from `app.use(path, mw)`', () => { type verify = Expect> }) }) + describe('generic typed variables', () => { const okHelper = (c: Context) => { return (data: TData) => c.json({ data }) @@ -2271,6 +2272,7 @@ describe('generic typed variables', () => { expectTypeOf().toEqualTypeOf() }) }) + describe('status code', () => { const app = new Hono() @@ -2279,14 +2281,51 @@ describe('status code', () => { type Actual = ExtractSchema['/']['$get']['status'] expectTypeOf().toEqualTypeOf() }) + it('should only allow to return .body(null) with all status codes', async () => { const route = app.get('/', async (c) => c.body(null)) type Actual = ExtractSchema['/']['$get']['status'] expectTypeOf().toEqualTypeOf() }) + it('should only allow to return .text() with contentful status codes', async () => { const route = app.get('/', async (c) => c.text('whatever')) type Actual = ExtractSchema['/']['$get']['status'] expectTypeOf().toEqualTypeOf() }) + + it('should throw type error when .json({}) is used with contentless status codes', async () => { + // @ts-expect-error 204 is not contentful status code + app.get('/', async (c) => c.json({}, 204)) + app.get('/', async (c) => + c.json( + {}, + // @ts-expect-error 204 is not contentful status code + { + status: 204, + } + ) + ) + }) + + it('should throw type error when .body(content) is used with contentless status codes', async () => { + // @ts-expect-error 204 is not contentful status code + app.get('/', async (c) => c.body('content', 204)) + // @ts-expect-error 204 is not contentful status code + app.get('/', async (c) => c.body('content', { status: 204 })) + }) + + it('should throw type error when .text(content) is used with contentless status codes', async () => { + // @ts-expect-error 204 is not contentful status code + app.get('/', async (c) => c.text('content', 204)) + // @ts-expect-error 204 is not contentful status code + app.get('/', async (c) => c.text('content', { status: 204 })) + }) + + it('should throw type error when .html(content) is used with contentless status codes', async () => { + // @ts-expect-error 204 is not contentful status code + app.get('/', async (c) => c.html('

title

', 204)) + // @ts-expect-error 204 is not contentful status code + app.get('/', async (c) => c.html('

title

', { status: 204 })) + }) })