Skip to content

Commit 486df33

Browse files
Merge pull request #998 from BitGo/deduplicate-headers
fix: deduplicate header parameters
2 parents 18321bd + 4d1de4b commit 486df33

File tree

2 files changed

+83
-17
lines changed

2 files changed

+83
-17
lines changed

packages/openapi-generator/src/route.ts

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -157,25 +157,30 @@ function parseRequestUnion(
157157
});
158158
}
159159
if (headerSchema.schemas.length > 0) {
160-
// For headers in unions, take properties from first schema that has headers
161-
// Also not perfect but we cannot use the `explode: true` trick for headers
162-
const firstHeaderSchema = schema.schemas.find(
163-
(s) => s.type === 'object' && s.properties['headers']?.type === 'object',
164-
);
165-
if (
166-
firstHeaderSchema?.type === 'object' &&
167-
firstHeaderSchema.properties['headers']?.type === 'object'
168-
) {
169-
const headers = firstHeaderSchema.properties['headers'];
170-
for (const [name, prop] of Object.entries(headers.properties)) {
171-
parameters.push({
172-
type: 'header',
173-
name,
174-
schema: prop,
175-
required: headers.required.includes(name),
176-
});
160+
// For headers in unions, deduplicate and merge properties from all schemas
161+
const headerParams = new Map<string, Parameter>();
162+
163+
for (const subSchema of schema.schemas) {
164+
if (
165+
subSchema.type === 'object' &&
166+
subSchema.properties['headers']?.type === 'object'
167+
) {
168+
const headers = subSchema.properties['headers'];
169+
for (const [name, prop] of Object.entries(headers.properties)) {
170+
// Only add if not already present
171+
if (!headerParams.has(name)) {
172+
headerParams.set(name, {
173+
type: 'header',
174+
name,
175+
schema: prop,
176+
required: headers.required.includes(name),
177+
});
178+
}
179+
}
177180
}
178181
}
182+
183+
parameters.push(...headerParams.values());
179184
}
180185

181186
const firstSubSchema = schema.schemas[0];

packages/openapi-generator/test/openapi/union.test.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,67 @@ testCase("route with unknown unions", ROUTE_WITH_UNKNOWN_UNIONS, {
375375
},
376376
});
377377

378+
const ROUTE_WITH_DUPLICATE_HEADERS = `
379+
import * as t from 'io-ts';
380+
import * as h from '@api-ts/io-ts-http';
381+
382+
export const route = h.httpRoute({
383+
path: '/foo',
384+
method: 'GET',
385+
request: t.union([
386+
h.httpRequest({
387+
headers: {
388+
'x-foo': t.string,
389+
'x-common': t.string,
390+
},
391+
}),
392+
h.httpRequest({
393+
headers: {
394+
'x-bar': t.number,
395+
'x-common': t.string,
396+
},
397+
}),
398+
]),
399+
response: {
400+
200: t.string,
401+
},
402+
});
403+
`;
404+
405+
testCase("route with duplicate headers in request union", ROUTE_WITH_DUPLICATE_HEADERS, {
406+
info: {
407+
title: 'Test',
408+
version: '1.0.0'
409+
},
410+
openapi: '3.0.3',
411+
paths: {
412+
'/foo': {
413+
get: {
414+
parameters: [
415+
{ in: 'header', name: 'x-foo', required: true, schema: { type: 'string' } },
416+
{ in: 'header', name: 'x-common', required: true, schema: { type: 'string' } },
417+
{ in: 'header', name: 'x-bar', required: true, schema: { type: 'number' } },
418+
],
419+
responses: {
420+
'200': {
421+
description: 'OK',
422+
content: {
423+
'application/json': {
424+
schema: {
425+
type: 'string'
426+
}
427+
}
428+
}
429+
}
430+
}
431+
}
432+
}
433+
},
434+
components: {
435+
schemas: {}
436+
}
437+
});
438+
378439
const ROUTE_WITH_REQUEST_UNION = `
379440
import * as t from 'io-ts';
380441
import * as h from '@api-ts/io-ts-http';

0 commit comments

Comments
 (0)