0
0
mirror of https://github.com/honojs/hono.git synced 2024-11-21 18:18:57 +01:00

feat(client): improve handling status code types

This commit is contained in:
Yusuke Wada 2024-08-18 18:22:30 +09:00
parent 8f1680238b
commit 3670716311
2 changed files with 85 additions and 6 deletions

View File

@ -1,7 +1,12 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { expectTypeOf } from 'vitest' import { expectTypeOf } from 'vitest'
import { Hono } from '..' import { Hono } from '..'
import { upgradeWebSocket } from '../adapter/deno/websocket' import { upgradeWebSocket } from '../adapter/deno/websocket'
import { hc } from '.' import { hc } from '.'
import { HTTPException } from '../http-exception'
import type { Equal, Expect } from '../utils/types'
import { setupServer } from 'msw/node'
import { HttpResponse, http } from 'msw'
describe('WebSockets', () => { describe('WebSockets', () => {
const app = new Hono() const app = new Hono()
@ -57,3 +62,65 @@ describe('with the leading slash', () => {
expectTypeOf(client.foo[':id'].baz).toHaveProperty('$get') expectTypeOf(client.foo[':id'].baz).toHaveProperty('$get')
}) })
}) })
describe('Status Code - test only Types', () => {
const server = setupServer(
http.get('http://localhost/foo', async () => {
return HttpResponse.json({})
})
)
beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
const app = new Hono()
const flag = {}
const routes = app.get('/foo', (c) => {
if (flag) {
throw new HTTPException(500, {
message: 'Server Error!',
})
}
if (flag) {
return c.json({ message: 'invalid!' }, 401)
}
if (flag) {
return c.redirect('/', 301)
}
return c.json({ ok: true }, 200)
})
const client = hc<typeof routes>('http://localhost')
it('Should handle different status codes', async () => {
const res = await client.foo.$get()
if (res.status === 500) {
const data = await res.json<{ errorMessage: string }>()
type Expected = { errorMessage: string }
type verify = Expect<Equal<Expected, typeof data>>
}
if (res.status === 401) {
const data = await res.json()
type Expected = { message: string }
type verify = Expect<Equal<Expected, typeof data>>
}
if (res.status === 200) {
const data = await res.json()
type Expected = { ok: boolean }
type verify = Expect<Equal<Expected, typeof data>>
}
})
it('Should infer union types', async () => {
const res = await client.foo.$get()
const data = await res.json()
type Expected = { message: string } | { ok: boolean }
type verify = Expect<Equal<Expected, typeof data>>
})
})

View File

@ -1,6 +1,6 @@
import type { Hono } from '../hono' import type { Hono } from '../hono'
import type { Endpoint, ResponseFormat, Schema } from '../types' import type { Endpoint, ResponseFormat, Schema } from '../types'
import type { StatusCode, SuccessStatusCode } from '../utils/http-status' import type { RedirectStatusCode, StatusCode, SuccessStatusCode } from '../utils/http-status'
import type { HasRequiredKeys } from '../utils/types' import type { HasRequiredKeys } from '../utils/types'
type HonoRequest = (typeof Hono.prototype)['request'] type HonoRequest = (typeof Hono.prototype)['request']
@ -63,7 +63,15 @@ type ClientResponseOfEndpoint<T extends Endpoint = Endpoint> = T extends {
outputFormat: infer F outputFormat: infer F
status: infer S status: infer S
} }
? ClientResponse<O, S extends number ? S : never, F extends ResponseFormat ? F : never> ? F extends 'redirect'
? ClientResponse<O, S extends RedirectStatusCode ? S : never, 'redirect'>
:
| ClientResponse<O, S extends StatusCode ? S : never, F extends ResponseFormat ? F : never>
| ClientResponse<
{},
S extends StatusCode ? Exclude<Exclude<StatusCode, RedirectStatusCode>, S> : never,
F extends ResponseFormat ? F : never
>
: never : never
export interface ClientResponse< export interface ClientResponse<
@ -84,11 +92,13 @@ export interface ClientResponse<
url: string url: string
redirect(url: string, status: number): Response redirect(url: string, status: number): Response
clone(): Response clone(): Response
json(): F extends 'text' json<JSONT>(): F extends 'text'
? Promise<never> ? Promise<never>
: F extends 'json' : F extends 'json'
? undefined extends JSONT
? Promise<BlankRecordToNever<T>> ? Promise<BlankRecordToNever<T>>
: Promise<unknown> : Promise<JSONT>
: Promise<T & {}>
text(): F extends 'text' ? (T extends string ? Promise<T> : Promise<never>) : Promise<string> text(): F extends 'text' ? (T extends string ? Promise<T> : Promise<never>) : Promise<string>
blob(): Promise<Blob> blob(): Promise<Blob>
formData(): Promise<FormData> formData(): Promise<FormData>
@ -119,12 +129,14 @@ export type InferResponseType<T, U extends StatusCode = StatusCode> = InferRespo
U U
> >
type WithoutEmptyObject<T> = T extends {} ? (keyof T extends never ? never : T) : T
type InferResponseTypeFromEndpoint<T extends Endpoint, U extends StatusCode> = T extends { type InferResponseTypeFromEndpoint<T extends Endpoint, U extends StatusCode> = T extends {
output: infer O output: infer O
status: infer S status: infer S
} }
? S extends U ? S extends U
? O ? WithoutEmptyObject<O>
: never : never
: never : never