Skip to content

Commit 73ebe10

Browse files
committed
[IO-363] add Accept and ContentType headers
1 parent 4f9ad6f commit 73ebe10

File tree

7 files changed

+184
-81
lines changed

7 files changed

+184
-81
lines changed

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

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import {
2+
DEFAULT_MEDIA_TYPE,
23
getJSDoc,
34
getKindValue,
5+
getResponseTypeFromMediaType,
46
getSafePropertyName,
57
getTypeName,
68
getURL,
@@ -38,12 +40,12 @@ import {
3840
} from '../../common/data/serialized-path-parameter';
3941
import { concatIf } from '../../../../utils/array';
4042
import { when } from '../../../../utils/string';
41-
import { serializeRequestBodyObject } from './request-body-object';
43+
import { getRequestMedia, serializeRequestBodyObject } from './request-body-object';
4244
import { ResolveRefContext, fromString, getRelativePath, Ref } from '../../../../utils/ref';
4345
import { OperationObject } from '../../../../schema/3.0/operation-object';
4446
import { ParameterObject, ParameterObjectCodec } from '../../../../schema/3.0/parameter-object';
4547
import { RequestBodyObjectCodec } from '../../../../schema/3.0/request-body-object';
46-
import { chain, isSome, none, Option, some, map, fromEither, fold } from 'fp-ts/lib/Option';
48+
import { chain, isSome, none, Option, some, map, fromEither, getOrElse, fold } from 'fp-ts/lib/Option';
4749
import { constFalse } from 'fp-ts/lib/function';
4850
import { clientRef } from '../../common/bundled/client';
4951
import { Kind } from '../../../../utils/types';
@@ -58,13 +60,14 @@ import {
5860
SerializedFragment,
5961
} from '../../common/data/serialized-fragment';
6062
import { SchemaObjectCodec } from '../../../../schema/3.0/schema-object';
61-
import { lookup, keys } from 'fp-ts/lib/Record';
63+
import { lookup } from 'fp-ts/lib/Record';
6264
import { ResponseObjectCodec } from '../../../../schema/3.0/response-object';
6365
import {
6466
fromSerializedHeaderParameter,
6567
getSerializedHeaderParameterType,
6668
SerializedHeaderParameter,
6769
} from '../../common/data/serialized-header-parameters';
70+
import { getResponseMedia } from './response-object';
6871

6972
const getOperationName = (pattern: string, operation: OperationObject, method: HTTPMethod): string =>
7073
pipe(
@@ -266,9 +269,6 @@ export const getParameters = combineReader(
266269
},
267270
);
268271

269-
const blobMediaRegexp = /^(video|audio|image|application)/;
270-
const textMediaRegexp = /^text/;
271-
272272
export const serializeOperationObject = combineReader(
273273
ask<ResolveRefContext>(),
274274
getParameters,
@@ -289,7 +289,7 @@ export const serializeOperationObject = combineReader(
289289
);
290290

291291
const serializedResponses = serializeResponsesObject(from)(operation.responses);
292-
const responseType: XHRResponseType = pipe(
292+
const mediaType: string = pipe(
293293
SUCCESSFUL_CODES,
294294
array.findFirstMap(code => lookup(code, operation.responses)),
295295
chain(response =>
@@ -298,23 +298,30 @@ export const serializeOperationObject = combineReader(
298298
: some(response),
299299
),
300300
chain(response => response.content),
301-
map(keys),
301+
chain(getResponseMedia),
302+
map(({ key }) => key),
303+
getOrElse(() => DEFAULT_MEDIA_TYPE),
304+
);
305+
const responseType: XHRResponseType = getResponseTypeFromMediaType(mediaType);
306+
const serializedContentType = pipe(
307+
operation.requestBody,
308+
chain(requestBody =>
309+
ReferenceObjectCodec.is(requestBody)
310+
? fromEither(e.resolveRef(requestBody.$ref, RequestBodyObjectCodec))
311+
: some(requestBody),
312+
),
313+
map(request => request.content),
314+
chain(getRequestMedia),
315+
map(({ key }) => key),
302316
fold(
303-
() => 'json',
304-
types => {
305-
if (types.includes('application/json')) {
306-
return 'json';
307-
}
308-
if (types.some(s => blobMediaRegexp.test(s))) {
309-
return 'blob';
310-
}
311-
if (types.some(s => textMediaRegexp.test(s))) {
312-
return 'text';
313-
}
314-
return 'json';
315-
},
317+
() => '',
318+
contentType => `'Content-type': '${contentType}',`,
316319
),
317320
);
321+
const requestHeaders = `{
322+
Accept: '${mediaType}',
323+
${serializedContentType}
324+
}`;
318325

319326
return combineEither(
320327
parameters,
@@ -382,6 +389,7 @@ export const serializeOperationObject = combineReader(
382389
${bodyIO}
383390
${queryIO}
384391
${headersIO}
392+
const requestHeaders = ${requestHeaders}
385393
386394
return e.httpClient.chain(
387395
e.httpClient.request({
@@ -390,7 +398,7 @@ export const serializeOperationObject = combineReader(
390398
responseType: '${responseType}',
391399
${when(hasQueryParameters, 'query,')}
392400
${when(hasBodyParameter, 'body,')}
393-
${when(hasHeaderParameters, 'headers')}
401+
headers: {${hasHeaderParameters ? '...headers,' : ''} ...requestHeaders}
394402
}),
395403
value =>
396404
pipe(
Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,59 @@
11
import { serializeSchemaObject } from './schema-object';
2-
import { getSerializedRefType, SerializedType } from '../../common/data/serialized-type';
2+
import {
3+
getSerializedBlobType,
4+
getSerializedRefType,
5+
SerializedType,
6+
SERIALIZED_STRING_TYPE,
7+
} from '../../common/data/serialized-type';
38
import { Either, mapLeft } from 'fp-ts/lib/Either';
49
import { pipe } from 'fp-ts/lib/pipeable';
510
import { either, option } from 'fp-ts';
611
import { fromString, Ref } from '../../../../utils/ref';
712
import { RequestBodyObject } from '../../../../schema/3.0/request-body-object';
813
import { ReferenceObjectCodec, ReferenceObject } from '../../../../schema/3.0/reference-object';
914
import { SchemaObject } from '../../../../schema/3.0/schema-object';
10-
import { getKeyMatchValue } from '../../common/utils';
15+
import { getKeyMatchValue, getResponseTypeFromMediaType, XHRResponseType } from '../../common/utils';
16+
import { MediaTypeObject } from '../../../../schema/3.0/media-type-object';
17+
18+
const requestMediaRegexp = /^(video|audio|image|application|text|multipart\/form-data)/;
19+
export const getRequestMedia = (content: Record<string, MediaTypeObject>) =>
20+
getKeyMatchValue(content, requestMediaRegexp);
1121

1222
export const serializeRequestBodyObject = (from: Ref, body: RequestBodyObject): Either<Error, SerializedType> =>
1323
pipe(
14-
getSchema(body),
15-
either.chain(schema =>
16-
ReferenceObjectCodec.is(schema)
24+
getRequestMedia(body.content),
25+
option.chain(({ key: mediaType, value: { schema } }) =>
26+
pipe(
27+
schema,
28+
option.map(schema => ({ mediaType, schema })),
29+
),
30+
),
31+
either.fromOption(() => new Error('No schema found for ReqeustBodyObject')),
32+
either.chain(({ mediaType, schema }) => {
33+
const resType = getResponseTypeFromMediaType(mediaType);
34+
return serializeRequestSchema(resType, schema, from);
35+
}),
36+
);
37+
38+
const serializeRequestSchema = (
39+
responseType: XHRResponseType,
40+
schema: ReferenceObject | SchemaObject,
41+
from: Ref,
42+
): Either<Error, SerializedType> => {
43+
switch (responseType) {
44+
case 'json':
45+
return ReferenceObjectCodec.is(schema)
1746
? pipe(
18-
schema.$ref,
19-
fromString,
47+
fromString(schema.$ref),
2048
mapLeft(
2149
() => new Error(`Invalid MediaObject.content.$ref "${schema.$ref}" for RequestBodyObject`),
2250
),
2351
either.map(getSerializedRefType(from)),
2452
)
25-
: serializeSchemaObject(from)(schema),
26-
),
27-
);
28-
29-
const requestMediaRegexp = /^(video|audio|image|application|text|multipart\/form-data)/;
30-
const getSchema = (requestBodyObject: RequestBodyObject): Either<Error, ReferenceObject | SchemaObject> =>
31-
pipe(
32-
getKeyMatchValue(requestBodyObject.content, requestMediaRegexp),
33-
option.chain(media => media.schema),
34-
either.fromOption(() => new Error('No schema found for ReqeustBodyObject')),
35-
);
53+
: serializeSchemaObject(from)(schema);
54+
case 'text':
55+
return either.right(SERIALIZED_STRING_TYPE);
56+
case 'blob':
57+
return getSerializedBlobType(from);
58+
}
59+
};
Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,57 @@
1-
import { SerializedType, getSerializedRefType } from '../../common/data/serialized-type';
1+
import {
2+
SerializedType,
3+
getSerializedRefType,
4+
SERIALIZED_STRING_TYPE,
5+
getSerializedBlobType,
6+
} from '../../common/data/serialized-type';
27
import { pipe } from 'fp-ts/lib/pipeable';
38
import { serializeSchemaObject } from './schema-object';
49
import { Either } from 'fp-ts/lib/Either';
510
import { fromString, Ref } from '../../../../utils/ref';
611
import { either, option } from 'fp-ts';
712
import { ResponseObject } from '../../../../schema/3.0/response-object';
813
import { Option } from 'fp-ts/lib/Option';
9-
import { ReferenceObjectCodec } from '../../../../schema/3.0/reference-object';
10-
import { getKeyMatchValue } from '../../common/utils';
14+
import { ReferenceObject, ReferenceObjectCodec } from '../../../../schema/3.0/reference-object';
15+
import { getKeyMatchValue, getResponseTypeFromMediaType, XHRResponseType } from '../../common/utils';
16+
import { SchemaObject } from '../../../../schema/3.0/schema-object';
17+
import { MediaTypeObject } from '../../../../schema/3.0/media-type-object';
1118

1219
const requestMediaRegexp = /^(video|audio|image|application|text)/;
20+
export const getResponseMedia = (content: Record<string, MediaTypeObject>) =>
21+
getKeyMatchValue(content, requestMediaRegexp);
1322

1423
export const serializeResponseObject = (
1524
from: Ref,
1625
responseObject: ResponseObject,
1726
): Option<Either<Error, SerializedType>> =>
1827
pipe(
1928
responseObject.content,
20-
option.chain(content => getKeyMatchValue(content, requestMediaRegexp)),
21-
option.chain(media => media.schema),
22-
option.map(schema =>
23-
ReferenceObjectCodec.is(schema)
24-
? pipe(fromString(schema.$ref), either.map(getSerializedRefType(from)))
25-
: serializeSchemaObject(from)(schema),
29+
option.chain(content => getResponseMedia(content)),
30+
option.chain(({ key: mediaType, value: { schema } }) =>
31+
pipe(
32+
schema,
33+
option.map(schema => ({ mediaType, schema })),
34+
),
2635
),
36+
option.map(({ mediaType, schema }) => {
37+
const resType = getResponseTypeFromMediaType(mediaType);
38+
return serializeResponseSchema(resType, schema, from);
39+
}),
2740
);
41+
42+
const serializeResponseSchema = (
43+
responseType: XHRResponseType,
44+
schema: ReferenceObject | SchemaObject,
45+
from: Ref,
46+
): Either<Error, SerializedType> => {
47+
switch (responseType) {
48+
case 'json':
49+
return ReferenceObjectCodec.is(schema)
50+
? pipe(fromString(schema.$ref), either.map(getSerializedRefType(from)))
51+
: serializeSchemaObject(from)(schema);
52+
case 'text':
53+
return either.right(SERIALIZED_STRING_TYPE);
54+
case 'blob':
55+
return getSerializedBlobType(from);
56+
}
57+
};

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

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,19 @@ import { fromRef } from '../../../../utils/fs';
66
export const utilsRef = fromString('#/utils/utils');
77

88
const utils = `
9-
import { either } from 'fp-ts/lib/Either';
10-
import { Type, type, TypeOf, failure, success, string as tstring, literal } from 'io-ts';
9+
import { either, left, right } from 'fp-ts/lib/Either';
10+
import {
11+
Type,
12+
type,
13+
TypeOf,
14+
failure,
15+
success,
16+
string as tstring,
17+
literal,
18+
Validate,
19+
Context,
20+
getValidationError,
21+
} from 'io-ts';
1122
1223
export const DateFromISODateStringIO = new Type<Date, string, unknown>(
1324
'DateFromISODateString',
@@ -53,6 +64,16 @@ const utils = `
5364
a => a.string,
5465
);
5566
67+
const validateBlob: Validate<unknown, Blob> = (u: unknown, c: Context) =>
68+
u instanceof Blob ? right(u) : left([getValidationError(u, c)]);
69+
70+
export const BlobToBlobIO = new Type<Blob, Blob, unknown>(
71+
'Base64FromString',
72+
(u): u is Blob => u instanceof Blob,
73+
validateBlob,
74+
a => a,
75+
);
76+
5677
`;
5778

5879
export const utilsFile = pipe(

src/language/typescript/common/data/serialized-type.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,16 @@ export const SERIALIZED_UNKNOWN_TYPE = serializedType(
6868
[serializedDependency('unknown', 'io-ts')],
6969
[],
7070
);
71+
export const getSerializedBlobType = (from: Ref): Either<Error, SerializedType> => {
72+
return combineEither(utilsRef, utilsRef =>
73+
serializedType(
74+
'Blob',
75+
'BlobToBlobIO',
76+
[serializedDependency('BlobToBlobIO', getRelativePath(from, utilsRef))],
77+
[],
78+
),
79+
);
80+
};
7181
export const SERIALIZED_BOOLEAN_TYPE = serializedType(
7282
'boolean',
7383
'boolean',
@@ -109,6 +119,7 @@ export const getSerializedStringType = (from: Ref, format: Option<string>): Eith
109119
),
110120
);
111121
}
122+
case 'byte':
112123
case 'base64': {
113124
return some(
114125
serializedType(

src/language/typescript/common/utils.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,26 @@ export const getSafePropertyName = (value: string): string =>
8181

8282
export const context = ask<ResolveRefContext>();
8383

84-
export const getKeyMatchValue = <T>(record: Record<string, T>, regexp: RegExp): option.Option<T> =>
84+
export const getKeyMatchValue = <T extends string, A>(record: Record<T, A>, regexp: RegExp) =>
8585
pipe(
8686
record,
8787
keys,
8888
array.findFirst(s => regexp.test(s)),
89-
option.map(key => record[key]),
89+
option.map(key => ({ key, value: record[key] })),
9090
);
91+
92+
const blobMediaRegexp = /^(video|audio|image|application)/;
93+
const textMediaRegexp = /^text/;
94+
export const DEFAULT_MEDIA_TYPE = 'application/json';
95+
export const getResponseTypeFromMediaType = (mediaType: string): XHRResponseType => {
96+
if (mediaType === 'application/json') {
97+
return 'json';
98+
}
99+
if (blobMediaRegexp.test(mediaType)) {
100+
return 'blob';
101+
}
102+
if (textMediaRegexp.test(mediaType)) {
103+
return 'text';
104+
}
105+
return 'json';
106+
};

0 commit comments

Comments
 (0)