diff --git a/src/language/typescript/3.0/index.ts b/src/language/typescript/3.0/index.ts index b14ecbc..e7f5898 100644 --- a/src/language/typescript/3.0/index.ts +++ b/src/language/typescript/3.0/index.ts @@ -3,27 +3,33 @@ import { format } from 'prettier'; import { fragment, FSEntity, map as mapFS } from '../../../utils/fs'; import { Either } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; -import { combineReader } from '@devexperts/utils/dist/adt/reader.utils'; +import { combineReader, deferReader } from '@devexperts/utils/dist/adt/reader.utils'; import { either, record } from 'fp-ts'; import { Dictionary } from '../../../utils/types'; import { sequenceEither } from '@devexperts/utils/dist/adt/either.utils'; import { OpenapiObject } from '../../../schema/3.0/openapi-object'; import { defaultPrettierConfig, SerializeOptions } from '../common/utils'; +import { serializePrimitiveDefault } from './serializers/schema-object'; export { serializeDocument } from './serializers/document'; -export const serialize = combineReader( - serializeDocument, - serializeDocument => ( - documents: Dictionary, - options: SerializeOptions = {}, - ): Either => - pipe( - documents, - record.collect(serializeDocument), - sequenceEither, - either.map(e => - mapFS(fragment(e), content => format(content, options.prettierConfig || defaultPrettierConfig)), +export const serializeCustom = pipe( + combineReader( + serializeDocument, + serializeDocument => ( + documents: Dictionary, + options: SerializeOptions = {}, + ): Either => + pipe( + documents, + record.collect(serializeDocument), + sequenceEither, + either.map(e => + mapFS(fragment(e), content => format(content, options.prettierConfig || defaultPrettierConfig)), + ), ), - ), + ), + reader => deferReader(reader, 'resolveRef'), ); + +export const serialize = serializeCustom({ serializePrimitive: serializePrimitiveDefault }); diff --git a/src/language/typescript/3.0/serializers/__tests__/schema-object.spec.ts b/src/language/typescript/3.0/serializers/__tests__/schema-object.spec.ts index 53f5832..76bc957 100644 --- a/src/language/typescript/3.0/serializers/__tests__/schema-object.spec.ts +++ b/src/language/typescript/3.0/serializers/__tests__/schema-object.spec.ts @@ -1,4 +1,4 @@ -import { serializeSchemaObject } from '../schema-object'; +import { serializeSchemaObjectDefault } from '../schema-object'; import { getSerializedArrayType, getSerializedDictionaryType, @@ -50,7 +50,7 @@ describe('SchemaObject', () => { }, ], }); - const serialized = pipe(schema, reportIfFailed, either.chain(serializeSchemaObject(ref))); + const serialized = pipe(schema, reportIfFailed, either.chain(serializeSchemaObjectDefault(ref))); pipe( serialized, either.fold(fail, result => { @@ -85,10 +85,10 @@ describe('SchemaObject', () => { property($refArbitrary, schema, string(), (from, schema, name) => { const expected = pipe( schema.items, - serializeSchemaObject(from, name), + serializeSchemaObjectDefault(from, name), either.map(getSerializedArrayType(none, name)), ); - const serialized = pipe(schema, serializeSchemaObject(from, name)); + const serialized = pipe(schema, serializeSchemaObjectDefault(from, name)); expect(serialized).toEqual(expected); }), ); @@ -107,9 +107,9 @@ describe('SchemaObject', () => { getSerializedRefType(from), getSerializedArrayType(none, name), ); - expect(pipe(schema, reportIfFailed, either.chain(serializeSchemaObject(from, name)))).toEqual( - right(expected), - ); + expect( + pipe(schema, reportIfFailed, either.chain(serializeSchemaObjectDefault(from, name))), + ).toEqual(right(expected)); }), ); }); @@ -138,7 +138,11 @@ describe('SchemaObject', () => { getSerializedObjectType(undefined), getSerializedRecursiveType(ref, true), ); - const serialized = pipe(schema, reportIfFailed, either.chain(serializeSchemaObject(ref))); + const serialized = pipe( + schema, + reportIfFailed, + either.chain(serializeSchemaObjectDefault(ref)), + ); expect(serialized).toEqual(right(expected)); }), @@ -162,7 +166,11 @@ describe('SchemaObject', () => { }, }, }); - const serialized = pipe(schema, reportIfFailed, either.chain(serializeSchemaObject(ref))); + const serialized = pipe( + schema, + reportIfFailed, + either.chain(serializeSchemaObjectDefault(ref)), + ); const expected = pipe( ref, getSerializedRefType(ref), @@ -185,7 +193,11 @@ describe('SchemaObject', () => { $ref: ref.$ref, // references self }, }); - const serialized = pipe(schema, reportIfFailed, either.chain(serializeSchemaObject(ref))); + const serialized = pipe( + schema, + reportIfFailed, + either.chain(serializeSchemaObjectDefault(ref)), + ); const expected = pipe( ref, diff --git a/src/language/typescript/3.0/serializers/components-object.ts b/src/language/typescript/3.0/serializers/components-object.ts index cd1c76b..cac22b9 100644 --- a/src/language/typescript/3.0/serializers/components-object.ts +++ b/src/language/typescript/3.0/serializers/components-object.ts @@ -19,30 +19,37 @@ import { combineReader } from '@devexperts/utils/dist/adt/reader.utils'; import { RequestBodyObject, RequestBodyObjectCodec } from '../../../../schema/3.0/request-body-object'; import { serializeRequestBodyObject } from './request-body-object'; -const serializeSchema = (from: Ref, schema: SchemaObject): Either => { - const typeName = getTypeName(from.name); - const ioName = getIOName(from.name); - const serialized = pipe(schema, serializeSchemaObject(from, typeName)); - const dependencies = pipe( - serialized, - either.map(serialized => serializeDependencies(serialized.dependencies)), - ); - return combineEither(serialized, dependencies, (serialized, dependencies) => - file( - `${from.name}.ts`, - ` +const serializeSchema = combineReader( + serializeSchemaObject, + serializeSchemaObject => (from: Ref, schema: SchemaObject): Either => { + const typeName = getTypeName(from.name); + const ioName = getIOName(from.name); + const serialized = pipe(schema, serializeSchemaObject(from, typeName)); + const dependencies = pipe( + serialized, + either.map(serialized => serializeDependencies(serialized.dependencies)), + ); + return combineEither(serialized, dependencies, (serialized, dependencies) => + file( + `${from.name}.ts`, + ` ${dependencies} export type ${typeName} = ${serialized.type}; export const ${ioName} = ${serialized.io}; `, - ), - ); -}; + ), + ); + }, +); const serializeSchemas = combineReader( context, - e => (from: Ref, schemas: Record): Either => + serializeSchema, + (e, serializeSchema) => ( + from: Ref, + schemas: Record, + ): Either => pipe( schemas, record.collect((name, schema) => { @@ -60,27 +67,34 @@ const serializeSchemas = combineReader( ), ); -const serializeParameter = (from: Ref, parameterObject: ParameterObject): Either => - pipe( - serializeParameterObject(from, parameterObject), - either.map(serialized => { - const dependencies = serializeDependencies(serialized.dependencies); +const serializeParameter = combineReader( + serializeParameterObject, + serializeParameterObject => (from: Ref, parameterObject: ParameterObject): Either => + pipe( + serializeParameterObject(from, parameterObject), + either.map(serialized => { + const dependencies = serializeDependencies(serialized.dependencies); - return file( - `${from.name}.ts`, - ` + return file( + `${from.name}.ts`, + ` ${dependencies} export type ${getTypeName(from.name)} = ${serialized.type}; export const ${getIOName(from.name)} = ${serialized.io}; `, - ); - }), - ); + ); + }), + ), +); const serializeParameters = combineReader( context, - e => (from: Ref, parameters: Record): Either => + serializeParameter, + (e, serializeParameter) => ( + from: Ref, + parameters: Record, + ): Either => pipe( parameters, record.collect((name, parameter) => { @@ -98,28 +112,35 @@ const serializeParameters = combineReader( ), ); -const serializeResponse = (from: Ref, responseObject: ResponseObject): Either => - pipe( - serializeResponseObject(from, responseObject), - option.getOrElse(() => right(SERIALIZED_VOID_TYPE)), - either.map(serialized => { - const dependencies = serializeDependencies(serialized.dependencies); +const serializeResponse = combineReader( + serializeResponseObject, + serializeResponseObject => (from: Ref, responseObject: ResponseObject): Either => + pipe( + serializeResponseObject(from, responseObject), + option.getOrElse(() => right(SERIALIZED_VOID_TYPE)), + either.map(serialized => { + const dependencies = serializeDependencies(serialized.dependencies); - return file( - `${from.name}.ts`, - ` + return file( + `${from.name}.ts`, + ` ${dependencies} export type ${getTypeName(from.name)} = ${serialized.type}; export const ${getIOName(from.name)} = ${serialized.io}; `, - ); - }), - ); + ); + }), + ), +); const serializeResponses = combineReader( context, - e => (from: Ref, responses: Record): Either => + serializeResponse, + (e, serializeResponse) => ( + from: Ref, + responses: Record, + ): Either => pipe( responses, record.collect((name, response) => { @@ -137,25 +158,32 @@ const serializeResponses = combineReader( ), ); -const serializeRequestBody = (from: Ref, requestBody: RequestBodyObject): Either => - pipe( - serializeRequestBodyObject(from, requestBody), - either.map(serialized => - file( - `${from.name}.ts`, - ` +const serializeRequestBody = combineReader( + serializeRequestBodyObject, + serializeRequestBodyObject => (from: Ref, requestBody: RequestBodyObject): Either => + pipe( + serializeRequestBodyObject(from, requestBody), + either.map(serialized => + file( + `${from.name}.ts`, + ` ${serializeDependencies(serialized.dependencies)} export type ${getTypeName(from.name)} = ${serialized.type}; export const ${getIOName(from.name)} = ${serialized.io}; `, + ), ), ), - ); +); const serializeRequestBodies = combineReader( context, - e => (from: Ref, requestBodies: Record): Either => { + serializeRequestBody, + (e, serializeRequestBody) => ( + from: Ref, + requestBodies: Record, + ): Either => { return pipe( requestBodies, record.collect((name, requestBody) => { diff --git a/src/language/typescript/3.0/serializers/operation-object.ts b/src/language/typescript/3.0/serializers/operation-object.ts index 79d5c58..823c927 100644 --- a/src/language/typescript/3.0/serializers/operation-object.ts +++ b/src/language/typescript/3.0/serializers/operation-object.ts @@ -78,7 +78,13 @@ const contains = array.elem(eqParameterByNameAndIn); export const getParameters = combineReader( ask(), - e => (from: Ref, operation: OperationObject, pathItem: PathItemObject): Either => { + serializeParameterObject, + serializeRequestBodyObject, + (e, serializeParameterObject, serializeRequestBodyObject) => ( + from: Ref, + operation: OperationObject, + pathItem: PathItemObject, + ): Either => { const processedParameters: ParameterObject[] = []; const pathParameters: ParameterObject[] = []; const serializedPathParameters: SerializedPathParameter[] = []; @@ -258,7 +264,8 @@ export const getParameters = combineReader( export const serializeOperationObject = combineReader( ask(), getParameters, - (e, getParameters) => ( + serializeResponsesObject, + (e, getParameters, serializeResponsesObject) => ( pattern: string, method: HTTPMethod, from: Ref, diff --git a/src/language/typescript/3.0/serializers/parameter-object.ts b/src/language/typescript/3.0/serializers/parameter-object.ts index 56d58c5..23e7458 100644 --- a/src/language/typescript/3.0/serializers/parameter-object.ts +++ b/src/language/typescript/3.0/serializers/parameter-object.ts @@ -21,27 +21,28 @@ import { } from '../../common/data/serialized-fragment'; import { serializedDependency } from '../../common/data/serialized-dependency'; import { openapi3utilsRef } from '../bundled/openapi-3-utils'; +import { combineReader } from '@devexperts/utils/dist/adt/reader.utils'; const forParameter = (parameter: ParameterObject): string => `for parameter "${parameter.name}" in "${parameter.in}"`; export const isRequired = (parameter: ParameterObject): boolean => parameter.in === 'path' ? parameter.required : pipe(parameter.required, option.getOrElse(constFalse)); -export const serializeParameterObject = ( - from: Ref, - parameterObject: ParameterObject, -): Either => - pipe( - getParameterObjectSchema(parameterObject), - either.chain(schema => { - if (ReferenceObjectCodec.is(schema)) { - return pipe(fromString(schema.$ref), either.map(getSerializedRefType(from))); - } else { - return pipe(schema, serializeSchemaObject(from)); - } - }), - either.map(fromSerializedType(isRequired(parameterObject))), - ); +export const serializeParameterObject = combineReader( + serializeSchemaObject, + serializeSchemaObject => (from: Ref, parameterObject: ParameterObject): Either => + pipe( + getParameterObjectSchema(parameterObject), + either.chain(schema => { + if (ReferenceObjectCodec.is(schema)) { + return pipe(fromString(schema.$ref), either.map(getSerializedRefType(from))); + } else { + return pipe(schema, serializeSchemaObject(from)); + } + }), + either.map(fromSerializedType(isRequired(parameterObject))), + ), +); export const getParameterObjectSchema = ( parameterObject: ParameterObject, diff --git a/src/language/typescript/3.0/serializers/paths-object.ts b/src/language/typescript/3.0/serializers/paths-object.ts index dc913f2..29dd75b 100644 --- a/src/language/typescript/3.0/serializers/paths-object.ts +++ b/src/language/typescript/3.0/serializers/paths-object.ts @@ -9,7 +9,7 @@ import { decapitalize, camelize } from '@devexperts/utils/dist/string/string'; import { Either, isLeft, right } from 'fp-ts/lib/Either'; import { sequenceEither } from '@devexperts/utils/dist/adt/either.utils'; import { combineReader } from '@devexperts/utils/dist/adt/reader.utils'; -import { array, either, option, record } from 'fp-ts'; +import { array, either, option, reader, record } from 'fp-ts'; import { addPathParts, getRelativePath, Ref } from '../../../../utils/ref'; import { combineEither } from '@devexperts/utils/dist/adt/either.utils'; import { applyTo } from '../../../../utils/function'; @@ -17,10 +17,13 @@ import { PathsObject } from '../../../../schema/3.0/paths-object'; import { clientRef } from '../../common/bundled/client'; import { getControllerName } from '../../common/utils'; import { serializeResponseMaps } from './response-maps'; +import { asks, Reader } from 'fp-ts/lib/Reader'; +import { FunctionN } from 'fp-ts/lib/function'; const serializeGrouppedPaths = combineReader( serializePathItemObject, - serializePathItemObject => (from: Ref) => (groupped: PathsObject): Either => { + serializeResponseMaps, + (serializePathItemObject, serializeResponseMaps) => (from: Ref) => (groupped: PathsObject): Either => { const serializedHKT = pipe( serializeDictionary(groupped, (pattern, item) => serializePathItemObject(pattern, item, from, 'HKT')), sequenceEither, @@ -65,19 +68,19 @@ const serializeGrouppedPaths = combineReader( ${dependencies} ${serializedMaps.type} - + export interface ${from.name} { ${serializedHKT.type} } - + export interface ${from.name}1 { - ${serializedKind.type} + ${serializedKind.type} } - + export interface ${from.name}2 { - ${serializedKind2.type} + ${serializedKind2.type} } - + export function ${decapitalize(from.name)}(e: { httpClient: HTTPClient2 }): ${from.name}2 export function ${decapitalize(from.name)}(e: { httpClient: HTTPClient1 }): ${from.name}1 export function ${decapitalize(from.name)}(e: { httpClient: HTTPClient }): ${from.name}; @@ -156,16 +159,16 @@ const serializePathsIndex = (from: Ref, pathNames: string[]): Either { - ${pathNames.map(name => `${decapitalize(name)}: ${name};`).join('\n')} + export interface Controllers { + ${pathNames.map(name => `${decapitalize(name)}: ${name};`).join('\n')} } export interface Controllers1 { - ${pathNames.map(name => `${decapitalize(name)}: ${name}1;`).join('\n')} + ${pathNames.map(name => `${decapitalize(name)}: ${name}1;`).join('\n')} } export interface Controllers2 { - ${pathNames.map(name => `${decapitalize(name)}: ${name}2;`).join('\n')} - } - + ${pathNames.map(name => `${decapitalize(name)}: ${name}2;`).join('\n')} + } + export function controllers(e: { httpClient: HTTPClient2 }): Controllers2 export function controllers(e: { httpClient: HTTPClient1 }): Controllers1 export function controllers(e: { httpClient: HTTPClient }): Controllers; @@ -180,7 +183,7 @@ const serializePathsIndex = (from: Ref, pathNames: string[]): Either) => getKeyMatchValue(content, requestMediaRegexp); -export const serializeRequestBodyObject = (from: Ref, body: RequestBodyObject): Either => - pipe( - getRequestMedia(body.content), - option.chain(({ key: mediaType, value: { schema } }) => - pipe( - schema, - option.map(schema => ({ mediaType, schema })), +export const serializeRequestBodyObject = combineReader(serializeSchemaObject, serializeSchemaObject => { + const serializeRequestBodyObject = (from: Ref, body: RequestBodyObject): Either => + pipe( + 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); - }), - ); + either.fromOption(() => new Error('No schema found for RequestBodyObject')), + 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( - fromString(schema.$ref), - mapLeft( - () => new Error(`Invalid MediaObject.content.$ref "${schema.$ref}" for RequestBodyObject`), - ), - either.map(getSerializedRefType(from)), - ) - : serializeSchemaObject(from)(schema); - case 'text': - return either.right(SERIALIZED_STRING_TYPE); - case 'blob': - return getSerializedBlobType(from); - } -}; + const serializeRequestSchema = ( + responseType: XHRResponseType, + schema: ReferenceObject | SchemaObject, + from: Ref, + ): Either => { + switch (responseType) { + case 'json': + return ReferenceObjectCodec.is(schema) + ? pipe( + fromString(schema.$ref), + mapLeft( + () => + new Error( + `Invalid MediaObject.content.$ref "${schema.$ref}" for RequestBodyObject`, + ), + ), + either.map(getSerializedRefType(from)), + ) + : serializeSchemaObject(from)(schema); + case 'text': + return either.right(SERIALIZED_STRING_TYPE); + case 'blob': + return getSerializedBlobType(from); + } + }; + + return serializeRequestBodyObject; +}); diff --git a/src/language/typescript/3.0/serializers/response-maps.ts b/src/language/typescript/3.0/serializers/response-maps.ts index 39de6c9..8713344 100644 --- a/src/language/typescript/3.0/serializers/response-maps.ts +++ b/src/language/typescript/3.0/serializers/response-maps.ts @@ -11,57 +11,60 @@ import { foldSerializedTypes, serializedType, SerializedType } from '../../commo import { Ref } from '../../../../utils/ref'; import { PathItemObject } from '../../../../schema/3.0/path-item-object'; import { OperationObject } from '../../../../schema/3.0/operation-object'; +import { combineReader } from '@devexperts/utils/dist/adt/reader.utils'; -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 - }, +const serializeResponseMap = combineReader( + serializeResponsesObject, + serializeResponsesObject => ( + 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], - ]; +export const serializeResponseMaps = combineReader( + serializeResponseMap, + serializeResponseMap => (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)), + return pipe( + methods, + array.map(([method, opObject]) => + pipe( + opObject, + option.map(operation => serializeResponseMap(pattern, method, from, operation)), + ), ), - ), - array.compact, - sequenceEither, - either.map(foldSerializedTypes), - ); -}; + 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 d90b377..41013b6 100644 --- a/src/language/typescript/3.0/serializers/response-object.ts +++ b/src/language/typescript/3.0/serializers/response-object.ts @@ -1,95 +1,106 @@ import { - SerializedType, + getSerializedBlobType, getSerializedRefType, SERIALIZED_STRING_TYPE, - getSerializedBlobType, + SerializedType, } 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, array, nonEmptyArray } from 'fp-ts'; +import { array, either, nonEmptyArray, option } from 'fp-ts'; import { ResponseObject } from '../../../../schema/3.0/response-object'; import { Option } from 'fp-ts/lib/Option'; 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'; +import { combineReader } from '@devexperts/utils/dist/adt/reader.utils'; const requestMediaRegexp = /^(video|audio|image|application|text|\*)\/(\w+|\*)/; export type SerializedResponse = { mediaType: string; schema: SerializedType }; -export const serializeResponseObject = ( - from: Ref, - responseObject: ResponseObject, -): Option> => - pipe( - responseObject.content, - option.chain(content => getKeyMatchValue(content, requestMediaRegexp)), - option.chain(({ key: mediaType, value: { schema } }) => - pipe( - schema, - option.map(schema => ({ mediaType, schema })), +const serializeResponseSchema = combineReader( + serializeSchemaObject, + serializeSchemaObject => ( + 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); + } + }, +); + +export const serializeResponseObject = combineReader( + serializeSchemaObject, + serializeResponseSchema, + (serializeSchemaObject, serializeResponseSchema) => ( + from: Ref, + responseObject: ResponseObject, + ): Option> => + pipe( + responseObject.content, + option.chain(content => getKeyMatchValue(content, requestMediaRegexp)), + option.chain(({ key: mediaType, value: { schema } }) => + pipe( + schema, + option.map(schema => ({ mediaType, schema })), + ), ), + option.map(({ mediaType, schema }) => { + const resType = getResponseTypeFromMediaType(mediaType); + return serializeResponseSchema(resType, schema, from); + }), ), - 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 })), +export const serializeResponseObjectWithMediaType = combineReader( + serializeResponseSchema, + serializeResponseSchema => ( + 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, ), - 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, + 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 40d7355..e744add 100644 --- a/src/language/typescript/3.0/serializers/responses-object.ts +++ b/src/language/typescript/3.0/serializers/responses-object.ts @@ -18,6 +18,7 @@ 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'; +import { combineReader } from '@devexperts/utils/dist/adt/reader.utils'; const concatNonUniqResonses = (responses: SerializedResponse[]): SerializedResponse[] => pipe( @@ -45,43 +46,46 @@ const concatNonUniqResonses = (responses: SerializedResponse[]): SerializedRespo }), ); -export const serializeResponsesObject = (from: Ref) => ( - responsesObject: ResponsesObject, -): Either> => { - const serializedResponses = pipe( - SUCCESSFUL_CODES, - array.map(code => - pipe( - record.lookup(code, responsesObject), - option.chain(r => - ReferenceObjectCodec.is(r) - ? pipe( - fromString(r.$ref), - either.mapLeft( - () => new Error(`Invalid ${r.$ref} for ResponsesObject'c code "${code}"`), - ), - either.map(getSerializedRefType(from)), - either.map(type => [{ mediaType: DEFAULT_MEDIA_TYPE, schema: type }]), - some, - ) - : serializeResponseObjectWithMediaType(from, r), +export const serializeResponsesObject = combineReader( + serializeResponseObjectWithMediaType, + serializeResponseObjectWithMediaType => (from: Ref) => ( + responsesObject: ResponsesObject, + ): Either> => { + const serializedResponses = pipe( + SUCCESSFUL_CODES, + array.map(code => + pipe( + record.lookup(code, responsesObject), + option.chain(r => + ReferenceObjectCodec.is(r) + ? pipe( + fromString(r.$ref), + either.mapLeft( + () => new Error(`Invalid ${r.$ref} for ResponsesObject'c code "${code}"`), + ), + either.map(getSerializedRefType(from)), + either.map(type => [{ mediaType: DEFAULT_MEDIA_TYPE, schema: type }]), + some, + ) + : serializeResponseObjectWithMediaType(from, r), + ), ), ), - ), - array.compact, - sequenceEither, - either.map(flow(array.flatten, concatNonUniqResonses)), - ); - return pipe( - serializedResponses, - either.map(serializedResponses => { - if (serializedResponses.length === 0) { - 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); - } - }), - ); -}; + array.compact, + sequenceEither, + either.map(flow(array.flatten, concatNonUniqResonses)), + ); + return pipe( + serializedResponses, + either.map(serializedResponses => { + if (serializedResponses.length === 0) { + 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); + } + }), + ); + }, +); diff --git a/src/language/typescript/3.0/serializers/schema-object.ts b/src/language/typescript/3.0/serializers/schema-object.ts index af25a2f..225dfea 100644 --- a/src/language/typescript/3.0/serializers/schema-object.ts +++ b/src/language/typescript/3.0/serializers/schema-object.ts @@ -20,7 +20,7 @@ import { } from '../../common/data/serialized-type'; import { Either, mapLeft, right } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; -import { either, option } from 'fp-ts'; +import { either, option, reader } from 'fp-ts'; import { serializeDictionary } from '../../../../utils/types'; import { constFalse, identity } from 'fp-ts/lib/function'; import { includes } from '../../../../utils/array'; @@ -36,6 +36,7 @@ import { import { ReferenceObject, ReferenceObjectCodec } from '../../../../schema/3.0/reference-object'; import { traverseNEAEither } from '../../../../utils/either'; import { NonEmptyArray } from 'fp-ts/lib/NonEmptyArray'; +import { asks } from 'fp-ts/lib/Reader'; type AdditionalProperties = boolean | ReferenceObject | SchemaObject; type AllowedAdditionalProperties = true | ReferenceObject | SchemaObject; @@ -43,147 +44,162 @@ const isAllowedAdditionalProperties = ( additionalProperties: AdditionalProperties, ): additionalProperties is AllowedAdditionalProperties => additionalProperties !== false; -export const serializeSchemaObject = ( +export type SerializeSchemaObjectWithRecursion = ( from: Ref, + shouldTrackRecursion: boolean, name?: string, -): ((schemaObject: SchemaObject) => Either) => - serializeSchemaObjectWithRecursion(from, true, name); +) => (schemaObject: SchemaObject) => Either; +export type SerializePrimitive = (from: Ref, schemaObject: PrimitiveSchemaObject) => Either; -const serializeSchemaObjectWithRecursion = (from: Ref, shouldTrackRecursion: boolean, name?: string) => ( - schemaObject: SchemaObject, -): Either => { - const isNullable = pipe(schemaObject.nullable, option.exists(identity)); - if (OneOfSchemaObjectCodec.is(schemaObject)) { - return pipe( - serializeChildren(from, schemaObject.oneOf), - either.map(getSerializedUnionType), - either.map(getSerializedRecursiveType(from, shouldTrackRecursion)), - either.map(getSerializedNullableType(isNullable)), - ); - } +export interface SerializeSchemaObjectWithRecursionContext { + serializePrimitive: SerializePrimitive; +} - if (AllOfSchemaObjectCodec.is(schemaObject)) { - return pipe( - serializeChildren(from, schemaObject.allOf), - either.map(getSerializedIntersectionType), - either.map(getSerializedRecursiveType(from, shouldTrackRecursion)), - either.map(getSerializedNullableType(isNullable)), - ); - } - - if (EnumSchemaObjectCodec.is(schemaObject)) { - return pipe(getSerializedEnumType(schemaObject.enum), getSerializedNullableType(isNullable), right); - } - - switch (schemaObject.type) { - case 'string': - case 'boolean': - case 'integer': - case 'number': { - return pipe(serializePrimitive(from, schemaObject), either.map(getSerializedNullableType(isNullable))); +export const serializeSchemaObjectWithRecursion = asks< + SerializeSchemaObjectWithRecursionContext, + SerializeSchemaObjectWithRecursion +>(({ serializePrimitive }) => { + const doSerialize: SerializeSchemaObjectWithRecursion = (from, shouldTrackRecursion, name?) => schemaObject => { + const isNullable = pipe(schemaObject.nullable, option.exists(identity)); + if (OneOfSchemaObjectCodec.is(schemaObject)) { + return pipe( + serializeChildren(from, schemaObject.oneOf), + either.map(getSerializedUnionType), + either.map(getSerializedRecursiveType(from, shouldTrackRecursion)), + either.map(getSerializedNullableType(isNullable)), + ); } - case 'array': { - const { items } = schemaObject; - const serialized = ReferenceObjectCodec.is(items) - ? pipe( - fromString(items.$ref), - mapLeft(() => new Error(`Unable to serialize SchemaObject array items ref "${items.$ref}"`)), - either.map(getSerializedRefType(from)), - ) - : pipe(items, serializeSchemaObjectWithRecursion(from, false, undefined)); + if (AllOfSchemaObjectCodec.is(schemaObject)) { return pipe( - serialized, - either.map(getSerializedArrayType(schemaObject.minItems, name)), + serializeChildren(from, schemaObject.allOf), + either.map(getSerializedIntersectionType), + either.map(getSerializedRecursiveType(from, shouldTrackRecursion)), either.map(getSerializedNullableType(isNullable)), ); } - case 'object': { - const additionalProperties = pipe( - schemaObject.additionalProperties, - option.filter(isAllowedAdditionalProperties), - option.map(additionalProperties => { - if (ReferenceObjectCodec.is(additionalProperties)) { - return pipe( - additionalProperties.$ref, - fromString, + + if (EnumSchemaObjectCodec.is(schemaObject)) { + return pipe(getSerializedEnumType(schemaObject.enum), getSerializedNullableType(isNullable), right); + } + + switch (schemaObject.type) { + case 'string': + case 'boolean': + case 'integer': + case 'number': { + return pipe(serializePrimitive(from, schemaObject), either.map(getSerializedNullableType(isNullable))); + } + case 'array': { + const { items } = schemaObject; + const serialized = ReferenceObjectCodec.is(items) + ? pipe( + fromString(items.$ref), mapLeft( - () => - new Error( - `Unable to serialize SchemaObject additionalProperties ref "${additionalProperties.$ref}"`, - ), + () => new Error(`Unable to serialize SchemaObject array items ref "${items.$ref}"`), ), either.map(getSerializedRefType(from)), - ); - } else { - return additionalProperties !== true - ? pipe(additionalProperties, serializeSchemaObjectWithRecursion(from, false, undefined)) - : right(SERIALIZED_UNKNOWN_TYPE); - } - }), - option.map(either.map(getSerializedDictionaryType(name))), - option.map(either.map(getSerializedRecursiveType(from, shouldTrackRecursion))), - ); - const properties = pipe( - schemaObject.properties, - option.map(properties => - pipe( - serializeDictionary(properties, (name, property) => { - const isRequired = pipe( - schemaObject.required, - option.map(includes(name)), - option.getOrElse(constFalse), - ); + ) + : pipe(items, doSerialize(from, false, undefined)); - if (ReferenceObjectCodec.is(property)) { - return pipe( - property.$ref, - fromString, - mapLeft( - () => - new Error( - `Unable to serialize SchemaObject property "${name}" ref "${property.$ref}"`, - ), - ), - either.map(getSerializedRefType(from)), - either.map(getSerializedOptionPropertyType(name, isRequired)), - ); - } else { - return pipe( - property, - serializeSchemaObjectWithRecursion(from, false, undefined), - either.map(getSerializedOptionPropertyType(name, isRequired)), + return pipe( + serialized, + either.map(getSerializedArrayType(schemaObject.minItems, name)), + either.map(getSerializedNullableType(isNullable)), + ); + } + case 'object': { + const additionalProperties = pipe( + schemaObject.additionalProperties, + option.filter(isAllowedAdditionalProperties), + option.map(additionalProperties => { + if (ReferenceObjectCodec.is(additionalProperties)) { + return pipe( + additionalProperties.$ref, + fromString, + mapLeft( + () => + new Error( + `Unable to serialize SchemaObject additionalProperties ref "${additionalProperties.$ref}"`, + ), + ), + either.map(getSerializedRefType(from)), + ); + } else { + return additionalProperties !== true + ? pipe(additionalProperties, doSerialize(from, false, undefined)) + : right(SERIALIZED_UNKNOWN_TYPE); + } + }), + option.map(either.map(getSerializedDictionaryType(name))), + option.map(either.map(getSerializedRecursiveType(from, shouldTrackRecursion))), + ); + const properties = pipe( + schemaObject.properties, + option.map(properties => + pipe( + serializeDictionary(properties, (name, property) => { + const isRequired = pipe( + schemaObject.required, + option.map(includes(name)), + option.getOrElse(constFalse), ); - } - }), - sequenceEither, - either.map(s => intercalateSerializedTypes(serializedType(';', ',', [], []), s)), - either.map(getSerializedObjectType(name)), - either.map(getSerializedRecursiveType(from, shouldTrackRecursion)), + + if (ReferenceObjectCodec.is(property)) { + return pipe( + property.$ref, + fromString, + mapLeft( + () => + new Error( + `Unable to serialize SchemaObject property "${name}" ref "${property.$ref}"`, + ), + ), + either.map(getSerializedRefType(from)), + either.map(getSerializedOptionPropertyType(name, isRequired)), + ); + } else { + return pipe( + property, + doSerialize(from, false, undefined), + either.map(getSerializedOptionPropertyType(name, isRequired)), + ); + } + }), + sequenceEither, + either.map(s => intercalateSerializedTypes(serializedType(';', ',', [], []), s)), + either.map(getSerializedObjectType(name)), + either.map(getSerializedRecursiveType(from, shouldTrackRecursion)), + ), ), - ), - ); - return pipe( - additionalProperties, - option.alt(() => properties), - option.map(either.map(getSerializedNullableType(isNullable))), - option.getOrElse(() => right(SERIALIZED_UNKNOWN_TYPE)), - ); + ); + return pipe( + additionalProperties, + option.alt(() => properties), + option.map(either.map(getSerializedNullableType(isNullable))), + option.getOrElse(() => right(SERIALIZED_UNKNOWN_TYPE)), + ); + } } - } -}; + }; -const serializeChildren = ( - from: Ref, - children: NonEmptyArray, -): Either> => - traverseNEAEither(children, item => - ReferenceObjectCodec.is(item) - ? pipe(fromString(item.$ref), either.map(getSerializedRefType(from))) - : serializeSchemaObjectWithRecursion(from, false)(item), - ); + const serializeChildren = ( + from: Ref, + children: NonEmptyArray, + ): Either> => + traverseNEAEither(children, item => + ReferenceObjectCodec.is(item) + ? pipe(fromString(item.$ref), either.map(getSerializedRefType(from))) + : doSerialize(from, false)(item), + ); + + return doSerialize; +}); -const serializePrimitive = (from: Ref, schemaObject: PrimitiveSchemaObject): Either => { +export const serializePrimitiveDefault = ( + from: Ref, + schemaObject: PrimitiveSchemaObject, +): Either => { switch (schemaObject.type) { case 'string': { return getSerializedStringType(from, schemaObject.format); @@ -199,3 +215,18 @@ const serializePrimitive = (from: Ref, schemaObject: PrimitiveSchemaObject): Eit } } }; + +export const serializeSchemaObject = pipe( + serializeSchemaObjectWithRecursion, + reader.map( + (serializeSchemaObjectWithRecursion: SerializeSchemaObjectWithRecursion) => ( + from: Ref, + name?: string, + ): ((schemaObject: SchemaObject) => Either) => + serializeSchemaObjectWithRecursion(from, true, name), + ), +); + +export const serializeSchemaObjectDefault = serializeSchemaObject({ + serializePrimitive: serializePrimitiveDefault, +}); diff --git a/yarn.lock b/yarn.lock index 6085ab9..55b5fdf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -661,14 +661,14 @@ ansi-regex@^2.0.0: integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + version "3.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.1.tgz#123d6479e92ad45ad897d4054e3c7ca7db4944e1" + integrity sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw== ansi-regex@^4.0.0, ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" + integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== ansi-regex@^5.0.0: version "5.0.1" @@ -3389,10 +3389,10 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= +json-schema@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" @@ -3424,13 +3424,13 @@ jsonparse@^1.2.0: integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA= jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + version "1.4.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" + integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw== dependencies: assert-plus "1.0.0" extsprintf "1.3.0" - json-schema "0.2.3" + json-schema "0.4.0" verror "1.10.0" keyv@^3.0.0: @@ -5340,9 +5340,9 @@ trim-newlines@^3.0.0: integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== ts-jest@^24.1.0: - version "24.1.0" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-24.1.0.tgz#2eaa813271a2987b7e6c3fefbda196301c131734" - integrity sha512-HEGfrIEAZKfu1pkaxB9au17b1d9b56YZSqz5eCVE8mX68+5reOvlM93xGOzzCREIov9mdH7JBG+s0UyNAqr0tQ== + version "24.3.0" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-24.3.0.tgz#b97814e3eab359ea840a1ac112deae68aa440869" + integrity sha512-Hb94C/+QRIgjVZlJyiWwouYUF+siNJHJHknyspaOcZ+OQAIdFG/UrdQVXw/0B8Z3No34xkUXZJpOTy9alOWdVQ== dependencies: bs-logger "0.x" buffer-from "1.x"