Skip to content

Commit 37e8837

Browse files
authored
fix(openapi): only treat actual array values as multi-value headers when serializing (#1745)
Header values that serialize into arrays (`Set`, `Map`, custom handlers) were treated as multi-value headers and split into multiple lines. Now only an actual array value is a multi-value header — the structure is checked before serialization, and serialized-to-array values are merged into a single comma-delimited line. ## Fixes - `new Set(['a', 'b'])` as a header value now produces the single line `a,b` instead of two separate header lines. - Each item of a multi-value array is now serialized individually, so items like `Set` or plain objects render as `b,c` / `k,v` instead of `[object Object]`. - Single values and multi-value items share the same formatting logic, so a value formats identically whether it appears alone or inside an array. ## Testing - New tests cover serialized-to-array merging and per-item serialization of multi-value arrays; all 423 `@orpc/openapi` tests pass, typecheck and lint clean.
1 parent 5b554ff commit 37e8837

2 files changed

Lines changed: 65 additions & 13 deletions

File tree

packages/openapi/src/adapters/standard/utils.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,24 @@ describe('serializeHeaders', () => {
4040
})
4141
})
4242

43+
it('merges values that serialize into arrays as a single comma-delimited line', () => {
44+
expect(serializeHeaders({
45+
'x-set': new Set(['a', 'b']),
46+
'x-map': new Map([['k1', 'v1'], ['k2', 'v2']]),
47+
}, serializer)).toEqual({
48+
'x-set': 'a,b',
49+
'x-map': 'k1,v1,k2,v2',
50+
})
51+
})
52+
53+
it('treats only actual arrays as multi-value headers, array items stay a single line each', () => {
54+
expect(serializeHeaders({
55+
'x-array': ['a', new Set(['b', 'c']), { k: 'v' }],
56+
}, serializer)).toEqual({
57+
'x-array': ['a', 'b,c', 'k,v'],
58+
})
59+
})
60+
4361
it('drops undefined and null values, including array items', () => {
4462
const serialized = serializeHeaders({
4563
'x-null': null,

packages/openapi/src/adapters/standard/utils.ts

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,59 @@ export function serializeHeaders(
99
const result = new NullProtoObj<Record<string, string | string[]>>()
1010

1111
for (const [key, value] of Object.entries(headers)) {
12-
const serialized = serializer.serialize(value)
12+
/**
13+
* Only an actual array value represents a multi-value header (sent as multiple lines).
14+
* Serialization can turn non-array values into arrays (e.g. Set, Map, custom handlers),
15+
* and those must stay a single line, so the structure is checked before serializing.
16+
*/
17+
if (Array.isArray(value)) {
18+
const lines: string[] = []
1319

14-
if (Array.isArray(serialized)) {
15-
result[key] = serialized
16-
.filter(item => item !== undefined && item !== null)
17-
.map(String)
18-
}
20+
for (const item of value) {
21+
const line = serializeHeaderValue(item, serializer)
22+
23+
if (line !== undefined) {
24+
lines.push(line)
25+
}
26+
}
1927

20-
else if (isTypescriptObject(serialized)) {
21-
result[key] = Object.entries(serialized)
22-
.filter(([, val]) => val !== undefined && val !== null)
23-
.map(([key, val]) => `${String(key)},${String(val)}`)
24-
.join(',')
28+
result[key] = lines
29+
continue
2530
}
2631

27-
else if (serialized !== undefined && serialized !== null) {
28-
result[key] = String(serialized)
32+
const line = serializeHeaderValue(value, serializer)
33+
34+
if (line !== undefined) {
35+
result[key] = line
2936
}
3037
}
3138

3239
return result
3340
}
41+
42+
function serializeHeaderValue(
43+
value: unknown,
44+
serializer: Pick<OpenAPISerializer, 'serialize'>,
45+
): string | undefined {
46+
const serialized = serializer.serialize(value)
47+
48+
if (Array.isArray(serialized)) {
49+
return serialized
50+
.filter(item => item !== undefined && item !== null)
51+
.map(String)
52+
.join(',')
53+
}
54+
55+
if (isTypescriptObject(serialized)) {
56+
return Object.entries(serialized)
57+
.filter(([, val]) => val !== undefined && val !== null)
58+
.map(([key, val]) => `${String(key)},${String(val)}`)
59+
.join(',')
60+
}
61+
62+
if (serialized !== undefined && serialized !== null) {
63+
return String(serialized)
64+
}
65+
66+
return undefined
67+
}

0 commit comments

Comments
 (0)