mirror of
https://github.com/honojs/hono.git
synced 2024-11-24 19:26:56 +01:00
feat(jsonT
): check JSON type (#939)
* feat(`jsonT`): check JSON type * denoify * use `JSONValue` * create `TrueAndFalseToBoolean` and add a comment
This commit is contained in:
parent
73860a1595
commit
b1c5059708
@ -4,6 +4,7 @@ import type { Env, NotFoundHandler, Input } from './types.ts'
|
||||
import type { CookieOptions } from './utils/cookie.ts'
|
||||
import { serialize } from './utils/cookie.ts'
|
||||
import type { StatusCode } from './utils/http-status.ts'
|
||||
import type { PrettyJSON, JSONValue } from './utils/types.ts'
|
||||
|
||||
type Runtime = 'node' | 'deno' | 'bun' | 'workerd' | 'fastly' | 'edge-light' | 'lagon' | 'other'
|
||||
type HeaderRecord = Record<string, string | string[]>
|
||||
@ -237,14 +238,15 @@ export class Context<
|
||||
return this.newResponse(body, status, headers)
|
||||
}
|
||||
|
||||
jsonT = <T = object>(
|
||||
object: T,
|
||||
jsonT = <T>(
|
||||
object: T extends JSONValue ? T : JSONValue,
|
||||
status: StatusCode = this._status,
|
||||
headers?: HeaderRecord
|
||||
): TypedResponse<T> => {
|
||||
): TypedResponse<T extends JSONValue ? (JSONValue extends T ? never : PrettyJSON<T>) : never> => {
|
||||
return {
|
||||
response: this.json(object, status, headers),
|
||||
data: object,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
data: object as any,
|
||||
format: 'json',
|
||||
}
|
||||
}
|
||||
|
@ -10,3 +10,21 @@ export type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) ex
|
||||
) => void
|
||||
? I
|
||||
: never
|
||||
|
||||
export type JSONPrimitive = string | boolean | number | null | undefined
|
||||
export type JSONArray = (JSONPrimitive | JSONObject | JSONArray)[]
|
||||
export type JSONObject = { [key: string]: JSONPrimitive | JSONArray | JSONObject }
|
||||
export type JSONValue = JSONObject | JSONArray | JSONPrimitive
|
||||
|
||||
// `boolean` will be `true` or `false` because it's an alias of them.
|
||||
// See: https://github.com/microsoft/TypeScript/issues/22596
|
||||
// This type converts `true | false` to `boolean` that we expect.
|
||||
type TrueAndFalseToBoolean<T> = T extends true ? boolean : T extends false ? boolean : T
|
||||
|
||||
export type PrettyJSON<T> = T extends JSONPrimitive
|
||||
? TrueAndFalseToBoolean<T>
|
||||
: T extends JSONArray
|
||||
? PrettyJSON<T[number]>
|
||||
: T extends JSONObject
|
||||
? { [K in keyof T]: PrettyJSON<T[K]> }
|
||||
: never
|
||||
|
@ -332,6 +332,17 @@ describe('Merge path with `app.route()`', () => {
|
||||
expect(data.ok).toBe(true)
|
||||
})
|
||||
|
||||
it('Should not allow the incorrect JSON type', async () => {
|
||||
const app = new Hono()
|
||||
// @ts-ignore
|
||||
const route = app.get('/api/foo', (c) => c.jsonT({ datetime: new Date() }))
|
||||
type AppType = typeof route
|
||||
const client = hc<AppType>('http://localhost')
|
||||
const res = await client.api.foo.$get()
|
||||
const data = await res.json()
|
||||
type verify = Expect<Equal<never, typeof data>>
|
||||
})
|
||||
|
||||
describe('Multiple endpoints', () => {
|
||||
const api = new Hono()
|
||||
.get('/foo', (c) => c.jsonT({ foo: '' }))
|
||||
|
@ -4,6 +4,7 @@ import type { Env, NotFoundHandler, Input } from './types'
|
||||
import type { CookieOptions } from './utils/cookie'
|
||||
import { serialize } from './utils/cookie'
|
||||
import type { StatusCode } from './utils/http-status'
|
||||
import type { PrettyJSON, JSONValue } from './utils/types'
|
||||
|
||||
type Runtime = 'node' | 'deno' | 'bun' | 'workerd' | 'fastly' | 'edge-light' | 'lagon' | 'other'
|
||||
type HeaderRecord = Record<string, string | string[]>
|
||||
@ -237,14 +238,15 @@ export class Context<
|
||||
return this.newResponse(body, status, headers)
|
||||
}
|
||||
|
||||
jsonT = <T = object>(
|
||||
object: T,
|
||||
jsonT = <T>(
|
||||
object: T extends JSONValue ? T : JSONValue,
|
||||
status: StatusCode = this._status,
|
||||
headers?: HeaderRecord
|
||||
): TypedResponse<T> => {
|
||||
): TypedResponse<T extends JSONValue ? (JSONValue extends T ? never : PrettyJSON<T>) : never> => {
|
||||
return {
|
||||
response: this.json(object, status, headers),
|
||||
data: object,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
data: object as any,
|
||||
format: 'json',
|
||||
}
|
||||
}
|
||||
|
@ -10,3 +10,21 @@ export type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) ex
|
||||
) => void
|
||||
? I
|
||||
: never
|
||||
|
||||
export type JSONPrimitive = string | boolean | number | null | undefined
|
||||
export type JSONArray = (JSONPrimitive | JSONObject | JSONArray)[]
|
||||
export type JSONObject = { [key: string]: JSONPrimitive | JSONArray | JSONObject }
|
||||
export type JSONValue = JSONObject | JSONArray | JSONPrimitive
|
||||
|
||||
// `boolean` will be `true` or `false` because it's an alias of them.
|
||||
// See: https://github.com/microsoft/TypeScript/issues/22596
|
||||
// This type converts `true | false` to `boolean` that we expect.
|
||||
type TrueAndFalseToBoolean<T> = T extends true ? boolean : T extends false ? boolean : T
|
||||
|
||||
export type PrettyJSON<T> = T extends JSONPrimitive
|
||||
? TrueAndFalseToBoolean<T>
|
||||
: T extends JSONArray
|
||||
? PrettyJSON<T[number]>
|
||||
: T extends JSONObject
|
||||
? { [K in keyof T]: PrettyJSON<T[K]> }
|
||||
: never
|
||||
|
Loading…
Reference in New Issue
Block a user