From 81d271279c77f6734869b8bbdb9b5924c6c597de Mon Sep 17 00:00:00 2001 From: ayame113 <40050810+ayame113@users.noreply.github.com> Date: Tue, 6 Jun 2023 10:23:09 +0900 Subject: [PATCH] feat: Allow `context.jsonT` to take interface as an argument (#1162) --- deno_dist/context.ts | 47 ++++++++++++++++++++++++++++++++++----- deno_dist/utils/types.ts | 2 ++ src/client/client.test.ts | 15 +++++++++++++ src/context.ts | 47 ++++++++++++++++++++++++++++++++++----- src/utils/types.ts | 2 ++ 5 files changed, 103 insertions(+), 10 deletions(-) diff --git a/deno_dist/context.ts b/deno_dist/context.ts index 0ebcb283..a06e6c8a 100644 --- a/deno_dist/context.ts +++ b/deno_dist/context.ts @@ -4,7 +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 { JSONValue } from './utils/types.ts' +import type { JSONValue, InterfaceToType } from './utils/types.ts' type Runtime = 'node' | 'deno' | 'bun' | 'workerd' | 'fastly' | 'edge-light' | 'lagon' | 'other' type HeaderRecord = Record @@ -48,9 +48,40 @@ interface JSONTRespond { object: T extends JSONValue ? T : JSONValue, status?: StatusCode, headers?: HeaderRecord - ): TypedResponse + ): TypedResponse< + InterfaceToType extends JSONValue + ? JSONValue extends InterfaceToType + ? never + : T + : never + > + ( + object: InterfaceToType extends JSONValue ? T : JSONValue, + status?: StatusCode, + headers?: HeaderRecord + ): TypedResponse< + InterfaceToType extends JSONValue + ? JSONValue extends InterfaceToType + ? never + : T + : never + > (object: T extends JSONValue ? T : JSONValue, init?: ResponseInit): TypedResponse< - T extends JSONValue ? (JSONValue extends T ? never : T) : never + InterfaceToType extends JSONValue + ? JSONValue extends InterfaceToType + ? never + : T + : never + > + ( + object: InterfaceToType extends JSONValue ? T : JSONValue, + init?: ResponseInit + ): TypedResponse< + InterfaceToType extends JSONValue + ? JSONValue extends InterfaceToType + ? never + : T + : never > } @@ -309,10 +340,16 @@ export class Context< } jsonT: JSONTRespond = ( - object: T extends JSONValue ? T : JSONValue, + object: InterfaceToType extends JSONValue ? T : JSONValue, arg?: StatusCode | RequestInit, headers?: HeaderRecord - ): TypedResponse => { + ): TypedResponse< + InterfaceToType extends JSONValue + ? JSONValue extends InterfaceToType + ? never + : T + : never + > => { return { response: typeof arg === 'number' ? this.json(object, arg, headers) : this.json(object, arg), // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/deno_dist/utils/types.ts b/deno_dist/utils/types.ts index 3e761ede..b23be62c 100644 --- a/deno_dist/utils/types.ts +++ b/deno_dist/utils/types.ts @@ -21,3 +21,5 @@ 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 + +export type InterfaceToType = T extends Function ? T : { [K in keyof T]: InterfaceToType } diff --git a/src/client/client.test.ts b/src/client/client.test.ts index 84260fbe..d4d539ea 100644 --- a/src/client/client.test.ts +++ b/src/client/client.test.ts @@ -359,6 +359,21 @@ describe('Merge path with `app.route()`', () => { expect(data.ok).toBe(true) }) + it('Should have correct types - with interface', async () => { + interface Result { + ok: boolean + } + const result: Result = { ok: true } + const base = new Hono().basePath('/api') + const app = base.get('/search', (c) => c.jsonT(result)) + type AppType = typeof app + const client = hc('http://localhost') + const res = await client.api.search.$get() + const data = await res.json() + type verify = Expect> + expect(data.ok).toBe(true) + }) + it('Should not allow the incorrect JSON type', async () => { const app = new Hono() // @ts-ignore diff --git a/src/context.ts b/src/context.ts index c2cc06d3..cc3443a4 100644 --- a/src/context.ts +++ b/src/context.ts @@ -4,7 +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 { JSONValue } from './utils/types' +import type { JSONValue, InterfaceToType } from './utils/types' type Runtime = 'node' | 'deno' | 'bun' | 'workerd' | 'fastly' | 'edge-light' | 'lagon' | 'other' type HeaderRecord = Record @@ -48,9 +48,40 @@ interface JSONTRespond { object: T extends JSONValue ? T : JSONValue, status?: StatusCode, headers?: HeaderRecord - ): TypedResponse + ): TypedResponse< + InterfaceToType extends JSONValue + ? JSONValue extends InterfaceToType + ? never + : T + : never + > + ( + object: InterfaceToType extends JSONValue ? T : JSONValue, + status?: StatusCode, + headers?: HeaderRecord + ): TypedResponse< + InterfaceToType extends JSONValue + ? JSONValue extends InterfaceToType + ? never + : T + : never + > (object: T extends JSONValue ? T : JSONValue, init?: ResponseInit): TypedResponse< - T extends JSONValue ? (JSONValue extends T ? never : T) : never + InterfaceToType extends JSONValue + ? JSONValue extends InterfaceToType + ? never + : T + : never + > + ( + object: InterfaceToType extends JSONValue ? T : JSONValue, + init?: ResponseInit + ): TypedResponse< + InterfaceToType extends JSONValue + ? JSONValue extends InterfaceToType + ? never + : T + : never > } @@ -309,10 +340,16 @@ export class Context< } jsonT: JSONTRespond = ( - object: T extends JSONValue ? T : JSONValue, + object: InterfaceToType extends JSONValue ? T : JSONValue, arg?: StatusCode | RequestInit, headers?: HeaderRecord - ): TypedResponse => { + ): TypedResponse< + InterfaceToType extends JSONValue + ? JSONValue extends InterfaceToType + ? never + : T + : never + > => { return { response: typeof arg === 'number' ? this.json(object, arg, headers) : this.json(object, arg), // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/src/utils/types.ts b/src/utils/types.ts index 3e761ede..b23be62c 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -21,3 +21,5 @@ 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 + +export type InterfaceToType = T extends Function ? T : { [K in keyof T]: InterfaceToType }