diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f6a00fc..4b2d2339 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -271,8 +271,8 @@ const primitiveTypes = { "relative-json-pointer": () => Ts.Keyword.String, regex: () => Ts.Keyword.String, }, - array: ({ items, ...schemaPart }, parser) => { - const content = parser.getInlineParseContent(items); + array: async ({ items, ...schemaPart }, parser) => { + const content = await parser.getInlineParseContent(items); return parser.safeAddNullToType(schemaPart, Ts.ArrayType(content)); }, } diff --git a/README.md b/README.md index aa1f4aa1..d47acb32 100644 --- a/README.md +++ b/README.md @@ -399,16 +399,15 @@ generateApi({ ### `primitiveTypeConstructs` It is type mapper or translator swagger schema objects. `primitiveTypeConstructs` translates `type`/`format` schema fields to typescript structs. -This option has type +This option has type + ```ts type PrimitiveTypeStructValue = - | string - | ((schema: Record, parser: import("./src/schema-parser/schema-parser").SchemaParser) => string); + | string + | ((schema: Record, parser: import("./schema-parser-engine").SchemaProcessor) => string); -type PrimitiveTypeStruct = Record< - "integer" | "number" | "boolean" | "object" | "file" | "string" | "array", - string | ({ $default: PrimitiveTypeStructValue } & Record) -> +type PrimitiveTypeStruct = Record<"integer" | "number" | "boolean" | "object" | "file" | "string" | "array", + string | ({ $default: PrimitiveTypeStructValue } & Record)> declare const primitiveTypeConstructs: (struct: PrimitiveTypeStruct) => Partial @@ -443,8 +442,8 @@ generateApi({ "relative-json-pointer": () => "string", regex: () => "string", }, - array: (schema, parser) => { - const content = parser.getInlineParseContent(schema.items); + array: async (schema, parser) => { + const content = await parser.getInlineParseContent(schema.items); return parser.safeAddNullToType(schema, `(${content})[]`); }, }) diff --git a/index.d.ts b/index.d.ts index 97ddaa3f..86220d7b 100644 --- a/index.d.ts +++ b/index.d.ts @@ -214,7 +214,7 @@ type CodeGenConstruct = { type PrimitiveTypeStructValue = | string - | ((schema: Record, parser: import("./src/schema-parser/schema-parser").SchemaParser) => string); + | ((schema: Record, parser: import("./src/schema-parser/schema-processor").SchemaProcessor) => string); type PrimitiveTypeStruct = Record< "integer" | "number" | "boolean" | "object" | "file" | "string" | "array", diff --git a/package.json b/package.json index e92d6147..870da462 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swagger-typescript-api", - "version": "12.0.2", + "version": "13.0.0-experimental-1", "description": "Generate typescript/javascript api from swagger schema", "scripts": { "cli:json": "node index.js -r -d -p ./swagger-test-cli.json -n swagger-test-cli.ts", diff --git a/src/code-gen-process.js b/src/code-gen-process.js index cb0f36a7..38bb74fd 100644 --- a/src/code-gen-process.js +++ b/src/code-gen-process.js @@ -2,9 +2,9 @@ const { SwaggerSchemaResolver } = require("./swagger-schema-resolver.js"); const { SchemaComponentsMap } = require("./schema-components-map.js"); const { NameResolver } = require("./util/name-resolver"); const { Logger } = require("./util/logger.js"); -const { TypeName } = require("./type-name.js"); +const { TypeNameFormatter } = require("./type-name-formatter.js"); const _ = require("lodash"); -const { SchemaParser } = require("./schema-parser/schema-parser.js"); +const { SchemaProcessor } = require("./schema-parser/schema-processor.js"); const { SchemaRoutes } = require("./schema-parser/schema-routes.js"); const { CodeGenConfig } = require("./configuration.js"); const { FileSystem } = require("./util/file-system"); @@ -33,11 +33,11 @@ class CodeGenProcess { */ logger; /** - * @type {TypeName} + * @type {TypeNameFormatter} */ typeName; /** - * @type {SchemaParser} + * @type {SchemaProcessor} */ schemaParser; /** @@ -63,10 +63,10 @@ class CodeGenProcess { this.fileSystem = new FileSystem(); this.swaggerSchemaResolver = new SwaggerSchemaResolver(this.config, this.logger, this.fileSystem); this.schemaComponentMap = new SchemaComponentsMap(this.config); - this.typeName = new TypeName(this.config, this.logger); + this.typeName = new TypeNameFormatter(this.config, this.logger); this.templates = new Templates(this.config, this.logger, this.fileSystem, this.getRenderTemplateData); this.codeFormatter = new CodeFormatter(this.config); - this.schemaParser = new SchemaParser( + this.schemaParser = new SchemaProcessor( this.config, this.logger, this.templates, @@ -85,6 +85,7 @@ class CodeGenProcess { } async start() { + await this.schemaRoutes.init(); this.config.update({ templatePaths: this.templates.getTemplatePaths(this.config) }); this.config.update({ templatesToRender: this.templates.getTemplates(this.config) }); @@ -107,11 +108,13 @@ class CodeGenProcess { this.config.componentTypeNameResolver.reserve(componentSchemaNames); - const parsedSchemas = _.map(_.get(swagger.usageSchema.components, "schemas"), (schema, typeName) => - this.schemaParser.parseSchema(schema, typeName), + const parsedSchemas = await Promise.all( + _.map(_.get(swagger.usageSchema.components, "schemas"), (schema, typeName) => + this.schemaParser.parseSchema(schema, typeName), + ), ); - this.schemaRoutes.attachSchema({ + await this.schemaRoutes.attachSchema({ usageSchema: swagger.usageSchema, parsedSchemas, }); @@ -150,10 +153,12 @@ class CodeGenProcess { return schemas; }; + const modelTypes = await this.collectModelTypes(usageComponentSchemas); + const rawConfiguration = { apiConfig: this.createApiConfig(swagger.usageSchema), config: this.config, - modelTypes: _.map(sortSchemas(usageComponentSchemas), this.prepareModelType).filter(Boolean), + modelTypes: modelTypes, rawModelTypes: usageComponentSchemas, hasSecurityRoutes: this.schemaRoutes.hasSecurityRoutes, hasQueryRoutes: this.schemaRoutes.hasQueryRoutes, @@ -178,7 +183,7 @@ class CodeGenProcess { this.fileSystem.createDir(this.config.output); } - const files = this.generateOutputFiles({ + const files = await this.generateOutputFiles({ configuration: configuration, }); @@ -218,12 +223,60 @@ class CodeGenProcess { files: generatedFiles, configuration, getTemplate: this.templates.getTemplate, - renderTemplate: this.templates.renderTemplate, + renderTemplate: await this.templates.renderTemplate, createFile: this.fileSystem.createFile, formatTSContent: this.codeFormatter.formatCode, }; } + collectModelTypes = async (componentSchemas) => { + const modelTypes = []; + + const sortByProperty = (propertyName) => (o1, o2) => { + if (o1[propertyName] > o2[propertyName]) { + return 1; + } + if (o1[propertyName] < o2[propertyName]) { + return -1; + } + return 0; + }; + + const sortSchemas = (schemas) => { + if (this.config.sortTypes) { + return schemas.sort(sortByProperty("typeName")).map((schema) => { + if (schema.rawTypeData?.properties) { + return { + ...schema, + rawTypeData: { + ...schema.rawTypeData, + $parsed: schema.rawTypeData.$parsed && { + ...schema.rawTypeData.$parsed, + content: Array.isArray(schema.rawTypeData.$parsed.content) + ? schema.rawTypeData.$parsed.content.sort(sortByProperty("name")) + : schema.rawTypeData.$parsed.content, + }, + }, + }; + } + return schema; + }); + } + return schemas; + }; + + const sortedComponentSchemas = sortSchemas(componentSchemas); + + for await (const componentSchema of sortedComponentSchemas) { + const modelType = await this.prepareModelType(componentSchema); + if (modelType) { + modelTypes.push(modelType); + } + } + + return modelTypes; + }; + getRenderTemplateData = () => { return { utils: { @@ -253,14 +306,15 @@ class CodeGenProcess { }; }; - prepareModelType = (typeInfo) => { + prepareModelType = async (typeInfo) => { if (!typeInfo.typeData) { - typeInfo.typeData = this.schemaParser.parseSchema(typeInfo.rawTypeData, typeInfo.typeName); + typeInfo.typeData = await this.schemaParser.parseSchema(typeInfo.rawTypeData, typeInfo.typeName); } - const rawTypeData = typeInfo.typeData; - const typeData = this.schemaParser.schemaFormatters.base[rawTypeData.type] - ? this.schemaParser.schemaFormatters.base[rawTypeData.type](rawTypeData) - : rawTypeData; + const typeData = await this.schemaParser.schemaFormatters.formatSchema(typeInfo.typeData, { + formatType: "base", + schemaType: typeInfo.typeData.type, + }); + let { typeIdentifier, name: originalName, content, description } = typeData; const name = this.typeName.format(originalName); @@ -271,43 +325,48 @@ class CodeGenProcess { typeIdentifier, name, description, - $content: rawTypeData.content, - rawContent: rawTypeData.content, + $content: typeInfo.typeData.content, + rawContent: typeInfo.typeData.content, content: content, typeData, }; }; - generateOutputFiles = ({ configuration }) => { + generateOutputFiles = async ({ configuration }) => { const { modular, templatesToRender } = this.config; const output = modular - ? this.createMultipleFileInfos(templatesToRender, configuration) - : this.createSingleFileInfo(templatesToRender, configuration); + ? await this.createMultipleFileInfos(templatesToRender, configuration) + : await this.createSingleFileInfo(templatesToRender, configuration); if (!_.isEmpty(configuration.extraTemplates)) { output.push( - ..._.map(configuration.extraTemplates, (extraTemplate) => { - return this.createOutputFileInfo( - configuration, - extraTemplate.name, - this.templates.renderTemplate(this.fileSystem.getFileContent(extraTemplate.path), configuration), - ); - }), + ...(await Promise.all( + _.map(configuration.extraTemplates, async (extraTemplate) => { + return this.createOutputFileInfo( + configuration, + extraTemplate.name, + await await this.templates.renderTemplate( + this.fileSystem.getFileContent(extraTemplate.path), + configuration, + ), + ); + }), + )), ); } return output.filter((fileInfo) => !!fileInfo && !!fileInfo.content); }; - createMultipleFileInfos = (templatesToRender, configuration) => { + createMultipleFileInfos = async (templatesToRender, configuration) => { const { routes } = configuration; const { fileNames, generateRouteTypes, generateClient } = configuration.config; const modularApiFileInfos = []; if (routes.$outOfModule) { if (generateRouteTypes) { - const outOfModuleRouteContent = this.templates.renderTemplate(templatesToRender.routeTypes, { + const outOfModuleRouteContent = await this.templates.renderTemplate(templatesToRender.routeTypes, { ...configuration, route: configuration.routes.$outOfModule, }); @@ -317,7 +376,7 @@ class CodeGenProcess { ); } if (generateClient) { - const outOfModuleApiContent = this.templates.renderTemplate(templatesToRender.api, { + const outOfModuleApiContent = await this.templates.renderTemplate(templatesToRender.api, { ...configuration, route: configuration.routes.$outOfModule, }); @@ -329,56 +388,48 @@ class CodeGenProcess { } if (routes.combined) { - modularApiFileInfos.push( - ..._.reduce( - routes.combined, - (apiFileInfos, route) => { - if (generateRouteTypes) { - const routeModuleContent = this.templates.renderTemplate(templatesToRender.routeTypes, { - ...configuration, - route, - }); - - apiFileInfos.push( - this.createOutputFileInfo(configuration, pascalCase(`${route.moduleName}_Route`), routeModuleContent), - ); - } - - if (generateClient) { - const apiModuleContent = this.templates.renderTemplate(templatesToRender.api, { - ...configuration, - route, - }); - - apiFileInfos.push( - this.createOutputFileInfo(configuration, pascalCase(route.moduleName), apiModuleContent), - ); - } - - return apiFileInfos; - }, - [], - ), - ); + for await (const route of routes.combined) { + if (generateRouteTypes) { + const routeModuleContent = await this.templates.renderTemplate(templatesToRender.routeTypes, { + ...configuration, + route, + }); + + modularApiFileInfos.push( + this.createOutputFileInfo(configuration, pascalCase(`${route.moduleName}_Route`), routeModuleContent), + ); + } + + if (generateClient) { + const apiModuleContent = await this.templates.renderTemplate(templatesToRender.api, { + ...configuration, + route, + }); + + modularApiFileInfos.push( + this.createOutputFileInfo(configuration, pascalCase(route.moduleName), apiModuleContent), + ); + } + } } return [ this.createOutputFileInfo( configuration, fileNames.dataContracts, - this.templates.renderTemplate(templatesToRender.dataContracts, configuration), + await this.templates.renderTemplate(templatesToRender.dataContracts, configuration), ), generateClient && this.createOutputFileInfo( configuration, fileNames.httpClient, - this.templates.renderTemplate(templatesToRender.httpClient, configuration), + await this.templates.renderTemplate(templatesToRender.httpClient, configuration), ), ...modularApiFileInfos, ]; }; - createSingleFileInfo = (templatesToRender, configuration) => { + createSingleFileInfo = async (templatesToRender, configuration) => { const { generateRouteTypes, generateClient } = configuration.config; return [ @@ -386,10 +437,10 @@ class CodeGenProcess { configuration, configuration.fileName, _.compact([ - this.templates.renderTemplate(templatesToRender.dataContracts, configuration), - generateRouteTypes && this.templates.renderTemplate(templatesToRender.routeTypes, configuration), - generateClient && this.templates.renderTemplate(templatesToRender.httpClient, configuration), - generateClient && this.templates.renderTemplate(templatesToRender.api, configuration), + await this.templates.renderTemplate(templatesToRender.dataContracts, configuration), + generateRouteTypes && (await this.templates.renderTemplate(templatesToRender.routeTypes, configuration)), + generateClient && (await this.templates.renderTemplate(templatesToRender.httpClient, configuration)), + generateClient && (await this.templates.renderTemplate(templatesToRender.api, configuration)), ]).join("\n"), ), ]; diff --git a/src/configuration.js b/src/configuration.js index 9631dbb7..2591a5b4 100644 --- a/src/configuration.js +++ b/src/configuration.js @@ -263,7 +263,7 @@ class CodeGenConfig { /** * swagger schema type -> typescript type * https://json-schema.org/understanding-json-schema/reference/string.html#dates-and-times - * @type {Record string) | ({ $default: string } & Record string)>)>} + * @type {Record string) | ({ $default: string } & Record string)>)>} */ primitiveTypes = { integer: () => this.Ts.Keyword.Number, @@ -294,10 +294,6 @@ class CodeGenConfig { "relative-json-pointer": () => this.Ts.Keyword.String, regex: () => this.Ts.Keyword.String, }, - array: ({ items, ...schemaPart }, parser) => { - const content = parser.getInlineParseContent(items); - return parser.schemaUtils.safeAddNullToType(schemaPart, this.Ts.ArrayType(content)); - }, }; templateInfos = [ diff --git a/src/schema-parser/schema-formatters.js b/src/schema-parser/schema-formatters.js index 82c790c6..07346370 100644 --- a/src/schema-parser/schema-formatters.js +++ b/src/schema-parser/schema-formatters.js @@ -11,7 +11,7 @@ class SchemaFormatters { */ logger; /** - * @type {SchemaParser} + * @type {SchemaProcessor} */ schemaParser; /** @@ -42,12 +42,12 @@ class SchemaFormatters { content: this.config.Ts.EnumFieldsWrapper(parsedSchema.content), }; }, - [SCHEMA_TYPES.OBJECT]: (parsedSchema) => { + [SCHEMA_TYPES.OBJECT]: async (parsedSchema) => { if (parsedSchema.nullable) return this.inline[SCHEMA_TYPES.OBJECT](parsedSchema); return { ...parsedSchema, $content: parsedSchema.content, - content: this.formatObjectContent(parsedSchema.content), + content: await this.formatObjectContent(parsedSchema.content), }; }, [SCHEMA_TYPES.PRIMITIVE]: (parsedSchema) => { @@ -71,7 +71,7 @@ class SchemaFormatters { ), }; }, - [SCHEMA_TYPES.OBJECT]: (parsedSchema) => { + [SCHEMA_TYPES.OBJECT]: async (parsedSchema) => { if (_.isString(parsedSchema.content)) { return { ...parsedSchema, @@ -86,7 +86,7 @@ class SchemaFormatters { content: this.schemaParser.schemaUtils.safeAddNullToType( parsedSchema, parsedSchema.content.length - ? this.config.Ts.ObjectWrapper(this.formatObjectContent(parsedSchema.content)) + ? this.config.Ts.ObjectWrapper(await this.formatObjectContent(parsedSchema.content)) : this.config.Ts.RecordType(Ts.Keyword.String, this.config.Ts.Keyword.Any), ), }; @@ -95,12 +95,13 @@ class SchemaFormatters { /** * @param parsedSchema {Record} - * @param formatType {"base" | "inline"} + * @param cfg {{ formatType?: "base" | "inline", schemaType?: string } } */ - formatSchema = (parsedSchema, formatType = "base") => { - const schemaType = _.get(parsedSchema, ["schemaType"]) || _.get(parsedSchema, ["$parsed", "schemaType"]); + formatSchema = async (parsedSchema, { schemaType: outerSchemaType, formatType = "base" } = {}) => { + const schemaType = + outerSchemaType || _.get(parsedSchema, ["schemaType"]) || _.get(parsedSchema, ["$parsed", "schemaType"]); const formatterFn = _.get(this, [formatType, schemaType]); - return (formatterFn && formatterFn(parsedSchema)) || parsedSchema; + return (formatterFn && (await formatterFn(parsedSchema))) || parsedSchema; }; formatDescription = (description, inline) => { @@ -126,24 +127,28 @@ class SchemaFormatters { return _.replace(prettified, /\n$/g, ""); }; - formatObjectContent = (content) => { - return _.map(content, (part) => { - const extraSpace = " "; - const result = `${extraSpace}${part.field},\n`; + formatObjectContent = async (content) => { + return ( + await Promise.all( + _.map(content, async (part) => { + const extraSpace = " "; + const result = `${extraSpace}${part.field},\n`; - const renderedJsDoc = this.templates.renderTemplate(this.config.templatesToRender.dataContractJsDoc, { - data: part, - }); + const renderedJsDoc = await this.templates.renderTemplate(this.config.templatesToRender.dataContractJsDoc, { + data: part, + }); - const routeNameFromTemplate = renderedJsDoc - .split("\n") - .map((c) => `${extraSpace}${c}`) - .join("\n"); + const routeNameFromTemplate = renderedJsDoc + .split("\n") + .map((c) => `${extraSpace}${c}`) + .join("\n"); - if (routeNameFromTemplate) return `${routeNameFromTemplate}${result}`; + if (routeNameFromTemplate) return `${routeNameFromTemplate}${result}`; - return `${result}`; - }).join(""); + return `${result}`; + }), + ) + ).join(""); }; } diff --git a/src/schema-parser/schema-parser.js b/src/schema-parser/schema-parser.js index abdc18db..cd28a03e 100644 --- a/src/schema-parser/schema-parser.js +++ b/src/schema-parser/schema-parser.js @@ -1,26 +1,24 @@ -const { SCHEMA_TYPES } = require("../constants.js"); -const _ = require("lodash"); const { SchemaFormatters } = require("./schema-formatters"); -const { internalCase } = require("../util/internal-case"); const { SchemaUtils } = require("./schema-utils"); -const { camelCase } = require("lodash"); +const { SCHEMA_TYPES } = require("../constants"); +const _ = require("lodash"); const { pascalCase } = require("../util/pascal-case"); const { DiscriminatorSchemaParser } = require("./schema-parsers/discriminator"); +const { camelCase } = require("lodash"); class SchemaParser { /** * @type {CodeGenConfig} */ config; - /** * @type {SchemaComponentsMap} */ schemaComponentsMap; /** - * @type {TypeName} + * @type {TypeNameFormatter} */ - typeName; + typeNameFormatter; /** * @type {SchemaFormatters} */ @@ -33,20 +31,41 @@ class SchemaParser { schemaPath = []; - constructor(config, logger, templates, schemaComponentsMap, typeName) { + typeName; + schema; + + constructor( + config, + schemaComponentsMap, + typeNameFormatter, + schemaFormatters, + schemaUtils, + schema, + typeName = null, + schemaPath = [], + ) { this.config = config; this.schemaComponentsMap = schemaComponentsMap; + this.typeNameFormatter = typeNameFormatter; + this.schemaFormatters = schemaFormatters; + this.schemaUtils = schemaUtils; + this.schema = schema; this.typeName = typeName; - this.schemaFormatters = new SchemaFormatters(config, logger, this, templates); - this.schemaUtils = new SchemaUtils(config, schemaComponentsMap); + this.schemaPath = schemaPath; } - complexSchemaParsers = { + _complexSchemaParsers = { // T1 | T2 - [SCHEMA_TYPES.COMPLEX_ONE_OF]: (schema) => { + [SCHEMA_TYPES.COMPLEX_ONE_OF]: async (schema) => { const ignoreTypes = [this.config.Ts.Keyword.Any]; - const combined = _.map(schema.oneOf, (childSchema) => - this.getInlineParseContent(this.schemaUtils.makeAddRequiredToChildSchema(schema, childSchema)), + const combined = await Promise.all( + _.map(schema.oneOf, (childSchema) => + this.createParser( + this.schemaUtils.makeAddRequiredToChildSchema(schema, childSchema), + undefined, + this.schemaPath, + ).getInlineContent(), + ), ); const filtered = this.schemaUtils.filterSchemaContents(combined, (content) => !ignoreTypes.includes(content)); @@ -55,10 +74,16 @@ class SchemaParser { return this.schemaUtils.safeAddNullToType(schema, type); }, // T1 & T2 - [SCHEMA_TYPES.COMPLEX_ALL_OF]: (schema) => { + [SCHEMA_TYPES.COMPLEX_ALL_OF]: async (schema) => { const ignoreTypes = [...this.config.jsPrimitiveTypes, this.config.Ts.Keyword.Any]; - const combined = _.map(schema.allOf, (childSchema) => - this.getInlineParseContent(this.schemaUtils.makeAddRequiredToChildSchema(schema, childSchema)), + const combined = await Promise.all( + _.map(schema.allOf, (childSchema) => + this.createParser( + this.schemaUtils.makeAddRequiredToChildSchema(schema, childSchema), + undefined, + this.schemaPath, + ).getInlineContent(), + ), ); const filtered = this.schemaUtils.filterSchemaContents(combined, (content) => !ignoreTypes.includes(content)); @@ -67,10 +92,16 @@ class SchemaParser { return this.schemaUtils.safeAddNullToType(schema, type); }, // T1 | T2 | (T1 & T2) - [SCHEMA_TYPES.COMPLEX_ANY_OF]: (schema) => { + [SCHEMA_TYPES.COMPLEX_ANY_OF]: async (schema) => { const ignoreTypes = [...this.config.jsPrimitiveTypes, this.config.Ts.Keyword.Any]; - const combined = _.map(schema.anyOf, (childSchema) => - this.getInlineParseContent(this.schemaUtils.makeAddRequiredToChildSchema(schema, childSchema)), + const combined = await Promise.all( + _.map(schema.anyOf, (childSchema) => + this.createParser( + this.schemaUtils.makeAddRequiredToChildSchema(schema, childSchema), + undefined, + this.schemaPath, + ).getInlineContent(), + ), ); const filtered = this.schemaUtils.filterSchemaContents(combined, (content) => !ignoreTypes.includes(content)); @@ -89,9 +120,8 @@ class SchemaParser { return this.config.Ts.Keyword.Any; }, }; - - baseSchemaParsers = { - [SCHEMA_TYPES.ENUM]: (schema, typeName) => { + _baseSchemaParsers = { + [SCHEMA_TYPES.ENUM]: async (schema, typeName) => { const pathTypeName = this.buildTypeNameFromPath(); if (this.config.extractEnums && !typeName && pathTypeName != null) { const generatedTypeName = this.config.componentTypeNameResolver.resolve([ @@ -99,14 +129,15 @@ class SchemaParser { pascalCase(`${pathTypeName} Enum`), ]); const schemaComponent = this.schemaComponentsMap.createComponent("schemas", generatedTypeName, { ...schema }); - return this.parseSchema(schemaComponent, generatedTypeName); + const parser = this.createParser(schemaComponent, generatedTypeName); + return await parser.parse(); } const refType = this.schemaUtils.getSchemaRefType(schema); const $ref = (refType && refType.$ref) || null; if (Array.isArray(schema.enum) && Array.isArray(schema.enum[0])) { - return this.parseSchema( + return await this.createParser( { oneOf: schema.enum.map((enumNames) => ({ type: "array", @@ -114,21 +145,22 @@ class SchemaParser { })), }, typeName, - ); + this.schemaPath, + ).parse(); } - const keyType = this.getSchemaType(schema); + const keyType = await this.schemaUtils.getSchemaType(schema); const enumNames = this.schemaUtils.getEnumNames(schema); let content = null; - const formatValue = (value) => { + const formatValue = async (value) => { if (value === null) { return this.config.Ts.NullValue(value); } - if (keyType === this.getSchemaType({ type: "number" })) { + if (keyType === (await this.schemaUtils.getSchemaType({ type: "number" }))) { return this.config.Ts.NumberValue(value); } - if (keyType === this.getSchemaType({ type: "boolean" })) { + if (keyType === (await this.schemaUtils.getSchemaType({ type: "boolean" }))) { return this.config.Ts.BooleanValue(value); } @@ -136,41 +168,45 @@ class SchemaParser { }; if (_.isArray(enumNames) && _.size(enumNames)) { - content = _.map(enumNames, (enumName, index) => { - const enumValue = _.get(schema.enum, index); - const formattedKey = - (enumName && - this.typeName.format(enumName, { + content = await Promise.all( + _.map(enumNames, async (enumName, index) => { + const enumValue = _.get(schema.enum, index); + const formattedKey = + (enumName && + this.typeNameFormatter.format(enumName, { + type: "enum-key", + })) || + this.typeNameFormatter.format(`${enumValue}`, { type: "enum-key", - })) || - this.typeName.format(`${enumValue}`, { - type: "enum-key", - }); + }); + + if (this.config.enumNamesAsValues || _.isUndefined(enumValue)) { + return { + key: formattedKey, + type: this.config.Ts.Keyword.String, + value: this.config.Ts.StringValue(enumName), + }; + } - if (this.config.enumNamesAsValues || _.isUndefined(enumValue)) { return { key: formattedKey, - type: this.config.Ts.Keyword.String, - value: this.config.Ts.StringValue(enumName), + type: keyType, + value: await formatValue(enumValue), }; - } - - return { - key: formattedKey, - type: keyType, - value: formatValue(enumValue), - }; - }); + }), + ); } else { - content = _.map(schema.enum, (key) => { - return { - key: this.typeName.format(`${key}`, { - type: "enum-key", - }), - type: keyType, - value: formatValue(key), - }; - }); + content = await Promise.all( + _.map(schema.enum, async (key) => { + return { + key: this.typeNameFormatter.format(`${key}`, { + type: "enum-key", + }), + type: keyType, + value: await formatValue(key), + }; + }), + ); } return { @@ -187,8 +223,54 @@ class SchemaParser { content, }; }, - [SCHEMA_TYPES.OBJECT]: (schema, typeName) => { - const contentProperties = this.getObjectSchemaContent(schema); + [SCHEMA_TYPES.OBJECT]: async (schema, typeName) => { + const { properties, additionalProperties } = schema || {}; + const contentProperties = []; + + const propertyEntries = _.entries(properties); + + for await (const [name, property] of propertyEntries) { + this.schemaPath.push(name); + const required = this.schemaUtils.isPropertyRequired(name, property, schema); + const rawTypeData = _.get(this.schemaUtils.getSchemaRefType(property), "rawTypeData", {}); + const nullable = !!(rawTypeData.nullable || property.nullable); + const fieldName = this.typeNameFormatter.isValidName(name) ? name : this.config.Ts.StringValue(name); + const fieldValue = await this.createParser(property, null, this.schemaPath).getInlineContent(); + const readOnly = property.readOnly; + + this.schemaPath.pop(); + + contentProperties.push({ + ...property, + $$raw: property, + title: property.title, + description: + property.description || + _.compact(_.map(property[this.schemaUtils.getComplexType(property)], "description"))[0] || + rawTypeData.description || + _.compact(_.map(rawTypeData[this.schemaUtils.getComplexType(rawTypeData)], "description"))[0] || + "", + isRequired: required, + isNullable: nullable, + name: fieldName, + value: fieldValue, + field: this.config.Ts.TypeField({ + readonly: readOnly && this.config.addReadonly, + optional: !required, + key: fieldName, + value: fieldValue, + }), + }); + } + + if (additionalProperties) { + contentProperties.push({ + $$raw: { additionalProperties }, + description: "", + isRequired: false, + field: this.config.Ts.InterfaceDynamicField(this.config.Ts.Keyword.String, this.config.Ts.Keyword.Any), + }); + } return { ...(_.isObject(schema) ? schema : {}), @@ -202,10 +284,10 @@ class SchemaParser { content: contentProperties, }; }, - [SCHEMA_TYPES.COMPLEX]: (schema, typeName) => { - const complexType = this.getComplexType(schema); - const simpleSchema = _.omit(_.clone(schema), _.keys(this.complexSchemaParsers)); - const complexSchemaContent = this.complexSchemaParsers[complexType](schema); + [SCHEMA_TYPES.COMPLEX]: async (schema, typeName) => { + const complexType = this.schemaUtils.getComplexType(schema); + const simpleSchema = _.omit(_.clone(schema), _.keys(this._complexSchemaParsers)); + const complexSchemaContent = await this._complexSchemaParsers[complexType](schema); return { ...(_.isObject(schema) ? schema : {}), @@ -221,32 +303,61 @@ class SchemaParser { this.config.Ts.IntersectionType( _.compact([ this.config.Ts.ExpressionGroup(complexSchemaContent), - this.getInternalSchemaType(simpleSchema) === SCHEMA_TYPES.OBJECT && - this.config.Ts.ExpressionGroup(this.getInlineParseContent(simpleSchema)), + this.schemaUtils.getInternalSchemaType(simpleSchema) === SCHEMA_TYPES.OBJECT && + this.config.Ts.ExpressionGroup( + await this.createParser(simpleSchema, null, this.schemaPath).getInlineContent(), + ), ]), ) || this.config.Ts.Keyword.Any, }; }, - [SCHEMA_TYPES.PRIMITIVE]: (schema, typeName) => { + [SCHEMA_TYPES.ARRAY]: async (schema, typeName) => { + let contentType; + const { type, description, items } = schema || {}; + + if (_.isArray(items) && type === SCHEMA_TYPES.ARRAY) { + const tupleContent = await Promise.all( + items.map((item) => this.createParser(item, null, this.schemaPath).getInlineContent()), + ); + contentType = this.config.Ts.Tuple(tupleContent); + } else { + const content = await this.createParser(items, null, this.schemaPath).getInlineContent(); + contentType = this.config.Ts.ArrayType(content); + } + + return { + ...(_.isObject(schema) ? schema : {}), + $parsedSchema: true, + schemaType: SCHEMA_TYPES.PRIMITIVE, + type: SCHEMA_TYPES.PRIMITIVE, + typeIdentifier: this.config.Ts.Keyword.Type, + name: typeName, + description: this.schemaFormatters.formatDescription(description), + content: this.schemaUtils.safeAddNullToType(schema, contentType), + }; + }, + [SCHEMA_TYPES.PRIMITIVE]: async (schema, typeName) => { let contentType = null; const { additionalProperties, type, description, items } = schema || {}; if (type === this.config.Ts.Keyword.Object && additionalProperties) { const fieldType = _.isObject(additionalProperties) - ? this.getInlineParseContent(additionalProperties) + ? await this.createParser(additionalProperties, null, this.schemaPath).getInlineContent() : this.config.Ts.Keyword.Any; contentType = this.config.Ts.RecordType(this.config.Ts.Keyword.String, fieldType); } if (_.isArray(type) && type.length) { - contentType = this.complexSchemaParsers.oneOf({ + contentType = await this._complexSchemaParsers.oneOf({ ...(_.isObject(schema) ? schema : {}), oneOf: type.map((type) => ({ type })), }); } if (_.isArray(items) && type === SCHEMA_TYPES.ARRAY) { - contentType = this.config.Ts.Tuple(items.map((item) => this.getInlineParseContent(item))); + contentType = this.config.Ts.Tuple( + await Promise.all(items.map((item) => this.createParser(item, null, this.schemaPath).getInlineContent())), + ); } return { @@ -258,194 +369,22 @@ class SchemaParser { name: typeName, description: this.schemaFormatters.formatDescription(description), // TODO: probably it should be refactored. `type === 'null'` is not flexible - content: type === this.config.Ts.Keyword.Null ? type : contentType || this.getSchemaType(schema), + content: + type === this.config.Ts.Keyword.Null ? type : contentType || (await this.schemaUtils.getSchemaType(schema)), }; }, - [SCHEMA_TYPES.DISCRIMINATOR]: (schema, typeName) => { + [SCHEMA_TYPES.DISCRIMINATOR]: async (schema, typeName) => { const schemaParser = new DiscriminatorSchemaParser(this, schema, typeName, this.schemaPath); - - return schemaParser.parse(); - const { discriminator, ...noDiscriminatorSchema } = schema; - - if (typeName == null || !discriminator.mapping) return this.parseSchema(noDiscriminatorSchema, typeName); - - const refPath = this.schemaComponentsMap.createRef("schemas", typeName); - const complexSchemaKeys = _.keys(this.complexSchemaParsers); - const abstractSchema = _.omit(_.clone(noDiscriminatorSchema), complexSchemaKeys); - const discTypeName = this.config.componentTypeNameResolver.resolve([ - pascalCase(`Abstract ${typeName}`), - pascalCase(`Discriminator ${typeName}`), - pascalCase(`Internal ${typeName}`), - pascalCase(`Polymorph ${typeName}`), - ]); - const abstractSchemaIsEmpty = !_.keys(abstractSchema).length; - const abstractComponent = - !abstractSchemaIsEmpty && - this.schemaComponentsMap.createComponent("schemas", discTypeName, { - ...abstractSchema, - internal: true, - }); - const complexType = this.getComplexType(schema); - - const abstractSchemaContent = !abstractSchemaIsEmpty && this.getInlineParseContent(abstractComponent); - const complexSchemaContent = - complexType !== SCHEMA_TYPES.COMPLEX_UNKNOWN - ? this.config.Ts.ExpressionGroup(this.complexSchemaParsers[complexType](schema)) - : null; - const discriminatorSchemaContent = this.config.Ts.ExpressionGroup( - this.config.Ts.UnionType( - _.map(discriminator.mapping, (schema, key) => { - const mappingSchema = typeof schema === "string" ? { $ref: schema } : schema; - if (mappingSchema.$ref) { - const mappingRefSchema = this.schemaUtils.getSchemaRefType(mappingSchema)?.rawTypeData; - if (mappingRefSchema) { - complexSchemaKeys.forEach((schemaKey) => { - if (_.isArray(mappingRefSchema[schemaKey])) { - mappingRefSchema[schemaKey] = mappingRefSchema[schemaKey].map((schema) => { - if (schema.$ref === refPath) { - return { ...schema, $ref: abstractComponent.$ref }; - } - return schema; - }); - } - }); - } - } - return this.config.Ts.ExpressionGroup( - this.config.Ts.IntersectionType([ - this.config.Ts.ObjectWrapper( - this.config.Ts.TypeField({ - key: discriminator.propertyName, - value: this.config.Ts.StringValue(key), - }), - ), - this.getInlineParseContent(mappingSchema), - ]), - ); - }), - ), - ); - - return { - ...(_.isObject(schema) ? schema : {}), - $parsedSchema: true, - schemaType: SCHEMA_TYPES.COMPLEX, - type: SCHEMA_TYPES.PRIMITIVE, - typeIdentifier: this.config.Ts.Keyword.Type, - name: typeName, - description: this.schemaFormatters.formatDescription(schema.description), - content: this.config.Ts.IntersectionType( - [ - abstractSchemaContent, - this.config.Ts.ExpressionGroup( - this.config.Ts.UnionType([complexSchemaContent, discriminatorSchemaContent].filter(Boolean)), - ), - ].filter(Boolean), - ), - }; + return await schemaParser.parse(); }, }; - getInternalSchemaType = (schema) => { - if (!_.isEmpty(schema.enum) || !_.isEmpty(this.schemaUtils.getEnumNames(schema))) return SCHEMA_TYPES.ENUM; - if (schema.discriminator) return SCHEMA_TYPES.DISCRIMINATOR; - if (schema.allOf || schema.oneOf || schema.anyOf || schema.not) return SCHEMA_TYPES.COMPLEX; - if (!_.isEmpty(schema.properties)) return SCHEMA_TYPES.OBJECT; - - return SCHEMA_TYPES.PRIMITIVE; - }; - - getSchemaType = (schema) => { - if (!schema) return this.config.Ts.Keyword.Any; - - const refTypeInfo = this.schemaUtils.getSchemaRefType(schema); - - if (refTypeInfo) { - return this.schemaUtils.checkAndAddRequiredKeys( - schema, - this.schemaUtils.safeAddNullToType(schema, this.typeName.format(refTypeInfo.typeName)), - ); - } - - const primitiveType = this.schemaUtils.getSchemaPrimitiveType(schema); - - if (primitiveType == null) return this.config.Ts.Keyword.Any; - - let resultType; - - const typeAlias = - _.get(this.config.primitiveTypes, [primitiveType, schema.format]) || - _.get(this.config.primitiveTypes, [primitiveType, "$default"]) || - this.config.primitiveTypes[primitiveType]; - - if (_.isFunction(typeAlias)) { - resultType = typeAlias(schema, this); - } else { - resultType = typeAlias || primitiveType; - } - - if (!resultType) return this.config.Ts.Keyword.Any; - - return this.schemaUtils.checkAndAddRequiredKeys(schema, this.schemaUtils.safeAddNullToType(schema, resultType)); - }; - - getObjectSchemaContent = (schema) => { - const { properties, additionalProperties } = schema || {}; - - const propertiesContent = _.map(properties, (property, name) => { - this.schemaPath.push(name); - const required = this.schemaUtils.isPropertyRequired(name, property, schema); - const rawTypeData = _.get(this.schemaUtils.getSchemaRefType(property), "rawTypeData", {}); - const nullable = !!(rawTypeData.nullable || property.nullable); - const fieldName = this.typeName.isValidName(name) ? name : this.config.Ts.StringValue(name); - const fieldValue = this.getInlineParseContent(property); - const readOnly = property.readOnly; - - this.schemaPath.pop(); - - return { - ...property, - $$raw: property, - title: property.title, - description: - property.description || - _.compact(_.map(property[this.getComplexType(property)], "description"))[0] || - rawTypeData.description || - _.compact(_.map(rawTypeData[this.getComplexType(rawTypeData)], "description"))[0] || - "", - isRequired: required, - isNullable: nullable, - name: fieldName, - value: fieldValue, - field: this.config.Ts.TypeField({ - readonly: readOnly && this.config.addReadonly, - optional: !required, - key: fieldName, - value: fieldValue, - }), - }; - }); - - if (additionalProperties) { - propertiesContent.push({ - $$raw: { additionalProperties }, - description: "", - isRequired: false, - field: this.config.Ts.InterfaceDynamicField(this.config.Ts.Keyword.String, this.config.Ts.Keyword.Any), - }); - } - - return propertiesContent; - }; + buildTypeNameFromPath = () => { + const schemaPath = _.uniq(_.compact(this.schemaPath)); - getComplexType = (schema) => { - if (schema.oneOf) return SCHEMA_TYPES.COMPLEX_ONE_OF; - if (schema.allOf) return SCHEMA_TYPES.COMPLEX_ALL_OF; - if (schema.anyOf) return SCHEMA_TYPES.COMPLEX_ANY_OF; - // TODO :( - if (schema.not) return SCHEMA_TYPES.COMPLEX_NOT; + if (!schemaPath || !schemaPath[0]) return null; - return SCHEMA_TYPES.COMPLEX_UNKNOWN; + return pascalCase(camelCase(_.uniq([schemaPath[0], schemaPath[schemaPath.length - 1]]).join("_"))); }; /** @@ -453,62 +392,110 @@ class SchemaParser { * @param schema {any} * @param typeName {null | string} * @param formatter {"inline" | "base"} - * @return {Record} + * @return {Promise>} */ - parseSchema = (schema, typeName = null, schemaPath) => { - if (!schema) return this.baseSchemaParsers[SCHEMA_TYPES.PRIMITIVE](null, typeName); + parse = async () => { + if (!this.schema) return await this._baseSchemaParsers[SCHEMA_TYPES.PRIMITIVE](null, this.typeName); let schemaType = null; let parsedSchema = null; - if (typeof schema === "string") { - return schema; + if (typeof this.schema === "string") { + return this.schema; } - if (!schema.$parsed) { - if (!typeName && this.schemaUtils.isRefSchema(schema)) { - typeName = this.getSchemaType(schema); + if (!this.schema.$parsed) { + if (!this.typeName && this.schemaUtils.isRefSchema(this.schema)) { + this.typeName = await this.schemaUtils.getSchemaType(this.schema); } - if (schema.items && !Array.isArray(schema.items) && !schema.type) { - schema.type = SCHEMA_TYPES.ARRAY; + if (this.schema.items && !Array.isArray(this.schema.items) && !this.schema.type) { + this.schema.type = SCHEMA_TYPES.ARRAY; } - schemaType = this.getInternalSchemaType(schema); + schemaType = this.schemaUtils.getInternalSchemaType(this.schema); - this.schemaPath.push(...(schemaPath || [])); - this.schemaPath.push(typeName); + this.schemaPath.push(this.typeName); - _.merge(schema, this.config.hooks.onPreParseSchema(schema, typeName, schemaType)); - parsedSchema = this.baseSchemaParsers[schemaType](schema, typeName); - schema.$parsed = this.config.hooks.onParseSchema(schema, parsedSchema) || parsedSchema; + _.merge(this.schema, this.config.hooks.onPreParseSchema(this.schema, this.typeName, schemaType)); + parsedSchema = await this._baseSchemaParsers[schemaType](this.schema, this.typeName); + this.schema.$parsed = this.config.hooks.onParseSchema(this.schema, parsedSchema) || parsedSchema; } this.schemaPath.pop(); - return schema.$parsed; + return this.schema.$parsed; }; - getInlineParseContent = (rawTypeData, typeName, schemaPath) => { - this.schemaPath.push(...(schemaPath || [])); - const parsedSchema = this.parseSchema(rawTypeData, typeName); - const formattedSchema = this.schemaFormatters.formatSchema(parsedSchema, "inline"); - return formattedSchema.content; + /** + * @param cfg {{ formatType?: "base" | "inline", schemaType?: string } } + * @return {Promise>} + */ + format = async (cfg) => { + const parsedSchema = await this.parse(); + return await this.schemaFormatters.formatSchema(parsedSchema, cfg); }; - getParseContent = (rawTypeData, typeName, schemaPath) => { - this.schemaPath.push(...(schemaPath || [])); - const parsedSchema = this.parseSchema(rawTypeData, typeName); - const formattedSchema = this.schemaFormatters.formatSchema(parsedSchema, "base"); - return formattedSchema.content; + getInlineContent = async () => { + const schema = await this.format({ formatType: "inline" }); + return schema.content; }; - buildTypeNameFromPath = () => { - const schemaPath = _.uniq(_.compact(this.schemaPath)); - - if (!schemaPath || !schemaPath[0]) return null; + getContent = async () => { + const schema = await this.format({ formatType: "base" }); + return schema.content; + }; - return pascalCase(camelCase(_.uniq([schemaPath[0], schemaPath[schemaPath.length - 1]]).join("_"))); + /** + * @param {Record} [schema] + * @param {string | null | undefined} [typeName] + * @param {string[] | undefined} [schemaPath] + * @return {SchemaParser} + */ + createParser = (schema, typeName, schemaPath) => { + return new SchemaParser( + this.config, + this.schemaComponentsMap, + this.typeNameFormatter, + this.schemaFormatters, + this.schemaUtils, + schema, + typeName, + schemaPath, + ); }; + + /** + * @param config {CodeGenConfig} + * @param schemaComponentsMap {SchemaComponentsMap} + * @param typeNameFormatter {TypeNameFormatter} + * @param schemaFormatters {SchemaFormatters} + * @param schemaUtils {SchemaUtils} + * @param {Record} [schema] + * @param {string | null | undefined} [typeName] + * @param {string[] | undefined} [schemaPath] + * @return {SchemaParser} + */ + static create( + config, + schemaComponentsMap, + typeNameFormatter, + schemaFormatters, + schemaUtils, + schema, + typeName, + schemaPath, + ) { + return new SchemaParser( + config, + schemaComponentsMap, + typeNameFormatter, + schemaFormatters, + schemaUtils, + schema, + typeName, + schemaPath, + ); + } } module.exports = { diff --git a/src/schema-parser/schema-parsers/discriminator.js b/src/schema-parser/schema-parsers/discriminator.js index bb8608f9..7f30973f 100644 --- a/src/schema-parser/schema-parsers/discriminator.js +++ b/src/schema-parser/schema-parsers/discriminator.js @@ -46,15 +46,15 @@ class DiscriminatorSchemaParser { this.refPath = this.schemaComponentsMap.createRef("schemas", typeName); } - parse() { + async parse() { const { discriminator, ...noDiscriminatorSchema } = this.schema; if (this.typeName == null || !discriminator.mapping) - return this.schemaParser.parseSchema(noDiscriminatorSchema, this.typeName, this.schemaPath); + return await this.schemaParser.createParser(noDiscriminatorSchema, this.typeName, this.schemaPath).parse(); - const abstractSchemaStruct = this.createAbstractSchemaStruct(); - const complexSchemaStruct = this.createComplexSchemaStruct(); - const discriminatorSchemaStruct = this.createDiscriminatorSchema({ abstractSchemaStruct }); + const abstractSchemaStruct = await this.createAbstractSchemaStruct(); + const complexSchemaStruct = await this.createComplexSchemaStruct(); + const discriminatorSchemaStruct = await this.createDiscriminatorSchema({ abstractSchemaStruct }); const schemaContent = this.config.Ts.IntersectionType( [ @@ -77,11 +77,11 @@ class DiscriminatorSchemaParser { }; } - createDiscriminatorSchema = ({ abstractSchemaStruct }) => { + createDiscriminatorSchema = async ({ abstractSchemaStruct }) => { const { discriminator } = this.schema; const { mapping, propertyName } = discriminator; const mappingEntries = _.entries(mapping); - const complexSchemaKeys = _.keys(this.schemaParser.complexSchemaParsers); + const complexSchemaKeys = _.keys(this.schemaParser._complexSchemaParsers); const ableToCreateMappingType = !!(abstractSchemaStruct?.typeName && mappingEntries.length); const mappingContents = []; let mappingTypeName; @@ -97,7 +97,7 @@ class DiscriminatorSchemaParser { const component = this.schemaComponentsMap.createComponent("schemas", mappingTypeName, { internal: true, }); - const schema = this.schemaParser.parseSchema(component); + const schema = await this.schemaParser.createParser(component).parse(); schema.genericArgs = [{ name: "Key" }, { name: "Type" }]; schema.internal = true; schema.content = this.config.Ts.IntersectionType([ @@ -107,8 +107,8 @@ class DiscriminatorSchemaParser { component.typeData = schema; } - const createMappingContent = (mappingSchema, mappingKey) => { - const content = this.schemaParser.getInlineParseContent(mappingSchema); + const createMappingContent = async (mappingSchema, mappingKey) => { + const content = await this.schemaParser.createParser(mappingSchema).getInlineContent(); if (ableToCreateMappingType) { return this.config.Ts.TypeWithGeneric(mappingTypeName, [this.config.Ts.StringValue(mappingKey), content]); @@ -127,27 +127,31 @@ class DiscriminatorSchemaParser { } }; - for (const [mappingKey, schema] of mappingEntries) { + for await (const [mappingKey, schema] of mappingEntries) { const mappingSchema = typeof schema === "string" ? { $ref: schema } : schema; // override parent dependencies if (mappingSchema.$ref && abstractSchemaStruct?.component?.$ref) { - const mappingRefSchema = this.schemaUtils.getSchemaRefType(mappingSchema)?.rawTypeData; - if (mappingRefSchema) { - complexSchemaKeys.forEach((schemaKey) => { - if (_.isArray(mappingRefSchema[schemaKey])) { - mappingRefSchema[schemaKey] = mappingRefSchema[schemaKey].map((schema) => { - if (schema.$ref === this.refPath) { - return { ...schema, $ref: abstractSchemaStruct.component.$ref }; - } - return schema; - }); + const mappingSchemaRefType = this.schemaUtils.getSchemaRefType(mappingSchema); + if (mappingSchemaRefType?.rawTypeData) { + for await (const schemaKey of complexSchemaKeys) { + if (_.isArray(mappingSchemaRefType.rawTypeData[schemaKey])) { + mappingSchemaRefType.rawTypeData[schemaKey] = mappingSchemaRefType.rawTypeData[schemaKey].map( + (schema) => { + if (schema.$ref === this.refPath) { + schema.$parsed = abstractSchemaStruct.component.$parsed; + return { ...schema, $ref: abstractSchemaStruct.component.$ref }; + } + return schema; + }, + ); } - }); + } } } - mappingContents.push(createMappingContent(mappingSchema, mappingKey)); + const mappingContent = await createMappingContent(mappingSchema, mappingKey); + mappingContents.push(mappingContent); } const content = this.config.Ts.ExpressionGroup(this.config.Ts.UnionType(mappingContents)); @@ -157,9 +161,9 @@ class DiscriminatorSchemaParser { }; }; - createAbstractSchemaStruct = () => { + createAbstractSchemaStruct = async () => { const { discriminator, ...noDiscriminatorSchema } = this.schema; - const complexSchemaKeys = _.keys(this.schemaParser.complexSchemaParsers); + const complexSchemaKeys = _.keys(this.schemaParser._complexSchemaParsers); const schema = _.omit(_.clone(noDiscriminatorSchema), complexSchemaKeys); const schemaIsEmpty = !_.keys(schema).length; @@ -175,7 +179,7 @@ class DiscriminatorSchemaParser { ...schema, internal: true, }); - const content = this.schemaParser.getInlineParseContent(component); + const content = await this.schemaParser.createParser(component).getInlineContent(); return { typeName, @@ -184,13 +188,13 @@ class DiscriminatorSchemaParser { }; }; - createComplexSchemaStruct = () => { - const complexType = this.schemaParser.getComplexType(this.schema); + createComplexSchemaStruct = async () => { + const complexType = this.schemaParser.schemaUtils.getComplexType(this.schema); if (complexType === SCHEMA_TYPES.COMPLEX_UNKNOWN) return null; return { - content: this.config.Ts.ExpressionGroup(this.schemaParser.complexSchemaParsers[complexType](this.schema)), + content: this.config.Ts.ExpressionGroup(await this.schemaParser._complexSchemaParsers[complexType](this.schema)), }; }; diff --git a/src/schema-parser/schema-processor.js b/src/schema-parser/schema-processor.js new file mode 100644 index 00000000..4c3f8397 --- /dev/null +++ b/src/schema-parser/schema-processor.js @@ -0,0 +1,79 @@ +const { SCHEMA_TYPES } = require("../constants.js"); +const _ = require("lodash"); +const { SchemaFormatters } = require("./schema-formatters"); +const { internalCase } = require("../util/internal-case"); +const { SchemaUtils } = require("./schema-utils"); +const { camelCase } = require("lodash"); +const { pascalCase } = require("../util/pascal-case"); +const { SchemaParser } = require("./schema-parser"); + +class SchemaProcessor { + /** + * @type {CodeGenConfig} + */ + config; + + /** + * @type {SchemaComponentsMap} + */ + schemaComponentsMap; + /** + * @type {TypeNameFormatter} + */ + typeNameFormatter; + /** + * @type {SchemaFormatters} + */ + schemaFormatters; + + /** + * @type {SchemaUtils} + */ + schemaUtils; + + /** + * @type {((schema, typeName) => SchemaParser)} + */ + createSchemaParser; + + constructor(config, logger, templates, schemaComponentsMap, typeNameFormatter) { + this.config = config; + this.schemaComponentsMap = schemaComponentsMap; + this.typeNameFormatter = typeNameFormatter; + this.schemaFormatters = new SchemaFormatters(config, logger, this, templates); + this.schemaUtils = new SchemaUtils(config, schemaComponentsMap, typeNameFormatter); + this.createSchemaParser = SchemaParser.create.bind( + null, + config, + schemaComponentsMap, + typeNameFormatter, + this.schemaFormatters, + this.schemaUtils, + ); + } + + /** + * + * @param schema {any} + * @param typeName {null | string} + * @return {Promise>} + */ + parseSchema = async (schema, typeName = null, schemaPath = []) => { + const schemaParser = this.createSchemaParser(schema, typeName, schemaPath); + return await schemaParser.parse(); + }; + + getInlineParseContent = async (schema, typeName, schemaPath) => { + const parser = this.createSchemaParser(schema, typeName, schemaPath); + return await parser.getInlineContent(); + }; + + getParseContent = async (schema, typeName, schemaPath) => { + const parser = this.createSchemaParser(schema, typeName, schemaPath); + return await parser.getContent(); + }; +} + +module.exports = { + SchemaProcessor, +}; diff --git a/src/schema-parser/schema-routes.js b/src/schema-parser/schema-routes.js index 21d2808f..3b877141 100644 --- a/src/schema-parser/schema-routes.js +++ b/src/schema-parser/schema-routes.js @@ -25,7 +25,7 @@ class SchemaRoutes { */ config; /** - * @type {SchemaParser} + * @type {SchemaProcessor} */ schemaParser; /** @@ -33,7 +33,7 @@ class SchemaRoutes { */ schemaUtils; /** - * @type {TypeName} + * @type {TypeNameFormatter} */ typeName; /** @@ -64,10 +64,12 @@ class SchemaRoutes { this.schemaComponentMap = schemaComponentMap; this.logger = logger; this.templates = templates; + } + async init() { this.FORM_DATA_TYPES = _.uniq([ - this.schemaParser.getSchemaType({ type: "string", format: "file" }), - this.schemaParser.getSchemaType({ type: "string", format: "binary" }), + await this.schemaUtils.getSchemaType({ type: "string", format: "file" }), + await this.schemaUtils.getSchemaType({ type: "string", format: "binary" }), ]); } @@ -308,13 +310,13 @@ class SchemaRoutes { return null; }; - getTypeFromRequestInfo = ({ requestInfo, parsedSchemas, operationId, defaultType, typeName }) => { + getTypeFromRequestInfo = async ({ requestInfo, parsedSchemas, operationId, defaultType, typeName }) => { // TODO: make more flexible pick schema without content type const schema = this.getSchemaFromRequestType(requestInfo); const refTypeInfo = this.schemaParser.schemaUtils.getSchemaRefType(requestInfo); if (schema) { - const content = this.schemaParser.getInlineParseContent(schema, typeName, [operationId]); + const content = await this.schemaParser.getInlineParseContent(schema, typeName, [operationId]); const foundedSchemaByName = _.find( parsedSchemas, (parsedSchema) => this.typeName.format(parsedSchema.name) === content, @@ -341,13 +343,13 @@ class SchemaRoutes { return this.typeName.format(refTypeInfo.typeName); case "responses": case "requestBodies": - return this.schemaParser.getInlineParseContent( + return await this.schemaParser.getInlineParseContent( this.getSchemaFromRequestType(refTypeInfo.rawTypeData), refTypeInfo.typeName || null, [operationId], ); default: - return this.schemaParser.getInlineParseContent(refTypeInfo.rawTypeData, refTypeInfo.typeName || null, [ + return await this.schemaParser.getInlineParseContent(refTypeInfo.rawTypeData, refTypeInfo.typeName || null, [ operationId, ]); } @@ -356,42 +358,44 @@ class SchemaRoutes { return defaultType || this.config.Ts.Keyword.Any; }; - getRequestInfoTypes = ({ requestInfos, parsedSchemas, operationId, defaultType }) => - _.reduce( - requestInfos, - (acc, requestInfo, status) => { - const contentTypes = this.getContentTypes([requestInfo]); - - return [ - ...acc, - { - ...(requestInfo || {}), - contentTypes: contentTypes, - contentKind: this.getContentKind(contentTypes), - type: this.schemaParser.schemaUtils.safeAddNullToType( - requestInfo, - this.getTypeFromRequestInfo({ - requestInfo, - parsedSchemas, - operationId, - defaultType, - }), - ), - description: this.schemaParser.schemaFormatters.formatDescription(requestInfo.description || "", true), - status: _.isNaN(+status) ? status : +status, - isSuccess: this.isSuccessStatus(status), - }, - ]; - }, - [], - ); + getRequestInfoTypes = async ({ requestInfos, parsedSchemas, operationId, defaultType }) => { + const requestInfoTypes = []; + + const statuses = _.entries(requestInfos); + + for await (const [status, requestInfo] of statuses) { + const contentTypes = this.getContentTypes([requestInfo]); - getResponseBodyInfo = (routeInfo, routeParams, parsedSchemas) => { + const requestInfoType = { + ...(requestInfo || {}), + contentTypes: contentTypes, + contentKind: this.getContentKind(contentTypes), + type: this.schemaParser.schemaUtils.safeAddNullToType( + requestInfo, + await this.getTypeFromRequestInfo({ + requestInfo, + parsedSchemas, + operationId, + defaultType, + }), + ), + description: this.schemaParser.schemaFormatters.formatDescription(requestInfo.description || "", true), + status: _.isNaN(+status) ? status : +status, + isSuccess: this.isSuccessStatus(status), + }; + + requestInfoTypes.push(requestInfoType); + } + + return requestInfoTypes; + }; + + getResponseBodyInfo = async (routeInfo, routeParams, parsedSchemas) => { const { produces, operationId, responses } = routeInfo; const contentTypes = this.getContentTypes(responses, [...(produces || []), routeInfo["x-accepts"]]); - const responseInfos = this.getRequestInfoTypes({ + const responseInfos = await this.getRequestInfoTypes({ requestInfos: responses, parsedSchemas, operationId, @@ -403,14 +407,16 @@ class SchemaRoutes { (response) => !response.isSuccess && response.type !== this.config.Ts.Keyword.Any, ); - const handleResponseHeaders = (src) => { + const handleResponseHeaders = async (src) => { if (!src) { return "headers: {},"; } const headerTypes = Object.fromEntries( - Object.entries(src).map(([k, v]) => { - return [k, this.schemaParser.getSchemaType(v)]; - }), + await Promise.all( + Object.entries(src).map(async ([k, v]) => { + return [k, await this.schemaUtils.getSchemaType(v)]; + }), + ), ); const r = `headers: { ${Object.entries(headerTypes) .map(([k, v]) => `"${k}": ${v}`) @@ -432,11 +438,13 @@ class SchemaRoutes { full: { types: this.config.Ts.UnionType( - responseInfos.map( - (response) => `{ + await Promise.all( + responseInfos.map( + async (response) => `{ data: ${response.type}, status: ${response.status}, statusCode: ${response.status}, statusText: "${ - response.description - }", ${handleResponseHeaders(response.headers)} config: {} }`, + response.description + }", ${await handleResponseHeaders(response.headers)} config: {} }`, + ), ), ) || this.config.Ts.Keyword.Any, }, @@ -467,7 +475,7 @@ class SchemaRoutes { ); }; - getRequestBodyInfo = (routeInfo, routeParams, parsedSchemas, routeName) => { + getRequestBodyInfo = async (routeInfo, routeParams, parsedSchemas, routeName) => { const { requestBody, consumes, requestBodyName, operationId } = routeInfo; let schema = null; let type = null; @@ -488,15 +496,15 @@ class SchemaRoutes { if (routeParams.formData.length) { contentKind = CONTENT_KIND.FORM_DATA; schema = this.convertRouteParamsIntoObject(routeParams.formData); - type = this.schemaParser.getInlineParseContent(schema, typeName, [operationId]); + type = await this.schemaParser.getInlineParseContent(schema, typeName, [operationId]); } else if (contentKind === CONTENT_KIND.FORM_DATA) { schema = this.getSchemaFromRequestType(requestBody); - type = this.schemaParser.getInlineParseContent(schema, typeName, [operationId]); + type = await this.schemaParser.getInlineParseContent(schema, typeName, [operationId]); } else if (requestBody) { schema = this.getSchemaFromRequestType(requestBody); type = this.schemaParser.schemaUtils.safeAddNullToType( requestBody, - this.getTypeFromRequestInfo({ + await this.getTypeFromRequestInfo({ requestInfo: requestBody, parsedSchemas, operationId, @@ -514,7 +522,7 @@ class SchemaRoutes { if (schema && !schema.$ref && this.config.extractRequestBody) { schema = this.schemaComponentMap.createComponent("schemas", typeName, { ...schema }); - type = this.schemaParser.getInlineParseContent(schema, null, [operationId]); + type = await this.schemaParser.getInlineParseContent(schema, null, [operationId]); } return { @@ -591,7 +599,7 @@ class SchemaRoutes { return schema; }; - extractResponseBodyIfItNeeded = (routeInfo, responseBodyInfo, routeName) => { + extractResponseBodyIfItNeeded = async (routeInfo, responseBodyInfo, routeName) => { if (responseBodyInfo.responses.length && responseBodyInfo.success && responseBodyInfo.success.schema) { const typeName = this.schemaUtils.resolveTypeName( routeName.usage, @@ -606,7 +614,7 @@ class SchemaRoutes { if (successResponse.schema && !successResponse.schema.$ref) { const schema = this.getSchemaFromRequestType(successResponse.schema); successResponse.schema = this.schemaComponentMap.createComponent("schemas", typeName, { ...schema }); - successResponse.type = this.schemaParser.getInlineParseContent(successResponse.schema, [routeInfo.open]); + successResponse.type = await this.schemaParser.getInlineParseContent(successResponse.schema, [routeInfo.open]); if (idx > -1) { _.assign(responseBodyInfo.responses[idx], { @@ -618,7 +626,7 @@ class SchemaRoutes { } }; - extractResponseErrorIfItNeeded = (routeInfo, responseBodyInfo, routeName) => { + extractResponseErrorIfItNeeded = async (routeInfo, responseBodyInfo, routeName) => { if (responseBodyInfo.responses.length && responseBodyInfo.error.schemas && responseBodyInfo.error.schemas.length) { const typeName = this.schemaUtils.resolveTypeName( routeName.usage, @@ -630,7 +638,7 @@ class SchemaRoutes { if (!errorSchemas.length) return; - const schema = this.schemaParser.parseSchema({ + const schema = await this.schemaParser.parseSchema({ oneOf: errorSchemas, title: errorSchemas .map((schema) => schema.title) @@ -647,12 +655,12 @@ class SchemaRoutes { } }; - getRouteName = (rawRouteInfo) => { + getRouteName = async (rawRouteInfo) => { const { moduleName } = rawRouteInfo; const { routeNameDuplicatesMap, templatesToRender } = this.config; const routeNameTemplate = templatesToRender.routeName; - const routeNameFromTemplate = this.templates.renderTemplate(routeNameTemplate, { + const routeNameFromTemplate = await this.templates.renderTemplate(routeNameTemplate, { routeInfo: rawRouteInfo, }); @@ -684,7 +692,7 @@ class SchemaRoutes { return this.config.hooks.onCreateRouteName(routeNameInfo, rawRouteInfo) || routeNameInfo; }; - parseRouteInfo = (rawRouteName, routeInfo, method, usageSchema, parsedSchemas) => { + parseRouteInfo = async (rawRouteName, routeInfo, method, usageSchema, parsedSchemas) => { const { security: globalSecurity } = usageSchema; const { moduleNameIndex, moduleNameFirstTag, extractRequestParams } = this.config; const { @@ -720,15 +728,17 @@ class SchemaRoutes { const routeParams = this.getRouteParams(routeInfo, pathParamsFromRouteName, queryParamsFromRouteName); - const pathArgs = routeParams.path.map((pathArgSchema) => ({ - name: pathArgSchema.name, - optional: !pathArgSchema.required, - type: this.schemaParser.getInlineParseContent(pathArgSchema.schema), - description: pathArgSchema.description, - })); + const pathArgs = await Promise.all( + routeParams.path.map(async (pathArgSchema) => ({ + name: pathArgSchema.name, + optional: !pathArgSchema.required, + type: await this.schemaParser.getInlineParseContent(pathArgSchema.schema), + description: pathArgSchema.description, + })), + ); const pathArgsNames = pathArgs.map((arg) => arg.name); - const responseBodyInfo = this.getResponseBodyInfo(routeInfo, routeParams, parsedSchemas); + const responseBodyInfo = await this.getResponseBodyInfo(routeInfo, routeParams, parsedSchemas); const rawRouteInfo = { ...otherInfo, @@ -751,9 +761,9 @@ class SchemaRoutes { const pathObjectSchema = this.convertRouteParamsIntoObject(routeParams.path); const headersObjectSchema = this.convertRouteParamsIntoObject(routeParams.header); - const routeName = this.getRouteName(rawRouteInfo); + const routeName = await this.getRouteName(rawRouteInfo); - const requestBodyInfo = this.getRequestBodyInfo(routeInfo, routeParams, parsedSchemas, routeName); + const requestBodyInfo = await this.getRequestBodyInfo(routeInfo, routeParams, parsedSchemas, routeName); const requestParamsSchema = this.createRequestParamsSchema({ queryParams: routeParams.query, @@ -764,15 +774,19 @@ class SchemaRoutes { }); if (this.config.extractResponseBody) { - this.extractResponseBodyIfItNeeded(routeInfo, responseBodyInfo, routeName); + await this.extractResponseBodyIfItNeeded(routeInfo, responseBodyInfo, routeName); } if (this.config.extractResponseError) { - this.extractResponseErrorIfItNeeded(routeInfo, responseBodyInfo, routeName); + await this.extractResponseErrorIfItNeeded(routeInfo, responseBodyInfo, routeName); } - const queryType = routeParams.query.length ? this.schemaParser.getInlineParseContent(queryObjectSchema) : null; - const pathType = routeParams.path.length ? this.schemaParser.getInlineParseContent(pathObjectSchema) : null; - const headersType = routeParams.header.length ? this.schemaParser.getInlineParseContent(headersObjectSchema) : null; + const queryType = routeParams.query.length + ? await this.schemaParser.getInlineParseContent(queryObjectSchema) + : null; + const pathType = routeParams.path.length ? await this.schemaParser.getInlineParseContent(pathObjectSchema) : null; + const headersType = routeParams.header.length + ? await this.schemaParser.getInlineParseContent(headersObjectSchema) + : null; const nameResolver = new SpecificArgNameResolver(this.logger, pathArgsNames); @@ -780,7 +794,7 @@ class SchemaRoutes { query: queryType ? { name: nameResolver.resolve(RESERVED_QUERY_ARG_NAMES), - optional: this.schemaParser.parseSchema(queryObjectSchema).allFieldsAreOptional, + optional: (await this.schemaParser.parseSchema(queryObjectSchema)).allFieldsAreOptional, type: queryType, } : void 0, @@ -794,14 +808,14 @@ class SchemaRoutes { pathParams: pathType ? { name: nameResolver.resolve(RESERVED_PATH_ARG_NAMES), - optional: this.schemaParser.parseSchema(pathObjectSchema).allFieldsAreOptional, + optional: (await this.schemaParser.parseSchema(pathObjectSchema)).allFieldsAreOptional, type: pathType, } : void 0, headers: headersType ? { name: nameResolver.resolve(RESERVED_HEADER_ARG_NAMES), - optional: this.schemaParser.parseSchema(headersObjectSchema).allFieldsAreOptional, + optional: (await this.schemaParser.parseSchema(headersObjectSchema)).allFieldsAreOptional, type: headersType, } : void 0, @@ -846,16 +860,16 @@ class SchemaRoutes { }; }; - attachSchema = ({ usageSchema, parsedSchemas }) => { + attachSchema = async ({ usageSchema, parsedSchemas }) => { this.config.routeNameDuplicatesMap.clear(); const pathsEntries = _.entries(usageSchema.paths); - _.forEach(pathsEntries, ([rawRouteName, routeInfoByMethodsMap]) => { + for await (const [rawRouteName, routeInfoByMethodsMap] of pathsEntries) { const routeInfosMap = this.createRequestsMap(routeInfoByMethodsMap); - _.forEach(routeInfosMap, (routeInfo, method) => { - const parsedRouteInfo = this.parseRouteInfo(rawRouteName, routeInfo, method, usageSchema, parsedSchemas); + for await (const [method, routeInfo] of _.entries(routeInfosMap)) { + const parsedRouteInfo = await this.parseRouteInfo(rawRouteName, routeInfo, method, usageSchema, parsedSchemas); const processedRouteInfo = this.config.hooks.onCreateRoute(parsedRouteInfo); const route = processedRouteInfo || parsedRouteInfo; @@ -870,8 +884,8 @@ class SchemaRoutes { } this.routes.push(route); - }); - }); + } + } }; getGroupedRoutes = () => { diff --git a/src/schema-parser/schema-utils.js b/src/schema-parser/schema-utils.js index 97bdd1a7..7274240c 100644 --- a/src/schema-parser/schema-utils.js +++ b/src/schema-parser/schema-utils.js @@ -12,10 +12,15 @@ class SchemaUtils { * @type {SchemaComponentsMap} */ schemaComponentsMap; + /** + * @type {TypeNameFormatter} + */ + typeNameFormatter; - constructor(config, schemaComponentsMap) { + constructor(config, schemaComponentsMap, typeNameFormatter) { this.config = config; this.schemaComponentsMap = schemaComponentsMap; + this.typeNameFormatter = typeNameFormatter; } getRequiredProperties = (schema) => { @@ -141,6 +146,63 @@ class SchemaUtils { return childSchema; }; + getComplexType = (schema) => { + if (schema.oneOf) return SCHEMA_TYPES.COMPLEX_ONE_OF; + if (schema.allOf) return SCHEMA_TYPES.COMPLEX_ALL_OF; + if (schema.anyOf) return SCHEMA_TYPES.COMPLEX_ANY_OF; + // TODO :( + if (schema.not) return SCHEMA_TYPES.COMPLEX_NOT; + + return SCHEMA_TYPES.COMPLEX_UNKNOWN; + }; + + getInternalSchemaType = (schema) => { + if (!_.isEmpty(schema.enum) || !_.isEmpty(this.getEnumNames(schema))) return SCHEMA_TYPES.ENUM; + if (schema.discriminator) return SCHEMA_TYPES.DISCRIMINATOR; + if (schema.allOf || schema.oneOf || schema.anyOf || schema.not) return SCHEMA_TYPES.COMPLEX; + if (!_.isEmpty(schema.properties)) return SCHEMA_TYPES.OBJECT; + if (schema.type === SCHEMA_TYPES.ARRAY) return SCHEMA_TYPES.ARRAY; + + return SCHEMA_TYPES.PRIMITIVE; + }; + + getSchemaType = async (schema) => { + if (!schema) return this.config.Ts.Keyword.Any; + + const refTypeInfo = this.getSchemaRefType(schema); + + if (refTypeInfo) { + return this.checkAndAddRequiredKeys( + schema, + this.safeAddNullToType(schema, this.typeNameFormatter.format(refTypeInfo.typeName)), + ); + } + + const primitiveType = this.getSchemaPrimitiveType(schema); + + if (primitiveType == null) return this.config.Ts.Keyword.Any; + + let resultType; + + /** + * @type {string | (() => Promise)} + */ + const typeAlias = + _.get(this.config.primitiveTypes, [primitiveType, schema.format]) || + _.get(this.config.primitiveTypes, [primitiveType, "$default"]) || + this.config.primitiveTypes[primitiveType]; + + if (_.isFunction(typeAlias)) { + resultType = await typeAlias(schema, this); + } else { + resultType = typeAlias || primitiveType; + } + + if (!resultType) return this.config.Ts.Keyword.Any; + + return this.checkAndAddRequiredKeys(schema, this.safeAddNullToType(schema, resultType)); + }; + filterSchemaContents = (contents, filterFn) => { return _.uniq(_.filter(contents, (type) => filterFn(type))); }; diff --git a/src/templates.js b/src/templates.js index 90203a9e..ed06c207 100644 --- a/src/templates.js +++ b/src/templates.js @@ -152,23 +152,28 @@ class Templates { return ""; }; - renderTemplate = (template, configuration, options) => { + renderTemplate = async (template, configuration, options) => { if (!template) return ""; - return Eta.render( - template, - { - ...this.getRenderTemplateData(), - ...configuration, - }, - { - async: false, - ...(options || {}), - includeFile: (path, configuration, options) => { - return this.renderTemplate(this.getTemplateContent(path), configuration, options); + try { + return await Eta.renderAsync( + template, + { + ...this.getRenderTemplateData(), + ...configuration, }, - }, - ); + { + async: true, + ...(options || {}), + includeFile: async (path, configuration, options) => { + return await this.renderTemplate(this.getTemplateContent(path), configuration, options); + }, + }, + ); + } catch (e) { + console.error("problem in this templates\n", template); + throw e; + } }; } diff --git a/src/type-name.js b/src/type-name-formatter.js similarity index 98% rename from src/type-name.js rename to src/type-name-formatter.js index 373e33b4..dbc28c7a 100644 --- a/src/type-name.js +++ b/src/type-name-formatter.js @@ -4,7 +4,7 @@ const _ = require("lodash"); * @typedef {"enum-key" | "type-name"} FormattingSchemaType */ -class TypeName { +class TypeNameFormatter { /** @type {Map} */ formattedModelNamesMap = new Map(); @@ -94,5 +94,5 @@ class TypeName { } module.exports = { - TypeName, + TypeNameFormatter, }; diff --git a/templates/base/data-contracts.ejs b/templates/base/data-contracts.ejs index c7e8c547..2fa3f0fe 100644 --- a/templates/base/data-contracts.ejs +++ b/templates/base/data-contracts.ejs @@ -32,9 +32,9 @@ const dataContractTemplates = { type <%~ config.Ts.CodeGenKeyword.UtilRequiredKeys %> = Omit & Required> <% } %> -<% modelTypes.forEach((contract) => { %> - <%~ includeFile('@base/data-contract-jsdoc.ejs', { ...it, data: { ...contract, ...contract.typeData } }) %> - <%~ contract.internal ? '' : 'export'%> <%~ (dataContractTemplates[contract.typeIdentifier] || dataContractTemplates.type)(contract) %> +<% for await (const contract of modelTypes) { %> + <%~ await includeFile('@base/data-contract-jsdoc.ejs', { ...it, data: { ...contract, ...contract.typeData } }) %> + <%~ contract.internal ? '' : 'export'%> <%~ (dataContractTemplates[contract.typeIdentifier] || dataContractTemplates.type)(contract) %> -<% }) %> +<% } %> diff --git a/templates/base/http-client.ejs b/templates/base/http-client.ejs index 043c9e1e..201adb9e 100644 --- a/templates/base/http-client.ejs +++ b/templates/base/http-client.ejs @@ -1,3 +1,3 @@ <% const { config } = it; %> <% /* https://github.com/acacode/swagger-typescript-api/tree/next/templates/base/http-clients/ */ %> -<%~ includeFile(`@base/http-clients/${config.httpClientType}-http-client`, it) %> \ No newline at end of file +<%~ await includeFile(`@base/http-clients/${config.httpClientType}-http-client`, it) %> \ No newline at end of file diff --git a/templates/base/interface-data-contract.ejs b/templates/base/interface-data-contract.ejs index 29bbed5f..4f075751 100644 --- a/templates/base/interface-data-contract.ejs +++ b/templates/base/interface-data-contract.ejs @@ -3,8 +3,8 @@ const { contract, utils } = it; const { formatDescription, require, _ } = utils; %> export interface <%~ contract.name %> { - <% _.forEach(contract.$content, (field) => { %> - <%~ includeFile('@base/object-field-jsdoc.ejs', { ...it, field }) %> + <% for await (const field of contract.$content) { %> + <%~ await includeFile('@base/object-field-jsdoc.ejs', { ...it, field }) %> <%~ field.name %><%~ field.isRequired ? '' : '?' %>: <%~ field.value %><%~ field.isNullable ? ' | null' : ''%>; - <% }) %> + <% } %> } diff --git a/templates/base/route-type.ejs b/templates/base/route-type.ejs index bdad929f..99e10592 100644 --- a/templates/base/route-type.ejs +++ b/templates/base/route-type.ejs @@ -3,7 +3,7 @@ const { route, utils, config } = it; const { _, pascalCase, require } = utils; const { query, payload, pathParams, headers } = route.request; -const routeDocs = includeFile("@base/route-docs", { config, route, utils }); +const routeDocs = await includeFile("@base/route-docs", { config, route, utils }); const routeNamespace = pascalCase(route.routeName.usage); %> diff --git a/templates/base/type-data-contract.ejs b/templates/base/type-data-contract.ejs index fb43ce22..b5311091 100644 --- a/templates/base/type-data-contract.ejs +++ b/templates/base/type-data-contract.ejs @@ -5,10 +5,10 @@ const { formatDescription, require, _ } = utils; %> <% if (contract.$content.length) { %> export type <%~ contract.name %> = { - <% _.forEach(contract.$content, (field) => { %> - <%~ includeFile('@base/object-field-jsdoc.ejs', { ...it, field }) %> + <% for await (const field of contract.$content) { %> + <%~ await includeFile('@base/object-field-jsdoc.ejs', { ...it, field }) %> <%~ field.field %>; - <% }) %> + <% } %> }<%~ utils.isNeedToAddNull(contract) ? ' | null' : ''%> <% } else { %> export type <%~ contract.name %> = Record; diff --git a/templates/default/api.ejs b/templates/default/api.ejs index 5aee183b..60c724ca 100644 --- a/templates/default/api.ejs +++ b/templates/default/api.ejs @@ -23,7 +23,6 @@ const descriptionLines = _.compact([ info.description && " ", info.description && _.replace(formatDescription(info.description), /\n/g, "\n * "), ]); - %> <% if (config.httpClientType === config.constants.HTTP_CLIENT.AXIOS) { %> import { AxiosRequestConfig, AxiosResponse } from "axios"; <% } %> @@ -47,19 +46,23 @@ export class <%~ config.apiClassName %><% if ( <% } %> -<% routes.outOfModule && routes.outOfModule.forEach((route) => { %> +<% if (routes.outOfModule) { %> + <% for await (const route of routes.outOfModule) { %> - <%~ includeFile('./procedure-call.ejs', { ...it, route }) %> -<% }) %> + <%~ await includeFile('./procedure-call.ejs', { ...it, route }) %> + <% } %> +<% } %> -<% routes.combined && routes.combined.forEach(({ routes = [], moduleName }) => { %> +<% if (routes.combined) { %> + <% for await (const { routes: combinedRoutes = [], moduleName } of routes.combined) { %> <%~ moduleName %> = { - <% routes.forEach((route) => { %> + <% for await (const route of combinedRoutes) { %> - <%~ includeFile('./procedure-call.ejs', { ...it, route }) %> - <% }) %> + <%~ await includeFile('./procedure-call.ejs', { ...it, route }) %> + <% } %> } -<% }) %> + <% } %> +<% } %> } diff --git a/templates/default/procedure-call.ejs b/templates/default/procedure-call.ejs index 465d6327..7efd5b7b 100644 --- a/templates/default/procedure-call.ejs +++ b/templates/default/procedure-call.ejs @@ -5,7 +5,7 @@ const { _, getInlineParseContent, getParseContent, parseSchema, getComponentByRe const { parameters, path, method, payload, query, formData, security, requestParams } = route.request; const { type, errorType, contentTypes } = route.response; const { HTTP_CLIENT, RESERVED_REQ_PARAMS_ARG_NAMES } = config.constants; -const routeDocs = includeFile("@base/route-docs", { config, route, utils }); +const routeDocs = await includeFile("@base/route-docs", { config, route, utils }); const queryName = (query && query.name) || "query"; const pathParams = _.values(parameters); const pathParamsNames = _.map(pathParams, "name"); @@ -26,7 +26,7 @@ const rawWrapperArgs = config.extractRequestParams ? requestParams && { name: pathParams.length ? `{ ${_.join(pathParamsNames, ", ")}, ...${queryName} }` : queryName, optional: false, - type: getInlineParseContent(requestParams), + type: await getInlineParseContent(requestParams), }, ...(!requestParams ? pathParams : []), payload, diff --git a/templates/default/route-types.ejs b/templates/default/route-types.ejs index 5c9ff567..d9116e60 100644 --- a/templates/default/route-types.ejs +++ b/templates/default/route-types.ejs @@ -12,17 +12,21 @@ import { <%~ dataContracts.join(", ") %> } from "./<%~ config.fileNames.dataCont /* TODO: outOfModule, combined should be attributes of route, which will allow to avoid duplication of code */ %> -<% routes.outOfModule && routes.outOfModule.forEach(({ routes = [] }) => { %> - <% routes.forEach((route) => { %> - <%~ includeFile('@base/route-type.ejs', { ...it, route }) %> - <% }) %> -<% }) %> -<% routes.combined && routes.combined.forEach(({ routes = [], moduleName }) => { %> +<% if (routes.outOfModule) { %> + <% for await (const route of routes.outOfModule) { %> + + <%~ await includeFile('@base/route-type.ejs', { ...it, route }) %> + <% } %> +<% } %> + +<% if (routes.combined) { %> + <% for await (const { routes: combinedRoutes = [], moduleName } of routes.combined) { %> + export namespace <%~ pascalCase(moduleName) %> { - <% routes.forEach((route) => { %> - <%~ includeFile('@base/route-type.ejs', { ...it, route }) %> - <% }) %> + <% for await (const route of combinedRoutes) { %> + <%~ await includeFile('@base/route-type.ejs', { ...it, route }) %> + <% } %> } - -<% }) %> + <% } %> +<% } %> diff --git a/templates/modular/api.ejs b/templates/modular/api.ejs index ab371e31..c1335727 100644 --- a/templates/modular/api.ejs +++ b/templates/modular/api.ejs @@ -22,7 +22,7 @@ export class <%= apiClassName %><% if (!config.singl } <% } %> - <% routes.forEach((route) => { %> - <%~ includeFile('./procedure-call.ejs', { ...it, route }) %> - <% }) %> + <% for await (const route of routes) { %> + <%~ await includeFile('./procedure-call.ejs', { ...it, route }) %> + <% } %> } diff --git a/templates/modular/procedure-call.ejs b/templates/modular/procedure-call.ejs index 6f15500f..29ef9b85 100644 --- a/templates/modular/procedure-call.ejs +++ b/templates/modular/procedure-call.ejs @@ -5,7 +5,7 @@ const { _, getInlineParseContent, getParseContent, parseSchema, getComponentByRe const { parameters, path, method, payload, query, formData, security, requestParams } = route.request; const { type, errorType, contentTypes } = route.response; const { HTTP_CLIENT, RESERVED_REQ_PARAMS_ARG_NAMES } = config.constants; -const routeDocs = includeFile("@base/route-docs", { config, route, utils }); +const routeDocs = await includeFile("@base/route-docs", { config, route, utils }); const queryName = (query && query.name) || "query"; const pathParams = _.values(parameters); const pathParamsNames = _.map(pathParams, "name"); @@ -26,7 +26,7 @@ const rawWrapperArgs = config.extractRequestParams ? requestParams && { name: pathParams.length ? `{ ${_.join(pathParamsNames, ", ")}, ...${queryName} }` : queryName, optional: false, - type: getInlineParseContent(requestParams), + type: await getInlineParseContent(requestParams), }, ...(!requestParams ? pathParams : []), payload, diff --git a/templates/modular/route-types.ejs b/templates/modular/route-types.ejs index d1f52eea..c5afd86f 100644 --- a/templates/modular/route-types.ejs +++ b/templates/modular/route-types.ejs @@ -10,9 +10,9 @@ import { <%~ dataContracts.join(", ") %> } from "./<%~ config.fileNames.dataCont <% } %> export namespace <%~ pascalCase(moduleName) %> { - <% _.forEach(routes, (route) => { %> + <% for await (const route of routes) { %> - <%~ includeFile('@base/route-type.ejs', { ...it, route }) %> - <% }) %> + <%~ await includeFile('@base/route-type.ejs', { ...it, route }) %> + <% } %> } diff --git a/tests/generated/v2.0/api-with-examples.ts b/tests/generated/v2.0/api-with-examples.ts index c57736d7..4ea76d2e 100644 --- a/tests/generated/v2.0/api-with-examples.ts +++ b/tests/generated/v2.0/api-with-examples.ts @@ -238,7 +238,6 @@ export class Api extends HttpClient extends HttpClient extends HttpClient extends HttpClient { if (process.env.UPDATE_SNAPSHOTS) { @@ -9,14 +10,88 @@ const assertGeneratedModule = (pathToModule1, pathToModule2) => { const output = fs.readFileSync(pathToModule1).toString("utf8"); const expected = fs.readFileSync(pathToModule2).toString("utf8"); - const diff = gitDiff(output, expected, { + const diff = gitDiff(expected, output, { color: true, flags: "--diff-algorithm=default --ignore-space-at-eol --ignore-cr-at-eol --ignore-space-change --ignore-all-space", }); if (diff && diff.length) { - console.log("\n" + diff); + const minusLinePrefix1 = "-"; + const minusLinePrefix2 = "-"; + const plusLinePrefix1 = "+"; + const plusLinePrefix2 = "+"; + const plusLinePrefix3 = "+"; + const lines = diff.split("\n"); + const lineStructs = []; + let deletedLines = 0; + let addedLines = 0; + const printPos = (pos) => { + const fills = lines.length.toString().length; + return `${pos + 1}`.padStart(fills, "0") + ": "; + }; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + if (line.startsWith(minusLinePrefix1) || line.startsWith(minusLinePrefix2)) { + lineStructs.push({ + pos: i, + id: Math.random().toString() + i, + deleted: true, + line: `${printPos(i + addedLines)}${line}`, + }); + ++deletedLines; + } else if ( + line.startsWith(plusLinePrefix1) || + line.startsWith(plusLinePrefix2) || + line.startsWith(plusLinePrefix3) + ) { + lineStructs.push({ + pos: i, + id: Math.random().toString() + i, + added: true, + line: `${printPos(i - deletedLines)}${line}`, + }); + ++addedLines; + } else { + lineStructs.push({ + pos: i, + id: Math.random().toString() + i, + line: `${printPos(i - deletedLines)}${line}`, + }); + addedLines = 0; + deletedLines = 0; + } + } + + const computedLines = []; + for (let i = 0; i < lineStructs.length; i++) { + const lineStruct = lineStructs[i]; + if (lineStruct.deleted) { + const sliced = lineStructs.slice(i - 1, i + 1); + computedLines.push(...sliced); + } else if (lineStruct.added) { + const sliced = lineStructs.slice(i, i + 1 + 1); + computedLines.push(...sliced); + } + } + + const sortedLines = _.sortBy(_.uniqBy(computedLines, "id"), "pos"); + const maxLine = (sortedLines.map((v) => v.line).sort((a, b) => b.length - a.length)[0] || "").length; + const fixedLines = sortedLines.reduce((acc, computedLine, i, arr) => { + const prev = arr[i - 1]; + if ((prev && computedLine.pos - prev.pos > 10) || !i) { + acc.push("".padEnd(maxLine, "-")); + } + acc.push(computedLine.line); + if (arr.length - 1 === i) { + acc.push("".padEnd(maxLine, "-")); + } + return acc; + }, []); + console.log("\n"); + fixedLines.forEach((line) => { + console.log(line); + }); console.error(new Error("expected another output").stack); process.exit(1); } diff --git a/tests/spec/discriminator/schema.ts b/tests/spec/discriminator/schema.ts index b522d7b1..3d70a275 100644 --- a/tests/spec/discriminator/schema.ts +++ b/tests/spec/discriminator/schema.ts @@ -25,13 +25,13 @@ export type BlockDTO = AbstractBlockDto & | (AbstractBlockDtoTypeMapping<"csv", CsvBlockDTO> | AbstractBlockDtoTypeMapping<"file", FileBlockDTO>) ); -export type CsvBlockDTO = AbstractBlockDto & { +export type CsvBlockDTO = BlockDTO & { /** @default "csv" */ type: "csv"; text: string; }; -export type FileBlockDTO = AbstractBlockDto & { +export type FileBlockDTO = BlockDTO & { /** @default "file" */ type: "file"; fileId: string; @@ -44,11 +44,11 @@ export type Pet = AbstractPet & | AbstractPetPetTypeMapping<"lizard", Lizard> ); -export type Cat = AbstractPet & { +export type Cat = Pet & { name?: string; }; -export type Dog = AbstractPet & { +export type Dog = Pet & { bark?: string; }; diff --git a/tests/spec/extract-enums/expected.ts b/tests/spec/extract-enums/expected.ts index 77b9b329..c7fda85f 100644 --- a/tests/spec/extract-enums/expected.ts +++ b/tests/spec/extract-enums/expected.ts @@ -9,63 +9,32 @@ * --------------------------------------------------------------- */ -export enum TNPEnumRootTNS { - EKPInvalidKey100644EKS = "100644", - EKPInvalidKey100755EKS = "100755", - EKPInvalidKey040000EKS = "040000", - EKPInvalidKey160000EKS = "160000", - EKPInvalidKey120000EKS = "120000", - EKPTest1EKS = "test1", - EKPTest2EKS = "test2", -} - -export interface TNPTreeTNS { - tree?: { - mode?: TNPTreeModeTNS; - "mode-num"?: TNPTreeModeNumTNS; - type?: TNPTreeTypeTNS; - bereke?: TNPTreeBerekeTNS; - }[]; -} - -export enum TNPOnlyEnumNamesTNS { +/** @format int32 */ +export enum TNPEmptyEnumTNS { EKPBlaEKS = "Bla", EKPBlablaEKS = "Blabla", EKPBoilerEKS = "Boiler", } -export enum TNPStringOnlyEnumNamesTNS { - EKPBlaEKS = "Bla", +/** @format int32 */ +export enum TNPEnumWithMoreNamesTNS { + EKPBlaEKS = 1, EKPBlablaEKS = "Blabla", EKPBoilerEKS = "Boiler", } -export enum TNPStringEnumsTNS { - EKPBlaEKS = "foo", - EKPBlablaEKS = "bar", - EKPBoilerEKS = "Boiler", -} - -export enum TNPStringCompleteEnumsTNS { - EKPBlaEKS = "foo", - EKPBlablaEKS = "bar", - EKPBoilerEKS = "baz", +/** @example "APPROVED" */ +export enum TNPNameSpaceAddSuperDuperTNS { + EKP_NEW_EKS = "NEW", + EKP_PENDING_EKS = "PENDING", } -/** @format int32 */ -export enum TNPEmptyEnumTNS { +export enum TNPOnlyEnumNamesTNS { EKPBlaEKS = "Bla", EKPBlablaEKS = "Blabla", EKPBoilerEKS = "Boiler", } -/** @format int32 */ -export enum TNPEnumWithMoreNamesTNS { - EKPBlaEKS = 1, - EKPBlablaEKS = "Blabla", - EKPBoilerEKS = "Boiler", -} - /** @format int32 */ export enum TNPSomeInterestEnumTNS { EKPBlaEKS = 6, @@ -87,6 +56,24 @@ export enum TNPSomeInterestEnumTNS { EKP_HSDFDS_EKS = "HSDFDS", } +export enum TNPStringCompleteEnumsTNS { + EKPBlaEKS = "foo", + EKPBlablaEKS = "bar", + EKPBoilerEKS = "baz", +} + +export enum TNPStringEnumsTNS { + EKPBlaEKS = "foo", + EKPBlablaEKS = "bar", + EKPBoilerEKS = "Boiler", +} + +export enum TNPStringOnlyEnumNamesTNS { + EKPBlaEKS = "Bla", + EKPBlablaEKS = "Blabla", + EKPBoilerEKS = "Boiler", +} + export interface TNPSuperDuperStructDTOTNS { /** @example "100" */ id: number; @@ -94,6 +81,18 @@ export interface TNPSuperDuperStructDTOTNS { state: TNPSuperDuperStructDtoStateTNS; } +/** @example "APPROVED" */ +export enum TNPSuperDuperStructDtoStateTNS { + EKP_NEW_EKS = "NEW", + EKP_PENDING_EKS = "PENDING", +} + +export enum TNPTreeBerekeTNS { + EKPBlaEKS = "Bla", + EKPBlablaEKS = "Blabla", + EKPBoilerEKS = "Boiler", +} + export enum TNPTreeModeTNS { EKPInvalidKey100644EKS = "100644", EKPInvalidKey100755EKS = "100755", @@ -116,20 +115,21 @@ export enum TNPTreeTypeTNS { EKPCommitEKS = "commit", } -export enum TNPTreeBerekeTNS { - EKPBlaEKS = "Bla", - EKPBlablaEKS = "Blabla", - EKPBoilerEKS = "Boiler", -} - -/** @example "APPROVED" */ -export enum TNPSuperDuperStructDtoStateTNS { - EKP_NEW_EKS = "NEW", - EKP_PENDING_EKS = "PENDING", +export enum TNPEnumRootTNS { + EKPInvalidKey100644EKS = "100644", + EKPInvalidKey100755EKS = "100755", + EKPInvalidKey040000EKS = "040000", + EKPInvalidKey160000EKS = "160000", + EKPInvalidKey120000EKS = "120000", + EKPTest1EKS = "test1", + EKPTest2EKS = "test2", } -/** @example "APPROVED" */ -export enum TNPNameSpaceAddSuperDuperTNS { - EKP_NEW_EKS = "NEW", - EKP_PENDING_EKS = "PENDING", +export interface TNPTreeTNS { + tree?: { + mode?: TNPTreeModeTNS; + "mode-num"?: TNPTreeModeNumTNS; + type?: TNPTreeTypeTNS; + bereke?: TNPTreeBerekeTNS; + }[]; } diff --git a/tests/spec/extract-enums/schema.ts b/tests/spec/extract-enums/schema.ts index 77b9b329..c7fda85f 100644 --- a/tests/spec/extract-enums/schema.ts +++ b/tests/spec/extract-enums/schema.ts @@ -9,63 +9,32 @@ * --------------------------------------------------------------- */ -export enum TNPEnumRootTNS { - EKPInvalidKey100644EKS = "100644", - EKPInvalidKey100755EKS = "100755", - EKPInvalidKey040000EKS = "040000", - EKPInvalidKey160000EKS = "160000", - EKPInvalidKey120000EKS = "120000", - EKPTest1EKS = "test1", - EKPTest2EKS = "test2", -} - -export interface TNPTreeTNS { - tree?: { - mode?: TNPTreeModeTNS; - "mode-num"?: TNPTreeModeNumTNS; - type?: TNPTreeTypeTNS; - bereke?: TNPTreeBerekeTNS; - }[]; -} - -export enum TNPOnlyEnumNamesTNS { +/** @format int32 */ +export enum TNPEmptyEnumTNS { EKPBlaEKS = "Bla", EKPBlablaEKS = "Blabla", EKPBoilerEKS = "Boiler", } -export enum TNPStringOnlyEnumNamesTNS { - EKPBlaEKS = "Bla", +/** @format int32 */ +export enum TNPEnumWithMoreNamesTNS { + EKPBlaEKS = 1, EKPBlablaEKS = "Blabla", EKPBoilerEKS = "Boiler", } -export enum TNPStringEnumsTNS { - EKPBlaEKS = "foo", - EKPBlablaEKS = "bar", - EKPBoilerEKS = "Boiler", -} - -export enum TNPStringCompleteEnumsTNS { - EKPBlaEKS = "foo", - EKPBlablaEKS = "bar", - EKPBoilerEKS = "baz", +/** @example "APPROVED" */ +export enum TNPNameSpaceAddSuperDuperTNS { + EKP_NEW_EKS = "NEW", + EKP_PENDING_EKS = "PENDING", } -/** @format int32 */ -export enum TNPEmptyEnumTNS { +export enum TNPOnlyEnumNamesTNS { EKPBlaEKS = "Bla", EKPBlablaEKS = "Blabla", EKPBoilerEKS = "Boiler", } -/** @format int32 */ -export enum TNPEnumWithMoreNamesTNS { - EKPBlaEKS = 1, - EKPBlablaEKS = "Blabla", - EKPBoilerEKS = "Boiler", -} - /** @format int32 */ export enum TNPSomeInterestEnumTNS { EKPBlaEKS = 6, @@ -87,6 +56,24 @@ export enum TNPSomeInterestEnumTNS { EKP_HSDFDS_EKS = "HSDFDS", } +export enum TNPStringCompleteEnumsTNS { + EKPBlaEKS = "foo", + EKPBlablaEKS = "bar", + EKPBoilerEKS = "baz", +} + +export enum TNPStringEnumsTNS { + EKPBlaEKS = "foo", + EKPBlablaEKS = "bar", + EKPBoilerEKS = "Boiler", +} + +export enum TNPStringOnlyEnumNamesTNS { + EKPBlaEKS = "Bla", + EKPBlablaEKS = "Blabla", + EKPBoilerEKS = "Boiler", +} + export interface TNPSuperDuperStructDTOTNS { /** @example "100" */ id: number; @@ -94,6 +81,18 @@ export interface TNPSuperDuperStructDTOTNS { state: TNPSuperDuperStructDtoStateTNS; } +/** @example "APPROVED" */ +export enum TNPSuperDuperStructDtoStateTNS { + EKP_NEW_EKS = "NEW", + EKP_PENDING_EKS = "PENDING", +} + +export enum TNPTreeBerekeTNS { + EKPBlaEKS = "Bla", + EKPBlablaEKS = "Blabla", + EKPBoilerEKS = "Boiler", +} + export enum TNPTreeModeTNS { EKPInvalidKey100644EKS = "100644", EKPInvalidKey100755EKS = "100755", @@ -116,20 +115,21 @@ export enum TNPTreeTypeTNS { EKPCommitEKS = "commit", } -export enum TNPTreeBerekeTNS { - EKPBlaEKS = "Bla", - EKPBlablaEKS = "Blabla", - EKPBoilerEKS = "Boiler", -} - -/** @example "APPROVED" */ -export enum TNPSuperDuperStructDtoStateTNS { - EKP_NEW_EKS = "NEW", - EKP_PENDING_EKS = "PENDING", +export enum TNPEnumRootTNS { + EKPInvalidKey100644EKS = "100644", + EKPInvalidKey100755EKS = "100755", + EKPInvalidKey040000EKS = "040000", + EKPInvalidKey160000EKS = "160000", + EKPInvalidKey120000EKS = "120000", + EKPTest1EKS = "test1", + EKPTest2EKS = "test2", } -/** @example "APPROVED" */ -export enum TNPNameSpaceAddSuperDuperTNS { - EKP_NEW_EKS = "NEW", - EKP_PENDING_EKS = "PENDING", +export interface TNPTreeTNS { + tree?: { + mode?: TNPTreeModeTNS; + "mode-num"?: TNPTreeModeNumTNS; + type?: TNPTreeTypeTNS; + bereke?: TNPTreeBerekeTNS; + }[]; } diff --git a/tests/spec/extract-enums/test.js b/tests/spec/extract-enums/test.js index 134178b6..82675ddb 100644 --- a/tests/spec/extract-enums/test.js +++ b/tests/spec/extract-enums/test.js @@ -20,6 +20,7 @@ schemas.forEach(({ absolutePath, apiFileName }) => { enumKeySuffix: "EKS", typePrefix: "TNP", typeSuffix: "TNS", + sortTypes: true, }).then(() => { validateGeneratedModule(resolve(__dirname, `./${apiFileName}`)); assertGeneratedModule(resolve(__dirname, `./${apiFileName}`), resolve(__dirname, `./expected.ts`)); diff --git a/tests/spec/multiple-schemas/paths/repro.yaml b/tests/spec/multiple-schemas/paths/repro.yaml new file mode 100644 index 00000000..27b8d933 --- /dev/null +++ b/tests/spec/multiple-schemas/paths/repro.yaml @@ -0,0 +1,8 @@ +hello: + get: + responses: + '200': + content: + application/json: + schema: + type: object \ No newline at end of file diff --git a/tests/spec/multiple-schemas/schema.yaml b/tests/spec/multiple-schemas/schema.yaml new file mode 100644 index 00000000..b262e564 --- /dev/null +++ b/tests/spec/multiple-schemas/schema.yaml @@ -0,0 +1,8 @@ +openapi: 3.0.2 +info: + version: 2.0.0 + title: repro + +paths: + /hello: + $ref: './paths/repro.yaml#/hello' \ No newline at end of file diff --git a/tests/spec/partialDefaultTemplate/spec_templates/api.eta b/tests/spec/partialDefaultTemplate/spec_templates/api.eta index b81eea38..e2dfa557 100644 --- a/tests/spec/partialDefaultTemplate/spec_templates/api.eta +++ b/tests/spec/partialDefaultTemplate/spec_templates/api.eta @@ -29,19 +29,23 @@ export class Api<% if (!config.singleHttpClien <% } %> -<% routes.outOfModule && routes.outOfModule.forEach((route) => { %> +<% if (routes.outOfModule) { %> + <% for await (const route of routes.outOfModule) { %> - <%~ includeFile('./procedure-call.ejs', { route, utils, config }) %> + <%~ await includeFile('./procedure-call.ejs', { ...it, route }) %> -<% }) %> + <% } %> +<% } %> -<% routes.combined && routes.combined.forEach(({ routes = [], moduleName }) => { %> +<% if (routes.combined) { %> + <% for await (const { routes: combinedRoutes = [], moduleName } of routes.combined) { %> <%~ moduleName %> = { - <% routes.forEach((route) => { %> + <% for await (const route of combinedRoutes) { %> - <%~ includeFile('./procedure-call.ejs', { route, utils, config }) %> + <%~ await includeFile('./procedure-call.ejs', { ...it, route }) %> - <% }) %> + <% } %> } -<% }) %> + <% } %> +<% } %> } diff --git a/tests/spec/templates/spec_templates/api.eta b/tests/spec/templates/spec_templates/api.eta index bcd21d95..383f22ff 100644 --- a/tests/spec/templates/spec_templates/api.eta +++ b/tests/spec/templates/spec_templates/api.eta @@ -43,21 +43,25 @@ export class Api<% if (!config.singleHttpClien <% } %> -<% routes.outOfModule && routes.outOfModule.forEach((route) => { %> +<% if (routes.outOfModule) { %> + <% for await (const route of routes.outOfModule) { %> - <%~ includeFile('./procedure-call.eta', { ...it, route }) %> + <%~ await includeFile('./procedure-call.ejs', { ...it, route }) %> -<% }) %> + <% } %> +<% } %> -<% routes.combined && routes.combined.forEach(({ routes = [], moduleName }) => { %> +<% if (routes.combined) { %> + <% for await (const { routes: combinedRoutes = [], moduleName } of routes.combined) { %> <%~ moduleName %> = { - <% routes.forEach((route) => { %> + <% for await (const route of combinedRoutes) { %> - <%~ includeFile('./procedure-call.eta', { ...it, route }) %> + <%~ await includeFile('./procedure-call.ejs', { ...it, route }) %> - <% }) %> + <% } %> } -<% }) %> + <% } %> +<% } %> } /* CUSTOM TEMPLATE */ diff --git a/tests/spec/templates/spec_templates/http-client.eta b/tests/spec/templates/spec_templates/http-client.eta index 3b7e1a5f..1e4be481 100644 --- a/tests/spec/templates/spec_templates/http-client.eta +++ b/tests/spec/templates/spec_templates/http-client.eta @@ -1,5 +1,5 @@ <% const { config } = it; %> <% /* https://github.com/acacode/swagger-typescript-api/tree/next/templates/base/http-clients/ */ %> -<%~ includeFile(`@base/http-clients/${config.httpClientType}-http-client`, it) %> +<%~ await includeFile(`@base/http-clients/${config.httpClientType}-http-client`, it) %> /* HTTP CLIENT TEMPLATE */ \ No newline at end of file diff --git a/tests/spec/templates/spec_templates/procedure-call.eta b/tests/spec/templates/spec_templates/procedure-call.eta index d5a85b00..57bf46bf 100644 --- a/tests/spec/templates/spec_templates/procedure-call.eta +++ b/tests/spec/templates/spec_templates/procedure-call.eta @@ -5,7 +5,7 @@ const { _, getInlineParseContent, getParseContent, parseSchema, getComponentByRe const { parameters, path, method, payload, query, formData, security, requestParams } = route.request; const { type, errorType, contentTypes } = route.response; const { HTTP_CLIENT, RESERVED_REQ_PARAMS_ARG_NAMES } = config.constants; -const routeDocs = includeFile("@base/route-docs", { config, route, utils }); +const routeDocs = await includeFile("@base/route-docs", { config, route, utils }); const queryName = (query && query.name) || "query"; const pathParams = _.values(parameters); const pathParamsNames = _.map(pathParams, "name"); @@ -26,7 +26,7 @@ const rawWrapperArgs = config.extractRequestParams ? requestParams && { name: pathParams.length ? `{ ${_.join(pathParamsNames, ", ")}, ...${queryName} }` : queryName, optional: false, - type: getInlineParseContent(requestParams), + type: await getInlineParseContent(requestParams), }, ...(!requestParams ? pathParams : []), payload, diff --git a/tests/spec/templates/spec_templates/route-type.eta b/tests/spec/templates/spec_templates/route-type.eta index 1c7b84c6..5f19835a 100644 --- a/tests/spec/templates/spec_templates/route-type.eta +++ b/tests/spec/templates/spec_templates/route-type.eta @@ -4,7 +4,7 @@ const { _, pascalCase } = utils const { query, payload } = route.request const { type: responseType } = route.response -const routeDocs = includeFile("./route-docs", { config, route, utils }); +const routeDocs = await includeFile("./route-docs", { config, route, utils }); const routeNamespace = pascalCase(route.routeName.usage) const queryType = (query && query.type) || '{}' const bodyType = (payload && payload.type) || 'never' diff --git a/tests/spec/templates/spec_templates/route-types.eta b/tests/spec/templates/spec_templates/route-types.eta index 286141e7..c3667c79 100644 --- a/tests/spec/templates/spec_templates/route-types.eta +++ b/tests/spec/templates/spec_templates/route-types.eta @@ -1,23 +1,29 @@ <% -const { utils, config, routes } = it; +const { utils, config, routes, modelTypes } = it; +const { _, pascalCase } = utils; +const dataContracts = config.modular ? _.map(modelTypes, "name") : []; %> <% /* TODO: outOfModule, combined should be attributes of route, which will allow to avoid duplication of code */ %> -<% routes.outOfModule && routes.outOfModule.forEach(({ routes = [] }) => { %> - <% routes.forEach((route) => { %> - <%~ includeFile('./route-type.eta', { route, utils, config }) %> - <% }) %> -<% }) %> -<% routes.combined && routes.combined.forEach(({ routes = [], moduleName }) => { %> - export namespace <%~ moduleName %> { - <% routes.forEach((route) => { %> - <%~ includeFile('./route-type.eta', { route, utils, config }) %> - <% }) %> - } +<% if (routes.outOfModule) { %> + <% for await (const route of routes.outOfModule) { %> + + <%~ await includeFile('@base/route-type.ejs', { ...it, route }) %> + <% } %> +<% } %> -<% }) %> +<% if (routes.combined) { %> + <% for await (const { routes: combinedRoutes = [], moduleName } of routes.combined) { %> + + export namespace <%~ pascalCase(moduleName) %> { + <% for await (const route of combinedRoutes) { %> + <%~ await includeFile('@base/route-type.ejs', { ...it, route }) %> + <% } %> + } + <% } %> +<% } %> -/* CUSTOM TEMPLATE */ +/* CUSTOM TEMPLATE */ \ No newline at end of file