From 8e9a954ab8a13a4d968caaf4aa67b70c2d38c914 Mon Sep 17 00:00:00 2001 From: unnoq Date: Tue, 19 Nov 2024 21:19:17 +0700 Subject: [PATCH] feat(server): control method on POST request with ?method=METHOD fix: zod-coerce create properties when it not exists --- .changeset/angry-tips-give.md | 5 ++ .changeset/hot-phones-act.md | 5 ++ packages/server/package.json | 1 + packages/server/src/adapters/fetch.test.ts | 67 +++++++++++++++++-- packages/server/src/adapters/fetch.ts | 13 ++-- packages/server/tsconfig.json | 3 +- .../transformer/src/openapi/zod-coerce.ts | 2 + pnpm-lock.yaml | 3 + 8 files changed, 89 insertions(+), 10 deletions(-) create mode 100644 .changeset/angry-tips-give.md create mode 100644 .changeset/hot-phones-act.md diff --git a/.changeset/angry-tips-give.md b/.changeset/angry-tips-give.md new file mode 100644 index 00000000..0248908b --- /dev/null +++ b/.changeset/angry-tips-give.md @@ -0,0 +1,5 @@ +--- +"@orpc/server": minor +--- + +feat: control method on POST request with ?method=METHOD diff --git a/.changeset/hot-phones-act.md b/.changeset/hot-phones-act.md new file mode 100644 index 00000000..3c4cc55e --- /dev/null +++ b/.changeset/hot-phones-act.md @@ -0,0 +1,5 @@ +--- +"@orpc/transformer": patch +--- + +fix: zod-coerce create properties when it not exists diff --git a/packages/server/package.json b/packages/server/package.json index df6890cf..c54b3d26 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -51,6 +51,7 @@ "@orpc/transformer": "workspace:*" }, "peerDependencies": { + "@orpc/zod": "workspace:*", "zod": "^3.23.8" }, "devDependencies": { diff --git a/packages/server/src/adapters/fetch.test.ts b/packages/server/src/adapters/fetch.test.ts index 8757f6d8..091e4da3 100644 --- a/packages/server/src/adapters/fetch.test.ts +++ b/packages/server/src/adapters/fetch.test.ts @@ -1,4 +1,5 @@ import { ORPC_HEADER, ORPC_HEADER_VALUE } from '@orpc/contract' +import { oz } from '@orpc/zod' import { describe, expect, it } from 'vitest' import { z } from 'zod' import { os, ORPCError } from '..' @@ -499,13 +500,14 @@ describe('dynamic params', () => { const router = os.router({ deep: os .route({ - method: 'GET', + method: 'POST', path: '/{id}/{id2}', }) .input( z.object({ id: z.number(), id2: z.string(), + file: oz.file(), }), ) .handler((input) => input), @@ -544,12 +546,69 @@ describe('dynamic params', () => { }) it.each(handlers)('should handle deep dynamic params', async (handler) => { + const form = new FormData() + form.append('file', new Blob(['hello']), 'hello.txt') + const response = await handler({ - request: new Request('http://localhost/123/dfdsfds'), + request: new Request('http://localhost/123/dfdsfds', { + method: 'POST', + body: form, + }), }) expect(response.status).toEqual(200) - expect(response.headers.get('Content-Type')).toEqual('application/json') - expect(await response.json()).toEqual({ id: 123, id2: 'dfdsfds' }) + const rForm = await response.formData() + expect(rForm.get('id')).toEqual('123') + expect(rForm.get('id2')).toEqual('dfdsfds') + }) +}) + +describe('can control method on POST request', () => { + const router = os.router({ + update: os + .route({ + method: 'PUT', + path: '/{id}', + }) + .input( + z.object({ + id: z.number(), + file: oz.file(), + }), + ) + .handler((input) => input), + }) + + const handlers = [ + createFetchHandler({ + router, + }), + createFetchHandler({ + router, + serverless: true, + }), + ] + + it.each(handlers)('work', async (handler) => { + const form = new FormData() + form.set('file', new File(['hello'], 'hello.txt')) + + const response = await handler({ + request: new Request('http://localhost/123', { + method: 'POST', + body: form, + }), + }) + + expect(response.status).toEqual(404) + + const response2 = await handler({ + request: new Request('http://localhost/123?method=PUT', { + method: 'POST', + body: form, + }), + }) + + expect(response2.status).toEqual(200) }) }) diff --git a/packages/server/src/adapters/fetch.ts b/packages/server/src/adapters/fetch.ts index 94af4b90..6317cca9 100644 --- a/packages/server/src/adapters/fetch.ts +++ b/packages/server/src/adapters/fetch.ts @@ -98,10 +98,13 @@ export function createFetchHandler>( procedure = val } } else { - const [matches, params_] = routing.match( - requestOptions.request.method, - pathname, - ) + const customMethod = + requestOptions.request.method === 'POST' + ? url.searchParams.get('method')?.toUpperCase() + : undefined + const method = customMethod || requestOptions.request.method + + const [matches, params_] = routing.match(method, pathname) const [match] = matches.sort((a, b) => { return Object.keys(a[1]).length - Object.keys(b[1]).length @@ -179,7 +182,7 @@ export function createFetchHandler>( ) as object) : params - if (input_ !== undefined && !isPlainObject(input_)) { + if (!isPlainObject(input_)) { return coercedParams } diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json index 936ce5d5..99061213 100644 --- a/packages/server/tsconfig.json +++ b/packages/server/tsconfig.json @@ -6,7 +6,8 @@ "references": [ { "path": "../contract" }, { "path": "../shared" }, - { "path": "../transformer" } + { "path": "../transformer" }, + { "path": "../zod" } ], "include": ["src"], "exclude": [ diff --git a/packages/transformer/src/openapi/zod-coerce.ts b/packages/transformer/src/openapi/zod-coerce.ts index 0bc4548b..138b56ef 100644 --- a/packages/transformer/src/openapi/zod-coerce.ts +++ b/packages/transformer/src/openapi/zod-coerce.ts @@ -219,6 +219,8 @@ export function zodCoerce( ]) for (const k of keys) { + if (!(k in value)) continue + const v = value[k] newObj[k] = zodCoerce( schema_.shape[k] ?? schema_._def.catchall, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 08ec3872..c24b7704 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -234,6 +234,9 @@ importers: '@orpc/transformer': specifier: workspace:* version: link:../transformer + '@orpc/zod': + specifier: workspace:* + version: link:../zod zod: specifier: ^3.23.8 version: 3.23.8