Skip to content

Commit dd2c671

Browse files
Merge pull request #942 from BitGo/VL-2470-header-support
Add support for request headers
2 parents d0d5873 + e8967c6 commit dd2c671

File tree

2 files changed

+101
-17
lines changed

2 files changed

+101
-17
lines changed

packages/openapi-generator/src/route.ts

Lines changed: 60 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { findSymbolInitializer } from './resolveInit';
88
import { errorLeft } from './error';
99

1010
export type Parameter = {
11-
type: 'path' | 'query';
11+
type: 'path' | 'query' | 'header';
1212
name: string;
1313
schema: Schema;
1414
explode?: boolean;
@@ -84,6 +84,22 @@ function parseRequestObject(schema: Schema): E.Either<string, Request> {
8484
}
8585
}
8686

87+
const headerSchema = schema.properties['headers'];
88+
if (headerSchema !== undefined) {
89+
if (headerSchema.type !== 'object') {
90+
return errorLeft('Route headers must be an object');
91+
} else {
92+
for (const [name, prop] of Object.entries(headerSchema.properties)) {
93+
parameters.push({
94+
type: 'header',
95+
name,
96+
schema: prop,
97+
required: headerSchema.required.includes(name),
98+
});
99+
}
100+
}
101+
}
102+
87103
return E.right({
88104
parameters,
89105
body: schema.properties['body'],
@@ -103,6 +119,7 @@ function parseRequestUnion(
103119
// This isn't perfect but it's about as good as we can do in openapi
104120
const parameters: Parameter[] = [];
105121
const querySchema: Schema = { type: 'union', schemas: [] };
122+
const headerSchema: Schema = { type: 'union', schemas: [] };
106123
let body: Schema | undefined;
107124

108125
for (let subSchema of schema.schemas) {
@@ -126,6 +143,9 @@ function parseRequestUnion(
126143
}
127144
(body as CombinedType).schemas.push(subSchema.properties['body']);
128145
}
146+
if (subSchema.properties['headers'] !== undefined) {
147+
headerSchema.schemas.push(subSchema.properties['headers']);
148+
}
129149
}
130150
if (querySchema.schemas.length > 0) {
131151
parameters.push({
@@ -136,6 +156,15 @@ function parseRequestUnion(
136156
schema: querySchema,
137157
});
138158
}
159+
if (headerSchema.schemas.length > 0) {
160+
parameters.push({
161+
type: 'header',
162+
name: 'union',
163+
explode: true,
164+
required: true,
165+
schema: headerSchema,
166+
});
167+
}
139168

140169
const firstSubSchema = schema.schemas[0];
141170
if (firstSubSchema !== undefined && firstSubSchema.type === 'object') {
@@ -203,28 +232,42 @@ function parseRequestSchema(
203232
}
204233
}
205234

235+
export function resolveStringProperty(
236+
project: Project,
237+
schema: Schema | undefined,
238+
name: string,
239+
): E.Either<string, string> {
240+
if (schema === undefined) {
241+
return errorLeft(`Route ${name} is missing`);
242+
} else if (schema.type === 'ref') {
243+
const derefE = derefRequestSchema(project, schema);
244+
if (E.isLeft(derefE)) {
245+
return derefE;
246+
}
247+
return resolveStringProperty(project, derefE.right, name);
248+
} else if (schema.type === 'string' && schema.enum?.length === 1) {
249+
return E.right(schema.enum[0]! as string);
250+
} else {
251+
return errorLeft(`Route ${name} must be a string literal`);
252+
}
253+
}
254+
206255
export function parseRoute(project: Project, schema: Schema): E.Either<string, Route> {
207256
if (schema.type !== 'object') {
208257
return errorLeft('Route must be an object');
209258
}
210259

211-
if (schema.properties['path'] === undefined) {
212-
return errorLeft('Route must have a path');
213-
} else if (
214-
schema.properties['path'].type !== 'string' ||
215-
schema.properties['path'].enum?.length !== 1
216-
) {
217-
return errorLeft('Route path must be a string literal');
260+
const pathE = resolveStringProperty(project, schema.properties['path'], 'path');
261+
if (E.isLeft(pathE)) {
262+
return pathE;
218263
}
264+
const path = pathE.right;
219265

220-
if (schema.properties['method'] === undefined) {
221-
return errorLeft('Route must have a method');
222-
} else if (
223-
schema.properties['method'].type !== 'string' ||
224-
schema.properties['method'].enum?.length !== 1
225-
) {
226-
return errorLeft('Route method must be a string literal');
266+
const methodE = resolveStringProperty(project, schema.properties['method'], 'method');
267+
if (E.isLeft(methodE)) {
268+
return methodE;
227269
}
270+
const method = methodE.right;
228271

229272
const requestSchema = schema.properties['request'];
230273
if (requestSchema === undefined) {
@@ -243,8 +286,8 @@ export function parseRoute(project: Project, schema: Schema): E.Either<string, R
243286
}
244287

245288
return E.right({
246-
path: schema.properties['path'].enum![0] as string,
247-
method: schema.properties['method'].enum![0] as string,
289+
path,
290+
method,
248291
parameters,
249292
response: schema.properties['response'].properties,
250293
...(body !== undefined ? { body } : {}),

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,3 +718,44 @@ testCase('route with operationId', WITH_OPERATION_ID, {
718718
},
719719
},
720720
});
721+
722+
const HEADER_PARAM = `
723+
import * as t from 'io-ts';
724+
import * as h from '@api-ts/io-ts-http';
725+
export const route = h.httpRoute({
726+
path: '/foo',
727+
method: 'GET',
728+
request: h.httpRequest({
729+
headers: {
730+
'x-foo': t.string,
731+
},
732+
}),
733+
response: {
734+
200: t.string
735+
},
736+
});
737+
`;
738+
739+
testCase('header param route', HEADER_PARAM, {
740+
route: {
741+
path: '/foo',
742+
method: 'GET',
743+
parameters: [
744+
{
745+
type: 'header',
746+
name: 'x-foo',
747+
required: true,
748+
schema: {
749+
type: 'string',
750+
primitive: true,
751+
},
752+
},
753+
],
754+
response: {
755+
200: {
756+
type: 'string',
757+
primitive: true,
758+
},
759+
},
760+
},
761+
});

0 commit comments

Comments
 (0)