Skip to content

Commit 411a4a3

Browse files
authored
Merge pull request #144 from Geksanit/feature/arbitrary-file-downloads-for-oas-3
Feature/arbitrary file downloads for oas 3
2 parents 8fa2b05 + 27bc575 commit 411a4a3

File tree

10 files changed

+616
-100
lines changed

10 files changed

+616
-100
lines changed

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

Lines changed: 118 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,4 @@
1-
import {
2-
getJSDoc,
3-
getKindValue,
4-
getSafePropertyName,
5-
getTypeName,
6-
getURL,
7-
HTTPMethod,
8-
SUCCESSFUL_CODES,
9-
XHRResponseType,
10-
} from '../../common/utils';
1+
import { getJSDoc, getKindValue, getSafePropertyName, getTypeName, getURL, HTTPMethod } from '../../common/utils';
112
import {
123
getSerializedPropertyType,
134
getSerializedObjectType,
@@ -38,13 +29,13 @@ import {
3829
} from '../../common/data/serialized-path-parameter';
3930
import { concatIf } from '../../../../utils/array';
4031
import { when } from '../../../../utils/string';
41-
import { serializeRequestBodyObject } from './request-body-object';
32+
import { getRequestMedia, serializeRequestBodyObject } from './request-body-object';
4233
import { ResolveRefContext, fromString, getRelativePath, Ref } from '../../../../utils/ref';
4334
import { OperationObject } from '../../../../schema/3.0/operation-object';
4435
import { ParameterObject, ParameterObjectCodec } from '../../../../schema/3.0/parameter-object';
4536
import { RequestBodyObjectCodec } from '../../../../schema/3.0/request-body-object';
4637
import { chain, isSome, none, Option, some, map, fromEither, fold } from 'fp-ts/lib/Option';
47-
import { constFalse } from 'fp-ts/lib/function';
38+
import { constFalse, flow } from 'fp-ts/lib/function';
4839
import { clientRef } from '../../common/bundled/client';
4940
import { Kind } from '../../../../utils/types';
5041
import { ReferenceObjectCodec } from '../../../../schema/3.0/reference-object';
@@ -58,15 +49,13 @@ import {
5849
SerializedFragment,
5950
} from '../../common/data/serialized-fragment';
6051
import { SchemaObjectCodec } from '../../../../schema/3.0/schema-object';
61-
import { lookup, keys } from 'fp-ts/lib/Record';
62-
import { ResponseObjectCodec } from '../../../../schema/3.0/response-object';
6352
import {
6453
fromSerializedHeaderParameter,
6554
getSerializedHeaderParameterType,
6655
SerializedHeaderParameter,
6756
} from '../../common/data/serialized-header-parameters';
6857

69-
const getOperationName = (pattern: string, operation: OperationObject, method: HTTPMethod): string =>
58+
export const getOperationName = (pattern: string, operation: OperationObject, method: HTTPMethod): string =>
7059
pipe(
7160
operation.operationId,
7261
option.getOrElse(() => `${method}_${getSafePropertyName(pattern)}`),
@@ -286,27 +275,19 @@ export const serializeOperationObject = combineReader(
286275
);
287276

288277
const serializedResponses = serializeResponsesObject(from)(operation.responses);
289-
const responseType: XHRResponseType = pipe(
290-
SUCCESSFUL_CODES,
291-
array.findFirstMap(code => lookup(code, operation.responses)),
292-
chain(response =>
293-
ReferenceObjectCodec.is(response)
294-
? fromEither(e.resolveRef(response.$ref, ResponseObjectCodec))
295-
: some(response),
278+
const serializedContentType = pipe(
279+
operation.requestBody,
280+
chain(requestBody =>
281+
ReferenceObjectCodec.is(requestBody)
282+
? fromEither(e.resolveRef(requestBody.$ref, RequestBodyObjectCodec))
283+
: some(requestBody),
296284
),
297-
chain(response => response.content),
298-
map(keys),
285+
map(request => request.content),
286+
chain(getRequestMedia),
287+
map(({ key }) => key),
299288
fold(
300-
() => 'json',
301-
types => {
302-
if (types.includes('application/octet-stream')) {
303-
return 'blob';
304-
}
305-
if (types.includes('text/plain')) {
306-
return 'text';
307-
}
308-
return 'json';
309-
},
289+
() => '',
290+
contentType => `'Content-type': '${contentType}',`,
310291
),
311292
);
312293

@@ -333,7 +314,7 @@ export const serializeOperationObject = combineReader(
333314

334315
const queryType = pipe(
335316
parameters.serializedQueryParameter,
336-
option.map(query => `query: ${query.type},`),
317+
option.map(query => `query: ${query.type};`),
337318
option.getOrElse(() => ''),
338319
);
339320
const queryIO = pipe(
@@ -344,7 +325,7 @@ export const serializeOperationObject = combineReader(
344325

345326
const headersType = pipe(
346327
parameters.serializedHeadersParameter,
347-
option.map(headers => `headers: ${headers.type}`),
328+
option.map(headers => `headers: ${headers.type};`),
348329
option.getOrElse(() => ''),
349330
);
350331

@@ -353,42 +334,117 @@ export const serializeOperationObject = combineReader(
353334
option.map(headers => `const headers = ${headers.io}.encode(parameters.headers)`),
354335
option.getOrElse(() => ''),
355336
);
356-
357337
const argsType = concatIf(
358338
hasParameters,
359339
parameters.serializedPathParameters.map(p => p.type),
360-
[`parameters: { ${queryType}${bodyType}${headersType} }`],
340+
[`parameters${hasParameters ? '' : '?'}: { ${queryType}${bodyType}${headersType} }`],
361341
).join(',');
362342

363-
const type = `
364-
${getJSDoc(array.compact([deprecated, operation.summary]))}
365-
readonly ${operationName}: (${argsType}) => ${getKindValue(kind, serializedResponses.type)};
366-
`;
343+
const argsTypeWithAccept = concatIf(
344+
true,
345+
parameters.serializedPathParameters.map(p => p.type),
346+
[`parameters${hasParameters ? '' : '?'}: { ${queryType}${bodyType}${headersType} accept: A; }`],
347+
).join(',');
348+
349+
const type = pipe(
350+
serializedResponses,
351+
either.fold(
352+
sr => `
353+
${getJSDoc(array.compact([deprecated, operation.summary]))}
354+
readonly ${operationName}: (${argsType}) => ${getKindValue(kind, sr.schema.type)};
355+
`,
356+
sr => `
357+
${getJSDoc(array.compact([deprecated, operation.summary]))}
358+
${operationName}(${argsType}): ${getKindValue(kind, `MapToResponse${operationName}['${sr[0].mediaType}']`)};
359+
${operationName}<A extends keyof MapToResponse${operationName}>(${argsTypeWithAccept}): ${getKindValue(
360+
kind,
361+
`MapToResponse${operationName}[A]`,
362+
)};`,
363+
),
364+
);
367365

368366
const argsIO = concatIf(
369367
hasParameters,
370368
parameters.pathParameters.map(p => p.name),
371369
['parameters'],
372370
).join(',');
373371

372+
const methodTypeIO = pipe(
373+
serializedResponses,
374+
either.fold(
375+
() => `(${argsIO})`,
376+
() => `
377+
<A extends keyof MapToResponse${operationName}>(${argsTypeWithAccept}): ${getKindValue(
378+
kind,
379+
`MapToResponse${operationName}[A]`,
380+
)}`,
381+
),
382+
);
383+
384+
const decode = pipe(
385+
serializedResponses,
386+
either.fold(
387+
sr => `${sr.schema.io}.decode(value)`,
388+
() => `decode(accept, value)`,
389+
),
390+
);
391+
const acceptIO = pipe(
392+
serializedResponses,
393+
either.fold(
394+
sr => `const accept = '${sr.mediaType}';`,
395+
sr => `const accept = (parameters && parameters.accept || '${sr[0].mediaType}') as A`,
396+
),
397+
);
398+
399+
const mapToIO = pipe(
400+
serializedResponses,
401+
either.fold(
402+
() => '',
403+
sr => {
404+
const rows = sr.map(s => `'${s.mediaType}': ${s.schema.io}`);
405+
return `const mapToIO = { ${rows.join()} };`;
406+
},
407+
),
408+
);
409+
410+
const decodeIO = pipe(
411+
serializedResponses,
412+
either.fold(
413+
() => '',
414+
() =>
415+
`const decode = <A extends keyof MapToResponse${operationName}>(a: A, b: unknown) =>
416+
(mapToIO[a].decode(b) as unknown) as Either<Errors, MapToResponse${operationName}[A]>;`,
417+
),
418+
);
419+
420+
const requestHeaders = `{
421+
Accept: accept,
422+
${serializedContentType}
423+
}`;
424+
374425
const io = `
375-
${operationName}: (${argsIO}) => {
426+
${operationName}: ${methodTypeIO} => {
376427
${bodyIO}
377428
${queryIO}
378429
${headersIO}
430+
${acceptIO}
431+
${mapToIO}
432+
${decodeIO}
433+
const responseType = getResponseTypeFromMediaType(accept);
434+
const requestHeaders = ${requestHeaders}
379435
380436
return e.httpClient.chain(
381437
e.httpClient.request({
382438
url: ${getURL(pattern, parameters.serializedPathParameters)},
383439
method: '${method}',
384-
responseType: '${responseType}',
440+
responseType,
385441
${when(hasQueryParameters, 'query,')}
386442
${when(hasBodyParameter, 'body,')}
387-
${when(hasHeaderParameters, 'headers')}
443+
headers: {${hasHeaderParameters ? '...headers,' : ''} ...requestHeaders}
388444
}),
389445
value =>
390446
pipe(
391-
${serializedResponses.io}.decode(value),
447+
${decode},
392448
either.mapLeft(ResponseValidationError.create),
393449
either.fold(error => e.httpClient.throwError(error), decoded => e.httpClient.of(decoded)),
394450
),
@@ -397,11 +453,26 @@ export const serializeOperationObject = combineReader(
397453
`;
398454

399455
const dependencies = [
456+
serializedDependency('getResponseTypeFromMediaType', '../utils/utils'),
400457
serializedDependency('ResponseValidationError', getRelativePath(from, clientRef)),
401458
serializedDependency('pipe', 'fp-ts/lib/pipeable'),
402459
serializedDependency('either', 'fp-ts'),
403460
getSerializedKindDependency(kind),
404-
...serializedResponses.dependencies,
461+
...pipe(
462+
serializedResponses,
463+
either.fold(
464+
s => s.schema.dependencies,
465+
flow(
466+
array.map(s => s.schema.dependencies),
467+
array.flatten,
468+
arr => [
469+
...arr,
470+
serializedDependency('Errors', 'io-ts'),
471+
serializedDependency('Either', 'fp-ts/lib/Either'),
472+
],
473+
),
474+
),
475+
),
405476
...array.flatten([
406477
...parameters.serializedPathParameters.map(p => p.dependencies),
407478
...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: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +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';
5-
import { either, option, record } from 'fp-ts';
10+
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';
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|\*)\/(\w+|\*)/;
19+
export const getRequestMedia = (content: Record<string, MediaTypeObject>) =>
20+
getKeyMatchValue(content, requestMediaRegexp);
1021

1122
export const serializeRequestBodyObject = (from: Ref, body: RequestBodyObject): Either<Error, SerializedType> =>
1223
pipe(
13-
getSchema(body),
14-
either.chain(schema =>
15-
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)
1646
? pipe(
17-
schema.$ref,
18-
fromString,
47+
fromString(schema.$ref),
1948
mapLeft(
2049
() => new Error(`Invalid MediaObject.content.$ref "${schema.$ref}" for RequestBodyObject`),
2150
),
2251
either.map(getSerializedRefType(from)),
2352
)
24-
: serializeSchemaObject(from)(schema),
25-
),
26-
);
27-
28-
const getSchema = (requestBodyObject: RequestBodyObject): Either<Error, ReferenceObject | SchemaObject> =>
29-
pipe(
30-
record.lookup('application/json', requestBodyObject.content),
31-
option.chain(media => media.schema),
32-
either.fromOption(() => new Error('No schema found for ReqeustBodyObject')),
33-
);
53+
: serializeSchemaObject(from)(schema);
54+
case 'text':
55+
return either.right(SERIALIZED_STRING_TYPE);
56+
case 'blob':
57+
return getSerializedBlobType(from);
58+
}
59+
};

0 commit comments

Comments
 (0)