Skip to content

Commit 24b70fa

Browse files
Lonelindraveclassic
authored andcommitted
feat: Support headers (#133)
partially closes #57
1 parent 7448720 commit 24b70fa

File tree

9 files changed

+206
-14
lines changed

9 files changed

+206
-14
lines changed

src/language/typescript/2.0/serializers/operation-object.ts

+46-7
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import {
2525
ArrayParameterObjectCollectionFormat,
2626
BodyParameterObject,
2727
FormDataParameterObject,
28-
HeaderParameterObject,
2928
ParameterObject,
3029
ParameterObjectCodec,
3130
PathParameterObject,
@@ -53,13 +52,18 @@ import {
5352
} from '../../common/data/serialized-fragment';
5453
import { sequenceOptionEither } from '../../../../utils/option';
5554
import { identity } from 'fp-ts/lib/function';
55+
import {
56+
fromSerializedHeaderParameter,
57+
getSerializedHeaderParameterType,
58+
SerializedHeaderParameter,
59+
} from '../../common/data/serialized-header-parameters';
5660

5761
interface Parameters {
5862
readonly pathParameters: PathParameterObject[];
5963
readonly serializedPathParameters: SerializedPathParameter[];
6064
readonly serializedQueryParameter: Option<SerializedType>;
6165
readonly serializedBodyParameter: Option<SerializedType>;
62-
readonly headerParameters: HeaderParameterObject[];
66+
readonly serializedHeadersParameter: Option<SerializedType>;
6367
readonly formDataParameters: FormDataParameterObject[];
6468
readonly queryString: Option<SerializedFragment>;
6569
}
@@ -79,7 +83,7 @@ const getParameters = combineReader(
7983
const serializedPathParameters: SerializedPathParameter[] = [];
8084
const serializedQueryParameters: SerializedType[] = [];
8185
const bodyParameters: BodyParameterObject[] = [];
82-
const headerParameters: HeaderParameterObject[] = [];
86+
const serializedHeaderParameters: SerializedHeaderParameter[] = [];
8387
const formDataParameters: FormDataParameterObject[] = [];
8488
const queryStringFragments: SerializedFragment[] = [];
8589

@@ -129,7 +133,13 @@ const getParameters = combineReader(
129133
break;
130134
}
131135
case 'header': {
132-
headerParameters.push(resolved.right);
136+
const serializedParameter = pipe(
137+
serialized.right,
138+
fromSerializedHeaderParameter(resolved.right.name),
139+
getSerializedHeaderParameterType,
140+
);
141+
142+
serializedHeaderParameters.push(serializedParameter);
133143
break;
134144
}
135145
case 'formData': {
@@ -195,6 +205,16 @@ const getParameters = combineReader(
195205
sequenceOptionEither,
196206
);
197207

208+
const serializedHeadersParameter = pipe(
209+
nonEmptyArray.fromArray(serializedHeaderParameters),
210+
option.map(parameters =>
211+
pipe(
212+
intercalateSerializedTypes(serializedType(';', ',', [], []), parameters),
213+
getSerializedObjectType(),
214+
),
215+
),
216+
);
217+
198218
const queryString = pipe(
199219
nonEmptyArray.fromArray(queryStringFragments),
200220
option.map(queryStringFragments =>
@@ -215,7 +235,7 @@ const getParameters = combineReader(
215235
serializedPathParameters,
216236
serializedQueryParameter,
217237
serializedBodyParameter,
218-
headerParameters,
238+
serializedHeadersParameter,
219239
formDataParameters,
220240
queryString,
221241
}));
@@ -258,7 +278,8 @@ export const serializeOperationObject = combineReader(
258278
(parameters, serializedResponses, clientRef) => {
259279
const hasQueryParameters = isSome(parameters.serializedQueryParameter);
260280
const hasBodyParameters = isSome(parameters.serializedBodyParameter);
261-
const hasParameters = hasQueryParameters || hasBodyParameters;
281+
const hasHeaderParameters = isSome(parameters.serializedHeadersParameter);
282+
const hasParameters = hasQueryParameters || hasBodyParameters || hasHeaderParameters;
262283

263284
const bodyType = pipe(
264285
parameters.serializedBodyParameter,
@@ -282,10 +303,22 @@ export const serializeOperationObject = combineReader(
282303
option.getOrElse(() => ''),
283304
);
284305

306+
const headersType = pipe(
307+
parameters.serializedHeadersParameter,
308+
option.map(headers => `headers: ${headers.type}`),
309+
option.getOrElse(() => ''),
310+
);
311+
312+
const headersIO = pipe(
313+
parameters.serializedHeadersParameter,
314+
option.map(headers => `const headers = ${headers.io}.encode(parameters.headers)`),
315+
option.getOrElse(() => ''),
316+
);
317+
285318
const argsType = concatIf(
286319
hasParameters,
287320
parameters.serializedPathParameters.map(p => p.type),
288-
[`parameters: { ${queryType}${bodyType} }`],
321+
[`parameters: { ${queryType}${bodyType}${headersType} }`],
289322
).join(',');
290323

291324
const type = `
@@ -303,13 +336,15 @@ export const serializeOperationObject = combineReader(
303336
${operationName}: (${argsIO}) => {
304337
${bodyIO}
305338
${queryIO}
339+
${headersIO}
306340
307341
return e.httpClient.chain(
308342
e.httpClient.request({
309343
url: ${getURL(url, parameters.serializedPathParameters)},
310344
method: '${method}',
311345
${when(hasQueryParameters, 'query,')}
312346
${when(hasBodyParameters, 'body,')}
347+
${when(hasHeaderParameters, 'headers')}
313348
}),
314349
value =>
315350
pipe(
@@ -342,6 +377,10 @@ export const serializeOperationObject = combineReader(
342377
parameters.queryString,
343378
option.map(p => p.dependencies),
344379
),
380+
pipe(
381+
parameters.serializedHeadersParameter,
382+
option.map(p => p.dependencies),
383+
),
345384
]),
346385
]),
347386
];

src/language/typescript/3.0/serializers/operation-object.ts

+49-2
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ import {
4949
SerializedFragment,
5050
} from '../../common/data/serialized-fragment';
5151
import { SchemaObjectCodec } from '../../../../schema/3.0/schema-object';
52+
import {
53+
fromSerializedHeaderParameter,
54+
getSerializedHeaderParameterType,
55+
SerializedHeaderParameter,
56+
} from '../../common/data/serialized-header-parameters';
5257

5358
const getOperationName = (pattern: string, operation: OperationObject, method: HTTPMethod): string =>
5459
pipe(
@@ -61,6 +66,7 @@ interface Parameters {
6166
readonly serializedPathParameters: SerializedPathParameter[];
6267
readonly serializedQueryParameter: Option<SerializedType>;
6368
readonly serializedBodyParameter: Option<SerializedType>;
69+
readonly serializedHeadersParameter: Option<SerializedType>;
6470
readonly serializedQueryString: Option<SerializedFragment>;
6571
}
6672

@@ -79,6 +85,7 @@ export const getParameters = combineReader(
7985
const serializedQueryParameters: SerializedType[] = [];
8086
let serializedBodyParameter: Option<SerializedType> = none;
8187
const queryStringFragments: SerializedFragment[] = [];
88+
const serializedHeaderParameters: SerializedHeaderParameter[] = [];
8289

8390
// note that PathItem parameters should go after OperationObject parameters because they have lower priority
8491
// this means that OperationObject can override PathItemObject parameters
@@ -126,6 +133,16 @@ export const getParameters = combineReader(
126133
serializedPathParameters.push(serializedParameter);
127134
break;
128135
}
136+
case 'header': {
137+
const serializedParameter = pipe(
138+
serialized.right,
139+
fromSerializedHeaderParameter(resolved.right.name),
140+
getSerializedHeaderParameterType,
141+
);
142+
143+
serializedHeaderParameters.push(serializedParameter);
144+
break;
145+
}
129146
case 'query': {
130147
const serializedParameter = getSerializedOptionalType(required, serialized.right);
131148
const schema = getParameterObjectSchema(resolved.right);
@@ -217,10 +234,21 @@ export const getParameters = combineReader(
217234
),
218235
);
219236

237+
const serializedHeadersParameter = pipe(
238+
nonEmptyArray.fromArray(serializedHeaderParameters),
239+
option.map(parameters =>
240+
pipe(
241+
intercalateSerializedTypes(serializedType(';', ',', [], []), parameters),
242+
getSerializedObjectType(),
243+
),
244+
),
245+
);
246+
220247
return right({
221248
pathParameters,
222249
serializedPathParameters,
223250
serializedQueryParameter,
251+
serializedHeadersParameter,
224252
serializedBodyParameter,
225253
serializedQueryString,
226254
});
@@ -254,7 +282,8 @@ export const serializeOperationObject = combineReader(
254282
(parameters, serializedResponses, clientRef) => {
255283
const hasQueryParameters = isSome(parameters.serializedQueryParameter);
256284
const hasBodyParameter = isSome(parameters.serializedBodyParameter);
257-
const hasParameters = hasQueryParameters || hasBodyParameter;
285+
const hasHeaderParameters = isSome(parameters.serializedHeadersParameter);
286+
const hasParameters = hasQueryParameters || hasBodyParameter || hasHeaderParameters;
258287

259288
const bodyType = pipe(
260289
parameters.serializedBodyParameter,
@@ -278,10 +307,22 @@ export const serializeOperationObject = combineReader(
278307
option.getOrElse(() => ''),
279308
);
280309

310+
const headersType = pipe(
311+
parameters.serializedHeadersParameter,
312+
option.map(headers => `headers: ${headers.type}`),
313+
option.getOrElse(() => ''),
314+
);
315+
316+
const headersIO = pipe(
317+
parameters.serializedHeadersParameter,
318+
option.map(headers => `const headers = ${headers.io}.encode(parameters.headers)`),
319+
option.getOrElse(() => ''),
320+
);
321+
281322
const argsType = concatIf(
282323
hasParameters,
283324
parameters.serializedPathParameters.map(p => p.type),
284-
[`parameters: { ${queryType}${bodyType} }`],
325+
[`parameters: { ${queryType}${bodyType}${headersType} }`],
285326
).join(',');
286327

287328
const type = `
@@ -299,13 +340,15 @@ export const serializeOperationObject = combineReader(
299340
${operationName}: (${argsIO}) => {
300341
${bodyIO}
301342
${queryIO}
343+
${headersIO}
302344
303345
return e.httpClient.chain(
304346
e.httpClient.request({
305347
url: ${getURL(pattern, parameters.serializedPathParameters)},
306348
method: '${method}',
307349
${when(hasQueryParameters, 'query,')}
308350
${when(hasBodyParameter, 'body,')}
351+
${when(hasHeaderParameters, 'headers')}
309352
}),
310353
value =>
311354
pipe(
@@ -338,6 +381,10 @@ export const serializeOperationObject = combineReader(
338381
parameters.serializedQueryString,
339382
option.map(p => p.dependencies),
340383
),
384+
pipe(
385+
parameters.serializedHeadersParameter,
386+
option.map(p => p.dependencies),
387+
),
341388
]),
342389
]),
343390
];

src/language/typescript/common/bundled/client.ts

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const client = `
1818
readonly url: string;
1919
readonly query?: string;
2020
readonly body?: unknown;
21+
readonly headers?: Record<string, unknown>;
2122
}
2223
2324
export interface HTTPClient<F> extends MonadThrow<F> {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { Ref, uniqRefs } from '../../../../utils/ref';
2+
import { getTypeName } from '../utils';
3+
import { serializedDependency, SerializedDependency, uniqSerializedDependencies } from './serialized-dependency';
4+
import { SerializedParameter } from './serialized-parameter';
5+
6+
export interface SerializedHeaderParameter extends SerializedParameter {
7+
readonly name: string;
8+
}
9+
10+
export const serializedHeaderParameter = (
11+
name: string,
12+
type: string,
13+
io: string,
14+
isRequired: boolean,
15+
dependencies: SerializedDependency[],
16+
refs: Ref[],
17+
): SerializedHeaderParameter => ({
18+
name,
19+
type,
20+
io,
21+
isRequired,
22+
dependencies: uniqSerializedDependencies(dependencies),
23+
refs: uniqRefs(refs),
24+
});
25+
26+
export const fromSerializedHeaderParameter = (name: string) => (
27+
serialized: SerializedParameter,
28+
): SerializedHeaderParameter => ({
29+
...serialized,
30+
name,
31+
});
32+
33+
export const getSerializedHeaderParameterType = (serialized: SerializedHeaderParameter): SerializedHeaderParameter => {
34+
const name = getTypeName(serialized.name);
35+
return serializedHeaderParameter(
36+
name,
37+
`${name}: ${serialized.isRequired ? serialized.type : `option.Option<${serialized.type}>`}`,
38+
`${name}: ${serialized.isRequired ? serialized.io : `optionFromNullable(${serialized.io})`}`,
39+
serialized.isRequired,
40+
serialized.dependencies.concat(
41+
serialized.isRequired
42+
? []
43+
: [
44+
serializedDependency('optionFromNullable', 'io-ts-types/lib/optionFromNullable'),
45+
serializedDependency('option', 'fp-ts'),
46+
],
47+
),
48+
serialized.refs,
49+
);
50+
};

test/specs/2.0/json/swagger.json

+19-5
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,7 @@
4141
"https",
4242
"http"
4343
],
44-
"parameters": {
45-
},
44+
"parameters": {},
4645
"paths": {
4746
"/pet": {
4847
"post": {
@@ -70,6 +69,21 @@
7069
"schema": {
7170
"$ref": "./common.json#/definitions/Pet"
7271
}
72+
},
73+
{
74+
"in": "header",
75+
"name": "Custom-Header",
76+
"required": true,
77+
"type": "string"
78+
},
79+
{
80+
"in": "header",
81+
"name": "Custom-Header-Two",
82+
"required": true,
83+
"type": "array",
84+
"items": {
85+
"type": "string"
86+
}
7387
}
7488
],
7589
"responses": {
@@ -122,9 +136,9 @@
122136
}
123137
}
124138
},
125-
"400":{
126-
"description":"Bad Request",
127-
"schema":{
139+
"400": {
140+
"description": "Bad Request",
141+
"schema": {
128142
"$ref": "./common.json#/definitions/ErrorResponse"
129143
}
130144
},

test/specs/2.0/yaml/demo.yml

+14
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,20 @@ paths:
3737
items:
3838
type: integer
3939
collectionFormat: multi
40+
- in: header
41+
name: Custom-Header
42+
required: true
43+
type: string
44+
- in: header
45+
name: Custom-Header-Two
46+
required: false
47+
type: string
48+
- in: header
49+
name: Custom-Header-Three
50+
required: true
51+
type: array
52+
items:
53+
type: string
4054
operationId: test
4155
responses:
4256
200:

0 commit comments

Comments
 (0)