Skip to content

Commit

Permalink
feat(context): ResponseInit accepts generics StatusCode for `stat…
Browse files Browse the repository at this point in the history
…us` (#3770)
  • Loading branch information
yusukebe authored Dec 28, 2024
1 parent b1335f9 commit efc8a46
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 13 deletions.
30 changes: 17 additions & 13 deletions src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ interface Set<E extends Env> {
*/
interface NewResponse {
(data: Data | null, status?: StatusCode, headers?: HeaderRecord): Response
(data: Data | null, init?: ResponseInit): Response
(data: Data | null, init?: ResponseOrInit): Response
}

/**
Expand All @@ -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<ContentfulStatusCode>): Response
(data: null, init?: ResponseOrInit): Response
}

/**
Expand All @@ -142,7 +143,7 @@ interface TextRespond {
): Response & TypedResponse<T, U, 'text'>
<T extends string, U extends ContentfulStatusCode = ContentfulStatusCode>(
text: T,
init?: ResponseInit
init?: ResponseOrInit<U>
): Response & TypedResponse<T, U, 'text'>
}

Expand Down Expand Up @@ -173,7 +174,7 @@ interface JSONRespond {
U extends ContentfulStatusCode = ContentfulStatusCode
>(
object: T,
init?: ResponseInit
init?: ResponseOrInit<U>
): JSONRespondReturn<T, U>
}

Expand Down Expand Up @@ -213,9 +214,10 @@ interface HTMLRespond {
status?: ContentfulStatusCode,
headers?: HeaderRecord
): T extends string ? Response : Promise<Response>
<T extends string | Promise<string>>(html: T, init?: ResponseInit): T extends string
? Response
: Promise<Response>
<T extends string | Promise<string>>(
html: T,
init?: ResponseOrInit<ContentfulStatusCode>
): T extends string ? Response : Promise<Response>
}

/**
Expand Down Expand Up @@ -257,12 +259,14 @@ type ResponseHeadersInit =
| Record<string, string>
| Headers

interface ResponseInit {
interface ResponseInit<T extends StatusCode = StatusCode> {
headers?: ResponseHeadersInit
status?: number
status?: T
statusText?: string
}

type ResponseOrInit<T extends StatusCode = StatusCode> = ResponseInit<T> | Response

export const TEXT_PLAIN = 'text/plain; charset=UTF-8'

/**
Expand Down Expand Up @@ -632,7 +636,7 @@ export class Context<

#newResponse(
data: Data | null,
arg?: StatusCode | ResponseInit,
arg?: StatusCode | ResponseOrInit,
headers?: HeaderRecord
): Response {
// Optimized
Expand Down Expand Up @@ -739,7 +743,7 @@ export class Context<
*/
text: TextRespond = (
text: string,
arg?: ContentfulStatusCode | ResponseInit,
arg?: ContentfulStatusCode | ResponseOrInit,
headers?: HeaderRecord
): ReturnType<TextRespond> => {
// If the header is empty, return Response immediately.
Expand Down Expand Up @@ -777,7 +781,7 @@ export class Context<
U extends ContentfulStatusCode = ContentfulStatusCode
>(
object: T,
arg?: U | ResponseInit,
arg?: U | ResponseOrInit<U>,
headers?: HeaderRecord
): JSONRespondReturn<T, U> => {
const body = JSON.stringify(object)
Expand All @@ -791,7 +795,7 @@ export class Context<

html: HTMLRespond = (
html: string | Promise<string>,
arg?: ContentfulStatusCode | ResponseInit,
arg?: ContentfulStatusCode | ResponseOrInit<ContentfulStatusCode>,
headers?: HeaderRecord
): Response | Promise<Response> => {
this.#preparedHeaders ??= {}
Expand Down
39 changes: 39 additions & 0 deletions src/types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2247,6 +2247,7 @@ describe('Returning type from `app.use(path, mw)`', () => {
type verify = Expect<Equal<Expected, Actual>>
})
})

describe('generic typed variables', () => {
const okHelper = (c: Context) => {
return <TData>(data: TData) => c.json({ data })
Expand All @@ -2271,6 +2272,7 @@ describe('generic typed variables', () => {
expectTypeOf<Actual>().toEqualTypeOf<Expected>()
})
})

describe('status code', () => {
const app = new Hono()

Expand All @@ -2279,14 +2281,51 @@ describe('status code', () => {
type Actual = ExtractSchema<typeof route>['/']['$get']['status']
expectTypeOf<Actual>().toEqualTypeOf<ContentfulStatusCode>()
})

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<typeof route>['/']['$get']['status']
expectTypeOf<Actual>().toEqualTypeOf<StatusCode>()
})

it('should only allow to return .text() with contentful status codes', async () => {
const route = app.get('/', async (c) => c.text('whatever'))
type Actual = ExtractSchema<typeof route>['/']['$get']['status']
expectTypeOf<Actual>().toEqualTypeOf<ContentfulStatusCode>()
})

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('<h1>title</h1>', 204))
// @ts-expect-error 204 is not contentful status code
app.get('/', async (c) => c.html('<h1>title</h1>', { status: 204 }))
})
})

0 comments on commit efc8a46

Please sign in to comment.