Skip to content

Commit 623a051

Browse files
committed
[IO-363] add mediaType generic controller
1 parent 73ebe10 commit 623a051

File tree

8 files changed

+346
-75
lines changed

8 files changed

+346
-75
lines changed

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

Lines changed: 90 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,4 @@
1-
import {
2-
DEFAULT_MEDIA_TYPE,
3-
getJSDoc,
4-
getKindValue,
5-
getResponseTypeFromMediaType,
6-
getSafePropertyName,
7-
getTypeName,
8-
getURL,
9-
HTTPMethod,
10-
SUCCESSFUL_CODES,
11-
XHRResponseType,
12-
} from '../../common/utils';
1+
import { getJSDoc, getKindValue, getSafePropertyName, getTypeName, getURL, HTTPMethod } from '../../common/utils';
132
import {
143
getSerializedPropertyType,
154
getSerializedObjectType,
@@ -45,8 +34,8 @@ import { ResolveRefContext, fromString, getRelativePath, Ref } from '../../../..
4534
import { OperationObject } from '../../../../schema/3.0/operation-object';
4635
import { ParameterObject, ParameterObjectCodec } from '../../../../schema/3.0/parameter-object';
4736
import { RequestBodyObjectCodec } from '../../../../schema/3.0/request-body-object';
48-
import { chain, isSome, none, Option, some, map, fromEither, getOrElse, fold } from 'fp-ts/lib/Option';
49-
import { constFalse } from 'fp-ts/lib/function';
37+
import { chain, isSome, none, Option, some, map, fromEither, fold } from 'fp-ts/lib/Option';
38+
import { constFalse, flow } from 'fp-ts/lib/function';
5039
import { clientRef } from '../../common/bundled/client';
5140
import { Kind } from '../../../../utils/types';
5241
import { ReferenceObjectCodec } from '../../../../schema/3.0/reference-object';
@@ -60,16 +49,13 @@ import {
6049
SerializedFragment,
6150
} from '../../common/data/serialized-fragment';
6251
import { SchemaObjectCodec } from '../../../../schema/3.0/schema-object';
63-
import { lookup } from 'fp-ts/lib/Record';
64-
import { ResponseObjectCodec } from '../../../../schema/3.0/response-object';
6552
import {
6653
fromSerializedHeaderParameter,
6754
getSerializedHeaderParameterType,
6855
SerializedHeaderParameter,
6956
} from '../../common/data/serialized-header-parameters';
70-
import { getResponseMedia } from './response-object';
7157

72-
const getOperationName = (pattern: string, operation: OperationObject, method: HTTPMethod): string =>
58+
export const getOperationName = (pattern: string, operation: OperationObject, method: HTTPMethod): string =>
7359
pipe(
7460
operation.operationId,
7561
option.getOrElse(() => `${method}_${getSafePropertyName(pattern)}`),
@@ -289,20 +275,6 @@ export const serializeOperationObject = combineReader(
289275
);
290276

291277
const serializedResponses = serializeResponsesObject(from)(operation.responses);
292-
const mediaType: string = pipe(
293-
SUCCESSFUL_CODES,
294-
array.findFirstMap(code => lookup(code, operation.responses)),
295-
chain(response =>
296-
ReferenceObjectCodec.is(response)
297-
? fromEither(e.resolveRef(response.$ref, ResponseObjectCodec))
298-
: some(response),
299-
),
300-
chain(response => response.content),
301-
chain(getResponseMedia),
302-
map(({ key }) => key),
303-
getOrElse(() => DEFAULT_MEDIA_TYPE),
304-
);
305-
const responseType: XHRResponseType = getResponseTypeFromMediaType(mediaType);
306278
const serializedContentType = pipe(
307279
operation.requestBody,
308280
chain(requestBody =>
@@ -318,10 +290,6 @@ export const serializeOperationObject = combineReader(
318290
contentType => `'Content-type': '${contentType}',`,
319291
),
320292
);
321-
const requestHeaders = `{
322-
Accept: '${mediaType}',
323-
${serializedContentType}
324-
}`;
325293

326294
return combineEither(
327295
parameters,
@@ -332,6 +300,7 @@ export const serializeOperationObject = combineReader(
332300
const hasBodyParameter = isSome(parameters.serializedBodyParameter);
333301
const hasHeaderParameters = isSome(parameters.serializedHeadersParameter);
334302
const hasParameters = hasQueryParameters || hasBodyParameter || hasHeaderParameters;
303+
const hasResponseMap = either.isRight(serializedResponses);
335304

336305
const bodyType = pipe(
337306
parameters.serializedBodyParameter,
@@ -346,7 +315,7 @@ export const serializeOperationObject = combineReader(
346315

347316
const queryType = pipe(
348317
parameters.serializedQueryParameter,
349-
option.map(query => `query: ${query.type},`),
318+
option.map(query => `query: ${query.type};`),
350319
option.getOrElse(() => ''),
351320
);
352321
const queryIO = pipe(
@@ -357,7 +326,7 @@ export const serializeOperationObject = combineReader(
357326

358327
const headersType = pipe(
359328
parameters.serializedHeadersParameter,
360-
option.map(headers => `headers: ${headers.type}`),
329+
option.map(headers => `headers: ${headers.type};`),
361330
option.getOrElse(() => ''),
362331
);
363332

@@ -366,43 +335,102 @@ export const serializeOperationObject = combineReader(
366335
option.map(headers => `const headers = ${headers.io}.encode(parameters.headers)`),
367336
option.getOrElse(() => ''),
368337
);
338+
const accepArg = hasResponseMap ? 'accept?: A;' : '';
369339

370340
const argsType = concatIf(
371-
hasParameters,
341+
hasParameters || hasResponseMap,
372342
parameters.serializedPathParameters.map(p => p.type),
373-
[`parameters: { ${queryType}${bodyType}${headersType} }`],
343+
[`parameters${hasParameters ? '' : '?'}: { ${queryType}${bodyType}${headersType}${accepArg} }`],
374344
).join(',');
375345

376-
const type = `
346+
const type = pipe(
347+
serializedResponses,
348+
either.fold(
349+
sr => `
377350
${getJSDoc(array.compact([deprecated, operation.summary]))}
378-
readonly ${operationName}: (${argsType}) => ${getKindValue(kind, serializedResponses.type)};
379-
`;
351+
readonly ${operationName}: (${argsType}) => ${getKindValue(kind, sr.schema.type)};
352+
`,
353+
sr => `
354+
${getJSDoc(array.compact([deprecated, operation.summary]))}
355+
readonly ${operationName}: <A extends keyof MapToResponse${operationName} = '${
356+
sr[0].mediaType
357+
}'>(${argsType}) => ${getKindValue(kind, `MapToResponse${operationName}[A]`)};
358+
`,
359+
),
360+
);
380361

381362
const argsIO = concatIf(
382-
hasParameters,
363+
hasParameters || hasResponseMap,
383364
parameters.pathParameters.map(p => p.name),
384365
['parameters'],
385366
).join(',');
386367

368+
const decode = pipe(
369+
serializedResponses,
370+
either.fold(
371+
sr => `${sr.schema.io}.decode(value)`,
372+
() => `decode(accept, value)`,
373+
),
374+
);
375+
const acceptIO = pipe(
376+
serializedResponses,
377+
either.fold(
378+
sr => `const accept = '${sr.mediaType}';`,
379+
sr =>
380+
`const rawAccept = (parameters && parameters.accept)!;
381+
const accept = (rawAccept || '${sr[0].mediaType}') as typeof rawAccept;`,
382+
),
383+
);
384+
385+
const mapToIO = pipe(
386+
serializedResponses,
387+
either.fold(
388+
() => '',
389+
sr => {
390+
const rows = sr.map(s => `'${s.mediaType}': ${s.schema.io}`);
391+
return `const mapToIO = { ${rows.join()} };`;
392+
},
393+
),
394+
);
395+
396+
const decodeIO = pipe(
397+
serializedResponses,
398+
either.fold(
399+
() => '',
400+
() =>
401+
`const decode = <A extends keyof MapToResponse${operationName}>(a: A, b: unknown) =>
402+
(mapToIO[a].decode(b) as unknown) as Either<Errors, MapToResponse${operationName}[A]>;`,
403+
),
404+
);
405+
406+
const requestHeaders = `{
407+
Accept: accept,
408+
${serializedContentType}
409+
}`;
410+
387411
const io = `
388412
${operationName}: (${argsIO}) => {
389413
${bodyIO}
390414
${queryIO}
391415
${headersIO}
416+
${acceptIO}
417+
${mapToIO}
418+
${decodeIO}
419+
const responseType = getResponseTypeFromMediaType(accept);
392420
const requestHeaders = ${requestHeaders}
393421
394422
return e.httpClient.chain(
395423
e.httpClient.request({
396424
url: ${getURL(pattern, parameters.serializedPathParameters)},
397425
method: '${method}',
398-
responseType: '${responseType}',
426+
responseType,
399427
${when(hasQueryParameters, 'query,')}
400428
${when(hasBodyParameter, 'body,')}
401429
headers: {${hasHeaderParameters ? '...headers,' : ''} ...requestHeaders}
402430
}),
403431
value =>
404432
pipe(
405-
${serializedResponses.io}.decode(value),
433+
${decode},
406434
either.mapLeft(ResponseValidationError.create),
407435
either.fold(error => e.httpClient.throwError(error), decoded => e.httpClient.of(decoded)),
408436
),
@@ -411,11 +439,26 @@ export const serializeOperationObject = combineReader(
411439
`;
412440

413441
const dependencies = [
442+
serializedDependency('getResponseTypeFromMediaType', '../utils/utils'),
414443
serializedDependency('ResponseValidationError', getRelativePath(from, clientRef)),
415444
serializedDependency('pipe', 'fp-ts/lib/pipeable'),
416445
serializedDependency('either', 'fp-ts'),
417446
getSerializedKindDependency(kind),
418-
...serializedResponses.dependencies,
447+
...pipe(
448+
serializedResponses,
449+
either.fold(
450+
s => s.schema.dependencies,
451+
flow(
452+
array.map(s => s.schema.dependencies),
453+
array.flatten,
454+
arr => [
455+
...arr,
456+
serializedDependency('Errors', 'io-ts'),
457+
serializedDependency('Either', 'fp-ts/lib/Either'),
458+
],
459+
),
460+
),
461+
),
419462
...array.flatten([
420463
...parameters.serializedPathParameters.map(p => p.dependencies),
421464
...array.compact([

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { applyTo } from '../../../../utils/function';
1616
import { PathsObject } from '../../../../schema/3.0/paths-object';
1717
import { clientRef } from '../../common/bundled/client';
1818
import { getControllerName } from '../../common/utils';
19+
import { serializeResponseMaps } from './response-maps';
1920

2021
const serializeGrouppedPaths = combineReader(
2122
serializePathItemObject,
@@ -35,13 +36,19 @@ const serializeGrouppedPaths = combineReader(
3536
sequenceEither,
3637
either.map(foldSerializedTypes),
3738
);
39+
const serializedResponseMaps = pipe(
40+
serializeDictionary(groupped, (pattern, item) => serializeResponseMaps(pattern, item, from)),
41+
sequenceEither,
42+
either.map(foldSerializedTypes),
43+
);
3844

3945
return combineEither(
4046
serializedHKT,
4147
serializedKind,
4248
serializedKind2,
4349
clientRef,
44-
(serializedHKT, serializedKind, serializedKind2, clientRef) => {
50+
serializedResponseMaps,
51+
(serializedHKT, serializedKind, serializedKind2, clientRef, serializedMaps) => {
4552
const dependencies = serializeDependencies([
4653
...serializedHKT.dependencies,
4754
...serializedKind.dependencies,
@@ -56,6 +63,8 @@ const serializeGrouppedPaths = combineReader(
5663
`${from.name}.ts`,
5764
`
5865
${dependencies}
66+
67+
${serializedMaps.type}
5968
6069
export interface ${from.name}<F> {
6170
${serializedHKT.type}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { array, either, option } from 'fp-ts';
2+
import { pipe } from 'fp-ts/lib/pipeable';
3+
import { flow } from 'fp-ts/lib/function';
4+
import { Either } from 'fp-ts/lib/Either';
5+
import { Option } from 'fp-ts/lib/Option';
6+
import { sequenceEither } from '@devexperts/utils/dist/adt/either.utils';
7+
import { getOperationName } from './operation-object';
8+
import { serializeResponsesObject } from './responses-object';
9+
import { HTTPMethod } from '../../common/utils';
10+
import { foldSerializedTypes, serializedType, SerializedType } from '../../common/data/serialized-type';
11+
import { Ref } from '../../../../utils/ref';
12+
import { PathItemObject } from '../../../../schema/3.0/path-item-object';
13+
import { OperationObject } from '../../../../schema/3.0/operation-object';
14+
15+
const serializeResponseMap = (
16+
pattern: string,
17+
method: HTTPMethod,
18+
from: Ref,
19+
operation: OperationObject,
20+
): Either<Error, SerializedType> => {
21+
const operationName = getOperationName(pattern, operation, method);
22+
const serializedResponses = serializeResponsesObject(from)(operation.responses);
23+
return pipe(
24+
serializedResponses,
25+
either.map(
26+
flow(
27+
either.fold(
28+
() => serializedType('', '', [], []),
29+
sr => {
30+
const rows = sr.map(s => `'${s.mediaType}': ${s.schema.type};`);
31+
const type = `type MapToResponse${operationName} = {${rows.join('')}};`;
32+
return serializedType(type, '', [], []); // dependecies in serializeOperationObject serializedResponses
33+
},
34+
),
35+
),
36+
),
37+
);
38+
};
39+
40+
export const serializeResponseMaps = (
41+
pattern: string,
42+
item: PathItemObject,
43+
from: Ref,
44+
): Either<Error, SerializedType> => {
45+
const methods: [HTTPMethod, Option<OperationObject>][] = [
46+
['GET', item.get],
47+
['POST', item.post],
48+
['PUT', item.put],
49+
['DELETE', item.delete],
50+
['PATCH', item.patch],
51+
['HEAD', item.head],
52+
['OPTIONS', item.options],
53+
];
54+
55+
return pipe(
56+
methods,
57+
array.map(([method, opObject]) =>
58+
pipe(
59+
opObject,
60+
option.map(operation => serializeResponseMap(pattern, method, from, operation)),
61+
),
62+
),
63+
array.compact,
64+
sequenceEither,
65+
either.map(foldSerializedTypes),
66+
);
67+
};

0 commit comments

Comments
 (0)