Skip to content

Commit

Permalink
fix(server): fallback on invalid Accept header
Browse files Browse the repository at this point in the history
  • Loading branch information
unnoq committed Nov 18, 2024
1 parent 8617fe9 commit 90282e4
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 16 deletions.
5 changes: 5 additions & 0 deletions .changeset/thick-shrimps-carry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@orpc/server": patch
---

fix(server): fallback on invalid `Accept` header
94 changes: 94 additions & 0 deletions packages/server/src/adapters/fetch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -397,3 +397,97 @@ describe('file upload', () => {
expect(await file1.text()).toBe('"world"')
})
})


describe("accept header", () => {
const router = os.router({
ping: os.handler(async () => 'pong'),
})
const handler = createFetchHandler({
router,
})

it("application/json", async () => {
const response = await handler({
prefix: '/orpc',
request: new Request('http://localhost/orpc/ping', {
method: 'POST',
headers: {
'Accept': 'application/json',
},
}),
})

expect(response.headers.get('Content-Type')).toEqual('application/json')

expect(await response.json()).toEqual('pong')
})

it("multipart/form-data", async () => {
const response = await handler({
prefix: '/orpc',
request: new Request('http://localhost/orpc/ping', {
method: 'POST',
headers: {
'Accept': 'multipart/form-data',
},
}),
})

expect(response.headers.get('Content-Type')).toContain('multipart/form-data')

const form = await response.formData()
expect(form.get('')).toEqual('pong')
})

it("application/x-www-form-urlencoded", async () => {
const response = await handler({
prefix: '/orpc',
request: new Request('http://localhost/orpc/ping', {
method: 'POST',
headers: {
'Accept': 'application/x-www-form-urlencoded',
},
}),
})

expect(response.headers.get('Content-Type')).toEqual('application/x-www-form-urlencoded')

const params = new URLSearchParams(await response.text())
expect(params.get('')).toEqual('pong')
})

it("*/*", async () => {
const response = await handler({
prefix: '/orpc',
request: new Request('http://localhost/orpc/ping', {
method: 'POST',
headers: {
'Accept': '*/*',
},
}),
})

expect(response.headers.get('Content-Type')).toEqual('application/json')
expect(await response.json()).toEqual('pong')
})

it("invalid", async () => {
const response = await handler({
prefix: '/orpc',
request: new Request('http://localhost/orpc/ping', {
method: 'POST',
headers: {
'Accept': 'invalid',
},
}),
})

expect(response.headers.get('Content-Type')).toEqual('application/json')
expect(await response.json()).toEqual({
"code": "NOT_ACCEPTABLE",
"message": "Unsupported content-type: invalid",
"status": 406,
})
})
})
48 changes: 32 additions & 16 deletions packages/server/src/adapters/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export function createFetchHandler<TRouter extends Router<any>>(
return async (requestOptions) => {
const isORPCTransformer =
requestOptions.request.headers.get(ORPC_HEADER) === ORPC_HEADER_VALUE
const accept = requestOptions.request.headers.get('Accept') ?? undefined
const accept = requestOptions.request.headers.get('Accept') || undefined

const serializer = isORPCTransformer
? new ORPCSerializer()
Expand Down Expand Up @@ -131,8 +131,8 @@ export function createFetchHandler<TRouter extends Router<any>>(
const deserializer = isORPCTransformer
? new ORPCDeserializer()
: new OpenAPIDeserializer({
schema: procedure.zz$p.contract.zz$cp.InputSchema,
})
schema: procedure.zz$p.contract.zz$cp.InputSchema,
})

const input_ = await (async () => {
try {
Expand Down Expand Up @@ -180,21 +180,26 @@ export function createFetchHandler<TRouter extends Router<any>>(
})
})
} catch (e) {
const error =
e instanceof ORPCError
? e
: new ORPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Internal server error',
cause: e,
})
const error = toORPCError(e)

const { body, headers } = serializer.serialize(error.toJSON())
try {
const { body, headers } = serializer.serialize(error.toJSON())

return new Response(body, {
status: error.status,
headers: headers,
})
return new Response(body, {
status: error.status,
headers: headers,
})
} catch (e) {
const error = toORPCError(e)

// fallback to OpenAPI serializer (without accept) when expected serializer has failed
const { body, headers } = new OpenAPISerializer().serialize(error.toJSON())

return new Response(body, {
status: error.status,
headers: headers,
})
}
}
}
}
Expand Down Expand Up @@ -226,3 +231,14 @@ export type FetchHandlerOptions<TRouter extends Router<any>> = {
export interface FetchHandler<TRouter extends Router<any>> {
(options: FetchHandlerOptions<TRouter>): Promise<Response>
}


function toORPCError(e: unknown): ORPCError<any, any> {
return e instanceof ORPCError
? e
: new ORPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Internal server error',
cause: e,
})
}

0 comments on commit 90282e4

Please sign in to comment.