From d00f4e3e9f74ea9aa703d87788b528f899b3dc62 Mon Sep 17 00:00:00 2001 From: unnoq Date: Tue, 19 Nov 2024 09:03:15 +0700 Subject: [PATCH] fix(server): dynamic params not work --- packages/server/src/adapters/fetch.test.ts | 59 ++++++++++++++++++++++ packages/server/src/adapters/fetch.ts | 57 +++++++++++++++------ packages/shared/src/index.ts | 2 +- packages/transformer/src/index.ts | 2 + 4 files changed, 104 insertions(+), 16 deletions(-) diff --git a/packages/server/src/adapters/fetch.test.ts b/packages/server/src/adapters/fetch.test.ts index 76d7ebbb..8757f6d8 100644 --- a/packages/server/src/adapters/fetch.test.ts +++ b/packages/server/src/adapters/fetch.test.ts @@ -494,3 +494,62 @@ describe('accept header', () => { }) }) }) + +describe('dynamic params', () => { + const router = os.router({ + deep: os + .route({ + method: 'GET', + path: '/{id}/{id2}', + }) + .input( + z.object({ + id: z.number(), + id2: z.string(), + }), + ) + .handler((input) => input), + + find: os + .route({ + method: 'GET', + path: '/{id}', + }) + .input( + z.object({ + id: z.number(), + }), + ) + .handler((input) => input), + }) + + const handlers = [ + createFetchHandler({ + router, + }), + createFetchHandler({ + router, + serverless: true, + }), + ] + + it.each(handlers)('should handle dynamic params', async (handler) => { + const response = await handler({ + request: new Request('http://localhost/123'), + }) + + expect(response.status).toEqual(200) + expect(response.headers.get('Content-Type')).toEqual('application/json') + expect(await response.json()).toEqual({ id: 123 }) + }) + + it.each(handlers)('should handle deep dynamic params', async (handler) => { + const response = await handler({ + request: new Request('http://localhost/123/dfdsfds'), + }) + + expect(response.status).toEqual(200) + expect(response.headers.get('Content-Type')).toEqual('application/json') + expect(await response.json()).toEqual({ id: 123, id2: 'dfdsfds' }) + }) +}) diff --git a/packages/server/src/adapters/fetch.ts b/packages/server/src/adapters/fetch.ts index 38581531..94af4b90 100644 --- a/packages/server/src/adapters/fetch.ts +++ b/packages/server/src/adapters/fetch.ts @@ -10,6 +10,7 @@ import { type PartialOnUndefinedDeep, get, isPlainObject, + mapValues, trim, } from '@orpc/shared' import { ORPCError } from '@orpc/shared/error' @@ -18,6 +19,7 @@ import { ORPCSerializer, OpenAPIDeserializer, OpenAPISerializer, + zodCoerce, } from '@orpc/transformer' import { LinearRouter } from 'hono/router/linear-router' import { RegExpRouter } from 'hono/router/reg-exp-router' @@ -86,7 +88,7 @@ export function createFetchHandler>( let path: string[] | undefined let procedure: WELL_DEFINED_PROCEDURE | undefined - let params: Record | undefined + let params: Record | undefined if (isORPCTransformer) { path = trim(pathname, '/').split('/').map(decodeURIComponent) @@ -96,13 +98,28 @@ export function createFetchHandler>( procedure = val } } else { - const [[match]] = routing.match( + const [matches, params_] = routing.match( requestOptions.request.method, pathname, ) - path = match?.[0][0] - procedure = match?.[0][1] - params = match?.[1] + + const [match] = matches.sort((a, b) => { + return Object.keys(a[1]).length - Object.keys(b[1]).length + }) + + if (match) { + path = match[0][0] + procedure = match[0][1] + + if (params_) { + params = mapValues( + (match as any)[1]!, + (v) => params_[v as number]!, + ) + } else { + params = match[1] as Record + } + } if (!path || !procedure) { path = trim(pathname, '/').split('/').map(decodeURIComponent) @@ -148,18 +165,28 @@ export function createFetchHandler>( })() const input = (() => { - if ( - params && - Object.keys(params).length > 0 && - (input_ === undefined || isPlainObject(input_)) - ) { - return { - ...params, - ...input_, - } + if (!params || Object.keys(params).length === 0) { + return input_ } - return input_ + const coercedParams = procedure.zz$p.contract.zz$cp.InputSchema + ? (zodCoerce( + procedure.zz$p.contract.zz$cp.InputSchema, + { ...params }, + { + bracketNotation: true, + }, + ) as object) + : params + + if (input_ !== undefined && !isPlainObject(input_)) { + return coercedParams + } + + return { + ...coercedParams, + ...input_, + } })() const caller = createProcedureCaller({ diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 5683402c..8e8f48cb 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -4,4 +4,4 @@ export * from './json' export * from './object' export { isPlainObject } from 'is-what' -export { guard, trim, mapEntries, omit } from 'radash' +export { guard, trim, mapEntries, omit, mapValues } from 'radash' diff --git a/packages/transformer/src/index.ts b/packages/transformer/src/index.ts index 579fbd8b..ebad8723 100644 --- a/packages/transformer/src/index.ts +++ b/packages/transformer/src/index.ts @@ -3,3 +3,5 @@ export * from './openapi/deserializer' export * from './openapi/serializer' export * from './orpc/deserializer' export * from './orpc/serializer' + +export { zodCoerce } from './openapi/zod-coerce'