Skip to content

Commit 6d75718

Browse files
authored
fix(server): ResponseHeadersPlugin context reference issues (#293)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Refactor** - Enhanced response header management to ensure consistent propagation of custom header values during API responses. - **Tests** - Reorganized and expanded test cases to verify proper header handling and context management, improving overall reliability. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent d17ef5e commit 6d75718

2 files changed

Lines changed: 109 additions & 41 deletions

File tree

packages/server/src/plugins/response-headers.test.ts

Lines changed: 100 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,48 +3,112 @@ import { OpenAPIHandler } from '../../../openapi/src/adapters/fetch/openapi-hand
33
import { os } from '../builder'
44
import { ResponseHeadersPlugin } from './response-headers'
55

6-
it('responseHeadersPlugin', async () => {
7-
const router = os
8-
.$context<ResponseHeadersPluginContext>()
9-
.use(({ context, next }) => {
10-
context.resHeaders?.set('x-custom-1', 'mid')
11-
context.resHeaders?.set('x-custom-2', 'mid')
12-
context.resHeaders?.set('x-custom-3', 'mid')
13-
context.resHeaders?.set('x-custom-4', 'mid')
14-
15-
return next()
16-
})
17-
.route({
18-
method: 'GET',
19-
path: '/ping',
20-
outputStructure: 'detailed',
6+
describe('responseHeadersPlugin', () => {
7+
it('works', async () => {
8+
const router = os
9+
.$context<ResponseHeadersPluginContext>()
10+
.use(({ context, next }) => {
11+
context.resHeaders?.set('x-custom-1', 'mid')
12+
context.resHeaders?.set('x-custom-2', 'mid')
13+
context.resHeaders?.set('x-custom-3', 'mid')
14+
context.resHeaders?.set('x-custom-4', 'mid')
15+
16+
return next()
17+
})
18+
.route({
19+
method: 'GET',
20+
path: '/ping',
21+
outputStructure: 'detailed',
22+
})
23+
.handler(() => {
24+
return {
25+
headers: {
26+
'x-custom-1': 'value',
27+
'x-custom-2': ['1', '2'],
28+
'x-custom-3': undefined,
29+
},
30+
}
31+
})
32+
33+
const handler = new OpenAPIHandler(router, {
34+
plugins: [
35+
new ResponseHeadersPlugin(),
36+
],
2137
})
22-
.handler(() => {
23-
return {
24-
headers: {
25-
'x-custom-1': 'value',
26-
'x-custom-2': ['1', '2'],
27-
'x-custom-3': undefined,
28-
},
29-
}
38+
39+
const { response } = await handler.handle(new Request('https://example.com/ping'))
40+
41+
if (!response) {
42+
throw new Error('response is undefined')
43+
}
44+
45+
expect(response.headers.get('x-custom-1')).toBe('value, mid')
46+
expect(response.headers.get('x-custom-2')).toBe('1, 2, mid')
47+
expect(response.headers.get('x-custom-3')).toBe('mid')
48+
expect(response.headers.get('x-custom-4')).toBe('mid')
49+
50+
await handler.handle(new Request('https://example.com/not_found'))
51+
})
52+
53+
it('should clone the context to avoid reference issues', async () => {
54+
const router = os
55+
.$context<ResponseHeadersPluginContext>()
56+
.route({
57+
method: 'GET',
58+
path: '/ping',
59+
})
60+
.handler(() => 'ping')
61+
62+
const interceptor = vi.fn(({ next }) => next())
63+
64+
const handler = new OpenAPIHandler(router, {
65+
plugins: [
66+
new ResponseHeadersPlugin(),
67+
],
68+
interceptors: [
69+
interceptor,
70+
],
3071
})
3172

32-
const handler = new OpenAPIHandler(router, {
33-
plugins: [
34-
new ResponseHeadersPlugin(),
35-
],
73+
const context = { a: 'value' }
74+
await handler.handle(new Request('https://example.com/ping'), { context })
75+
76+
expect(interceptor).toHaveBeenCalledOnce()
77+
expect(interceptor).toHaveBeenCalledWith(expect.objectContaining({
78+
context: { ...context, resHeaders: expect.any(Headers) },
79+
}))
80+
81+
expect(interceptor.mock.calls[0]![0].context).not.toBe(context)
3682
})
3783

38-
const { response } = await handler.handle(new Request('https://example.com/ping'))
84+
it('should use the provided resHeaders when already defined', async () => {
85+
const router = os
86+
.$context<ResponseHeadersPluginContext>()
87+
.route({
88+
method: 'GET',
89+
path: '/ping',
90+
})
91+
.handler(() => 'ping')
3992

40-
if (!response) {
41-
throw new Error('response is undefined')
42-
}
93+
const interceptor = vi.fn(({ next }) => next())
4394

44-
expect(response.headers.get('x-custom-1')).toBe('value, mid')
45-
expect(response.headers.get('x-custom-2')).toBe('1, 2, mid')
46-
expect(response.headers.get('x-custom-3')).toBe('mid')
47-
expect(response.headers.get('x-custom-4')).toBe('mid')
95+
const handler = new OpenAPIHandler(router, {
96+
plugins: [
97+
new ResponseHeadersPlugin(),
98+
],
99+
interceptors: [
100+
interceptor,
101+
],
102+
})
103+
104+
const context = { a: 'value', resHeaders: new Headers() }
105+
await handler.handle(new Request('https://example.com/ping'), { context })
48106

49-
await handler.handle(new Request('https://example.com/not_found'))
107+
expect(interceptor).toHaveBeenCalledOnce()
108+
expect(interceptor).toHaveBeenCalledWith(expect.objectContaining({
109+
context,
110+
}))
111+
112+
expect(interceptor.mock.calls[0]![0].context.resHeaders).toBe(context.resHeaders)
113+
})
50114
})

packages/server/src/plugins/response-headers.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,23 @@ export class ResponseHeadersPlugin<T extends ResponseHeadersPluginContext> imple
99
options.rootInterceptors ??= []
1010

1111
options.rootInterceptors.push(async (interceptorOptions) => {
12-
const headers = new Headers()
12+
const resHeaders = interceptorOptions.context.resHeaders ?? new Headers()
1313

14-
interceptorOptions.context.resHeaders = headers
15-
16-
const result = await interceptorOptions.next()
14+
const result = await interceptorOptions.next({
15+
...interceptorOptions,
16+
context: {
17+
...interceptorOptions.context,
18+
resHeaders,
19+
},
20+
})
1721

1822
if (!result.matched) {
1923
return result
2024
}
2125

2226
const responseHeaders = result.response.headers
2327

24-
for (const [key, value] of headers) {
28+
for (const [key, value] of resHeaders) {
2529
if (Array.isArray(responseHeaders[key])) {
2630
responseHeaders[key].push(value)
2731
}

0 commit comments

Comments
 (0)