Skip to content

Commit

Permalink
feat(server): control method on POST request with ?method=METHOD
Browse files Browse the repository at this point in the history
fix: zod-coerce create properties when it not exists
  • Loading branch information
unnoq committed Nov 19, 2024
1 parent 85d8e55 commit 8e9a954
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 10 deletions.
5 changes: 5 additions & 0 deletions .changeset/angry-tips-give.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@orpc/server": minor
---

feat: control method on POST request with ?method=METHOD
5 changes: 5 additions & 0 deletions .changeset/hot-phones-act.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@orpc/transformer": patch
---

fix: zod-coerce create properties when it not exists
1 change: 1 addition & 0 deletions packages/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"@orpc/transformer": "workspace:*"
},
"peerDependencies": {
"@orpc/zod": "workspace:*",
"zod": "^3.23.8"
},
"devDependencies": {
Expand Down
67 changes: 63 additions & 4 deletions packages/server/src/adapters/fetch.test.ts
Original file line number Diff line number Diff line change
@@ -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 '..'
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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)
})
})
13 changes: 8 additions & 5 deletions packages/server/src/adapters/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,13 @@ export function createFetchHandler<TRouter extends Router<any>>(
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
Expand Down Expand Up @@ -179,7 +182,7 @@ export function createFetchHandler<TRouter extends Router<any>>(
) as object)
: params

if (input_ !== undefined && !isPlainObject(input_)) {
if (!isPlainObject(input_)) {
return coercedParams
}

Expand Down
3 changes: 2 additions & 1 deletion packages/server/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"references": [
{ "path": "../contract" },
{ "path": "../shared" },
{ "path": "../transformer" }
{ "path": "../transformer" },
{ "path": "../zod" }
],
"include": ["src"],
"exclude": [
Expand Down
2 changes: 2 additions & 0 deletions packages/transformer/src/openapi/zod-coerce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 8e9a954

Please sign in to comment.