diff --git a/src/language/typescript/3.0/serializers/operation-object.ts b/src/language/typescript/3.0/serializers/operation-object.ts
index ceb82ed..79d5c58 100644
--- a/src/language/typescript/3.0/serializers/operation-object.ts
+++ b/src/language/typescript/3.0/serializers/operation-object.ts
@@ -1,13 +1,4 @@
-import {
- getJSDoc,
- getKindValue,
- getSafePropertyName,
- getTypeName,
- getURL,
- HTTPMethod,
- SUCCESSFUL_CODES,
- XHRResponseType,
-} from '../../common/utils';
+import { getJSDoc, getKindValue, getSafePropertyName, getTypeName, getURL, HTTPMethod } from '../../common/utils';
import {
getSerializedPropertyType,
getSerializedObjectType,
@@ -38,13 +29,13 @@ import {
} from '../../common/data/serialized-path-parameter';
import { concatIf } from '../../../../utils/array';
import { when } from '../../../../utils/string';
-import { serializeRequestBodyObject } from './request-body-object';
+import { getRequestMedia, serializeRequestBodyObject } from './request-body-object';
import { ResolveRefContext, fromString, getRelativePath, Ref } from '../../../../utils/ref';
import { OperationObject } from '../../../../schema/3.0/operation-object';
import { ParameterObject, ParameterObjectCodec } from '../../../../schema/3.0/parameter-object';
import { RequestBodyObjectCodec } from '../../../../schema/3.0/request-body-object';
import { chain, isSome, none, Option, some, map, fromEither, fold } from 'fp-ts/lib/Option';
-import { constFalse } from 'fp-ts/lib/function';
+import { constFalse, flow } from 'fp-ts/lib/function';
import { clientRef } from '../../common/bundled/client';
import { Kind } from '../../../../utils/types';
import { ReferenceObjectCodec } from '../../../../schema/3.0/reference-object';
@@ -58,15 +49,13 @@ import {
SerializedFragment,
} from '../../common/data/serialized-fragment';
import { SchemaObjectCodec } from '../../../../schema/3.0/schema-object';
-import { lookup, keys } from 'fp-ts/lib/Record';
-import { ResponseObjectCodec } from '../../../../schema/3.0/response-object';
import {
fromSerializedHeaderParameter,
getSerializedHeaderParameterType,
SerializedHeaderParameter,
} from '../../common/data/serialized-header-parameters';
-const getOperationName = (pattern: string, operation: OperationObject, method: HTTPMethod): string =>
+export const getOperationName = (pattern: string, operation: OperationObject, method: HTTPMethod): string =>
pipe(
operation.operationId,
option.getOrElse(() => `${method}_${getSafePropertyName(pattern)}`),
@@ -286,27 +275,19 @@ export const serializeOperationObject = combineReader(
);
const serializedResponses = serializeResponsesObject(from)(operation.responses);
- const responseType: XHRResponseType = pipe(
- SUCCESSFUL_CODES,
- array.findFirstMap(code => lookup(code, operation.responses)),
- chain(response =>
- ReferenceObjectCodec.is(response)
- ? fromEither(e.resolveRef(response.$ref, ResponseObjectCodec))
- : some(response),
+ const serializedContentType = pipe(
+ operation.requestBody,
+ chain(requestBody =>
+ ReferenceObjectCodec.is(requestBody)
+ ? fromEither(e.resolveRef(requestBody.$ref, RequestBodyObjectCodec))
+ : some(requestBody),
),
- chain(response => response.content),
- map(keys),
+ map(request => request.content),
+ chain(getRequestMedia),
+ map(({ key }) => key),
fold(
- () => 'json',
- types => {
- if (types.includes('application/octet-stream')) {
- return 'blob';
- }
- if (types.includes('text/plain')) {
- return 'text';
- }
- return 'json';
- },
+ () => '',
+ contentType => `'Content-type': '${contentType}',`,
),
);
@@ -333,7 +314,7 @@ export const serializeOperationObject = combineReader(
const queryType = pipe(
parameters.serializedQueryParameter,
- option.map(query => `query: ${query.type},`),
+ option.map(query => `query: ${query.type};`),
option.getOrElse(() => ''),
);
const queryIO = pipe(
@@ -344,7 +325,7 @@ export const serializeOperationObject = combineReader(
const headersType = pipe(
parameters.serializedHeadersParameter,
- option.map(headers => `headers: ${headers.type}`),
+ option.map(headers => `headers: ${headers.type};`),
option.getOrElse(() => ''),
);
@@ -353,17 +334,34 @@ export const serializeOperationObject = combineReader(
option.map(headers => `const headers = ${headers.io}.encode(parameters.headers)`),
option.getOrElse(() => ''),
);
-
const argsType = concatIf(
hasParameters,
parameters.serializedPathParameters.map(p => p.type),
- [`parameters: { ${queryType}${bodyType}${headersType} }`],
+ [`parameters${hasParameters ? '' : '?'}: { ${queryType}${bodyType}${headersType} }`],
).join(',');
- const type = `
- ${getJSDoc(array.compact([deprecated, operation.summary]))}
- readonly ${operationName}: (${argsType}) => ${getKindValue(kind, serializedResponses.type)};
- `;
+ const argsTypeWithAccept = concatIf(
+ true,
+ parameters.serializedPathParameters.map(p => p.type),
+ [`parameters${hasParameters ? '' : '?'}: { ${queryType}${bodyType}${headersType} accept: A; }`],
+ ).join(',');
+
+ const type = pipe(
+ serializedResponses,
+ either.fold(
+ sr => `
+ ${getJSDoc(array.compact([deprecated, operation.summary]))}
+ readonly ${operationName}: (${argsType}) => ${getKindValue(kind, sr.schema.type)};
+ `,
+ sr => `
+ ${getJSDoc(array.compact([deprecated, operation.summary]))}
+ ${operationName}(${argsType}): ${getKindValue(kind, `MapToResponse${operationName}['${sr[0].mediaType}']`)};
+ ${operationName}(${argsTypeWithAccept}): ${getKindValue(
+ kind,
+ `MapToResponse${operationName}[A]`,
+ )};`,
+ ),
+ );
const argsIO = concatIf(
hasParameters,
@@ -371,24 +369,82 @@ export const serializeOperationObject = combineReader(
['parameters'],
).join(',');
+ const methodTypeIO = pipe(
+ serializedResponses,
+ either.fold(
+ () => `(${argsIO})`,
+ () => `
+ (${argsTypeWithAccept}): ${getKindValue(
+ kind,
+ `MapToResponse${operationName}[A]`,
+ )}`,
+ ),
+ );
+
+ const decode = pipe(
+ serializedResponses,
+ either.fold(
+ sr => `${sr.schema.io}.decode(value)`,
+ () => `decode(accept, value)`,
+ ),
+ );
+ const acceptIO = pipe(
+ serializedResponses,
+ either.fold(
+ sr => `const accept = '${sr.mediaType}';`,
+ sr => `const accept = (parameters && parameters.accept || '${sr[0].mediaType}') as A`,
+ ),
+ );
+
+ const mapToIO = pipe(
+ serializedResponses,
+ either.fold(
+ () => '',
+ sr => {
+ const rows = sr.map(s => `'${s.mediaType}': ${s.schema.io}`);
+ return `const mapToIO = { ${rows.join()} };`;
+ },
+ ),
+ );
+
+ const decodeIO = pipe(
+ serializedResponses,
+ either.fold(
+ () => '',
+ () =>
+ `const decode = (a: A, b: unknown) =>
+ (mapToIO[a].decode(b) as unknown) as Either;`,
+ ),
+ );
+
+ const requestHeaders = `{
+ Accept: accept,
+ ${serializedContentType}
+ }`;
+
const io = `
- ${operationName}: (${argsIO}) => {
+ ${operationName}: ${methodTypeIO} => {
${bodyIO}
${queryIO}
${headersIO}
+ ${acceptIO}
+ ${mapToIO}
+ ${decodeIO}
+ const responseType = getResponseTypeFromMediaType(accept);
+ const requestHeaders = ${requestHeaders}
return e.httpClient.chain(
e.httpClient.request({
url: ${getURL(pattern, parameters.serializedPathParameters)},
method: '${method}',
- responseType: '${responseType}',
+ responseType,
${when(hasQueryParameters, 'query,')}
${when(hasBodyParameter, 'body,')}
- ${when(hasHeaderParameters, 'headers')}
+ headers: {${hasHeaderParameters ? '...headers,' : ''} ...requestHeaders}
}),
value =>
pipe(
- ${serializedResponses.io}.decode(value),
+ ${decode},
either.mapLeft(ResponseValidationError.create),
either.fold(error => e.httpClient.throwError(error), decoded => e.httpClient.of(decoded)),
),
@@ -397,11 +453,26 @@ export const serializeOperationObject = combineReader(
`;
const dependencies = [
+ serializedDependency('getResponseTypeFromMediaType', '../utils/utils'),
serializedDependency('ResponseValidationError', getRelativePath(from, clientRef)),
serializedDependency('pipe', 'fp-ts/lib/pipeable'),
serializedDependency('either', 'fp-ts'),
getSerializedKindDependency(kind),
- ...serializedResponses.dependencies,
+ ...pipe(
+ serializedResponses,
+ either.fold(
+ s => s.schema.dependencies,
+ flow(
+ array.map(s => s.schema.dependencies),
+ array.flatten,
+ arr => [
+ ...arr,
+ serializedDependency('Errors', 'io-ts'),
+ serializedDependency('Either', 'fp-ts/lib/Either'),
+ ],
+ ),
+ ),
+ ),
...array.flatten([
...parameters.serializedPathParameters.map(p => p.dependencies),
...array.compact([
diff --git a/src/language/typescript/3.0/serializers/paths-object.ts b/src/language/typescript/3.0/serializers/paths-object.ts
index 0d81dbd..dc913f2 100644
--- a/src/language/typescript/3.0/serializers/paths-object.ts
+++ b/src/language/typescript/3.0/serializers/paths-object.ts
@@ -16,6 +16,7 @@ import { applyTo } from '../../../../utils/function';
import { PathsObject } from '../../../../schema/3.0/paths-object';
import { clientRef } from '../../common/bundled/client';
import { getControllerName } from '../../common/utils';
+import { serializeResponseMaps } from './response-maps';
const serializeGrouppedPaths = combineReader(
serializePathItemObject,
@@ -35,13 +36,19 @@ const serializeGrouppedPaths = combineReader(
sequenceEither,
either.map(foldSerializedTypes),
);
+ const serializedResponseMaps = pipe(
+ serializeDictionary(groupped, (pattern, item) => serializeResponseMaps(pattern, item, from)),
+ sequenceEither,
+ either.map(foldSerializedTypes),
+ );
return combineEither(
serializedHKT,
serializedKind,
serializedKind2,
clientRef,
- (serializedHKT, serializedKind, serializedKind2, clientRef) => {
+ serializedResponseMaps,
+ (serializedHKT, serializedKind, serializedKind2, clientRef, serializedMaps) => {
const dependencies = serializeDependencies([
...serializedHKT.dependencies,
...serializedKind.dependencies,
@@ -56,6 +63,8 @@ const serializeGrouppedPaths = combineReader(
`${from.name}.ts`,
`
${dependencies}
+
+ ${serializedMaps.type}
export interface ${from.name} {
${serializedHKT.type}
diff --git a/src/language/typescript/3.0/serializers/request-body-object.ts b/src/language/typescript/3.0/serializers/request-body-object.ts
index 3993170..db69d7f 100644
--- a/src/language/typescript/3.0/serializers/request-body-object.ts
+++ b/src/language/typescript/3.0/serializers/request-body-object.ts
@@ -1,33 +1,59 @@
import { serializeSchemaObject } from './schema-object';
-import { getSerializedRefType, SerializedType } from '../../common/data/serialized-type';
+import {
+ getSerializedBlobType,
+ getSerializedRefType,
+ SerializedType,
+ SERIALIZED_STRING_TYPE,
+} from '../../common/data/serialized-type';
import { Either, mapLeft } from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/lib/pipeable';
-import { either, option, record } from 'fp-ts';
+import { either, option } from 'fp-ts';
import { fromString, Ref } from '../../../../utils/ref';
import { RequestBodyObject } from '../../../../schema/3.0/request-body-object';
import { ReferenceObjectCodec, ReferenceObject } from '../../../../schema/3.0/reference-object';
import { SchemaObject } from '../../../../schema/3.0/schema-object';
+import { getKeyMatchValue, getResponseTypeFromMediaType, XHRResponseType } from '../../common/utils';
+import { MediaTypeObject } from '../../../../schema/3.0/media-type-object';
+
+const requestMediaRegexp = /^(video|audio|image|application|text|multipart|\*)\/(\w+|\*)/;
+export const getRequestMedia = (content: Record) =>
+ getKeyMatchValue(content, requestMediaRegexp);
export const serializeRequestBodyObject = (from: Ref, body: RequestBodyObject): Either =>
pipe(
- getSchema(body),
- either.chain(schema =>
- ReferenceObjectCodec.is(schema)
+ getRequestMedia(body.content),
+ option.chain(({ key: mediaType, value: { schema } }) =>
+ pipe(
+ schema,
+ option.map(schema => ({ mediaType, schema })),
+ ),
+ ),
+ either.fromOption(() => new Error('No schema found for ReqeustBodyObject')),
+ either.chain(({ mediaType, schema }) => {
+ const resType = getResponseTypeFromMediaType(mediaType);
+ return serializeRequestSchema(resType, schema, from);
+ }),
+ );
+
+const serializeRequestSchema = (
+ responseType: XHRResponseType,
+ schema: ReferenceObject | SchemaObject,
+ from: Ref,
+): Either => {
+ switch (responseType) {
+ case 'json':
+ return ReferenceObjectCodec.is(schema)
? pipe(
- schema.$ref,
- fromString,
+ fromString(schema.$ref),
mapLeft(
() => new Error(`Invalid MediaObject.content.$ref "${schema.$ref}" for RequestBodyObject`),
),
either.map(getSerializedRefType(from)),
)
- : serializeSchemaObject(from)(schema),
- ),
- );
-
-const getSchema = (requestBodyObject: RequestBodyObject): Either =>
- pipe(
- record.lookup('application/json', requestBodyObject.content),
- option.chain(media => media.schema),
- either.fromOption(() => new Error('No schema found for ReqeustBodyObject')),
- );
+ : serializeSchemaObject(from)(schema);
+ case 'text':
+ return either.right(SERIALIZED_STRING_TYPE);
+ case 'blob':
+ return getSerializedBlobType(from);
+ }
+};
diff --git a/src/language/typescript/3.0/serializers/response-maps.ts b/src/language/typescript/3.0/serializers/response-maps.ts
new file mode 100644
index 0000000..39de6c9
--- /dev/null
+++ b/src/language/typescript/3.0/serializers/response-maps.ts
@@ -0,0 +1,67 @@
+import { array, either, option } from 'fp-ts';
+import { pipe } from 'fp-ts/lib/pipeable';
+import { flow } from 'fp-ts/lib/function';
+import { Either } from 'fp-ts/lib/Either';
+import { Option } from 'fp-ts/lib/Option';
+import { sequenceEither } from '@devexperts/utils/dist/adt/either.utils';
+import { getOperationName } from './operation-object';
+import { serializeResponsesObject } from './responses-object';
+import { HTTPMethod } from '../../common/utils';
+import { foldSerializedTypes, serializedType, SerializedType } from '../../common/data/serialized-type';
+import { Ref } from '../../../../utils/ref';
+import { PathItemObject } from '../../../../schema/3.0/path-item-object';
+import { OperationObject } from '../../../../schema/3.0/operation-object';
+
+const serializeResponseMap = (
+ pattern: string,
+ method: HTTPMethod,
+ from: Ref,
+ operation: OperationObject,
+): Either => {
+ const operationName = getOperationName(pattern, operation, method);
+ const serializedResponses = serializeResponsesObject(from)(operation.responses);
+ return pipe(
+ serializedResponses,
+ either.map(
+ flow(
+ either.fold(
+ () => serializedType('', '', [], []),
+ sr => {
+ const rows = sr.map(s => `'${s.mediaType}': ${s.schema.type};`);
+ const type = `type MapToResponse${operationName} = {${rows.join('')}};`;
+ return serializedType(type, '', [], []); // dependecies in serializeOperationObject serializedResponses
+ },
+ ),
+ ),
+ ),
+ );
+};
+
+export const serializeResponseMaps = (
+ pattern: string,
+ item: PathItemObject,
+ from: Ref,
+): Either => {
+ const methods: [HTTPMethod, Option][] = [
+ ['GET', item.get],
+ ['POST', item.post],
+ ['PUT', item.put],
+ ['DELETE', item.delete],
+ ['PATCH', item.patch],
+ ['HEAD', item.head],
+ ['OPTIONS', item.options],
+ ];
+
+ return pipe(
+ methods,
+ array.map(([method, opObject]) =>
+ pipe(
+ opObject,
+ option.map(operation => serializeResponseMap(pattern, method, from, operation)),
+ ),
+ ),
+ array.compact,
+ sequenceEither,
+ either.map(foldSerializedTypes),
+ );
+};
diff --git a/src/language/typescript/3.0/serializers/response-object.ts b/src/language/typescript/3.0/serializers/response-object.ts
index 8de777b..d90b377 100644
--- a/src/language/typescript/3.0/serializers/response-object.ts
+++ b/src/language/typescript/3.0/serializers/response-object.ts
@@ -1,12 +1,24 @@
-import { SerializedType, getSerializedRefType } from '../../common/data/serialized-type';
+import {
+ SerializedType,
+ getSerializedRefType,
+ SERIALIZED_STRING_TYPE,
+ getSerializedBlobType,
+} from '../../common/data/serialized-type';
import { pipe } from 'fp-ts/lib/pipeable';
import { serializeSchemaObject } from './schema-object';
import { Either } from 'fp-ts/lib/Either';
import { fromString, Ref } from '../../../../utils/ref';
-import { either, option } from 'fp-ts';
+import { either, option, array, nonEmptyArray } from 'fp-ts';
import { ResponseObject } from '../../../../schema/3.0/response-object';
import { Option } from 'fp-ts/lib/Option';
-import { ReferenceObjectCodec } from '../../../../schema/3.0/reference-object';
+import { ReferenceObject, ReferenceObjectCodec } from '../../../../schema/3.0/reference-object';
+import { getKeyMatchValue, getKeyMatchValues, getResponseTypeFromMediaType, XHRResponseType } from '../../common/utils';
+import { SchemaObject } from '../../../../schema/3.0/schema-object';
+import { sequenceEither } from '@devexperts/utils/dist/adt/either.utils';
+
+const requestMediaRegexp = /^(video|audio|image|application|text|\*)\/(\w+|\*)/;
+
+export type SerializedResponse = { mediaType: string; schema: SerializedType };
export const serializeResponseObject = (
from: Ref,
@@ -14,13 +26,70 @@ export const serializeResponseObject = (
): Option> =>
pipe(
responseObject.content,
- option.mapNullable(
- content => content['application/json'] || content['text/plain'] || content['application/octet-stream'],
+ option.chain(content => getKeyMatchValue(content, requestMediaRegexp)),
+ option.chain(({ key: mediaType, value: { schema } }) =>
+ pipe(
+ schema,
+ option.map(schema => ({ mediaType, schema })),
+ ),
),
- option.chain(media => media.schema),
- option.map(schema =>
- ReferenceObjectCodec.is(schema)
- ? pipe(fromString(schema.$ref), either.map(getSerializedRefType(from)))
- : serializeSchemaObject(from)(schema),
+ option.map(({ mediaType, schema }) => {
+ const resType = getResponseTypeFromMediaType(mediaType);
+ return serializeResponseSchema(resType, schema, from);
+ }),
+ );
+
+export const serializeResponseObjectWithMediaType = (
+ from: Ref,
+ responseObject: ResponseObject,
+): Option> =>
+ pipe(
+ responseObject.content,
+ option.chain(content => getKeyMatchValues(content, requestMediaRegexp)),
+ option.chain(arr =>
+ pipe(
+ arr,
+ array.map(({ key: mediaType, value: { schema } }) =>
+ pipe(
+ schema,
+ option.map(schema => ({ mediaType, schema })),
+ ),
+ ),
+ array.filterMap(a => a),
+ nonEmptyArray.fromArray,
+ ),
+ ),
+ option.map(arr =>
+ pipe(
+ arr,
+ array.map(({ mediaType, schema }) => {
+ const resType = getResponseTypeFromMediaType(mediaType);
+ return pipe(
+ serializeResponseSchema(resType, schema, from),
+ either.map(schema => ({
+ mediaType,
+ schema,
+ })),
+ );
+ }),
+ sequenceEither,
+ ),
),
);
+
+const serializeResponseSchema = (
+ responseType: XHRResponseType,
+ schema: ReferenceObject | SchemaObject,
+ from: Ref,
+): Either => {
+ switch (responseType) {
+ case 'json':
+ return ReferenceObjectCodec.is(schema)
+ ? pipe(fromString(schema.$ref), either.map(getSerializedRefType(from)))
+ : serializeSchemaObject(from)(schema);
+ case 'text':
+ return either.right(SERIALIZED_STRING_TYPE);
+ case 'blob':
+ return getSerializedBlobType(from);
+ }
+};
diff --git a/src/language/typescript/3.0/serializers/responses-object.ts b/src/language/typescript/3.0/serializers/responses-object.ts
index 7536679..40d7355 100644
--- a/src/language/typescript/3.0/serializers/responses-object.ts
+++ b/src/language/typescript/3.0/serializers/responses-object.ts
@@ -1,27 +1,53 @@
import {
- intercalateSerializedTypes,
- serializedType,
- SerializedType,
- uniqSerializedTypesByTypeAndIO,
- SERIALIZED_VOID_TYPE,
getSerializedRefType,
+ SERIALIZED_VOID_TYPE,
+ uniqSerializedTypesByTypeAndIO,
+ serializedType,
+ intercalateSerializedTypes,
} from '../../common/data/serialized-type';
-import { SUCCESSFUL_CODES } from '../../common/utils';
+import { DEFAULT_MEDIA_TYPE, SUCCESSFUL_CODES } from '../../common/utils';
import { pipe } from 'fp-ts/lib/pipeable';
-import { serializeResponseObject } from './response-object';
-import { serializedDependency } from '../../common/data/serialized-dependency';
-import { concatIfL } from '../../../../utils/array';
+import { SerializedResponse, serializeResponseObjectWithMediaType } from './response-object';
import { sequenceEither } from '@devexperts/utils/dist/adt/either.utils';
import { array, either, option, record } from 'fp-ts';
import { Either } from 'fp-ts/lib/Either';
import { fromString, Ref } from '../../../../utils/ref';
import { ResponsesObject } from '../../../../schema/3.0/responses-object';
-import { some } from 'fp-ts/lib/Option';
+import { flow } from 'fp-ts/lib/function';
import { ReferenceObjectCodec } from '../../../../schema/3.0/reference-object';
+import { some } from 'fp-ts/lib/Option';
+import { eqString } from 'fp-ts/lib/Eq';
+import { serializedDependency } from '../../common/data/serialized-dependency';
+
+const concatNonUniqResonses = (responses: SerializedResponse[]): SerializedResponse[] =>
+ pipe(
+ responses,
+ array.map(({ mediaType }) => mediaType),
+ array.uniq(eqString),
+ array.map(mediaType => {
+ const schemes = pipe(
+ responses,
+ array.filter(a => a.mediaType === mediaType),
+ array.map(a => a.schema),
+ uniqSerializedTypesByTypeAndIO,
+ );
+ if (schemes.length > 1) {
+ const combined = intercalateSerializedTypes(serializedType('|', ',', [], []), schemes);
+ const scheme = serializedType(
+ combined.type,
+ `union([${combined.io}])`,
+ combined.dependencies.concat([serializedDependency('union', 'io-ts')]),
+ [],
+ );
+ return { mediaType, schema: scheme };
+ }
+ return { mediaType, schema: schemes[0] };
+ }),
+ );
export const serializeResponsesObject = (from: Ref) => (
responsesObject: ResponsesObject,
-): Either => {
+): Either> => {
const serializedResponses = pipe(
SUCCESSFUL_CODES,
array.map(code =>
@@ -35,30 +61,27 @@ export const serializeResponsesObject = (from: Ref) => (
() => new Error(`Invalid ${r.$ref} for ResponsesObject'c code "${code}"`),
),
either.map(getSerializedRefType(from)),
+ either.map(type => [{ mediaType: DEFAULT_MEDIA_TYPE, schema: type }]),
some,
)
- : serializeResponseObject(from, r),
+ : serializeResponseObjectWithMediaType(from, r),
),
),
),
array.compact,
sequenceEither,
- either.map(uniqSerializedTypesByTypeAndIO),
+ either.map(flow(array.flatten, concatNonUniqResonses)),
);
return pipe(
serializedResponses,
either.map(serializedResponses => {
if (serializedResponses.length === 0) {
- return SERIALIZED_VOID_TYPE;
+ return either.left({ mediaType: DEFAULT_MEDIA_TYPE, schema: SERIALIZED_VOID_TYPE });
+ } else if (serializedResponses.length === 1) {
+ return either.left(serializedResponses[0]);
+ } else {
+ return either.right(serializedResponses);
}
- const combined = intercalateSerializedTypes(serializedType('|', ',', [], []), serializedResponses);
- const isUnion = serializedResponses.length > 1;
- return serializedType(
- combined.type,
- isUnion ? `union([${combined.io}])` : combined.io,
- concatIfL(isUnion, combined.dependencies, () => [serializedDependency('union', 'io-ts')]),
- [],
- );
}),
);
};
diff --git a/src/language/typescript/common/bundled/utils.ts b/src/language/typescript/common/bundled/utils.ts
index 5f8f1c3..0a13e24 100644
--- a/src/language/typescript/common/bundled/utils.ts
+++ b/src/language/typescript/common/bundled/utils.ts
@@ -6,8 +6,19 @@ import { fromRef } from '../../../../utils/fs';
export const utilsRef = fromString('#/utils/utils');
const utils = `
- import { either } from 'fp-ts/lib/Either';
- import { Type, failure, success, string as tstring } from 'io-ts';
+ import { either, left, right } from 'fp-ts/lib/Either';
+ import {
+ Type,
+ type,
+ TypeOf,
+ failure,
+ success,
+ string as tstring,
+ literal,
+ Validate,
+ Context,
+ getValidationError,
+ } from 'io-ts';
export const DateFromISODateStringIO = new Type(
'DateFromISODateString',
@@ -24,6 +35,60 @@ const utils = `
.toString()
.padStart(2, '0')}\`,
);
+
+ export type Base64 = TypeOf;
+
+ export const Base64IO = type({
+ string: tstring,
+ format: literal('base64'),
+ });
+
+ export const Base64FromStringIO = new Type(
+ 'Base64FromString',
+ (u): u is Base64 => Base64IO.is(u),
+ (u, c) => either.chain(tstring.validate(u, c), string => success({ string, format: 'base64' })),
+ a => a.string,
+ );
+
+ export type Binary = TypeOf;
+
+ export const BinaryIO = type({
+ string: tstring,
+ format: literal('binary'),
+ });
+
+ export const BinaryFromStringIO = new Type(
+ 'BinaryFromString',
+ (u): u is Binary => BinaryIO.is(u),
+ (u, c) => either.chain(tstring.validate(u, c), string => success({ string, format: 'binary' })),
+ a => a.string,
+ );
+
+ const validateBlob: Validate = (u: unknown, c: Context) =>
+ u instanceof Blob ? right(u) : left([getValidationError(u, c)]);
+
+ export const BlobToBlobIO = new Type(
+ 'Base64FromString',
+ (u): u is Blob => u instanceof Blob,
+ validateBlob,
+ a => a,
+ );
+
+ const blobMediaRegexp = /^(video|audio|image|application)/;
+ const textMediaRegexp = /^text/;
+ export const getResponseTypeFromMediaType = (mediaType: string) => {
+ if (mediaType === 'application/json') {
+ return 'json';
+ }
+ if (blobMediaRegexp.test(mediaType)) {
+ return 'blob';
+ }
+ if (textMediaRegexp.test(mediaType)) {
+ return 'text';
+ }
+ return 'json';
+ };
+
`;
export const utilsFile = pipe(
diff --git a/src/language/typescript/common/data/serialized-type.ts b/src/language/typescript/common/data/serialized-type.ts
index 89660bf..258bc6c 100644
--- a/src/language/typescript/common/data/serialized-type.ts
+++ b/src/language/typescript/common/data/serialized-type.ts
@@ -68,6 +68,16 @@ export const SERIALIZED_UNKNOWN_TYPE = serializedType(
[serializedDependency('unknown', 'io-ts')],
[],
);
+export const getSerializedBlobType = (from: Ref): Either => {
+ return combineEither(utilsRef, utilsRef =>
+ serializedType(
+ 'Blob',
+ 'BlobToBlobIO',
+ [serializedDependency('BlobToBlobIO', getRelativePath(from, utilsRef))],
+ [],
+ ),
+ );
+};
export const SERIALIZED_BOOLEAN_TYPE = serializedType(
'boolean',
'boolean',
@@ -109,8 +119,32 @@ export const getSerializedStringType = (from: Ref, format: Option): Eith
),
);
}
+ case 'byte':
+ case 'base64': {
+ return some(
+ serializedType(
+ 'Base64',
+ 'Base64FromStringIO',
+ [
+ serializedDependency('Base64FromStringIO', getRelativePath(from, utilsRef)),
+ serializedDependency('Base64', getRelativePath(from, utilsRef)),
+ ],
+ [],
+ ),
+ );
+ }
case 'binary': {
- return some(SERIALIZED_UNKNOWN_TYPE);
+ return some(
+ serializedType(
+ 'Binary',
+ 'BinaryFromStringIO',
+ [
+ serializedDependency('BinaryFromStringIO', getRelativePath(from, utilsRef)),
+ serializedDependency('Binary', getRelativePath(from, utilsRef)),
+ ],
+ [],
+ ),
+ );
}
}
return none;
diff --git a/src/language/typescript/common/utils.ts b/src/language/typescript/common/utils.ts
index ed055bf..728258a 100644
--- a/src/language/typescript/common/utils.ts
+++ b/src/language/typescript/common/utils.ts
@@ -4,6 +4,9 @@ import { Options } from 'prettier';
import { fromString, ResolveRefContext } from '../../../utils/ref';
import { Kind } from '../../../utils/types';
import { ask } from 'fp-ts/lib/Reader';
+import { pipe } from 'fp-ts/lib/pipeable';
+import { keys } from 'fp-ts/lib/Record';
+import { array, option, nonEmptyArray } from 'fp-ts';
export const SUCCESSFUL_CODES = ['200', '201', 'default'];
export const CONTROLLERS_DIRECTORY = 'controllers';
@@ -77,3 +80,36 @@ export const getSafePropertyName = (value: string): string =>
value.replace(REPLACE_PATTERN, '_').replace(/^(\d)/, '_$1') || '_';
export const context = ask();
+
+export const getKeyMatchValue = (record: Record, regexp: RegExp) =>
+ pipe(
+ record,
+ keys,
+ array.findFirst(s => regexp.test(s)),
+ option.map(key => ({ key, value: record[key] })),
+ );
+
+export const getKeyMatchValues = (record: Record, regexp: RegExp) =>
+ pipe(
+ record,
+ keys,
+ array.filter(s => regexp.test(s)),
+ nonEmptyArray.fromArray,
+ option.map(nonEmptyArray.map(key => ({ key, value: record[key] }))),
+ );
+
+const blobMediaRegexp = /^(video|audio|image|application)/;
+const textMediaRegexp = /^text/;
+export const DEFAULT_MEDIA_TYPE = 'application/json';
+export const getResponseTypeFromMediaType = (mediaType: string): XHRResponseType => {
+ if (mediaType === 'application/json') {
+ return 'json';
+ }
+ if (blobMediaRegexp.test(mediaType)) {
+ return 'blob';
+ }
+ if (textMediaRegexp.test(mediaType)) {
+ return 'text';
+ }
+ return 'json';
+};
diff --git a/test/specs/3.0/file-and-text.yml b/test/specs/3.0/file-and-text.yml
index 2f340d0..831d01c 100644
--- a/test/specs/3.0/file-and-text.yml
+++ b/test/specs/3.0/file-and-text.yml
@@ -91,6 +91,102 @@ paths:
text/plain:
schema:
$ref: '#/components/schemas/Text'
+ /table:
+ get:
+ tags:
+ - media
+ summary: get table with selected media
+ operationId: loadTable
+ parameters:
+ - in: header
+ required: true
+ name: version
+ schema:
+ type: number
+ responses:
+ 200:
+ description: succesfull operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/TableObject'
+ text/csv:
+ schema:
+ type: string
+ post:
+ tags:
+ - media
+ summary: post table
+ operationId: loadTable2
+ responses:
+ 200:
+ description: succesfull operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/TableObject'
+ text/csv:
+ schema:
+ type: string
+ 201:
+ description: succesfull operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Text'
+ text/csv:
+ schema:
+ type: string
+ /inline-image:
+ get:
+ tags:
+ - media
+ summary: load image in base64
+ operationId: loadInlineImage
+ responses:
+ 200:
+ description: succesfull operation
+ content:
+ application/json:
+ schema:
+ type: string
+ format: base64'
+ /image:
+ get:
+ tags:
+ - media
+ summary: get image
+ operationId: loadImage
+ responses:
+ 200:
+ description: succesfull operation
+ content:
+ image/png:
+ schema:
+ type: string
+ format: binary
+ image/jpeg:
+ schema:
+ type: string
+ format: binary
+ image/gif:
+ schema:
+ type: string
+ format: binary
+ post:
+ tags:
+ - media
+ summary: upload image
+ operationId: uploadImage
+ requestBody:
+ content:
+ image/png:
+ schema:
+ type: string
+ format: byte
+ responses:
+ 200:
+ description: succesfull operation
components:
schemas:
File:
@@ -98,6 +194,26 @@ components:
format: binary
Text:
type: string
+ TableObject:
+ type: object
+ required:
+ - tableName
+ - rows
+ properties:
+ tableName:
+ type: string
+ rows:
+ type: array
+ items:
+ type: object
+ required:
+ - id
+ - name
+ properties:
+ id:
+ type: integer
+ name:
+ type: string
responses:
Successful:
description: succesful operation