diff --git a/src/context.ts b/src/context.ts index 8447f2128..f5462be29 100644 --- a/src/context.ts +++ b/src/context.ts @@ -11,7 +11,7 @@ import type { } from './types' import type { ResponseHeader } from './utils/headers' import { HtmlEscapedCallbackPhase, resolveCallback } from './utils/html' -import type { RedirectStatusCode, StatusCode } from './utils/http-status' +import type { ContentfulStatusCode, RedirectStatusCode, StatusCode } from './utils/http-status' import type { BaseMime } from './utils/mime' import type { InvalidJSONValue, @@ -114,7 +114,12 @@ interface NewResponse { /** * Interface for responding with a body. */ -interface BodyRespond extends NewResponse {} +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 +} /** * Interface for responding with text. @@ -130,13 +135,15 @@ interface BodyRespond extends NewResponse {} * @returns {Response & TypedResponse} - The response after rendering the text content, typed with the provided text and status code types. */ interface TextRespond { - ( + ( text: T, status?: U, headers?: HeaderRecord ): Response & TypedResponse - (text: T, init?: ResponseInit): Response & - TypedResponse + ( + text: T, + init?: ResponseInit + ): Response & TypedResponse } /** @@ -155,7 +162,7 @@ interface TextRespond { interface JSONRespond { < T extends JSONValue | SimplifyDeepArray | InvalidJSONValue, - U extends StatusCode = StatusCode + U extends ContentfulStatusCode = ContentfulStatusCode >( object: T, status?: U, @@ -163,7 +170,7 @@ interface JSONRespond { ): JSONRespondReturn < T extends JSONValue | SimplifyDeepArray | InvalidJSONValue, - U extends StatusCode = StatusCode + U extends ContentfulStatusCode = ContentfulStatusCode >( object: T, init?: ResponseInit @@ -178,7 +185,7 @@ interface JSONRespond { */ type JSONRespondReturn< T extends JSONValue | SimplifyDeepArray | InvalidJSONValue, - U extends StatusCode + U extends ContentfulStatusCode > = Response & TypedResponse< SimplifyDeepArray extends JSONValue @@ -203,7 +210,7 @@ type JSONRespondReturn< interface HTMLRespond { >( html: T, - status?: StatusCode, + status?: ContentfulStatusCode, headers?: HeaderRecord ): T extends string ? Response : Promise >(html: T, init?: ResponseInit): T extends string @@ -712,11 +719,7 @@ export class Context< * }) * ``` */ - body: BodyRespond = ( - data: Data | null, - arg?: StatusCode | ResponseInit, - headers?: HeaderRecord - ): Response => { + body: BodyRespond = (data, arg?: StatusCode | RequestInit, headers?: HeaderRecord) => { return typeof arg === 'number' ? this.#newResponse(data, arg, headers) : this.#newResponse(data, arg) @@ -736,7 +739,7 @@ export class Context< */ text: TextRespond = ( text: string, - arg?: StatusCode | ResponseInit, + arg?: ContentfulStatusCode | ResponseInit, headers?: HeaderRecord ): ReturnType => { // If the header is empty, return Response immediately. @@ -769,7 +772,7 @@ export class Context< */ json: JSONRespond = < T extends JSONValue | SimplifyDeepArray | InvalidJSONValue, - U extends StatusCode = StatusCode + U extends ContentfulStatusCode = ContentfulStatusCode >( object: T, arg?: U | ResponseInit, @@ -786,7 +789,7 @@ export class Context< html: HTMLRespond = ( html: string | Promise, - arg?: StatusCode | ResponseInit, + arg?: ContentfulStatusCode | ResponseInit, headers?: HeaderRecord ): Response | Promise => { this.#preparedHeaders ??= {} diff --git a/src/types.test.ts b/src/types.test.ts index 421570abe..5118d3212 100644 --- a/src/types.test.ts +++ b/src/types.test.ts @@ -20,7 +20,7 @@ import type { ToSchema, TypedResponse, } from './types' -import type { StatusCode } from './utils/http-status' +import type { ContentfulStatusCode, StatusCode } from './utils/http-status' import type { Equal, Expect } from './utils/types' import { validator } from './validator' @@ -96,7 +96,7 @@ describe('HandlerInterface', () => { message: string } outputFormat: 'json' - status: StatusCode + status: ContentfulStatusCode } } } @@ -135,7 +135,7 @@ describe('HandlerInterface', () => { message: string } outputFormat: 'json' - status: StatusCode + status: ContentfulStatusCode } } } @@ -163,7 +163,7 @@ describe('HandlerInterface', () => { } output: 'foo' outputFormat: 'text' - status: StatusCode + status: ContentfulStatusCode } } } @@ -192,7 +192,7 @@ describe('HandlerInterface', () => { } output: string outputFormat: 'text' - status: StatusCode + status: ContentfulStatusCode } } } @@ -217,7 +217,7 @@ describe('HandlerInterface', () => { } output: string outputFormat: 'text' - status: StatusCode + status: ContentfulStatusCode } } } & { @@ -271,7 +271,7 @@ describe('OnHandlerInterface', () => { success: boolean } outputFormat: 'json' - status: StatusCode + status: ContentfulStatusCode } } } @@ -376,7 +376,7 @@ describe('Support c.json(undefined)', () => { input: {} output: never outputFormat: 'json' - status: StatusCode + status: ContentfulStatusCode } } } @@ -460,7 +460,7 @@ describe('`json()`', () => { message: string } outputFormat: 'json' - status: StatusCode + status: ContentfulStatusCode } } } @@ -902,7 +902,7 @@ describe('Different types using json()', () => { ng: boolean } outputFormat: 'json' - status: StatusCode + status: ContentfulStatusCode } | { input: {} @@ -910,7 +910,7 @@ describe('Different types using json()', () => { ok: boolean } outputFormat: 'json' - status: StatusCode + status: ContentfulStatusCode } | { input: {} @@ -918,7 +918,7 @@ describe('Different types using json()', () => { default: boolean } outputFormat: 'json' - status: StatusCode + status: ContentfulStatusCode } } } @@ -974,7 +974,7 @@ describe('Different types using json()', () => { default: boolean } outputFormat: 'json' - status: StatusCode + status: ContentfulStatusCode } } } @@ -1012,7 +1012,7 @@ describe('Different types using json()', () => { ng: boolean } outputFormat: 'json' - status: StatusCode + status: ContentfulStatusCode } | { input: {} @@ -1020,7 +1020,7 @@ describe('Different types using json()', () => { ok: boolean } outputFormat: 'json' - status: StatusCode + status: ContentfulStatusCode } | { input: {} @@ -1028,7 +1028,7 @@ describe('Different types using json()', () => { default: boolean } outputFormat: 'json' - status: StatusCode + status: ContentfulStatusCode } } } @@ -1084,7 +1084,7 @@ describe('Different types using json()', () => { default: boolean } outputFormat: 'json' - status: StatusCode + status: ContentfulStatusCode } } } @@ -1111,7 +1111,7 @@ describe('json() in an async handler', () => { ok: boolean } outputFormat: 'json' - status: StatusCode + status: ContentfulStatusCode } } } @@ -2271,3 +2271,22 @@ describe('generic typed variables', () => { expectTypeOf().toEqualTypeOf() }) }) +describe('status code', () => { + const app = new Hono() + + it('should only allow to return .json() with contentful status codes', async () => { + const route = app.get('/', async (c) => c.json({})) + 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() + }) +}) diff --git a/src/utils/http-status.ts b/src/utils/http-status.ts index 6882394f4..07610a39b 100644 --- a/src/utils/http-status.ts +++ b/src/utils/http-status.ts @@ -67,3 +67,6 @@ export type StatusCode = | ClientErrorStatusCode | ServerErrorStatusCode | UnofficialStatusCode + +export type ContentlessStatusCode = 101 | 204 | 205 | 304 +export type ContentfulStatusCode = Exclude