From b5d6b50d352f9435e3af49aca1e048f6dab3c960 Mon Sep 17 00:00:00 2001 From: Andrey Klimenko Date: Thu, 14 Mar 2024 09:42:43 +0100 Subject: [PATCH 1/7] allof support --- package.json | 1 + src/generators/ModelsGenerator.ts | 71 +++++++++++--- .../models-generator/InterfacesGenerator.ts | 43 ++++++-- src/generators/utils/is-defined.ts | 3 + src/models/InterfaceModel.ts | 1 + src/models/ObjectModel.ts | 3 + src/services/ModelMappingService.ts | 97 ++++++++++++++++++- src/swagger/OpenAPIService.ts | 5 +- src/swagger/OpenAPITypesGuard.ts | 6 ++ src/swagger/v3/schemas/one-of-schema.ts | 6 ++ 10 files changed, 207 insertions(+), 29 deletions(-) create mode 100644 src/generators/utils/is-defined.ts create mode 100644 src/swagger/v3/schemas/one-of-schema.ts diff --git a/package.json b/package.json index 4349c03..7b164e5 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "g:withRequestOptions": "node ./bin/index.js g --all --file=./swagger.json --output=./.output/withRequestOptions --withRequestOptions", "g:alias": "node ./bin/index.js g --file=./swagger.json --aliasName alias --output=./.output/selected", "e2e": "npm run g && npm run g:withRequestOptions && ts-node ./e2e/e2e.ts", + "g:b": "npm run build && npm run g", "test": "jest", "test:w": "jest --watch", "coverage": "jest --coverage", diff --git a/src/generators/ModelsGenerator.ts b/src/generators/ModelsGenerator.ts index d237733..2de8ddc 100644 --- a/src/generators/ModelsGenerator.ts +++ b/src/generators/ModelsGenerator.ts @@ -1,5 +1,6 @@ import { ClassDeclarationStructure, + CodeBlockWriter, ConstructorDeclarationStructure, ImportDeclarationStructure, OptionalKind, @@ -125,7 +126,7 @@ export class ModelsGenerator { kind: StructureKind.Class, isExported: true, name: z.name, - properties: this.getObjectProperties(z), + properties: this.getObjectProperties(z, objects), methods: [ { scope: Scope.Public, @@ -138,6 +139,9 @@ export class ModelsGenerator { z.properties.forEach((p) => x.withIndentationLevel(3, () => x.writeLine(`${p.name}: ${this.getToDtoPropertyInitializer(p)},`)) ); + this.printCombinedProprs(z, x, objects, (p) => + x.withIndentationLevel(3, () => x.writeLine(`${p.name}: ${this.getToDtoPropertyInitializer(p)},`)) + ); x.writeLine('};'); } }, @@ -152,6 +156,9 @@ export class ModelsGenerator { z.properties.forEach((p) => x.withIndentationLevel(2, () => x.writeLine(`model.${p.name} = ${this.getFromDtoPropertyInitializer(p)};`)) ); + this.printCombinedProprs(z, x, objects, (p) => + x.withIndentationLevel(2, () => x.writeLine(`model.${p.name} = ${this.getFromDtoPropertyInitializer(p)};`)) + ); x.writeLine('return model;'); } } @@ -159,25 +166,39 @@ export class ModelsGenerator { })); } - private getObjectProperties(objectModel: IObjectModel): PropertyDeclarationStructure[] { + private getObjectProperties(objectModel: IObjectModel, objects: IObjectModel[]): PropertyDeclarationStructure[] { return [ - ...objectModel.properties.map( - (objectProperty): PropertyDeclarationStructure => ({ - kind: StructureKind.Property, - scope: Scope.Public, - name: objectProperty.name, - type: new TypeSerializer({ - type: { name: objectProperty.type }, - isNullable: objectProperty.isNullable, - isCollection: objectProperty.isCollection - }).toString(), - initializer: objectProperty.isCollection ? ARRAY_STRING : UNDEFINED_STRING - }) - ), + ...this.getObjectCombinedProperties(objectModel, objects), + ...objectModel.properties.map((objectProperty) => this.getDeclarationStructure(objectProperty)), this.getGuardProperty(objectModel.name) ]; } + private getObjectCombinedProperties(objectModel: IObjectModel, objects: IObjectModel[]): PropertyDeclarationStructure[] { + return objectModel.combineTypes.reduce((acc, item) => { + const model = objects.find((x) => x.name === item); + const props = (model?.properties ?? []).map((objectProperty) => this.getDeclarationStructure(objectProperty)); + if (model) { + props.push(...this.getObjectCombinedProperties(model, objects)); + } + return [...acc, ...props]; + }, [] as PropertyDeclarationStructure[]); + } + + private getDeclarationStructure(objectProperty: IObjectPropertyModel): PropertyDeclarationStructure { + return { + kind: StructureKind.Property, + scope: Scope.Public, + name: objectProperty.name, + type: new TypeSerializer({ + type: { name: objectProperty.type }, + isNullable: objectProperty.isNullable, + isCollection: objectProperty.isCollection + }).toString(), + initializer: objectProperty.isCollection ? ARRAY_STRING : UNDEFINED_STRING + }; + } + private getGuardProperty(name: string): PropertyDeclarationStructure { return { kind: StructureKind.Property, @@ -271,4 +292,24 @@ export class ModelsGenerator { return dtoProperty; } } + + private printCombinedProprs( + model: IObjectModel | undefined, + writer: CodeBlockWriter, + objectsCollection: IObjectModel[], + printFunction: (p: IObjectPropertyModel) => void + ): void { + if (!model) { + return; + } + model.combineTypes.forEach((y) => { + (objectsCollection.find((x) => x.name === y)?.properties ?? []).forEach((p) => printFunction(p)); + this.printCombinedProprs( + objectsCollection.find((x) => x.name === y), + writer, + objectsCollection, + printFunction + ); + }); + } } diff --git a/src/generators/models-generator/InterfacesGenerator.ts b/src/generators/models-generator/InterfacesGenerator.ts index ad42d24..11265f8 100644 --- a/src/generators/models-generator/InterfacesGenerator.ts +++ b/src/generators/models-generator/InterfacesGenerator.ts @@ -1,15 +1,42 @@ -import { InterfaceDeclarationStructure, OptionalKind, PropertySignatureStructure, StructureKind } from 'ts-morph'; +import { + InterfaceDeclarationStructure, + OptionalKind, + PropertySignatureStructure, + StructureKind, + TypeAliasDeclarationStructure +} from 'ts-morph'; + import { IInterfaceModel, IInterfacePropertyModel } from '../../models/InterfaceModel'; import { TypeSerializer } from '../utils/TypeSerializer'; export class InterfacesGenerator { - public getCodeStructure(interfaces: IInterfaceModel[]): InterfaceDeclarationStructure[] { - return interfaces.map((z) => ({ - kind: StructureKind.Interface, - name: z.name, - isExported: true, - properties: z.properties.map((x) => this.getInterfaceProperty(x)) - })); + public getCodeStructure(interfaces: IInterfaceModel[]): (InterfaceDeclarationStructure | TypeAliasDeclarationStructure)[] { + const baseInterfaces: InterfaceDeclarationStructure[] = []; + const types: (InterfaceDeclarationStructure | TypeAliasDeclarationStructure)[] = interfaces.map((z) => { + if (z.combineInterfaces.length) { + const name = z.name + 'BaseInterface'; + baseInterfaces.push({ + kind: StructureKind.Interface, + name: name, + isExported: false, + properties: z.properties.map((x) => this.getInterfaceProperty(x)) + }); + return { + kind: StructureKind.TypeAlias, + type: name + ' & ' + z.combineInterfaces.join(' & '), + name: z.name, + isExported: true + }; + } else { + return { + kind: StructureKind.Interface, + name: z.name, + isExported: true, + properties: z.properties.map((x) => this.getInterfaceProperty(x)) + }; + } + }); + return [...baseInterfaces, ...types]; } protected getInterfaceProperty(model: IInterfacePropertyModel): OptionalKind { diff --git a/src/generators/utils/is-defined.ts b/src/generators/utils/is-defined.ts new file mode 100644 index 0000000..d375aee --- /dev/null +++ b/src/generators/utils/is-defined.ts @@ -0,0 +1,3 @@ +export function isDefined(value: T | undefined): value is T { + return value !== undefined; +} diff --git a/src/models/InterfaceModel.ts b/src/models/InterfaceModel.ts index 2ffc513..468293d 100644 --- a/src/models/InterfaceModel.ts +++ b/src/models/InterfaceModel.ts @@ -8,4 +8,5 @@ export interface IInterfacePropertyModel { export interface IInterfaceModel { name: string; properties: IInterfacePropertyModel[]; + combineInterfaces: string[]; } diff --git a/src/models/ObjectModel.ts b/src/models/ObjectModel.ts index ff64ecd..5d89ff3 100644 --- a/src/models/ObjectModel.ts +++ b/src/models/ObjectModel.ts @@ -1,3 +1,4 @@ +import { IOpenAPI3Reference } from '../swagger/v3/reference'; import { IType } from './TypeModel'; export interface IObjectPropertyModel extends IType { @@ -11,4 +12,6 @@ export interface IObjectModel { dtoType: string; isNullable: boolean; properties: IObjectPropertyModel[]; + combineTypes: string[]; + combineTypesRefs: IOpenAPI3Reference[]; } diff --git a/src/services/ModelMappingService.ts b/src/services/ModelMappingService.ts index df89ea7..361ba3e 100644 --- a/src/services/ModelMappingService.ts +++ b/src/services/ModelMappingService.ts @@ -12,11 +12,14 @@ import { IOpenAPI3GuidSchema } from '../swagger/v3/schemas/guid-schema'; import { IOpenAPI3ObjectSchema } from '../swagger/v3/schemas/object-schema'; import { OpenAPI3Schema, OpenAPI3SchemaContainer, OpenAPI3SimpleSchema } from '../swagger/v3/schemas/schema'; import { first, sortBy } from '../utils'; +import { isDefined } from '../generators/utils/is-defined'; import { TypesService } from './TypesService'; const IGNORE_PROPERTIES = ['startRow', 'rowCount']; export class ModelMappingService { + public additionalObjects: IObjectModel[] = []; + public additionalEnums: IEnumModel[] = []; constructor( private readonly openAPIService: OpenAPIService, private readonly typesGuard: OpenAPITypesGuard, @@ -53,11 +56,15 @@ export class ModelMappingService { } }); + objects.forEach((x) => this.addCombineObjectsByRefs(x)); + return { - enums: enums.sort(sortBy((z) => z.name)), + enums: this.getUnicItemsByProp('name', ...this.additionalEnums, ...enums).sort(sortBy((z) => z.name)), identities: identities.sort(sortBy((z) => z.name)), - interfaces: this.getInterfaces(identities, objects).sort(sortBy((z) => z.name)), - objects: objects.sort(sortBy((z) => z.name)) + interfaces: this.getInterfaces(identities, this.getUnicItemsByProp('name', ...this.additionalObjects, ...objects)).sort( + sortBy((z) => z.name) + ), + objects: this.getUnicItemsByProp('name', ...this.additionalObjects, ...objects).sort(sortBy((z) => z.name)) }; } @@ -75,7 +82,17 @@ export class ModelMappingService { } private toObjectModel(name: string, schema: IOpenAPI3ObjectSchema): IObjectModel { - const model: IObjectModel = { name, isNullable: schema.nullable ?? false, dtoType: this.getInterfaceName(name), properties: [] }; + const model: IObjectModel = { + name, + isNullable: schema.nullable ?? false, + dtoType: this.getInterfaceName(name), + properties: [], + combineTypes: [], + combineTypesRefs: [] + }; + + this.addCombineTypes(schema, model); + if (!schema.properties) { return model; } @@ -101,6 +118,9 @@ export class ModelMappingService { } else if (this.typesGuard.isReference(schema.items)) { property = this.getReferenceProperty(name, schema.items); } + if (this.typesGuard.isOneOf(schema.items)) { + property = this.getReferenceProperty(name, first(schema.items.oneOf)); + } if (property) { property.isCollection = true; @@ -113,6 +133,8 @@ export class ModelMappingService { property = this.getReferenceProperty(name, schema); } else if (this.typesGuard.isAllOf(schema)) { property = this.getReferenceProperty(name, first(schema.allOf)); + } else if (this.typesGuard.isOneOf(schema)) { + property = this.getReferenceProperty(name, first(schema.oneOf)); } if (property) { @@ -121,6 +143,64 @@ export class ModelMappingService { } } + private addCombineTypes(schema: IOpenAPI3ObjectSchema, model: IObjectModel): void { + if (!this.typesGuard.isAllOf(schema)) { + return; + } + schema.allOf.forEach((x) => { + const refSchema = this.openAPIService.getRefSchema(x); + const schemaKey = this.openAPIService.getSchemaKey(x); + if (this.typesGuard.isObject(refSchema)) { + model.combineTypes = [...model.combineTypes, schemaKey]; + model.combineTypesRefs = [...model.combineTypesRefs, x]; + } + }); + } + + private addCombineObjectsByRefs(model: IObjectModel): void { + if (!model.combineTypesRefs) { + return; + } + model.combineTypesRefs.forEach((ref) => this.addCombineObjectsByRef(ref)); + + if (!model.properties) { + return; + } + model.properties.forEach((prop) => this.addPropertiesCombineType(prop)); + } + + private addCombineObjectsByRef(ref: IOpenAPI3Reference): void { + const refSchema = this.openAPIService.getRefSchema(ref); + const schemaKey = this.openAPIService.getSchemaKey(ref); + if (this.typesGuard.isObject(refSchema)) { + const combinedModel = this.toObjectModel(schemaKey, refSchema); + + if (this.additionalObjects.find((x) => x.name === combinedModel.name)) { + //нужно ли помержить юнион типы тут + return; + } + + this.additionalObjects.push(combinedModel); + this.addCombineObjectsByRefs(combinedModel); + } + } + + private addPropertiesCombineType(prop: IObjectPropertyModel): void { + const ref = { $ref: '#/components/schemas/' + prop.type }; + if (prop.kind === PropertyKind.Object) { + this.addCombineObjectsByRef(ref); + } else if (prop.kind === PropertyKind.Enum) { + const refSchema = this.openAPIService.getRefSchema(ref); + const schemaKey = this.openAPIService.getSchemaKey(ref); + if (this.typesGuard.isEnum(refSchema)) { + const emun = this.toEnumModel(schemaKey, refSchema); + if (!this.additionalEnums.find((e) => e.name === emun.name)) { + this.additionalEnums.push(emun); + } + } + } + } + private getSimpleProperty(name: string, schema: OpenAPI3SimpleSchema): IObjectPropertyModel { return { ...this.typesService.getSimpleType(schema), @@ -160,12 +240,14 @@ export class ModelMappingService { private getInterfaces(identities: IIdentityModel[], objects: IObjectModel[]): IInterfaceModel[] { const interfaces: IInterfaceModel[] = identities.map((z) => ({ name: this.getInterfaceName(z.name), - properties: [{ name: z.property.name, dtoType: z.property.dtoType, isCollection: false, isNullable: false }] + properties: [{ name: z.property.name, dtoType: z.property.dtoType, isCollection: false, isNullable: false }], + combineInterfaces: [] })); return interfaces.concat( objects.map((z) => ({ name: this.getInterfaceName(z.name), + combineInterfaces: z.combineTypes.map((x) => this.getInterfaceName(x)), properties: z.properties.map((x) => ({ name: x.name, dtoType: x.dtoType, @@ -186,4 +268,9 @@ export class ModelMappingService { } return schema.properties && Object.keys(schema.properties)?.length === 1 && this.typesGuard.isGuid(schema.properties['id']); } + + private getUnicItemsByProp(key: T, ...array1: T2[]): T2[] { + const unionKeys = [...new Set(array1.map((x) => x[key]))]; + return unionKeys.map((x) => array1.find((y) => y[key] === x)).filter(isDefined); + } } diff --git a/src/swagger/OpenAPIService.ts b/src/swagger/OpenAPIService.ts index 35e5478..03f53c9 100644 --- a/src/swagger/OpenAPIService.ts +++ b/src/swagger/OpenAPIService.ts @@ -20,7 +20,10 @@ interface IOperation { export type IOpenAPI3Operations = { [key: string]: { method: MethodOperation; operation: IOpenAPI3Operation }[] }; export class OpenAPIService { - constructor(private readonly spec: IOpenAPI3, private readonly typesGuard: OpenAPITypesGuard) { + constructor( + private readonly spec: IOpenAPI3, + private readonly typesGuard: OpenAPITypesGuard + ) { const majorVersion = this.majorVersion; if (majorVersion !== SUPPORTED_VERSION.toString()) { throw new Error(`Only OpenApi version ${SUPPORTED_VERSION} supported yet.`); diff --git a/src/swagger/OpenAPITypesGuard.ts b/src/swagger/OpenAPITypesGuard.ts index 19008b9..c039167 100644 --- a/src/swagger/OpenAPITypesGuard.ts +++ b/src/swagger/OpenAPITypesGuard.ts @@ -7,6 +7,7 @@ import { IOpenAPI3EnumSchema } from './v3/schemas/enum-schema'; import { IOpenAPI3GuidSchema } from './v3/schemas/guid-schema'; import { IOpenAPI3NumberSchema } from './v3/schemas/number-schema'; import { IOpenAPI3ObjectSchema } from './v3/schemas/object-schema'; +import { IOpenAPI3OneOfSchema } from './v3/schemas/one-of-schema'; import { OpenAPI3SimpleSchema } from './v3/schemas/schema'; import { IOpenAPI3StringSchema } from './v3/schemas/string-schema'; @@ -17,6 +18,7 @@ type SchemaType = | IOpenAPI3ObjectSchema | IOpenAPI3EnumSchema | IOpenAPI3AllOfSchema + | IOpenAPI3OneOfSchema | undefined; export class OpenAPITypesGuard { @@ -40,6 +42,10 @@ export class OpenAPITypesGuard { return Boolean((schema as IOpenAPI3AllOfSchema)?.allOf); } + public isOneOf(schema: SchemaType): schema is IOpenAPI3OneOfSchema { + return Boolean((schema as IOpenAPI3OneOfSchema).oneOf); + } + public isEnum(schema: SchemaType): schema is IOpenAPI3EnumSchema { const enumSchema = schema as IOpenAPI3EnumSchema; if (!schema) { diff --git a/src/swagger/v3/schemas/one-of-schema.ts b/src/swagger/v3/schemas/one-of-schema.ts new file mode 100644 index 0000000..99cead3 --- /dev/null +++ b/src/swagger/v3/schemas/one-of-schema.ts @@ -0,0 +1,6 @@ +import { IOpenAPI3Reference } from '../reference'; +import { IOpenAPI3BaseSchema } from './base-schema'; + +export interface IOpenAPI3OneOfSchema extends IOpenAPI3BaseSchema { + oneOf: IOpenAPI3Reference[]; +} From a4ad113de032558254732da4ea802765a67f0b91 Mon Sep 17 00:00:00 2001 From: Andrey Klimenko Date: Mon, 25 Mar 2024 01:51:02 +0100 Subject: [PATCH 2/7] add discriminator support --- .snapshot/all/models.ts | 113 +++++++++++++- package-lock.json | 6 +- package.json | 2 +- src/generators/ModelsGenerator.ts | 81 +++++++++- .../models-generator/InterfacesGenerator.ts | 23 ++- src/models/EnumModel.ts | 2 +- src/models/InterfaceModel.ts | 6 + src/models/ModelsContainer.ts | 3 +- src/models/ObjectModel.ts | 10 +- src/services/ModelMappingService.ts | 140 +++++++++--------- src/swagger/OpenAPIService.ts | 39 ++++- src/swagger/OpenAPITypesGuard.ts | 7 +- .../v3/schemas/discriminator-schema.ts | 10 ++ src/swagger/v3/schemas/schema.ts | 3 +- src/utils.ts | 4 + swagger.json | 69 ++++++++- 16 files changed, 426 insertions(+), 92 deletions(-) create mode 100644 src/swagger/v3/schemas/discriminator-schema.ts diff --git a/.snapshot/all/models.ts b/.snapshot/all/models.ts index 732690a..9cc27d0 100644 --- a/.snapshot/all/models.ts +++ b/.snapshot/all/models.ts @@ -2,17 +2,35 @@ import { Guid } from './Guid'; import { toDateIn, toDateOut } from './date-converters'; import type * as $types from './types'; +export enum CategoryUnionTypes { + CategoryElectronicsDto = '1', + CategoryMotorsDto = '2' +} + export enum ProductStatus { InStock = 0, OutOfStock = -1, UnderTheOrder = 1 } +interface ICategoryElectronicsDtoBaseInterface { + syntheticTest: $types.TypeOrUndefinedNullable; +} + +interface ICategoryMotorsDtoBaseInterface { + volume: $types.TypeOrUndefinedNullable; +} + export interface ICategory { name: $types.TypeOrUndefinedNullable; + type: $types.TypeOrUndefined; } +export type ICategoryElectronicsDto = ICategoryElectronicsDtoBaseInterface & ICategory; +export type ICategoryMotorsDto = ICategoryMotorsDtoBaseInterface & ICategory; + export interface IProduct { + categories: $types.TypeOrUndefinedNullable; category: $types.TypeOrUndefinedNullable; colors: $types.TypeOrUndefined; expireDate: $types.TypeOrUndefined; @@ -27,6 +45,47 @@ export interface IProductIdentityDTO { id: $types.TypeOrUndefined; } +export type CategoryUnion = Category | CategoryElectronicsDto | CategoryMotorsDto; +export type ICategoryUnion = ICategory | ICategoryElectronicsDto | ICategoryMotorsDto; + +export class CategoryUnionClass { + public static fromDTO(dto: ICategoryUnion): CategoryUnion { + if (this.isCategoryElectronicsDto(dto)) { + return CategoryElectronicsDto.fromDTO(dto); + } + if (this.isCategoryMotorsDto(dto)) { + return CategoryMotorsDto.fromDTO(dto); + } + return Category.fromDTO(dto); + } + + public static toDTO(model: CategoryUnion): ICategoryUnion { + if (this.isICategoryElectronicsDto(model)) { + return CategoryElectronicsDto.toDTO(model); + } + if (this.isICategoryMotorsDto(model)) { + return CategoryMotorsDto.toDTO(model); + } + return Category.toDTO(model); + } + + private static isCategoryElectronicsDto(dto: ICategoryUnion): dto is ICategoryElectronicsDto { + return dto.type === CategoryUnionTypes.CategoryElectronicsDto; + } + + private static isCategoryMotorsDto(dto: ICategoryUnion): dto is ICategoryMotorsDto { + return dto.type === CategoryUnionTypes.CategoryMotorsDto; + } + + private static isICategoryElectronicsDto(dto: CategoryUnion): dto is CategoryElectronicsDto { + return dto.type === CategoryUnionTypes.CategoryElectronicsDto; + } + + private static isICategoryMotorsDto(dto: CategoryUnion): dto is CategoryMotorsDto { + return dto.type === CategoryUnionTypes.CategoryMotorsDto; + } +} + export class ProductIdentityDTO { public id: Guid; private __productIdentityDTO!: string; @@ -42,23 +101,73 @@ export class ProductIdentityDTO { export class Category { public name: $types.TypeOrUndefinedNullable = undefined; + public type: $types.TypeOrUndefined = undefined; private __category!: string; public static toDTO(model: Partial): ICategory { return { name: model.name, + type: model.type, }; } public static fromDTO(dto: ICategory): Category { const model = new Category(); model.name = dto.name; + model.type = dto.type; + return model; + } +} + +export class CategoryElectronicsDto { + public name: $types.TypeOrUndefinedNullable = undefined; + public type: $types.TypeOrUndefined = undefined; + public syntheticTest: $types.TypeOrUndefinedNullable = undefined; + private __categoryElectronicsDto!: string; + + public static toDTO(model: Partial): ICategoryElectronicsDto { + return { + syntheticTest: model.syntheticTest, + name: model.name, + type: model.type, + }; + } + + public static fromDTO(dto: ICategoryElectronicsDto): CategoryElectronicsDto { + const model = new CategoryElectronicsDto(); + model.syntheticTest = dto.syntheticTest; + model.name = dto.name; + model.type = dto.type; + return model; + } +} + +export class CategoryMotorsDto { + public name: $types.TypeOrUndefinedNullable = undefined; + public type: $types.TypeOrUndefined = undefined; + public volume: $types.TypeOrUndefinedNullable = undefined; + private __categoryMotorsDto!: string; + + public static toDTO(model: Partial): ICategoryMotorsDto { + return { + volume: model.volume, + name: model.name, + type: model.type, + }; + } + + public static fromDTO(dto: ICategoryMotorsDto): CategoryMotorsDto { + const model = new CategoryMotorsDto(); + model.volume = dto.volume; + model.name = dto.name; + model.type = dto.type; return model; } } export class Product { - public category: $types.TypeOrUndefinedNullable = undefined; + public categories: CategoryUnionClass[] = []; + public category: $types.TypeOrUndefinedNullable = undefined; public colors: string[] = []; public expireDate: $types.TypeOrUndefined = undefined; public externalId: $types.TypeOrUndefinedNullable = undefined; @@ -70,6 +179,7 @@ export class Product { public static toDTO(model: Partial): IProduct { return { + categories: model.categories ? model.categories.map(x => Category.toDTO(x)) : undefined, category: model.category ? Category.toDTO(model.category) : undefined, colors: model.colors, expireDate: toDateOut(model.expireDate), @@ -83,6 +193,7 @@ export class Product { public static fromDTO(dto: IProduct): Product { const model = new Product(); + model.categories = dto.categories ? dto.categories.map(x => Category.fromDTO(x)) : []; model.category = dto.category ? Category.fromDTO(dto.category) : undefined; model.colors = dto.colors ? dto.colors : []; model.expireDate = toDateIn(dto.expireDate); diff --git a/package-lock.json b/package-lock.json index a69510e..7838f22 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@luxbss/gengen", - "version": "1.2.6", + "version": "1.2.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@luxbss/gengen", - "version": "1.2.6", + "version": "1.2.7", "license": "MIT", "dependencies": { "commander": "11.1.0", @@ -18,7 +18,7 @@ }, "devDependencies": { "@types/jest": "29.5.6", - "@types/node": "^20.8.8", + "@types/node": "20.8.8", "@typescript-eslint/eslint-plugin": "6.9.0", "@typescript-eslint/parser": "6.9.0", "eslint": "8.52.0", diff --git a/package.json b/package.json index 7b164e5..8549a78 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@luxbss/gengen", - "version": "1.2.6", + "version": "1.2.7", "description": "Tool for generating models and Angular services based on OpenAPIs and Swagger's JSON", "bin": { "gengen": "./bin/index.js" diff --git a/src/generators/ModelsGenerator.ts b/src/generators/ModelsGenerator.ts index 2de8ddc..ab47f51 100644 --- a/src/generators/ModelsGenerator.ts +++ b/src/generators/ModelsGenerator.ts @@ -12,13 +12,13 @@ import { } from 'ts-morph'; import { IEnumModel } from '../models/EnumModel'; import { IIdentityModel } from '../models/IdentityModel'; -import { IInterfaceModel } from '../models/InterfaceModel'; +import { IInterfaceModel, IInterfaceUnionModel } from '../models/InterfaceModel'; import { IModelsContainer } from '../models/ModelsContainer'; import { IObjectModel, IObjectPropertyModel } from '../models/ObjectModel'; import { PropertyKind } from '../models/kinds/PropertyKind'; import { IOptions } from '../options'; import { PathBuilder } from '../services/PathBuilder'; -import { lowerFirst } from '../utils'; +import { getInterfaceName, lowerFirst } from '../utils'; import { InterfacesGenerator } from './models-generator/InterfacesGenerator'; import { TypeSerializer } from './utils/TypeSerializer'; import { ARRAY_STRING, NULL_STRING, TYPES_NAMESPACE, UNDEFINED_STRING } from './utils/consts'; @@ -37,6 +37,8 @@ export class ModelsGenerator { ...this.getImports(), ...this.getEnums(models.enums), ...this.interfaceGenerator.getCodeStructure(models.interfaces), + ...this.interfaceGenerator.getCodeUnionsStructure(models.unionInterfaces), + ...this.getUnionObjects(models.unionInterfaces), ...this.getIdentities(models.identities, models.interfaces), ...this.getObjects(models.objects) ]; @@ -120,6 +122,71 @@ export class ModelsGenerator { }) ); } + private getUnionObjects(objects: IInterfaceUnionModel[]): ClassDeclarationStructure[] { + return objects.map((z) => ({ + kind: StructureKind.Class, + isExported: true, + name: z.name + 'Class', + properties: [], + methods: [ + { + scope: Scope.Public, + isStatic: true, + name: FROM_DTO_METHOD, + parameters: [{ name: 'dto', type: getInterfaceName(z.name) }], + returnType: z.name, + statements: (x) => { + z.unionInterfaces.forEach((i) => { + x.writeLine('if (this.is' + i + '(dto)){'); + x.writeLine('return ' + i + '.fromDTO(dto);'); + x.writeLine('}'); + }); + + x.writeLine('return ' + z.parentInterface + '.fromDTO(dto);'); + } + }, + + { + scope: Scope.Public, + isStatic: true, + name: TO_DTO_METHOD, + parameters: [{ name: 'model', type: z.name }], + returnType: getInterfaceName(z.name), + statements: (x) => { + z.unionInterfaces.forEach((i) => { + x.writeLine('if (this.is' + getInterfaceName(i) + '(model)){'); + x.writeLine('return ' + i + '.toDTO(model);'); + x.writeLine('}'); + }); + + x.writeLine('return ' + z.parentInterface + '.toDTO(model);'); + } + }, + + ...z.unionInterfaces.map((i) => ({ + scope: Scope.Private, + isStatic: true, + name: 'is' + i, + parameters: [{ name: 'dto', type: getInterfaceName(z.name) }], + returnType: 'dto is ' + getInterfaceName(i), + statements: (x: CodeBlockWriter) => { + x.writeLine('return dto.type === ' + z.name + 'Types.' + i + ';'); + } + })), + + ...z.unionInterfaces.map((i) => ({ + scope: Scope.Private, + isStatic: true, + name: 'is' + getInterfaceName(i), + parameters: [{ name: 'dto', type: z.name }], + returnType: 'dto is ' + i, + statements: (x: CodeBlockWriter) => { + x.writeLine('return dto.type === ' + z.name + 'Types.' + i + ';'); + } + })) + ] + })); + } private getObjects(objects: IObjectModel[]): ClassDeclarationStructure[] { return objects.map((z) => ({ @@ -191,7 +258,7 @@ export class ModelsGenerator { scope: Scope.Public, name: objectProperty.name, type: new TypeSerializer({ - type: { name: objectProperty.type }, + type: { name: objectProperty.typeAlias ?? objectProperty.type }, isNullable: objectProperty.isNullable, isCollection: objectProperty.isCollection }).toString(), @@ -239,7 +306,9 @@ export class ModelsGenerator { case PropertyKind.Object: if (property.isCollection) { - return `${modelProperty} ? ${modelProperty}.map(x => ${property.type}.${TO_DTO_METHOD}(x)) : ${UNDEFINED_STRING}`; + return `${modelProperty} ? ${modelProperty}.map(x => ${ + property.dtoTypeAlias ?? property.type + }.${TO_DTO_METHOD}(x)) : ${UNDEFINED_STRING}`; } return `${modelProperty} ? ${property.type}.${TO_DTO_METHOD}(${modelProperty}) : ${UNDEFINED_STRING}`; @@ -279,7 +348,9 @@ export class ModelsGenerator { case PropertyKind.Object: if (property.isCollection) { - return `${dtoProperty} ? ${dtoProperty}.map(x => ${property.type}.${FROM_DTO_METHOD}(x)) : ${ARRAY_STRING}`; + return `${dtoProperty} ? ${dtoProperty}.map(x => ${ + property.dtoTypeAlias ?? property.type + }.${FROM_DTO_METHOD}(x)) : ${ARRAY_STRING}`; } return `${dtoProperty} ? ${property.type}.${FROM_DTO_METHOD}(${dtoProperty}) : ${UNDEFINED_STRING}`; diff --git a/src/generators/models-generator/InterfacesGenerator.ts b/src/generators/models-generator/InterfacesGenerator.ts index 11265f8..f1eb257 100644 --- a/src/generators/models-generator/InterfacesGenerator.ts +++ b/src/generators/models-generator/InterfacesGenerator.ts @@ -6,8 +6,9 @@ import { TypeAliasDeclarationStructure } from 'ts-morph'; -import { IInterfaceModel, IInterfacePropertyModel } from '../../models/InterfaceModel'; +import { IInterfaceModel, IInterfacePropertyModel, IInterfaceUnionModel } from '../../models/InterfaceModel'; import { TypeSerializer } from '../utils/TypeSerializer'; +import { getInterfaceName } from '../../utils'; export class InterfacesGenerator { public getCodeStructure(interfaces: IInterfaceModel[]): (InterfaceDeclarationStructure | TypeAliasDeclarationStructure)[] { @@ -39,6 +40,26 @@ export class InterfacesGenerator { return [...baseInterfaces, ...types]; } + public getCodeUnionsStructure(interfaces: IInterfaceUnionModel[]): TypeAliasDeclarationStructure[] { + const classUnion: TypeAliasDeclarationStructure[] = interfaces.map((z) => { + return { + kind: StructureKind.TypeAlias, + type: z.parentInterface + '|' + z.unionInterfaces.join(' | '), + name: z.name, + isExported: true + }; + }); + const interfacesUnion: TypeAliasDeclarationStructure[] = interfaces.map((z) => { + return { + kind: StructureKind.TypeAlias, + type: getInterfaceName(z.parentInterface) + '|' + z.unionInterfaces.map((x) => getInterfaceName(x)).join(' | '), + name: getInterfaceName(z.name), + isExported: true + }; + }); + return [...classUnion, ...interfacesUnion]; + } + protected getInterfaceProperty(model: IInterfacePropertyModel): OptionalKind { return { name: model.name, diff --git a/src/models/EnumModel.ts b/src/models/EnumModel.ts index 3900be3..62d0fa1 100644 --- a/src/models/EnumModel.ts +++ b/src/models/EnumModel.ts @@ -1,5 +1,5 @@ export interface IEnumModel { name: string; isNullable: boolean; - items: { key: string; value: number }[]; + items: { key: string; value: number | string }[]; } diff --git a/src/models/InterfaceModel.ts b/src/models/InterfaceModel.ts index 468293d..0ac7cb0 100644 --- a/src/models/InterfaceModel.ts +++ b/src/models/InterfaceModel.ts @@ -10,3 +10,9 @@ export interface IInterfaceModel { properties: IInterfacePropertyModel[]; combineInterfaces: string[]; } + +export interface IInterfaceUnionModel { + name: string; + parentInterface: string; + unionInterfaces: string[]; +} diff --git a/src/models/ModelsContainer.ts b/src/models/ModelsContainer.ts index 21ba597..344f4d2 100644 --- a/src/models/ModelsContainer.ts +++ b/src/models/ModelsContainer.ts @@ -1,11 +1,12 @@ import { IEnumModel } from './EnumModel'; import { IIdentityModel } from './IdentityModel'; -import { IInterfaceModel } from './InterfaceModel'; +import { IInterfaceModel, IInterfaceUnionModel } from './InterfaceModel'; import { IObjectModel } from './ObjectModel'; export interface IModelsContainer { enums: IEnumModel[]; interfaces: IInterfaceModel[]; + unionInterfaces: IInterfaceUnionModel[]; identities: IIdentityModel[]; objects: IObjectModel[]; } diff --git a/src/models/ObjectModel.ts b/src/models/ObjectModel.ts index 5d89ff3..72d2972 100644 --- a/src/models/ObjectModel.ts +++ b/src/models/ObjectModel.ts @@ -1,10 +1,11 @@ -import { IOpenAPI3Reference } from '../swagger/v3/reference'; import { IType } from './TypeModel'; export interface IObjectPropertyModel extends IType { name: string; isNullable: boolean; isCollection: boolean; + typeAlias?: string; + dtoTypeAlias?: string; } export interface IObjectModel { @@ -13,5 +14,10 @@ export interface IObjectModel { isNullable: boolean; properties: IObjectPropertyModel[]; combineTypes: string[]; - combineTypesRefs: IOpenAPI3Reference[]; +} + +export interface IObjectUnionModel { + name: string; + baseTypeName: string; + unionTypesNames: string[]; } diff --git a/src/services/ModelMappingService.ts b/src/services/ModelMappingService.ts index 361ba3e..5209d4c 100644 --- a/src/services/ModelMappingService.ts +++ b/src/services/ModelMappingService.ts @@ -1,25 +1,27 @@ import { IEnumModel } from '../models/EnumModel'; import { IIdentityModel } from '../models/IdentityModel'; -import { IInterfaceModel } from '../models/InterfaceModel'; +import { IInterfaceModel, IInterfaceUnionModel } from '../models/InterfaceModel'; import { PropertyKind } from '../models/kinds/PropertyKind'; import { IModelsContainer } from '../models/ModelsContainer'; -import { IObjectModel, IObjectPropertyModel } from '../models/ObjectModel'; +import { IObjectModel, IObjectPropertyModel, IObjectUnionModel } from '../models/ObjectModel'; import { OpenAPIService } from '../swagger/OpenAPIService'; import { OpenAPITypesGuard } from '../swagger/OpenAPITypesGuard'; import { IOpenAPI3Reference } from '../swagger/v3/reference'; +import { IOpenAPI3AllOfSchema } from '../swagger/v3/schemas/all-of-schema'; +import { IOpenAPI3DiscriminatorSchema } from '../swagger/v3/schemas/discriminator-schema'; import { IOpenAPI3EnumSchema } from '../swagger/v3/schemas/enum-schema'; import { IOpenAPI3GuidSchema } from '../swagger/v3/schemas/guid-schema'; import { IOpenAPI3ObjectSchema } from '../swagger/v3/schemas/object-schema'; import { OpenAPI3Schema, OpenAPI3SchemaContainer, OpenAPI3SimpleSchema } from '../swagger/v3/schemas/schema'; -import { first, sortBy } from '../utils'; -import { isDefined } from '../generators/utils/is-defined'; +import { first, getInterfaceName, sortBy } from '../utils'; import { TypesService } from './TypesService'; const IGNORE_PROPERTIES = ['startRow', 'rowCount']; export class ModelMappingService { - public additionalObjects: IObjectModel[] = []; public additionalEnums: IEnumModel[] = []; + public additionalUnions: IObjectUnionModel[] = []; + constructor( private readonly openAPIService: OpenAPIService, private readonly typesGuard: OpenAPITypesGuard, @@ -27,9 +29,9 @@ export class ModelMappingService { ) {} public toModelsContainer(schemas: OpenAPI3SchemaContainer): IModelsContainer { + const objects: IObjectModel[] = []; const enums: IEnumModel[] = []; const identities: IIdentityModel[] = []; - const objects: IObjectModel[] = []; Object.entries(schemas).forEach(([name, schema]) => { if (this.typesGuard.isEnum(schema)) { @@ -42,7 +44,7 @@ export class ModelMappingService { identities.push({ name, isNullable: false, - dtoType: this.getInterfaceName(name), + dtoType: getInterfaceName(name), property: { ...this.typesService.getSimpleType(schema.properties['id'] as IOpenAPI3GuidSchema), isCollection: false, @@ -56,15 +58,12 @@ export class ModelMappingService { } }); - objects.forEach((x) => this.addCombineObjectsByRefs(x)); - return { - enums: this.getUnicItemsByProp('name', ...this.additionalEnums, ...enums).sort(sortBy((z) => z.name)), + enums: [...this.additionalEnums, ...enums].sort(sortBy((z) => z.name)), identities: identities.sort(sortBy((z) => z.name)), - interfaces: this.getInterfaces(identities, this.getUnicItemsByProp('name', ...this.additionalObjects, ...objects)).sort( - sortBy((z) => z.name) - ), - objects: this.getUnicItemsByProp('name', ...this.additionalObjects, ...objects).sort(sortBy((z) => z.name)) + unionInterfaces: this.getUnionInterfaces(this.additionalUnions).sort(sortBy((z) => z.name)), + interfaces: this.getInterfaces(identities, objects).sort(sortBy((z) => z.name)), + objects: objects.sort(sortBy((z) => z.name)) }; } @@ -81,17 +80,22 @@ export class ModelMappingService { }; } - private toObjectModel(name: string, schema: IOpenAPI3ObjectSchema): IObjectModel { + private toObjectModel(name: string, schema: IOpenAPI3ObjectSchema | IOpenAPI3DiscriminatorSchema): IObjectModel { const model: IObjectModel = { name, isNullable: schema.nullable ?? false, - dtoType: this.getInterfaceName(name), + dtoType: getInterfaceName(name), properties: [], - combineTypes: [], - combineTypesRefs: [] + combineTypes: [] }; - this.addCombineTypes(schema, model); + if (this.typesGuard.isAllOf(schema)) { + this.addCombineTypes(schema, model); + } + + if (this.typesGuard.isDiscriminator(schema)) { + this.addUnionTypesByDiscriminator(schema, model.name); + } if (!schema.properties) { return model; @@ -102,6 +106,7 @@ export class ModelMappingService { .forEach(([name, propertySchema]) => this.addProperty(model, name, propertySchema)); model.properties = model.properties.sort(sortBy((z) => z.name)); + return model; } @@ -120,6 +125,7 @@ export class ModelMappingService { } if (this.typesGuard.isOneOf(schema.items)) { property = this.getReferenceProperty(name, first(schema.items.oneOf)); + this.updateOneOfProperty(first(schema.items.oneOf), property); } if (property) { @@ -135,6 +141,7 @@ export class ModelMappingService { property = this.getReferenceProperty(name, first(schema.allOf)); } else if (this.typesGuard.isOneOf(schema)) { property = this.getReferenceProperty(name, first(schema.oneOf)); + this.updateOneOfProperty(first(schema.oneOf), property); } if (property) { @@ -143,62 +150,52 @@ export class ModelMappingService { } } - private addCombineTypes(schema: IOpenAPI3ObjectSchema, model: IObjectModel): void { - if (!this.typesGuard.isAllOf(schema)) { - return; - } + private addCombineTypes(schema: IOpenAPI3AllOfSchema, model: IObjectModel): void { schema.allOf.forEach((x) => { const refSchema = this.openAPIService.getRefSchema(x); const schemaKey = this.openAPIService.getSchemaKey(x); if (this.typesGuard.isObject(refSchema)) { model.combineTypes = [...model.combineTypes, schemaKey]; - model.combineTypesRefs = [...model.combineTypesRefs, x]; } }); } - private addCombineObjectsByRefs(model: IObjectModel): void { - if (!model.combineTypesRefs) { - return; - } - model.combineTypesRefs.forEach((ref) => this.addCombineObjectsByRef(ref)); - - if (!model.properties) { - return; - } - model.properties.forEach((prop) => this.addPropertiesCombineType(prop)); + private updateOneOfProperty(firstObj: IOpenAPI3Reference, property: IObjectPropertyModel): void { + const schemaKey = this.openAPIService.getSchemaKey(firstObj); + property.typeAlias = schemaKey + 'Union'; + property.typeAlias = schemaKey + 'UnionClass'; } - private addCombineObjectsByRef(ref: IOpenAPI3Reference): void { - const refSchema = this.openAPIService.getRefSchema(ref); - const schemaKey = this.openAPIService.getSchemaKey(ref); - if (this.typesGuard.isObject(refSchema)) { - const combinedModel = this.toObjectModel(schemaKey, refSchema); + private addUnionTypesByDiscriminator(schema: IOpenAPI3DiscriminatorSchema, modelName: string): void { + const unionModel: IObjectUnionModel = { + name: modelName + 'Union', + unionTypesNames: [], + baseTypeName: modelName + }; - if (this.additionalObjects.find((x) => x.name === combinedModel.name)) { - //нужно ли помержить юнион типы тут - return; + Object.keys(schema.discriminator.mapping).forEach((key) => { + const value = schema.discriminator!.mapping[key]; + const refSchema = this.openAPIService.getRefSchema({ $ref: value }); + const schemaKey = this.openAPIService.getSchemaKey({ $ref: value }); + if (this.typesGuard.isObject(refSchema)) { + unionModel.unionTypesNames.push(schemaKey); } + }); - this.additionalObjects.push(combinedModel); - this.addCombineObjectsByRefs(combinedModel); - } - } - - private addPropertiesCombineType(prop: IObjectPropertyModel): void { - const ref = { $ref: '#/components/schemas/' + prop.type }; - if (prop.kind === PropertyKind.Object) { - this.addCombineObjectsByRef(ref); - } else if (prop.kind === PropertyKind.Enum) { - const refSchema = this.openAPIService.getRefSchema(ref); - const schemaKey = this.openAPIService.getSchemaKey(ref); - if (this.typesGuard.isEnum(refSchema)) { - const emun = this.toEnumModel(schemaKey, refSchema); - if (!this.additionalEnums.find((e) => e.name === emun.name)) { - this.additionalEnums.push(emun); - } - } - } + this.additionalUnions.push(unionModel); + + this.additionalEnums.push({ + name: modelName + 'UnionTypes', + isNullable: false, + items: Object.keys(schema.discriminator.mapping).map((key) => { + const value = schema.discriminator!.mapping[key]; + const schemaKey = this.openAPIService.getSchemaKey({ $ref: value }); + return { + key: schemaKey, + value: key + }; + }) + }); } private getSimpleProperty(name: string, schema: OpenAPI3SimpleSchema): IObjectPropertyModel { @@ -233,21 +230,21 @@ export class ModelMappingService { name, isNullable: true, type: schemaKey, - dtoType: this.getInterfaceName(schemaKey) + dtoType: getInterfaceName(schemaKey) }; } private getInterfaces(identities: IIdentityModel[], objects: IObjectModel[]): IInterfaceModel[] { const interfaces: IInterfaceModel[] = identities.map((z) => ({ - name: this.getInterfaceName(z.name), + name: getInterfaceName(z.name), properties: [{ name: z.property.name, dtoType: z.property.dtoType, isCollection: false, isNullable: false }], combineInterfaces: [] })); return interfaces.concat( objects.map((z) => ({ - name: this.getInterfaceName(z.name), - combineInterfaces: z.combineTypes.map((x) => this.getInterfaceName(x)), + name: getInterfaceName(z.name), + combineInterfaces: z.combineTypes.map((x) => getInterfaceName(x)), properties: z.properties.map((x) => ({ name: x.name, dtoType: x.dtoType, @@ -258,8 +255,12 @@ export class ModelMappingService { ); } - private getInterfaceName(name: string): string { - return `I${name}`; + private getUnionInterfaces(objects: IObjectUnionModel[]): IInterfaceUnionModel[] { + return objects.map((z) => ({ + name: z.name, + unionInterfaces: z.unionTypesNames.map((x) => x), + parentInterface: z.baseTypeName + })); } private isIdentity(schema: IOpenAPI3ObjectSchema | undefined): boolean { @@ -268,9 +269,4 @@ export class ModelMappingService { } return schema.properties && Object.keys(schema.properties)?.length === 1 && this.typesGuard.isGuid(schema.properties['id']); } - - private getUnicItemsByProp(key: T, ...array1: T2[]): T2[] { - const unionKeys = [...new Set(array1.map((x) => x[key]))]; - return unionKeys.map((x) => array1.find((y) => y[key] === x)).filter(isDefined); - } } diff --git a/src/swagger/OpenAPIService.ts b/src/swagger/OpenAPIService.ts index 03f53c9..80d689d 100644 --- a/src/swagger/OpenAPIService.ts +++ b/src/swagger/OpenAPIService.ts @@ -4,6 +4,7 @@ import { OpenAPITypesGuard } from './OpenAPITypesGuard'; import { IOpenAPI3 } from './v3/open-api'; import { IOpenAPI3Operation } from './v3/operation'; import { IOpenAPI3Reference } from './v3/reference'; +import { IOpenAPI3AllOfSchema } from './v3/schemas/all-of-schema'; import { IOpenAPI3EnumSchema } from './v3/schemas/enum-schema'; import { IOpenAPI3ObjectSchema } from './v3/schemas/object-schema'; import { OpenAPI3Schema, OpenAPI3SchemaContainer } from './v3/schemas/schema'; @@ -172,7 +173,6 @@ export class OpenAPIService { private getRefsByOperation(operation: IOpenAPI3Operation): IOpenAPI3Reference[] { const refs: IOpenAPI3Reference[] = []; - operation.parameters?.forEach((z) => { if (this.typesGuard.isReference(z.schema)) { refs.push(z.schema); @@ -206,6 +206,12 @@ export class OpenAPIService { const expanded = this.expandRefs(refsFromObject, refKeys); collectedRefs.push(...expanded); } + + if (this.typesGuard.isAllOf(schema)) { + const refsFromObject = this.getRefsByAllOf(schema, ref); + const expanded = this.expandRefs(refsFromObject, refKeys); + collectedRefs.push(...expanded); + } }); return collectedRefs; @@ -236,9 +242,38 @@ export class OpenAPIService { return refs; } + /** + * @description Gets refs from allof schema only one level down + */ + private getRefsByAllOf( + object: IOpenAPI3AllOfSchema, + objectRef: IOpenAPI3Reference, + outerRefs: IOpenAPI3Reference[] = [] + ): IOpenAPI3Reference[] { + const refs = outerRefs; + + Object.values(object.allOf || []).forEach((property) => { + this.getRefsFromSchema(property) + .filter((ref) => ref.$ref !== objectRef.$ref && !outerRefs.find((x) => x.$ref === ref.$ref)) + .forEach((ref) => { + refs.push(ref); + + if (this.typesGuard.isObject(property)) { + this.getRefsByObject(property, objectRef, refs); + } + }); + }); + + return refs; + } + private getRefsFromSchema(schema: OpenAPI3Schema | undefined): IOpenAPI3Reference[] { const refs: IOpenAPI3Reference[] = []; - if (this.typesGuard.isCollection(schema) && this.typesGuard.isReference(schema.items)) { + if (this.typesGuard.isCollection(schema) && this.typesGuard.isOneOf(schema.items)) { + refs.push(...schema.items.oneOf); + } else if (this.typesGuard.isOneOf(schema)) { + refs.push(...schema.oneOf); + } else if (this.typesGuard.isCollection(schema) && this.typesGuard.isReference(schema.items)) { refs.push(schema.items); } else if (this.typesGuard.isReference(schema)) { refs.push(schema); diff --git a/src/swagger/OpenAPITypesGuard.ts b/src/swagger/OpenAPITypesGuard.ts index c039167..34e8939 100644 --- a/src/swagger/OpenAPITypesGuard.ts +++ b/src/swagger/OpenAPITypesGuard.ts @@ -3,6 +3,7 @@ import { IOpenAPI3AllOfSchema } from './v3/schemas/all-of-schema'; import { IOpenAPI3ArraySchema } from './v3/schemas/array-schema'; import { IOpenAPI3BooleanSchema } from './v3/schemas/boolean-schema'; import { IOpenAPI3DateSchema } from './v3/schemas/date-schema'; +import { IOpenAPI3DiscriminatorSchema } from './v3/schemas/discriminator-schema'; import { IOpenAPI3EnumSchema } from './v3/schemas/enum-schema'; import { IOpenAPI3GuidSchema } from './v3/schemas/guid-schema'; import { IOpenAPI3NumberSchema } from './v3/schemas/number-schema'; @@ -38,12 +39,16 @@ export class OpenAPITypesGuard { return (schema as IOpenAPI3ObjectSchema)?.type === 'object'; } + public isDiscriminator(schema: SchemaType): schema is IOpenAPI3DiscriminatorSchema { + return Boolean((schema as IOpenAPI3DiscriminatorSchema)?.discriminator); + } + public isAllOf(schema: SchemaType): schema is IOpenAPI3AllOfSchema { return Boolean((schema as IOpenAPI3AllOfSchema)?.allOf); } public isOneOf(schema: SchemaType): schema is IOpenAPI3OneOfSchema { - return Boolean((schema as IOpenAPI3OneOfSchema).oneOf); + return Boolean((schema as IOpenAPI3OneOfSchema)?.oneOf); } public isEnum(schema: SchemaType): schema is IOpenAPI3EnumSchema { diff --git a/src/swagger/v3/schemas/discriminator-schema.ts b/src/swagger/v3/schemas/discriminator-schema.ts new file mode 100644 index 0000000..0f461df --- /dev/null +++ b/src/swagger/v3/schemas/discriminator-schema.ts @@ -0,0 +1,10 @@ +import { IOpenAPI3ObjectSchema } from './object-schema'; + +export interface IOpenAPI3DiscriminatorSchema extends IOpenAPI3ObjectSchema { + discriminator: { + propertyName: string; + mapping: { + [key: string]: string; + }; + }; +} diff --git a/src/swagger/v3/schemas/schema.ts b/src/swagger/v3/schemas/schema.ts index 864c7df..453a42a 100644 --- a/src/swagger/v3/schemas/schema.ts +++ b/src/swagger/v3/schemas/schema.ts @@ -3,6 +3,7 @@ import { IOpenAPI3AllOfSchema } from './all-of-schema'; import { IOpenAPI3ArraySchema } from './array-schema'; import { IOpenAPI3BooleanSchema } from './boolean-schema'; import { IOpenAPI3DateSchema } from './date-schema'; +import { IOpenAPI3DiscriminatorSchema } from './discriminator-schema'; import { IOpenAPI3EnumSchema } from './enum-schema'; import { IOpenAPI3GuidSchema } from './guid-schema'; import { IOpenAPI3NumberSchema } from './number-schema'; @@ -17,4 +18,4 @@ export type OpenAPI3SimpleSchema = | IOpenAPI3BooleanSchema; export type OpenAPI3Schema = IOpenAPI3ArraySchema | OpenAPI3SimpleSchema | IOpenAPI3Reference | IOpenAPI3AllOfSchema; -export type OpenAPI3SchemaContainer = { [key: string]: IOpenAPI3ObjectSchema | IOpenAPI3EnumSchema }; +export type OpenAPI3SchemaContainer = { [key: string]: IOpenAPI3ObjectSchema | IOpenAPI3DiscriminatorSchema | IOpenAPI3EnumSchema }; diff --git a/src/utils.ts b/src/utils.ts index a225dec..dfb768d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -19,3 +19,7 @@ export function last(array: T[]): T { export function sortBy(fn: (value: T) => string): (a: T, b: T) => number { return (a: T, b: T) => (fn(a) || '').localeCompare(fn(b) || ''); } + +export function getInterfaceName(name: string): string { + return `I${name}`; +} \ No newline at end of file diff --git a/swagger.json b/swagger.json index f07bd31..95f7865 100644 --- a/swagger.json +++ b/swagger.json @@ -642,11 +642,51 @@ "Category": { "type": "object", "properties": { + "type": { + "type": "string" + }, "name": { "type": "string", "nullable": true } + }, + "additionalProperties": false, + "discriminator": { + "propertyName": "type", + "mapping": { + "1": "#/components/schemas/CategoryElectronicsDto", + "2": "#/components/schemas/CategoryMotorsDto" + } + } + }, + "CategoryMotorsDto": { + "type": "object", + "properties": { + "volume": { + "type": "number", + "nullable": true + } }, + "allOf": [ + { + "$ref": "#/components/schemas/Category" + } + ], + "additionalProperties": false + }, + "CategoryElectronicsDto": { + "type": "object", + "properties": { + "syntheticTest": { + "type": "number", + "nullable": true + } + }, + "allOf": [ + { + "$ref": "#/components/schemas/Category" + } + ], "additionalProperties": false }, "ProductStatus": { @@ -699,7 +739,34 @@ "nullable": true }, "category": { - "$ref": "#/components/schemas/Category" + "type": "object", + "oneOf": [ + { + "$ref": "#/components/schemas/Category" + }, + { + "$ref": "#/components/schemas/CategoryElectronicsDto" + }, + { + "$ref": "#/components/schemas/CategoryMotorsDto" + } + ] + }, + "categories": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/Category" + }, + { + "$ref": "#/components/schemas/CategoryElectronicsDto" + }, + { + "$ref": "#/components/schemas/CategoryMotorsDto" + } + ] + } }, "status": { "allOf": [ From e8a14d9fb14a767aaf9abac58477516ec53f488d Mon Sep 17 00:00:00 2001 From: Andrey Klimenko Date: Mon, 25 Mar 2024 15:26:02 +0100 Subject: [PATCH 3/7] update getRefsByObject method --- src/swagger/OpenAPIService.ts | 48 +++++++++-------------------------- 1 file changed, 12 insertions(+), 36 deletions(-) diff --git a/src/swagger/OpenAPIService.ts b/src/swagger/OpenAPIService.ts index 80d689d..d4da30f 100644 --- a/src/swagger/OpenAPIService.ts +++ b/src/swagger/OpenAPIService.ts @@ -4,7 +4,6 @@ import { OpenAPITypesGuard } from './OpenAPITypesGuard'; import { IOpenAPI3 } from './v3/open-api'; import { IOpenAPI3Operation } from './v3/operation'; import { IOpenAPI3Reference } from './v3/reference'; -import { IOpenAPI3AllOfSchema } from './v3/schemas/all-of-schema'; import { IOpenAPI3EnumSchema } from './v3/schemas/enum-schema'; import { IOpenAPI3ObjectSchema } from './v3/schemas/object-schema'; import { OpenAPI3Schema, OpenAPI3SchemaContainer } from './v3/schemas/schema'; @@ -201,17 +200,11 @@ export class OpenAPIService { refKeys.add(ref.$ref); const schema = this.getSchemaByRef(ref); - if (this.typesGuard.isObject(schema)) { + if (schema) { const refsFromObject = this.getRefsByObject(schema, ref); const expanded = this.expandRefs(refsFromObject, refKeys); collectedRefs.push(...expanded); } - - if (this.typesGuard.isAllOf(schema)) { - const refsFromObject = this.getRefsByAllOf(schema, ref); - const expanded = this.expandRefs(refsFromObject, refKeys); - collectedRefs.push(...expanded); - } }); return collectedRefs; @@ -221,46 +214,29 @@ export class OpenAPIService { * @description Gets refs from object schema only one level down */ private getRefsByObject( - object: IOpenAPI3ObjectSchema, + schema: IOpenAPI3ObjectSchema | IOpenAPI3EnumSchema | OpenAPI3Schema, objectRef: IOpenAPI3Reference, outerRefs: IOpenAPI3Reference[] = [] ): IOpenAPI3Reference[] { const refs = outerRefs; - Object.values(object.properties || []).forEach((property) => { - this.getRefsFromSchema(property) - .filter((ref) => ref.$ref !== objectRef.$ref && !outerRefs.find((x) => x.$ref === ref.$ref)) - .forEach((ref) => { - refs.push(ref); - - if (this.typesGuard.isObject(property)) { - this.getRefsByObject(property, objectRef, refs); - } - }); - }); + const properties: OpenAPI3Schema[] = []; - return refs; - } + if (this.typesGuard.isAllOf(schema)) { + properties.push(...schema.allOf); + } - /** - * @description Gets refs from allof schema only one level down - */ - private getRefsByAllOf( - object: IOpenAPI3AllOfSchema, - objectRef: IOpenAPI3Reference, - outerRefs: IOpenAPI3Reference[] = [] - ): IOpenAPI3Reference[] { - const refs = outerRefs; + if (this.typesGuard.isObject(schema)) { + properties.push(...Object.values(schema.properties || [])); + } - Object.values(object.allOf || []).forEach((property) => { + properties.forEach((property) => { this.getRefsFromSchema(property) .filter((ref) => ref.$ref !== objectRef.$ref && !outerRefs.find((x) => x.$ref === ref.$ref)) .forEach((ref) => { refs.push(ref); - - if (this.typesGuard.isObject(property)) { - this.getRefsByObject(property, objectRef, refs); - } + this.getRefsByObject(property, objectRef, refs); + this.getRefsByObject(property, objectRef, refs); }); }); From 5e8a28da20922618af2d90970e6000e17d0d251b Mon Sep 17 00:00:00 2001 From: Andrey Klimenko Date: Wed, 24 Apr 2024 11:15:50 +0200 Subject: [PATCH 4/7] union types generate update --- .snapshot/all/models.ts | 35 +- src/generators/ModelsGenerator.ts | 349 +----------------- .../models-generator/IdentitiesGenerator.ts | 65 ++++ .../models-generator/InterfacesGenerator.ts | 104 +++--- .../models-generator/ObjectGenerator.ts | 218 +++++++++++ .../models-generator/UnionsGenerator.ts | 73 ++++ src/generators/utils/is-defined.ts | 3 - src/generators/utils/modelGuard.ts | 5 + src/generators/utils/propertyDeclaration.ts | 13 + src/models/InterfaceModel.ts | 9 +- src/models/ModelsContainer.ts | 11 +- src/models/ObjectModel.ts | 11 +- src/models/UnionModel.ts | 4 + src/models/kinds/PropertyKind.ts | 3 +- src/services/ModelMappingService.ts | 159 ++++---- src/utils.ts | 14 +- 16 files changed, 595 insertions(+), 481 deletions(-) create mode 100644 src/generators/models-generator/IdentitiesGenerator.ts create mode 100644 src/generators/models-generator/ObjectGenerator.ts create mode 100644 src/generators/models-generator/UnionsGenerator.ts delete mode 100644 src/generators/utils/is-defined.ts create mode 100644 src/generators/utils/modelGuard.ts create mode 100644 src/generators/utils/propertyDeclaration.ts create mode 100644 src/models/UnionModel.ts diff --git a/.snapshot/all/models.ts b/.snapshot/all/models.ts index 9cc27d0..99865bb 100644 --- a/.snapshot/all/models.ts +++ b/.snapshot/all/models.ts @@ -13,25 +13,29 @@ export enum ProductStatus { UnderTheOrder = 1 } +export type CategoryUnion = Category | CategoryElectronicsDto | CategoryMotorsDto; +export type ICategoryUnion = ICategory | ICategoryElectronicsDto | ICategoryMotorsDto; + +export interface ICategory { + name: $types.TypeOrUndefinedNullable; + type: $types.TypeOrUndefined; +} + interface ICategoryElectronicsDtoBaseInterface { syntheticTest: $types.TypeOrUndefinedNullable; } +export type ICategoryElectronicsDto = ICategoryElectronicsDtoBaseInterface & ICategory; + interface ICategoryMotorsDtoBaseInterface { volume: $types.TypeOrUndefinedNullable; } -export interface ICategory { - name: $types.TypeOrUndefinedNullable; - type: $types.TypeOrUndefined; -} - -export type ICategoryElectronicsDto = ICategoryElectronicsDtoBaseInterface & ICategory; export type ICategoryMotorsDto = ICategoryMotorsDtoBaseInterface & ICategory; export interface IProduct { - categories: $types.TypeOrUndefinedNullable; - category: $types.TypeOrUndefinedNullable; + categories: $types.TypeOrUndefined; + category: $types.TypeOrUndefined; colors: $types.TypeOrUndefined; expireDate: $types.TypeOrUndefined; externalId: $types.TypeOrUndefinedNullable; @@ -45,9 +49,6 @@ export interface IProductIdentityDTO { id: $types.TypeOrUndefined; } -export type CategoryUnion = Category | CategoryElectronicsDto | CategoryMotorsDto; -export type ICategoryUnion = ICategory | ICategoryElectronicsDto | ICategoryMotorsDto; - export class CategoryUnionClass { public static fromDTO(dto: ICategoryUnion): CategoryUnion { if (this.isCategoryElectronicsDto(dto)) { @@ -166,8 +167,8 @@ export class CategoryMotorsDto { } export class Product { - public categories: CategoryUnionClass[] = []; - public category: $types.TypeOrUndefinedNullable = undefined; + public categories: CategoryUnion[] = []; + public category: $types.TypeOrUndefined = undefined; public colors: string[] = []; public expireDate: $types.TypeOrUndefined = undefined; public externalId: $types.TypeOrUndefinedNullable = undefined; @@ -179,8 +180,8 @@ export class Product { public static toDTO(model: Partial): IProduct { return { - categories: model.categories ? model.categories.map(x => Category.toDTO(x)) : undefined, - category: model.category ? Category.toDTO(model.category) : undefined, + categories: model.categories ? model.categories.map(x => CategoryUnionClass.toDTO(x)) : undefined, + category: model.category ? CategoryUnionClass.toDTO(model.category) : undefined, colors: model.colors, expireDate: toDateOut(model.expireDate), externalId: model.externalId ? model.externalId.toString() : null, @@ -193,8 +194,8 @@ export class Product { public static fromDTO(dto: IProduct): Product { const model = new Product(); - model.categories = dto.categories ? dto.categories.map(x => Category.fromDTO(x)) : []; - model.category = dto.category ? Category.fromDTO(dto.category) : undefined; + model.categories = dto.categories ? dto.categories.map(x => CategoryUnionClass.fromDTO(x)) : []; + model.category = dto.category ? CategoryUnionClass.fromDTO(dto.category) : undefined; model.colors = dto.colors ? dto.colors : []; model.expireDate = toDateIn(dto.expireDate); model.externalId = dto.externalId ? new Guid(dto.externalId) : null; diff --git a/src/generators/ModelsGenerator.ts b/src/generators/ModelsGenerator.ts index ab47f51..770b3d8 100644 --- a/src/generators/ModelsGenerator.ts +++ b/src/generators/ModelsGenerator.ts @@ -1,33 +1,24 @@ -import { - ClassDeclarationStructure, - CodeBlockWriter, - ConstructorDeclarationStructure, - ImportDeclarationStructure, - OptionalKind, - ParameterDeclarationStructure, - PropertyDeclarationStructure, - Scope, - StatementStructures, - StructureKind -} from 'ts-morph'; +import { ImportDeclarationStructure, StatementStructures, StructureKind } from 'ts-morph'; + import { IEnumModel } from '../models/EnumModel'; -import { IIdentityModel } from '../models/IdentityModel'; -import { IInterfaceModel, IInterfaceUnionModel } from '../models/InterfaceModel'; import { IModelsContainer } from '../models/ModelsContainer'; -import { IObjectModel, IObjectPropertyModel } from '../models/ObjectModel'; -import { PropertyKind } from '../models/kinds/PropertyKind'; import { IOptions } from '../options'; import { PathBuilder } from '../services/PathBuilder'; -import { getInterfaceName, lowerFirst } from '../utils'; +import { IdentitiesGenerator } from './models-generator/IdentitiesGenerator'; import { InterfacesGenerator } from './models-generator/InterfacesGenerator'; -import { TypeSerializer } from './utils/TypeSerializer'; -import { ARRAY_STRING, NULL_STRING, TYPES_NAMESPACE, UNDEFINED_STRING } from './utils/consts'; +import { ObjectGenerator } from './models-generator/ObjectGenerator'; +import { UnionGenerator } from './models-generator/UnionsGenerator'; +import { TYPES_NAMESPACE } from './utils/consts'; -const TO_DTO_METHOD = 'toDTO'; -const FROM_DTO_METHOD = 'fromDTO'; +export const TO_DTO_METHOD = 'toDTO'; +export const FROM_DTO_METHOD = 'fromDTO'; export class ModelsGenerator { private interfaceGenerator = new InterfacesGenerator(); + private unionGenerator = new UnionGenerator(); + private identitiesGenerator = new IdentitiesGenerator(this.settings); + private objectGenerator = new ObjectGenerator(); + private pathBuilder = new PathBuilder(); constructor(private settings: IOptions) {} @@ -36,11 +27,10 @@ export class ModelsGenerator { return [ ...this.getImports(), ...this.getEnums(models.enums), - ...this.interfaceGenerator.getCodeStructure(models.interfaces), - ...this.interfaceGenerator.getCodeUnionsStructure(models.unionInterfaces), - ...this.getUnionObjects(models.unionInterfaces), - ...this.getIdentities(models.identities, models.interfaces), - ...this.getObjects(models.objects) + ...this.interfaceGenerator.getCodeStructure(models.interfaces, models.unions), + ...this.unionGenerator.getUnionObjects(models.unions), + ...this.identitiesGenerator.getIdentities(models.identities, models.interfaces), + ...this.objectGenerator.getObjects(models.objects) ]; } @@ -76,311 +66,4 @@ export class ModelsGenerator { members: z.items.map((x) => ({ name: x.key, value: x.value })) })); } - - private getIdentities(identities: IIdentityModel[], interfaces: IInterfaceModel[]): StatementStructures[] { - return identities.map( - (z): ClassDeclarationStructure => ({ - kind: StructureKind.Class, - isExported: true, - name: z.name, - ctors: [ - { - parameters: [ - { - name: z.property.name, - hasQuestionToken: true, - type: TypeSerializer.fromTypeName( - z.property.type !== z.property.dtoType - ? `${z.property.type} | ${z.property.dtoType}` - : `${z.property.type}` - ).toString() - } - ] as OptionalKind[], - statements: this.settings.unstrictId - ? `this.${z.property.name} = ${z.property.name} ?? '';` - : `this.${z.property.name} = new ${z.property.type}(${z.property.name});` - } - ] as OptionalKind[], - properties: [{ scope: Scope.Public, name: z.property.name, type: z.property.type }, this.getGuardProperty(z.name)], - methods: [ - { - scope: Scope.Public, - isStatic: true, - name: TO_DTO_METHOD, - parameters: [{ name: z.property.name, type: z.property.type }], - // TODO: would find first identity interface everytime - returnType: interfaces.find( - (i) => - i.properties.length === 1 && - i.properties.every((x) => x.dtoType === z.property.dtoType && x.name === z.property.name) - )?.name, - statements: this.settings.unstrictId - ? `return { ${z.property.name}: ${z.property.name} };` - : `return { ${z.property.name}: ${z.property.name}.toString() };` - } - ] - }) - ); - } - private getUnionObjects(objects: IInterfaceUnionModel[]): ClassDeclarationStructure[] { - return objects.map((z) => ({ - kind: StructureKind.Class, - isExported: true, - name: z.name + 'Class', - properties: [], - methods: [ - { - scope: Scope.Public, - isStatic: true, - name: FROM_DTO_METHOD, - parameters: [{ name: 'dto', type: getInterfaceName(z.name) }], - returnType: z.name, - statements: (x) => { - z.unionInterfaces.forEach((i) => { - x.writeLine('if (this.is' + i + '(dto)){'); - x.writeLine('return ' + i + '.fromDTO(dto);'); - x.writeLine('}'); - }); - - x.writeLine('return ' + z.parentInterface + '.fromDTO(dto);'); - } - }, - - { - scope: Scope.Public, - isStatic: true, - name: TO_DTO_METHOD, - parameters: [{ name: 'model', type: z.name }], - returnType: getInterfaceName(z.name), - statements: (x) => { - z.unionInterfaces.forEach((i) => { - x.writeLine('if (this.is' + getInterfaceName(i) + '(model)){'); - x.writeLine('return ' + i + '.toDTO(model);'); - x.writeLine('}'); - }); - - x.writeLine('return ' + z.parentInterface + '.toDTO(model);'); - } - }, - - ...z.unionInterfaces.map((i) => ({ - scope: Scope.Private, - isStatic: true, - name: 'is' + i, - parameters: [{ name: 'dto', type: getInterfaceName(z.name) }], - returnType: 'dto is ' + getInterfaceName(i), - statements: (x: CodeBlockWriter) => { - x.writeLine('return dto.type === ' + z.name + 'Types.' + i + ';'); - } - })), - - ...z.unionInterfaces.map((i) => ({ - scope: Scope.Private, - isStatic: true, - name: 'is' + getInterfaceName(i), - parameters: [{ name: 'dto', type: z.name }], - returnType: 'dto is ' + i, - statements: (x: CodeBlockWriter) => { - x.writeLine('return dto.type === ' + z.name + 'Types.' + i + ';'); - } - })) - ] - })); - } - - private getObjects(objects: IObjectModel[]): ClassDeclarationStructure[] { - return objects.map((z) => ({ - kind: StructureKind.Class, - isExported: true, - name: z.name, - properties: this.getObjectProperties(z, objects), - methods: [ - { - scope: Scope.Public, - isStatic: true, - name: TO_DTO_METHOD, - parameters: [{ name: 'model', type: `Partial<${z.name}>` }], - returnType: z.dtoType, - statements: (x) => { - x.writeLine('return {'); - z.properties.forEach((p) => - x.withIndentationLevel(3, () => x.writeLine(`${p.name}: ${this.getToDtoPropertyInitializer(p)},`)) - ); - this.printCombinedProprs(z, x, objects, (p) => - x.withIndentationLevel(3, () => x.writeLine(`${p.name}: ${this.getToDtoPropertyInitializer(p)},`)) - ); - x.writeLine('};'); - } - }, - { - scope: Scope.Public, - isStatic: true, - name: FROM_DTO_METHOD, - parameters: [{ name: 'dto', type: z.dtoType }], - returnType: z.name, - statements: (x) => { - x.writeLine(`const model = new ${z.name}();`); - z.properties.forEach((p) => - x.withIndentationLevel(2, () => x.writeLine(`model.${p.name} = ${this.getFromDtoPropertyInitializer(p)};`)) - ); - this.printCombinedProprs(z, x, objects, (p) => - x.withIndentationLevel(2, () => x.writeLine(`model.${p.name} = ${this.getFromDtoPropertyInitializer(p)};`)) - ); - x.writeLine('return model;'); - } - } - ] - })); - } - - private getObjectProperties(objectModel: IObjectModel, objects: IObjectModel[]): PropertyDeclarationStructure[] { - return [ - ...this.getObjectCombinedProperties(objectModel, objects), - ...objectModel.properties.map((objectProperty) => this.getDeclarationStructure(objectProperty)), - this.getGuardProperty(objectModel.name) - ]; - } - - private getObjectCombinedProperties(objectModel: IObjectModel, objects: IObjectModel[]): PropertyDeclarationStructure[] { - return objectModel.combineTypes.reduce((acc, item) => { - const model = objects.find((x) => x.name === item); - const props = (model?.properties ?? []).map((objectProperty) => this.getDeclarationStructure(objectProperty)); - if (model) { - props.push(...this.getObjectCombinedProperties(model, objects)); - } - return [...acc, ...props]; - }, [] as PropertyDeclarationStructure[]); - } - - private getDeclarationStructure(objectProperty: IObjectPropertyModel): PropertyDeclarationStructure { - return { - kind: StructureKind.Property, - scope: Scope.Public, - name: objectProperty.name, - type: new TypeSerializer({ - type: { name: objectProperty.typeAlias ?? objectProperty.type }, - isNullable: objectProperty.isNullable, - isCollection: objectProperty.isCollection - }).toString(), - initializer: objectProperty.isCollection ? ARRAY_STRING : UNDEFINED_STRING - }; - } - - private getGuardProperty(name: string): PropertyDeclarationStructure { - return { - kind: StructureKind.Property, - scope: Scope.Private, - name: `__${lowerFirst(name)}`, - type: 'string', - hasExclamationToken: true - }; - } - - private getToDtoPropertyInitializer(property: IObjectPropertyModel): string { - const modelProperty = `model.${property.name}`; - - switch (property.kind) { - case PropertyKind.Date: - if (property.isCollection) { - return `${modelProperty} ? ${modelProperty}.map(toDateOut) : ${UNDEFINED_STRING}`; - } - - return `toDateOut(${modelProperty})`; - - case PropertyKind.Guid: - if (property.isCollection) { - return `${modelProperty} ? ${modelProperty}.map(x => x.toString()) : ${UNDEFINED_STRING}`; - } - - return ( - `${modelProperty} ? ${modelProperty}.toString()` + - ` : ${property.isNullable ? NULL_STRING : `${property.type}.empty.toString()`}` - ); - - case PropertyKind.Identity: - if (property.isCollection) { - return `${modelProperty} ? ${modelProperty}.map(x => ${property.type}.${TO_DTO_METHOD}(x.id)) : ${UNDEFINED_STRING}`; - } - - return `${modelProperty} ? ${property.type}.${TO_DTO_METHOD}(${modelProperty}.id) : ${UNDEFINED_STRING}`; - - case PropertyKind.Object: - if (property.isCollection) { - return `${modelProperty} ? ${modelProperty}.map(x => ${ - property.dtoTypeAlias ?? property.type - }.${TO_DTO_METHOD}(x)) : ${UNDEFINED_STRING}`; - } - - return `${modelProperty} ? ${property.type}.${TO_DTO_METHOD}(${modelProperty}) : ${UNDEFINED_STRING}`; - } - - return modelProperty; - } - - private getFromDtoPropertyInitializer(property: IObjectPropertyModel): string { - const dtoProperty = `dto.${property.name}`; - - switch (property.kind) { - case PropertyKind.Date: - if (property.isCollection) { - return `${dtoProperty} ? ${dtoProperty}.map(toDateIn) : ${ARRAY_STRING}`; - } - - return `toDateIn(${dtoProperty})`; - - case PropertyKind.Guid: - if (property.isCollection) { - return `${dtoProperty} ? ${dtoProperty}.map(x => new ${property.type}(x)) : ${ARRAY_STRING}`; - } - - if (property.isNullable) { - return `${dtoProperty} ? new ${property.type}(${dtoProperty}) : ${NULL_STRING}`; - } - - return `new ${property.type}(${dtoProperty})`; - - case PropertyKind.Identity: - if (property.isCollection) { - return `${dtoProperty} ? ${dtoProperty}.map(x => new ${property.type}(x.id)) : ${ARRAY_STRING}`; - } - - return `${dtoProperty} ? new ${property.type}(${dtoProperty}.id) : ${UNDEFINED_STRING}`; - - case PropertyKind.Object: - if (property.isCollection) { - return `${dtoProperty} ? ${dtoProperty}.map(x => ${ - property.dtoTypeAlias ?? property.type - }.${FROM_DTO_METHOD}(x)) : ${ARRAY_STRING}`; - } - - return `${dtoProperty} ? ${property.type}.${FROM_DTO_METHOD}(${dtoProperty}) : ${UNDEFINED_STRING}`; - - default: - if (property.isCollection) { - return `${dtoProperty} ? ${dtoProperty} : ${ARRAY_STRING}`; - } - - return dtoProperty; - } - } - - private printCombinedProprs( - model: IObjectModel | undefined, - writer: CodeBlockWriter, - objectsCollection: IObjectModel[], - printFunction: (p: IObjectPropertyModel) => void - ): void { - if (!model) { - return; - } - model.combineTypes.forEach((y) => { - (objectsCollection.find((x) => x.name === y)?.properties ?? []).forEach((p) => printFunction(p)); - this.printCombinedProprs( - objectsCollection.find((x) => x.name === y), - writer, - objectsCollection, - printFunction - ); - }); - } } diff --git a/src/generators/models-generator/IdentitiesGenerator.ts b/src/generators/models-generator/IdentitiesGenerator.ts new file mode 100644 index 0000000..7075cd0 --- /dev/null +++ b/src/generators/models-generator/IdentitiesGenerator.ts @@ -0,0 +1,65 @@ +import { + ClassDeclarationStructure, + ConstructorDeclarationStructure, + OptionalKind, + ParameterDeclarationStructure, + Scope, + StatementStructures, + StructureKind +} from 'ts-morph'; + +import { IIdentityModel } from '../../models/IdentityModel'; +import { InterfaceModel } from '../../models/InterfaceModel'; +import { IOptions } from '../../options'; +import { TO_DTO_METHOD } from '../ModelsGenerator'; +import { getGuardProperty } from '../utils/propertyDeclaration'; +import { TypeSerializer } from '../utils/TypeSerializer'; + +export class IdentitiesGenerator { + constructor(private settings: IOptions) {} + public getIdentities(identities: IIdentityModel[], interfaces: InterfaceModel[]): StatementStructures[] { + return identities.map( + (z): ClassDeclarationStructure => ({ + kind: StructureKind.Class, + isExported: true, + name: z.name, + ctors: [ + { + parameters: [ + { + name: z.property.name, + hasQuestionToken: true, + type: TypeSerializer.fromTypeName( + z.property.type !== z.property.dtoType + ? `${z.property.type} | ${z.property.dtoType}` + : `${z.property.type}` + ).toString() + } + ] as OptionalKind[], + statements: this.settings.unstrictId + ? `this.${z.property.name} = ${z.property.name} ?? '';` + : `this.${z.property.name} = new ${z.property.type}(${z.property.name});` + } + ] as OptionalKind[], + properties: [{ scope: Scope.Public, name: z.property.name, type: z.property.type }, getGuardProperty(z.name)], + methods: [ + { + scope: Scope.Public, + isStatic: true, + name: TO_DTO_METHOD, + parameters: [{ name: z.property.name, type: z.property.type }], + // TODO: would find first identity interface everytime + returnType: interfaces.find( + (i) => + i.properties.length === 1 && + i.properties.every((x) => x.dtoType === z.property.dtoType && x.name === z.property.name) + )?.name, + statements: this.settings.unstrictId + ? `return { ${z.property.name}: ${z.property.name} };` + : `return { ${z.property.name}: ${z.property.name}.toString() };` + } + ] + }) + ); + } +} diff --git a/src/generators/models-generator/InterfacesGenerator.ts b/src/generators/models-generator/InterfacesGenerator.ts index f1eb257..f547b3b 100644 --- a/src/generators/models-generator/InterfacesGenerator.ts +++ b/src/generators/models-generator/InterfacesGenerator.ts @@ -6,58 +6,70 @@ import { TypeAliasDeclarationStructure } from 'ts-morph'; -import { IInterfaceModel, IInterfacePropertyModel, IInterfaceUnionModel } from '../../models/InterfaceModel'; +import { IExtendedInterfaceModel, IInterfaceModel, IInterfacePropertyModel, InterfaceModel } from '../../models/InterfaceModel'; +import { IUnionModel } from '../../models/UnionModel'; +import { getInterfaceName, getUnionName } from '../../utils'; import { TypeSerializer } from '../utils/TypeSerializer'; -import { getInterfaceName } from '../../utils'; + +type InterfacesSructures = InterfaceDeclarationStructure | TypeAliasDeclarationStructure; export class InterfacesGenerator { - public getCodeStructure(interfaces: IInterfaceModel[]): (InterfaceDeclarationStructure | TypeAliasDeclarationStructure)[] { - const baseInterfaces: InterfaceDeclarationStructure[] = []; - const types: (InterfaceDeclarationStructure | TypeAliasDeclarationStructure)[] = interfaces.map((z) => { - if (z.combineInterfaces.length) { - const name = z.name + 'BaseInterface'; - baseInterfaces.push({ - kind: StructureKind.Interface, - name: name, - isExported: false, - properties: z.properties.map((x) => this.getInterfaceProperty(x)) - }); - return { - kind: StructureKind.TypeAlias, - type: name + ' & ' + z.combineInterfaces.join(' & '), - name: z.name, - isExported: true - }; - } else { - return { - kind: StructureKind.Interface, - name: z.name, - isExported: true, - properties: z.properties.map((x) => this.getInterfaceProperty(x)) - }; - } + public getCodeStructure(interfaces: InterfaceModel[], unions: IUnionModel[]): InterfacesSructures[] { + return [ + ...unions.reduce((acc, curr) => { + this.generateUnionInterface(acc, curr); + return acc; + }, [] as InterfacesSructures[]), + ...interfaces.reduce((acc, curr) => { + if (this.isIExtendedInterfaceModel(curr)) { + this.generateExtendednterface(acc, curr); + } else { + this.generateCommonInterface(acc, curr); + } + return acc; + }, [] as InterfacesSructures[]) + ]; + } + + private generateExtendednterface(acc: InterfacesSructures[], curr: IExtendedInterfaceModel): void { + const name = curr.name + 'BaseInterface'; + acc.push({ + kind: StructureKind.Interface, + name: name, + isExported: false, + properties: curr.properties.map((x) => this.getInterfaceProperty(x)) + }); + acc.push({ + kind: StructureKind.TypeAlias, + type: name + ' & ' + curr.extendingInterfaces.join(' & '), + name: curr.name, + isExported: true }); - return [...baseInterfaces, ...types]; } - public getCodeUnionsStructure(interfaces: IInterfaceUnionModel[]): TypeAliasDeclarationStructure[] { - const classUnion: TypeAliasDeclarationStructure[] = interfaces.map((z) => { - return { - kind: StructureKind.TypeAlias, - type: z.parentInterface + '|' + z.unionInterfaces.join(' | '), - name: z.name, - isExported: true - }; + private generateUnionInterface(acc: InterfacesSructures[], curr: IUnionModel): void { + acc.push({ + kind: StructureKind.TypeAlias, + type: curr.name + '|' + curr.unionInterfaces.join(' | '), + name: getUnionName(curr.name), + isExported: true + }); + + acc.push({ + kind: StructureKind.TypeAlias, + type: getInterfaceName(curr.name) + '|' + curr.unionInterfaces.map((x) => getInterfaceName(x)).join(' | '), + name: getInterfaceName(getUnionName(curr.name)), + isExported: true }); - const interfacesUnion: TypeAliasDeclarationStructure[] = interfaces.map((z) => { - return { - kind: StructureKind.TypeAlias, - type: getInterfaceName(z.parentInterface) + '|' + z.unionInterfaces.map((x) => getInterfaceName(x)).join(' | '), - name: getInterfaceName(z.name), - isExported: true - }; + } + + private generateCommonInterface(acc: InterfacesSructures[], curr: IInterfaceModel): void { + acc.push({ + kind: StructureKind.Interface, + name: curr.name, + isExported: true, + properties: curr.properties.map((x) => this.getInterfaceProperty(x)) }); - return [...classUnion, ...interfacesUnion]; } protected getInterfaceProperty(model: IInterfacePropertyModel): OptionalKind { @@ -70,4 +82,8 @@ export class InterfacesGenerator { protected getInterfacePropertyType(model: IInterfacePropertyModel): string { return TypeSerializer.fromInterfaceProperty(model).toString(); } + + private isIExtendedInterfaceModel(objects: InterfaceModel): objects is IExtendedInterfaceModel { + return Boolean((objects as IExtendedInterfaceModel)?.extendingInterfaces); + } } diff --git a/src/generators/models-generator/ObjectGenerator.ts b/src/generators/models-generator/ObjectGenerator.ts new file mode 100644 index 0000000..a25ec89 --- /dev/null +++ b/src/generators/models-generator/ObjectGenerator.ts @@ -0,0 +1,218 @@ +import { ClassDeclarationStructure, CodeBlockWriter, PropertyDeclarationStructure, Scope, StructureKind } from 'ts-morph'; + +import { PropertyKind } from '../../models/kinds/PropertyKind'; +import { IExtendedObjectModel, IObjectPropertyModel, ObjectModel } from '../../models/ObjectModel'; +import { FROM_DTO_METHOD, TO_DTO_METHOD } from '../ModelsGenerator'; +import { ARRAY_STRING, NULL_STRING, UNDEFINED_STRING } from '../utils/consts'; +import { getGuardProperty } from '../utils/propertyDeclaration'; +import { TypeSerializer } from '../utils/TypeSerializer'; +import { getClassName } from '../../utils'; + +export class ObjectGenerator { + public getObjects(objects: ObjectModel[]): ClassDeclarationStructure[] { + return objects.map((z) => ({ + kind: StructureKind.Class, + isExported: true, + name: z.name, + properties: this.getObjectProperties(z, objects), + methods: [ + { + scope: Scope.Public, + isStatic: true, + name: TO_DTO_METHOD, + parameters: [{ name: 'model', type: `Partial<${z.name}>` }], + returnType: z.dtoType, + statements: (x) => { + x.writeLine('return {'); + z.properties.forEach((p) => + x.withIndentationLevel(3, () => x.writeLine(`${p.name}: ${this.getToDtoPropertyInitializer(p)},`)) + ); + this.printCombinedProprs(z, x, objects, (p) => + x.withIndentationLevel(3, () => x.writeLine(`${p.name}: ${this.getToDtoPropertyInitializer(p)},`)) + ); + x.writeLine('};'); + } + }, + { + scope: Scope.Public, + isStatic: true, + name: FROM_DTO_METHOD, + parameters: [{ name: 'dto', type: z.dtoType }], + returnType: z.name, + statements: (x) => { + x.writeLine(`const model = new ${z.name}();`); + z.properties.forEach((p) => + x.withIndentationLevel(2, () => x.writeLine(`model.${p.name} = ${this.getFromDtoPropertyInitializer(p)};`)) + ); + this.printCombinedProprs(z, x, objects, (p) => + x.withIndentationLevel(2, () => x.writeLine(`model.${p.name} = ${this.getFromDtoPropertyInitializer(p)};`)) + ); + x.writeLine('return model;'); + } + } + ] + })); + } + + private getObjectProperties(objectModel: ObjectModel, objects: ObjectModel[]): PropertyDeclarationStructure[] { + return [ + ...this.getObjectCombinedProperties(objectModel, objects), + ...objectModel.properties.map((objectProperty) => this.getDeclarationStructure(objectProperty)), + getGuardProperty(objectModel.name) + ]; + } + + private getObjectCombinedProperties(objectModel: ObjectModel, objects: ObjectModel[]): PropertyDeclarationStructure[] { + if (this.isIExtendedObjectModel(objectModel)) { + return objectModel.extendingTypes.reduce((acc, item) => { + const model = objects.find((x) => x.name === item); + const props = (model?.properties ?? []).map((objectProperty) => this.getDeclarationStructure(objectProperty)); + if (model) { + props.push(...this.getObjectCombinedProperties(model, objects)); + } + return [...acc, ...props]; + }, [] as PropertyDeclarationStructure[]); + } + return []; + } + + private getDeclarationStructure(objectProperty: IObjectPropertyModel): PropertyDeclarationStructure { + return { + kind: StructureKind.Property, + scope: Scope.Public, + name: objectProperty.name, + type: new TypeSerializer({ + type: { name: objectProperty.type }, + isNullable: objectProperty.isNullable, + isCollection: objectProperty.isCollection + }).toString(), + initializer: objectProperty.isCollection ? ARRAY_STRING : UNDEFINED_STRING + }; + } + + private getToDtoPropertyInitializer(property: IObjectPropertyModel): string { + const modelProperty = `model.${property.name}`; + + switch (property.kind) { + case PropertyKind.Date: + if (property.isCollection) { + return `${modelProperty} ? ${modelProperty}.map(toDateOut) : ${UNDEFINED_STRING}`; + } + + return `toDateOut(${modelProperty})`; + + case PropertyKind.Guid: + if (property.isCollection) { + return `${modelProperty} ? ${modelProperty}.map(x => x.toString()) : ${UNDEFINED_STRING}`; + } + + return ( + `${modelProperty} ? ${modelProperty}.toString()` + + ` : ${property.isNullable ? NULL_STRING : `${property.type}.empty.toString()`}` + ); + + case PropertyKind.Identity: + if (property.isCollection) { + return `${modelProperty} ? ${modelProperty}.map(x => ${property.type}.${TO_DTO_METHOD}(x.id)) : ${UNDEFINED_STRING}`; + } + + return `${modelProperty} ? ${property.type}.${TO_DTO_METHOD}(${modelProperty}.id) : ${UNDEFINED_STRING}`; + + case PropertyKind.Union: + if (property.isCollection) { + return `${modelProperty} ? ${modelProperty}.map(x => ${getClassName( + property.type + )}.${TO_DTO_METHOD}(x)) : ${UNDEFINED_STRING}`; + } + + return `${modelProperty} ? ${getClassName(property.type)}.${TO_DTO_METHOD}(${modelProperty}) : ${UNDEFINED_STRING}`; + + case PropertyKind.Object: + if (property.isCollection) { + return `${modelProperty} ? ${modelProperty}.map(x => ${property.type}.${TO_DTO_METHOD}(x)) : ${UNDEFINED_STRING}`; + } + + return `${modelProperty} ? ${property.type}.${TO_DTO_METHOD}(${modelProperty}) : ${UNDEFINED_STRING}`; + } + + return modelProperty; + } + + private getFromDtoPropertyInitializer(property: IObjectPropertyModel): string { + const dtoProperty = `dto.${property.name}`; + + switch (property.kind) { + case PropertyKind.Date: + if (property.isCollection) { + return `${dtoProperty} ? ${dtoProperty}.map(toDateIn) : ${ARRAY_STRING}`; + } + + return `toDateIn(${dtoProperty})`; + + case PropertyKind.Guid: + if (property.isCollection) { + return `${dtoProperty} ? ${dtoProperty}.map(x => new ${property.type}(x)) : ${ARRAY_STRING}`; + } + + if (property.isNullable) { + return `${dtoProperty} ? new ${property.type}(${dtoProperty}) : ${NULL_STRING}`; + } + + return `new ${property.type}(${dtoProperty})`; + + case PropertyKind.Identity: + if (property.isCollection) { + return `${dtoProperty} ? ${dtoProperty}.map(x => new ${property.type}(x.id)) : ${ARRAY_STRING}`; + } + + return `${dtoProperty} ? new ${property.type}(${dtoProperty}.id) : ${UNDEFINED_STRING}`; + + case PropertyKind.Union: + if (property.isCollection) { + return `${dtoProperty} ? ${dtoProperty}.map(x => ${getClassName( + property.type + )}.${FROM_DTO_METHOD}(x)) : ${ARRAY_STRING}`; + } + + return `${dtoProperty} ? ${getClassName(property.type)}.${FROM_DTO_METHOD}(${dtoProperty}) : ${UNDEFINED_STRING}`; + + case PropertyKind.Object: + if (property.isCollection) { + return `${dtoProperty} ? ${dtoProperty}.map(x => ${property.type}.${FROM_DTO_METHOD}(x)) : ${ARRAY_STRING}`; + } + + return `${dtoProperty} ? ${property.type}.${FROM_DTO_METHOD}(${dtoProperty}) : ${UNDEFINED_STRING}`; + + default: + if (property.isCollection) { + return `${dtoProperty} ? ${dtoProperty} : ${ARRAY_STRING}`; + } + + return dtoProperty; + } + } + + private printCombinedProprs( + model: ObjectModel | undefined, + writer: CodeBlockWriter, + objectsCollection: ObjectModel[], + printFunction: (p: IObjectPropertyModel) => void + ): void { + if (!model || !this.isIExtendedObjectModel(model)) { + return; + } + model.extendingTypes.forEach((y) => { + (objectsCollection.find((x) => x.name === y)?.properties ?? []).forEach((p) => printFunction(p)); + this.printCombinedProprs( + objectsCollection.find((x) => x.name === y), + writer, + objectsCollection, + printFunction + ); + }); + } + + private isIExtendedObjectModel(objects: ObjectModel): objects is IExtendedObjectModel { + return Boolean((objects as IExtendedObjectModel)?.extendingTypes); + } +} diff --git a/src/generators/models-generator/UnionsGenerator.ts b/src/generators/models-generator/UnionsGenerator.ts new file mode 100644 index 0000000..458d27f --- /dev/null +++ b/src/generators/models-generator/UnionsGenerator.ts @@ -0,0 +1,73 @@ +import { ClassDeclarationStructure, CodeBlockWriter, Scope, StructureKind } from 'ts-morph'; + +import { IUnionModel } from '../../models/UnionModel'; +import { getInterfaceName, getClassName, getUnionName, getUnionTypesName } from '../../utils'; +import { FROM_DTO_METHOD, TO_DTO_METHOD } from '../ModelsGenerator'; + +export class UnionGenerator { + public getUnionObjects(objects: IUnionModel[]): ClassDeclarationStructure[] { + return objects.map((z) => ({ + kind: StructureKind.Class, + isExported: true, + name: getClassName(getUnionName(z.name)), + properties: [], + methods: [ + { + scope: Scope.Public, + isStatic: true, + name: FROM_DTO_METHOD, + parameters: [{ name: 'dto', type: getInterfaceName(getUnionName(z.name)) }], + returnType: getUnionName(z.name), + statements: (x) => { + z.unionInterfaces.forEach((i) => { + x.writeLine('if (this.is' + i + '(dto)){'); + x.writeLine('return ' + i + '.fromDTO(dto);'); + x.writeLine('}'); + }); + + x.writeLine('return ' + z.name + '.fromDTO(dto);'); + } + }, + + { + scope: Scope.Public, + isStatic: true, + name: TO_DTO_METHOD, + parameters: [{ name: 'model', type: getUnionName(z.name) }], + returnType: getInterfaceName(getUnionName(z.name)), + statements: (x) => { + z.unionInterfaces.forEach((i) => { + x.writeLine('if (this.is' + getInterfaceName(i) + '(model)){'); + x.writeLine('return ' + i + '.toDTO(model);'); + x.writeLine('}'); + }); + + x.writeLine('return ' + z.name + '.toDTO(model);'); + } + }, + + ...z.unionInterfaces.map((i) => ({ + scope: Scope.Private, + isStatic: true, + name: 'is' + i, + parameters: [{ name: 'dto', type: getInterfaceName(getUnionName(z.name)) }], + returnType: 'dto is ' + getInterfaceName(i), + statements: (x: CodeBlockWriter) => { + x.writeLine('return dto.type === ' + getUnionTypesName(z.name) + '.' + i + ';'); + } + })), + + ...z.unionInterfaces.map((i) => ({ + scope: Scope.Private, + isStatic: true, + name: 'is' + getInterfaceName(i), + parameters: [{ name: 'dto', type: getUnionName(z.name) }], + returnType: 'dto is ' + i, + statements: (x: CodeBlockWriter) => { + x.writeLine('return dto.type === ' + getUnionTypesName(z.name) + '.' + i + ';'); + } + })) + ] + })); + } +} diff --git a/src/generators/utils/is-defined.ts b/src/generators/utils/is-defined.ts deleted file mode 100644 index d375aee..0000000 --- a/src/generators/utils/is-defined.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function isDefined(value: T | undefined): value is T { - return value !== undefined; -} diff --git a/src/generators/utils/modelGuard.ts b/src/generators/utils/modelGuard.ts new file mode 100644 index 0000000..f574fbb --- /dev/null +++ b/src/generators/utils/modelGuard.ts @@ -0,0 +1,5 @@ +import { IExtendedInterfaceModel, InterfaceModel } from '../../models/InterfaceModel'; + +export function isIExtendedInterfaceModel(objects: InterfaceModel): objects is IExtendedInterfaceModel { + return Boolean((objects as IExtendedInterfaceModel)?.extendingInterfaces); +} diff --git a/src/generators/utils/propertyDeclaration.ts b/src/generators/utils/propertyDeclaration.ts new file mode 100644 index 0000000..a34a21e --- /dev/null +++ b/src/generators/utils/propertyDeclaration.ts @@ -0,0 +1,13 @@ +import { PropertyDeclarationStructure, Scope, StructureKind } from 'ts-morph'; + +import { lowerFirst } from '../../utils'; + +export function getGuardProperty(name: string): PropertyDeclarationStructure { + return { + kind: StructureKind.Property, + scope: Scope.Private, + name: `__${lowerFirst(name)}`, + type: 'string', + hasExclamationToken: true + }; +} diff --git a/src/models/InterfaceModel.ts b/src/models/InterfaceModel.ts index 0ac7cb0..bc52919 100644 --- a/src/models/InterfaceModel.ts +++ b/src/models/InterfaceModel.ts @@ -8,11 +8,10 @@ export interface IInterfacePropertyModel { export interface IInterfaceModel { name: string; properties: IInterfacePropertyModel[]; - combineInterfaces: string[]; } -export interface IInterfaceUnionModel { - name: string; - parentInterface: string; - unionInterfaces: string[]; +export interface IExtendedInterfaceModel extends IInterfaceModel { + extendingInterfaces: string[]; } + +export type InterfaceModel = IInterfaceModel | IExtendedInterfaceModel; diff --git a/src/models/ModelsContainer.ts b/src/models/ModelsContainer.ts index 344f4d2..8b9b38a 100644 --- a/src/models/ModelsContainer.ts +++ b/src/models/ModelsContainer.ts @@ -1,12 +1,13 @@ import { IEnumModel } from './EnumModel'; import { IIdentityModel } from './IdentityModel'; -import { IInterfaceModel, IInterfaceUnionModel } from './InterfaceModel'; -import { IObjectModel } from './ObjectModel'; +import { InterfaceModel } from './InterfaceModel'; +import { ObjectModel } from './ObjectModel'; +import { IUnionModel } from './UnionModel'; export interface IModelsContainer { enums: IEnumModel[]; - interfaces: IInterfaceModel[]; - unionInterfaces: IInterfaceUnionModel[]; + interfaces: InterfaceModel[]; + unions: IUnionModel[]; identities: IIdentityModel[]; - objects: IObjectModel[]; + objects: ObjectModel[]; } diff --git a/src/models/ObjectModel.ts b/src/models/ObjectModel.ts index 72d2972..feb0497 100644 --- a/src/models/ObjectModel.ts +++ b/src/models/ObjectModel.ts @@ -4,8 +4,6 @@ export interface IObjectPropertyModel extends IType { name: string; isNullable: boolean; isCollection: boolean; - typeAlias?: string; - dtoTypeAlias?: string; } export interface IObjectModel { @@ -13,11 +11,10 @@ export interface IObjectModel { dtoType: string; isNullable: boolean; properties: IObjectPropertyModel[]; - combineTypes: string[]; } -export interface IObjectUnionModel { - name: string; - baseTypeName: string; - unionTypesNames: string[]; +export interface IExtendedObjectModel extends IObjectModel { + extendingTypes: string[]; } + +export type ObjectModel = IObjectModel | IExtendedObjectModel; diff --git a/src/models/UnionModel.ts b/src/models/UnionModel.ts new file mode 100644 index 0000000..85023a8 --- /dev/null +++ b/src/models/UnionModel.ts @@ -0,0 +1,4 @@ +export interface IUnionModel { + name: string; + unionInterfaces: string[]; +} diff --git a/src/models/kinds/PropertyKind.ts b/src/models/kinds/PropertyKind.ts index 1ec7368..24001ee 100644 --- a/src/models/kinds/PropertyKind.ts +++ b/src/models/kinds/PropertyKind.ts @@ -4,5 +4,6 @@ export enum PropertyKind { Guid, Object, Enum, - Identity + Identity, + Union } diff --git a/src/services/ModelMappingService.ts b/src/services/ModelMappingService.ts index 5209d4c..05187cf 100644 --- a/src/services/ModelMappingService.ts +++ b/src/services/ModelMappingService.ts @@ -1,9 +1,10 @@ import { IEnumModel } from '../models/EnumModel'; import { IIdentityModel } from '../models/IdentityModel'; -import { IInterfaceModel, IInterfaceUnionModel } from '../models/InterfaceModel'; +import { IInterfaceModel, InterfaceModel } from '../models/InterfaceModel'; import { PropertyKind } from '../models/kinds/PropertyKind'; import { IModelsContainer } from '../models/ModelsContainer'; -import { IObjectModel, IObjectPropertyModel, IObjectUnionModel } from '../models/ObjectModel'; +import { IExtendedObjectModel, IObjectModel, IObjectPropertyModel, ObjectModel } from '../models/ObjectModel'; +import { IUnionModel } from '../models/UnionModel'; import { OpenAPIService } from '../swagger/OpenAPIService'; import { OpenAPITypesGuard } from '../swagger/OpenAPITypesGuard'; import { IOpenAPI3Reference } from '../swagger/v3/reference'; @@ -13,14 +14,14 @@ import { IOpenAPI3EnumSchema } from '../swagger/v3/schemas/enum-schema'; import { IOpenAPI3GuidSchema } from '../swagger/v3/schemas/guid-schema'; import { IOpenAPI3ObjectSchema } from '../swagger/v3/schemas/object-schema'; import { OpenAPI3Schema, OpenAPI3SchemaContainer, OpenAPI3SimpleSchema } from '../swagger/v3/schemas/schema'; -import { first, getInterfaceName, sortBy } from '../utils'; +import { first, getInterfaceName, getUnionName, getUnionTypesName, sortBy } from '../utils'; import { TypesService } from './TypesService'; const IGNORE_PROPERTIES = ['startRow', 'rowCount']; export class ModelMappingService { public additionalEnums: IEnumModel[] = []; - public additionalUnions: IObjectUnionModel[] = []; + public unions: IUnionModel[] = []; constructor( private readonly openAPIService: OpenAPIService, @@ -29,7 +30,7 @@ export class ModelMappingService { ) {} public toModelsContainer(schemas: OpenAPI3SchemaContainer): IModelsContainer { - const objects: IObjectModel[] = []; + const objects: ObjectModel[] = []; const enums: IEnumModel[] = []; const identities: IIdentityModel[] = []; @@ -52,6 +53,8 @@ export class ModelMappingService { isNullable: true } }); + } else if (this.typesGuard.isAllOf(schema)) { + objects.push(this.toExtendedObjectModel(name, schema)); } else { objects.push(this.toObjectModel(name, schema)); } @@ -61,7 +64,7 @@ export class ModelMappingService { return { enums: [...this.additionalEnums, ...enums].sort(sortBy((z) => z.name)), identities: identities.sort(sortBy((z) => z.name)), - unionInterfaces: this.getUnionInterfaces(this.additionalUnions).sort(sortBy((z) => z.name)), + unions: this.unions.sort(sortBy((z) => z.name)), interfaces: this.getInterfaces(identities, objects).sort(sortBy((z) => z.name)), objects: objects.sort(sortBy((z) => z.name)) }; @@ -80,34 +83,53 @@ export class ModelMappingService { }; } - private toObjectModel(name: string, schema: IOpenAPI3ObjectSchema | IOpenAPI3DiscriminatorSchema): IObjectModel { - const model: IObjectModel = { + private toExtendedObjectModel( + name: string, + schema: IOpenAPI3AllOfSchema & (IOpenAPI3ObjectSchema | IOpenAPI3DiscriminatorSchema) + ): IExtendedObjectModel { + const model: IExtendedObjectModel = { name, isNullable: schema.nullable ?? false, dtoType: getInterfaceName(name), properties: [], - combineTypes: [] + extendingTypes: [] }; - if (this.typesGuard.isAllOf(schema)) { - this.addCombineTypes(schema, model); - } + schema.allOf.forEach((x) => { + const refSchema = this.openAPIService.getRefSchema(x); + const schemaKey = this.openAPIService.getSchemaKey(x); + if (this.typesGuard.isObject(refSchema)) { + model.extendingTypes = [...model.extendingTypes, schemaKey]; + } + }); - if (this.typesGuard.isDiscriminator(schema)) { - this.addUnionTypesByDiscriminator(schema, model.name); - } + this.addUnionTypesByDiscriminator(model.name, schema); + this.addProperties(model, schema); + + return model; + } + private toObjectModel(name: string, schema: IOpenAPI3ObjectSchema | IOpenAPI3DiscriminatorSchema): IObjectModel { + const model: IObjectModel = { + name, + isNullable: schema.nullable ?? false, + dtoType: getInterfaceName(name), + properties: [] + }; + this.addUnionTypesByDiscriminator(model.name, schema); + this.addProperties(model, schema); + return model; + } + + private addProperties(model: T, schema: IOpenAPI3ObjectSchema): void { if (!schema.properties) { - return model; + return; } - Object.entries(schema.properties) .filter(([name]) => !IGNORE_PROPERTIES.includes(name)) .forEach(([name, propertySchema]) => this.addProperty(model, name, propertySchema)); model.properties = model.properties.sort(sortBy((z) => z.name)); - - return model; } private addProperty(model: IObjectModel, name: string, schema: OpenAPI3Schema): void { @@ -124,8 +146,7 @@ export class ModelMappingService { property = this.getReferenceProperty(name, schema.items); } if (this.typesGuard.isOneOf(schema.items)) { - property = this.getReferenceProperty(name, first(schema.items.oneOf)); - this.updateOneOfProperty(first(schema.items.oneOf), property); + property = this.getUnionReferenceProperty(name, first(schema.items.oneOf)); } if (property) { @@ -140,8 +161,7 @@ export class ModelMappingService { } else if (this.typesGuard.isAllOf(schema)) { property = this.getReferenceProperty(name, first(schema.allOf)); } else if (this.typesGuard.isOneOf(schema)) { - property = this.getReferenceProperty(name, first(schema.oneOf)); - this.updateOneOfProperty(first(schema.oneOf), property); + property = this.getUnionReferenceProperty(name, first(schema.oneOf)); } if (property) { @@ -150,27 +170,14 @@ export class ModelMappingService { } } - private addCombineTypes(schema: IOpenAPI3AllOfSchema, model: IObjectModel): void { - schema.allOf.forEach((x) => { - const refSchema = this.openAPIService.getRefSchema(x); - const schemaKey = this.openAPIService.getSchemaKey(x); - if (this.typesGuard.isObject(refSchema)) { - model.combineTypes = [...model.combineTypes, schemaKey]; - } - }); - } - - private updateOneOfProperty(firstObj: IOpenAPI3Reference, property: IObjectPropertyModel): void { - const schemaKey = this.openAPIService.getSchemaKey(firstObj); - property.typeAlias = schemaKey + 'Union'; - property.typeAlias = schemaKey + 'UnionClass'; - } + private addUnionTypesByDiscriminator(modelName: string, schema: IOpenAPI3ObjectSchema | IOpenAPI3DiscriminatorSchema): void { + if (!this.typesGuard.isDiscriminator(schema)) { + return; + } - private addUnionTypesByDiscriminator(schema: IOpenAPI3DiscriminatorSchema, modelName: string): void { - const unionModel: IObjectUnionModel = { - name: modelName + 'Union', - unionTypesNames: [], - baseTypeName: modelName + const unionModel: IUnionModel = { + name: modelName, + unionInterfaces: [] }; Object.keys(schema.discriminator.mapping).forEach((key) => { @@ -178,14 +185,14 @@ export class ModelMappingService { const refSchema = this.openAPIService.getRefSchema({ $ref: value }); const schemaKey = this.openAPIService.getSchemaKey({ $ref: value }); if (this.typesGuard.isObject(refSchema)) { - unionModel.unionTypesNames.push(schemaKey); + unionModel.unionInterfaces.push(schemaKey); } }); - this.additionalUnions.push(unionModel); + this.unions.push(unionModel); this.additionalEnums.push({ - name: modelName + 'UnionTypes', + name: getUnionTypesName(modelName), isNullable: false, items: Object.keys(schema.discriminator.mapping).map((key) => { const value = schema.discriminator!.mapping[key]; @@ -207,6 +214,19 @@ export class ModelMappingService { }; } + private getUnionReferenceProperty(name: string, schema: IOpenAPI3Reference): IObjectPropertyModel { + const schemaKey = this.openAPIService.getSchemaKey(schema); + + return { + kind: PropertyKind.Union, + isCollection: false, + name: name, + isNullable: false, + type: getUnionName(schemaKey), + dtoType: getInterfaceName(getUnionName(schemaKey)) + }; + } + private getReferenceProperty(name: string, schema: IOpenAPI3Reference): IObjectPropertyModel { const schemaKey = this.openAPIService.getSchemaKey(schema); const refSchema = this.openAPIService.getRefSchema(schema); @@ -234,33 +254,42 @@ export class ModelMappingService { }; } - private getInterfaces(identities: IIdentityModel[], objects: IObjectModel[]): IInterfaceModel[] { + private getInterfaces(identities: IIdentityModel[], objects: ObjectModel[]): InterfaceModel[] { const interfaces: IInterfaceModel[] = identities.map((z) => ({ name: getInterfaceName(z.name), - properties: [{ name: z.property.name, dtoType: z.property.dtoType, isCollection: false, isNullable: false }], - combineInterfaces: [] + properties: [{ name: z.property.name, dtoType: z.property.dtoType, isCollection: false, isNullable: false }] })); return interfaces.concat( - objects.map((z) => ({ - name: getInterfaceName(z.name), - combineInterfaces: z.combineTypes.map((x) => getInterfaceName(x)), - properties: z.properties.map((x) => ({ - name: x.name, - dtoType: x.dtoType, - isCollection: x.isCollection, - isNullable: x.isNullable - })) - })) + objects.map((z) => { + if (this.isExtendedObjectModel(z)) { + return { + name: getInterfaceName(z.name), + extendingInterfaces: z.extendingTypes.map((x) => getInterfaceName(x)), + properties: z.properties.map((x) => ({ + name: x.name, + dtoType: x.dtoType, + isCollection: x.isCollection, + isNullable: x.isNullable + })) + }; + } else { + return { + name: getInterfaceName(z.name), + properties: z.properties.map((x) => ({ + name: x.name, + dtoType: x.dtoType, + isCollection: x.isCollection, + isNullable: x.isNullable + })) + }; + } + }) ); } - private getUnionInterfaces(objects: IObjectUnionModel[]): IInterfaceUnionModel[] { - return objects.map((z) => ({ - name: z.name, - unionInterfaces: z.unionTypesNames.map((x) => x), - parentInterface: z.baseTypeName - })); + private isExtendedObjectModel(objects: ObjectModel): objects is IExtendedObjectModel { + return Boolean((objects as IExtendedObjectModel)?.extendingTypes); } private isIdentity(schema: IOpenAPI3ObjectSchema | undefined): boolean { diff --git a/src/utils.ts b/src/utils.ts index dfb768d..4bf23aa 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -22,4 +22,16 @@ export function sortBy(fn: (value: T) => string): (a: T, b: T) => number { export function getInterfaceName(name: string): string { return `I${name}`; -} \ No newline at end of file +} + +export function getUnionName(name: string): string { + return `${name}Union`; +} + +export function getUnionTypesName(name: string): string { + return `${name}UnionTypes`; +} + +export function getClassName(name: string): string { + return `${name}Class`; +} From 008f7800191d16bd6477fa97f2c229bf67515352 Mon Sep 17 00:00:00 2001 From: Andrey Klimenko Date: Fri, 24 May 2024 01:30:07 +0200 Subject: [PATCH 5/7] update --- .../models-generator/IdentitiesGenerator.ts | 6 +- .../models-generator/InterfacesGenerator.ts | 90 ++++------------- .../models-generator/ObjectGenerator.ts | 21 ++-- .../models-generator/PropertiesGenerator.ts | 97 +++++++++++++++++++ .../models-generator/UnionsGenerator.ts | 27 +++--- src/generators/utils/modelGuard.ts | 5 - src/generators/utils/propertyDeclaration.ts | 13 --- src/services/ModelMappingService.ts | 26 ++--- src/swagger/nameService.ts | 17 ++++ src/utils.ts | 16 --- 10 files changed, 177 insertions(+), 141 deletions(-) create mode 100644 src/generators/models-generator/PropertiesGenerator.ts delete mode 100644 src/generators/utils/modelGuard.ts delete mode 100644 src/generators/utils/propertyDeclaration.ts create mode 100644 src/swagger/nameService.ts diff --git a/src/generators/models-generator/IdentitiesGenerator.ts b/src/generators/models-generator/IdentitiesGenerator.ts index 7075cd0..152ce4f 100644 --- a/src/generators/models-generator/IdentitiesGenerator.ts +++ b/src/generators/models-generator/IdentitiesGenerator.ts @@ -12,10 +12,12 @@ import { IIdentityModel } from '../../models/IdentityModel'; import { InterfaceModel } from '../../models/InterfaceModel'; import { IOptions } from '../../options'; import { TO_DTO_METHOD } from '../ModelsGenerator'; -import { getGuardProperty } from '../utils/propertyDeclaration'; +import { PropertiesGenerator } from './PropertiesGenerator'; import { TypeSerializer } from '../utils/TypeSerializer'; export class IdentitiesGenerator { + private propertiesGenerator = new PropertiesGenerator(); + constructor(private settings: IOptions) {} public getIdentities(identities: IIdentityModel[], interfaces: InterfaceModel[]): StatementStructures[] { return identities.map( @@ -41,7 +43,7 @@ export class IdentitiesGenerator { : `this.${z.property.name} = new ${z.property.type}(${z.property.name});` } ] as OptionalKind[], - properties: [{ scope: Scope.Public, name: z.property.name, type: z.property.type }, getGuardProperty(z.name)], + properties: [this.propertiesGenerator.getObjectProperty(z.property), this.propertiesGenerator.getGuardProperty(z.name)], methods: [ { scope: Scope.Public, diff --git a/src/generators/models-generator/InterfacesGenerator.ts b/src/generators/models-generator/InterfacesGenerator.ts index f547b3b..f779135 100644 --- a/src/generators/models-generator/InterfacesGenerator.ts +++ b/src/generators/models-generator/InterfacesGenerator.ts @@ -1,86 +1,30 @@ -import { - InterfaceDeclarationStructure, - OptionalKind, - PropertySignatureStructure, - StructureKind, - TypeAliasDeclarationStructure -} from 'ts-morph'; +import { InterfaceDeclarationStructure, TypeAliasDeclarationStructure } from 'ts-morph'; -import { IExtendedInterfaceModel, IInterfaceModel, IInterfacePropertyModel, InterfaceModel } from '../../models/InterfaceModel'; +import { IExtendedInterfaceModel, InterfaceModel } from '../../models/InterfaceModel'; import { IUnionModel } from '../../models/UnionModel'; -import { getInterfaceName, getUnionName } from '../../utils'; -import { TypeSerializer } from '../utils/TypeSerializer'; +import { PropertiesGenerator } from './PropertiesGenerator'; -type InterfacesSructures = InterfaceDeclarationStructure | TypeAliasDeclarationStructure; +type InterfacesStructures = InterfaceDeclarationStructure | TypeAliasDeclarationStructure; export class InterfacesGenerator { - public getCodeStructure(interfaces: InterfaceModel[], unions: IUnionModel[]): InterfacesSructures[] { - return [ - ...unions.reduce((acc, curr) => { - this.generateUnionInterface(acc, curr); - return acc; - }, [] as InterfacesSructures[]), - ...interfaces.reduce((acc, curr) => { - if (this.isIExtendedInterfaceModel(curr)) { - this.generateExtendednterface(acc, curr); - } else { - this.generateCommonInterface(acc, curr); - } - return acc; - }, [] as InterfacesSructures[]) - ]; - } - - private generateExtendednterface(acc: InterfacesSructures[], curr: IExtendedInterfaceModel): void { - const name = curr.name + 'BaseInterface'; - acc.push({ - kind: StructureKind.Interface, - name: name, - isExported: false, - properties: curr.properties.map((x) => this.getInterfaceProperty(x)) - }); - acc.push({ - kind: StructureKind.TypeAlias, - type: name + ' & ' + curr.extendingInterfaces.join(' & '), - name: curr.name, - isExported: true - }); - } + private propertiesGenerator = new PropertiesGenerator(); - private generateUnionInterface(acc: InterfacesSructures[], curr: IUnionModel): void { - acc.push({ - kind: StructureKind.TypeAlias, - type: curr.name + '|' + curr.unionInterfaces.join(' | '), - name: getUnionName(curr.name), - isExported: true - }); + public getCodeStructure(interfaces: InterfaceModel[], unions: IUnionModel[]): InterfacesStructures[] { + const unionStructures = unions.flatMap((curr) => this.propertiesGenerator.getUnionTypeAliases(curr)); + const interfaceStructures = interfaces.flatMap((curr) => + this.isIExtendedInterfaceModel(curr) ? this.generateExtendedInterface(curr) : this.propertiesGenerator.getInterface(curr) + ); - acc.push({ - kind: StructureKind.TypeAlias, - type: getInterfaceName(curr.name) + '|' + curr.unionInterfaces.map((x) => getInterfaceName(x)).join(' | '), - name: getInterfaceName(getUnionName(curr.name)), - isExported: true - }); + return [...unionStructures, ...interfaceStructures]; } - private generateCommonInterface(acc: InterfacesSructures[], curr: IInterfaceModel): void { - acc.push({ - kind: StructureKind.Interface, - name: curr.name, - isExported: true, - properties: curr.properties.map((x) => this.getInterfaceProperty(x)) - }); - } - - protected getInterfaceProperty(model: IInterfacePropertyModel): OptionalKind { - return { - name: model.name, - type: this.getInterfacePropertyType(model) - }; - } + private generateExtendedInterface(curr: IExtendedInterfaceModel): InterfacesStructures[] { + const baseInterfaceName = `${curr.name}BaseInterface`; - protected getInterfacePropertyType(model: IInterfacePropertyModel): string { - return TypeSerializer.fromInterfaceProperty(model).toString(); + return [ + this.propertiesGenerator.getExtendedInterfaceTypeAlias(baseInterfaceName, curr), + this.propertiesGenerator.getExtendedInterface(baseInterfaceName, curr) + ]; } private isIExtendedInterfaceModel(objects: InterfaceModel): objects is IExtendedInterfaceModel { diff --git a/src/generators/models-generator/ObjectGenerator.ts b/src/generators/models-generator/ObjectGenerator.ts index a25ec89..81ec6b2 100644 --- a/src/generators/models-generator/ObjectGenerator.ts +++ b/src/generators/models-generator/ObjectGenerator.ts @@ -2,13 +2,16 @@ import { ClassDeclarationStructure, CodeBlockWriter, PropertyDeclarationStructur import { PropertyKind } from '../../models/kinds/PropertyKind'; import { IExtendedObjectModel, IObjectPropertyModel, ObjectModel } from '../../models/ObjectModel'; +import { NameService } from '../../swagger/nameService'; import { FROM_DTO_METHOD, TO_DTO_METHOD } from '../ModelsGenerator'; import { ARRAY_STRING, NULL_STRING, UNDEFINED_STRING } from '../utils/consts'; -import { getGuardProperty } from '../utils/propertyDeclaration'; +import { PropertiesGenerator } from './PropertiesGenerator'; import { TypeSerializer } from '../utils/TypeSerializer'; -import { getClassName } from '../../utils'; export class ObjectGenerator { + private nameService = new NameService(); + private propertiesGenerator = new PropertiesGenerator(); + public getObjects(objects: ObjectModel[]): ClassDeclarationStructure[] { return objects.map((z) => ({ kind: StructureKind.Class, @@ -58,7 +61,7 @@ export class ObjectGenerator { return [ ...this.getObjectCombinedProperties(objectModel, objects), ...objectModel.properties.map((objectProperty) => this.getDeclarationStructure(objectProperty)), - getGuardProperty(objectModel.name) + this.propertiesGenerator.getGuardProperty(objectModel.name) ]; } @@ -120,12 +123,14 @@ export class ObjectGenerator { case PropertyKind.Union: if (property.isCollection) { - return `${modelProperty} ? ${modelProperty}.map(x => ${getClassName( + return `${modelProperty} ? ${modelProperty}.map(x => ${this.nameService.getClassName( property.type )}.${TO_DTO_METHOD}(x)) : ${UNDEFINED_STRING}`; } - return `${modelProperty} ? ${getClassName(property.type)}.${TO_DTO_METHOD}(${modelProperty}) : ${UNDEFINED_STRING}`; + return `${modelProperty} ? ${this.nameService.getClassName( + property.type + )}.${TO_DTO_METHOD}(${modelProperty}) : ${UNDEFINED_STRING}`; case PropertyKind.Object: if (property.isCollection) { @@ -169,12 +174,14 @@ export class ObjectGenerator { case PropertyKind.Union: if (property.isCollection) { - return `${dtoProperty} ? ${dtoProperty}.map(x => ${getClassName( + return `${dtoProperty} ? ${dtoProperty}.map(x => ${this.nameService.getClassName( property.type )}.${FROM_DTO_METHOD}(x)) : ${ARRAY_STRING}`; } - return `${dtoProperty} ? ${getClassName(property.type)}.${FROM_DTO_METHOD}(${dtoProperty}) : ${UNDEFINED_STRING}`; + return `${dtoProperty} ? ${this.nameService.getClassName( + property.type + )}.${FROM_DTO_METHOD}(${dtoProperty}) : ${UNDEFINED_STRING}`; case PropertyKind.Object: if (property.isCollection) { diff --git a/src/generators/models-generator/PropertiesGenerator.ts b/src/generators/models-generator/PropertiesGenerator.ts new file mode 100644 index 0000000..71d3038 --- /dev/null +++ b/src/generators/models-generator/PropertiesGenerator.ts @@ -0,0 +1,97 @@ +import { + InterfaceDeclarationStructure, + OptionalKind, + PropertyDeclarationStructure, + PropertySignatureStructure, + Scope, + StructureKind, + TypeAliasDeclarationStructure +} from 'ts-morph'; + +import { IExtendedInterfaceModel, IInterfaceModel, IInterfacePropertyModel } from '../../models/InterfaceModel'; +import { IObjectPropertyModel } from '../../models/ObjectModel'; +import { IUnionModel } from '../../models/UnionModel'; +import { NameService } from '../../swagger/nameService'; +import { lowerFirst } from '../../utils'; +import { TypeSerializer } from '../utils/TypeSerializer'; + +export class PropertiesGenerator { + private nameService = new NameService(); + + public getGuardProperty(name: string): PropertyDeclarationStructure { + return { + kind: StructureKind.Property, + scope: Scope.Private, + name: `__${lowerFirst(name)}`, + type: 'string', + hasExclamationToken: true + }; + } + + public getObjectProperty(property: IObjectPropertyModel): OptionalKind { + return { scope: Scope.Public, name: property.name, type: property.type }; + } + + public getUnionTypeAliases(unionModel: IUnionModel): TypeAliasDeclarationStructure[] { + const unionName = this.nameService.getUnionName(unionModel.name); + const interfaceName = this.nameService.getInterfaceName(unionModel.name); + + return [ + { + kind: StructureKind.TypeAlias, + type: `${unionModel.name} | ${unionModel.unionInterfaces.join(' | ')}`, + name: unionName, + isExported: true + }, + { + kind: StructureKind.TypeAlias, + type: `${interfaceName} | ${unionModel.unionInterfaces.map((x) => this.nameService.getInterfaceName(x)).join(' | ')}`, + name: this.nameService.getInterfaceName(unionName), + isExported: true + } + ]; + } + + public getExtendedInterfaceTypeAlias( + baseInterfaceName: string, + extendedInterfaceModel: IExtendedInterfaceModel + ): TypeAliasDeclarationStructure { + return { + kind: StructureKind.TypeAlias, + type: `${baseInterfaceName} & ${extendedInterfaceModel.extendingInterfaces.join(' & ')}`, + name: extendedInterfaceModel.name, + isExported: true + }; + } + + public getExtendedInterface(baseInterfaceName: string, extendedInterfaceModel: IExtendedInterfaceModel): InterfaceDeclarationStructure { + return { + kind: StructureKind.Interface, + name: baseInterfaceName, + isExported: false, + properties: extendedInterfaceModel.properties.map((property) => this.createInterfaceProperty(property)) + }; + } + + public getInterface(interfaceModel: IInterfaceModel): InterfaceDeclarationStructure[] { + return [ + { + kind: StructureKind.Interface, + name: interfaceModel.name, + isExported: true, + properties: interfaceModel.properties.map((property) => this.createInterfaceProperty(property)) + } + ]; + } + + private createInterfaceProperty(propertyModel: IInterfacePropertyModel): OptionalKind { + return { + name: propertyModel.name, + type: this.getInterfacePropertyType(propertyModel) + }; + } + + private getInterfacePropertyType(propertyModel: IInterfacePropertyModel): string { + return TypeSerializer.fromInterfaceProperty(propertyModel).toString(); + } +} diff --git a/src/generators/models-generator/UnionsGenerator.ts b/src/generators/models-generator/UnionsGenerator.ts index 458d27f..0188436 100644 --- a/src/generators/models-generator/UnionsGenerator.ts +++ b/src/generators/models-generator/UnionsGenerator.ts @@ -1,23 +1,24 @@ import { ClassDeclarationStructure, CodeBlockWriter, Scope, StructureKind } from 'ts-morph'; import { IUnionModel } from '../../models/UnionModel'; -import { getInterfaceName, getClassName, getUnionName, getUnionTypesName } from '../../utils'; +import { NameService } from '../../swagger/nameService'; import { FROM_DTO_METHOD, TO_DTO_METHOD } from '../ModelsGenerator'; export class UnionGenerator { + private nameService = new NameService(); public getUnionObjects(objects: IUnionModel[]): ClassDeclarationStructure[] { return objects.map((z) => ({ kind: StructureKind.Class, isExported: true, - name: getClassName(getUnionName(z.name)), + name: this.nameService.getClassName(this.nameService.getUnionName(z.name)), properties: [], methods: [ { scope: Scope.Public, isStatic: true, name: FROM_DTO_METHOD, - parameters: [{ name: 'dto', type: getInterfaceName(getUnionName(z.name)) }], - returnType: getUnionName(z.name), + parameters: [{ name: 'dto', type: this.nameService.getInterfaceName(this.nameService.getUnionName(z.name)) }], + returnType: this.nameService.getUnionName(z.name), statements: (x) => { z.unionInterfaces.forEach((i) => { x.writeLine('if (this.is' + i + '(dto)){'); @@ -33,11 +34,11 @@ export class UnionGenerator { scope: Scope.Public, isStatic: true, name: TO_DTO_METHOD, - parameters: [{ name: 'model', type: getUnionName(z.name) }], - returnType: getInterfaceName(getUnionName(z.name)), + parameters: [{ name: 'model', type: this.nameService.getUnionName(z.name) }], + returnType: this.nameService.getInterfaceName(this.nameService.getUnionName(z.name)), statements: (x) => { z.unionInterfaces.forEach((i) => { - x.writeLine('if (this.is' + getInterfaceName(i) + '(model)){'); + x.writeLine('if (this.is' + this.nameService.getInterfaceName(i) + '(model)){'); x.writeLine('return ' + i + '.toDTO(model);'); x.writeLine('}'); }); @@ -50,21 +51,21 @@ export class UnionGenerator { scope: Scope.Private, isStatic: true, name: 'is' + i, - parameters: [{ name: 'dto', type: getInterfaceName(getUnionName(z.name)) }], - returnType: 'dto is ' + getInterfaceName(i), + parameters: [{ name: 'dto', type: this.nameService.getInterfaceName(this.nameService.getUnionName(z.name)) }], + returnType: 'dto is ' + this.nameService.getInterfaceName(i), statements: (x: CodeBlockWriter) => { - x.writeLine('return dto.type === ' + getUnionTypesName(z.name) + '.' + i + ';'); + x.writeLine('return dto.type === ' + this.nameService.getUnionTypesName(z.name) + '.' + i + ';'); } })), ...z.unionInterfaces.map((i) => ({ scope: Scope.Private, isStatic: true, - name: 'is' + getInterfaceName(i), - parameters: [{ name: 'dto', type: getUnionName(z.name) }], + name: 'is' + this.nameService.getInterfaceName(i), + parameters: [{ name: 'dto', type: this.nameService.getUnionName(z.name) }], returnType: 'dto is ' + i, statements: (x: CodeBlockWriter) => { - x.writeLine('return dto.type === ' + getUnionTypesName(z.name) + '.' + i + ';'); + x.writeLine('return dto.type === ' + this.nameService.getUnionTypesName(z.name) + '.' + i + ';'); } })) ] diff --git a/src/generators/utils/modelGuard.ts b/src/generators/utils/modelGuard.ts deleted file mode 100644 index f574fbb..0000000 --- a/src/generators/utils/modelGuard.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { IExtendedInterfaceModel, InterfaceModel } from '../../models/InterfaceModel'; - -export function isIExtendedInterfaceModel(objects: InterfaceModel): objects is IExtendedInterfaceModel { - return Boolean((objects as IExtendedInterfaceModel)?.extendingInterfaces); -} diff --git a/src/generators/utils/propertyDeclaration.ts b/src/generators/utils/propertyDeclaration.ts deleted file mode 100644 index a34a21e..0000000 --- a/src/generators/utils/propertyDeclaration.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { PropertyDeclarationStructure, Scope, StructureKind } from 'ts-morph'; - -import { lowerFirst } from '../../utils'; - -export function getGuardProperty(name: string): PropertyDeclarationStructure { - return { - kind: StructureKind.Property, - scope: Scope.Private, - name: `__${lowerFirst(name)}`, - type: 'string', - hasExclamationToken: true - }; -} diff --git a/src/services/ModelMappingService.ts b/src/services/ModelMappingService.ts index 05187cf..771828e 100644 --- a/src/services/ModelMappingService.ts +++ b/src/services/ModelMappingService.ts @@ -5,6 +5,7 @@ import { PropertyKind } from '../models/kinds/PropertyKind'; import { IModelsContainer } from '../models/ModelsContainer'; import { IExtendedObjectModel, IObjectModel, IObjectPropertyModel, ObjectModel } from '../models/ObjectModel'; import { IUnionModel } from '../models/UnionModel'; +import { NameService } from '../swagger/nameService'; import { OpenAPIService } from '../swagger/OpenAPIService'; import { OpenAPITypesGuard } from '../swagger/OpenAPITypesGuard'; import { IOpenAPI3Reference } from '../swagger/v3/reference'; @@ -14,7 +15,7 @@ import { IOpenAPI3EnumSchema } from '../swagger/v3/schemas/enum-schema'; import { IOpenAPI3GuidSchema } from '../swagger/v3/schemas/guid-schema'; import { IOpenAPI3ObjectSchema } from '../swagger/v3/schemas/object-schema'; import { OpenAPI3Schema, OpenAPI3SchemaContainer, OpenAPI3SimpleSchema } from '../swagger/v3/schemas/schema'; -import { first, getInterfaceName, getUnionName, getUnionTypesName, sortBy } from '../utils'; +import { first, sortBy } from '../utils'; import { TypesService } from './TypesService'; const IGNORE_PROPERTIES = ['startRow', 'rowCount']; @@ -22,6 +23,7 @@ const IGNORE_PROPERTIES = ['startRow', 'rowCount']; export class ModelMappingService { public additionalEnums: IEnumModel[] = []; public unions: IUnionModel[] = []; + private nameService = new NameService(); constructor( private readonly openAPIService: OpenAPIService, @@ -45,7 +47,7 @@ export class ModelMappingService { identities.push({ name, isNullable: false, - dtoType: getInterfaceName(name), + dtoType: this.nameService.getInterfaceName(name), property: { ...this.typesService.getSimpleType(schema.properties['id'] as IOpenAPI3GuidSchema), isCollection: false, @@ -90,7 +92,7 @@ export class ModelMappingService { const model: IExtendedObjectModel = { name, isNullable: schema.nullable ?? false, - dtoType: getInterfaceName(name), + dtoType: this.nameService.getInterfaceName(name), properties: [], extendingTypes: [] }; @@ -113,7 +115,7 @@ export class ModelMappingService { const model: IObjectModel = { name, isNullable: schema.nullable ?? false, - dtoType: getInterfaceName(name), + dtoType: this.nameService.getInterfaceName(name), properties: [] }; this.addUnionTypesByDiscriminator(model.name, schema); @@ -192,7 +194,7 @@ export class ModelMappingService { this.unions.push(unionModel); this.additionalEnums.push({ - name: getUnionTypesName(modelName), + name: this.nameService.getUnionTypesName(modelName), isNullable: false, items: Object.keys(schema.discriminator.mapping).map((key) => { const value = schema.discriminator!.mapping[key]; @@ -222,8 +224,8 @@ export class ModelMappingService { isCollection: false, name: name, isNullable: false, - type: getUnionName(schemaKey), - dtoType: getInterfaceName(getUnionName(schemaKey)) + type: this.nameService.getUnionName(schemaKey), + dtoType: this.nameService.getInterfaceName(this.nameService.getUnionName(schemaKey)) }; } @@ -250,13 +252,13 @@ export class ModelMappingService { name, isNullable: true, type: schemaKey, - dtoType: getInterfaceName(schemaKey) + dtoType: this.nameService.getInterfaceName(schemaKey) }; } private getInterfaces(identities: IIdentityModel[], objects: ObjectModel[]): InterfaceModel[] { const interfaces: IInterfaceModel[] = identities.map((z) => ({ - name: getInterfaceName(z.name), + name: this.nameService.getInterfaceName(z.name), properties: [{ name: z.property.name, dtoType: z.property.dtoType, isCollection: false, isNullable: false }] })); @@ -264,8 +266,8 @@ export class ModelMappingService { objects.map((z) => { if (this.isExtendedObjectModel(z)) { return { - name: getInterfaceName(z.name), - extendingInterfaces: z.extendingTypes.map((x) => getInterfaceName(x)), + name: this.nameService.getInterfaceName(z.name), + extendingInterfaces: z.extendingTypes.map((x) => this.nameService.getInterfaceName(x)), properties: z.properties.map((x) => ({ name: x.name, dtoType: x.dtoType, @@ -275,7 +277,7 @@ export class ModelMappingService { }; } else { return { - name: getInterfaceName(z.name), + name: this.nameService.getInterfaceName(z.name), properties: z.properties.map((x) => ({ name: x.name, dtoType: x.dtoType, diff --git a/src/swagger/nameService.ts b/src/swagger/nameService.ts new file mode 100644 index 0000000..da0532d --- /dev/null +++ b/src/swagger/nameService.ts @@ -0,0 +1,17 @@ +export class NameService { + public getInterfaceName(name: string): string { + return `I${name}`; + } + + public getUnionName(name: string): string { + return `${name}Union`; + } + + public getUnionTypesName(name: string): string { + return `${name}UnionTypes`; + } + + public getClassName(name: string): string { + return `${name}Class`; + } +} diff --git a/src/utils.ts b/src/utils.ts index 4bf23aa..a225dec 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -19,19 +19,3 @@ export function last(array: T[]): T { export function sortBy(fn: (value: T) => string): (a: T, b: T) => number { return (a: T, b: T) => (fn(a) || '').localeCompare(fn(b) || ''); } - -export function getInterfaceName(name: string): string { - return `I${name}`; -} - -export function getUnionName(name: string): string { - return `${name}Union`; -} - -export function getUnionTypesName(name: string): string { - return `${name}UnionTypes`; -} - -export function getClassName(name: string): string { - return `${name}Class`; -} From 6559d930c7d1cdcf123172e31aa4846838b27815 Mon Sep 17 00:00:00 2001 From: Andrey Klimenko Date: Fri, 24 May 2024 01:53:56 +0200 Subject: [PATCH 6/7] fix e2e --- src/generators/models-generator/IdentitiesGenerator.ts | 2 +- src/generators/models-generator/InterfacesGenerator.ts | 4 ++-- src/generators/models-generator/ObjectGenerator.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/generators/models-generator/IdentitiesGenerator.ts b/src/generators/models-generator/IdentitiesGenerator.ts index 152ce4f..e1a3aa8 100644 --- a/src/generators/models-generator/IdentitiesGenerator.ts +++ b/src/generators/models-generator/IdentitiesGenerator.ts @@ -12,8 +12,8 @@ import { IIdentityModel } from '../../models/IdentityModel'; import { InterfaceModel } from '../../models/InterfaceModel'; import { IOptions } from '../../options'; import { TO_DTO_METHOD } from '../ModelsGenerator'; -import { PropertiesGenerator } from './PropertiesGenerator'; import { TypeSerializer } from '../utils/TypeSerializer'; +import { PropertiesGenerator } from './PropertiesGenerator'; export class IdentitiesGenerator { private propertiesGenerator = new PropertiesGenerator(); diff --git a/src/generators/models-generator/InterfacesGenerator.ts b/src/generators/models-generator/InterfacesGenerator.ts index f779135..1996f49 100644 --- a/src/generators/models-generator/InterfacesGenerator.ts +++ b/src/generators/models-generator/InterfacesGenerator.ts @@ -22,8 +22,8 @@ export class InterfacesGenerator { const baseInterfaceName = `${curr.name}BaseInterface`; return [ - this.propertiesGenerator.getExtendedInterfaceTypeAlias(baseInterfaceName, curr), - this.propertiesGenerator.getExtendedInterface(baseInterfaceName, curr) + this.propertiesGenerator.getExtendedInterface(baseInterfaceName, curr), + this.propertiesGenerator.getExtendedInterfaceTypeAlias(baseInterfaceName, curr) ]; } diff --git a/src/generators/models-generator/ObjectGenerator.ts b/src/generators/models-generator/ObjectGenerator.ts index 81ec6b2..78f2a51 100644 --- a/src/generators/models-generator/ObjectGenerator.ts +++ b/src/generators/models-generator/ObjectGenerator.ts @@ -5,8 +5,8 @@ import { IExtendedObjectModel, IObjectPropertyModel, ObjectModel } from '../../m import { NameService } from '../../swagger/nameService'; import { FROM_DTO_METHOD, TO_DTO_METHOD } from '../ModelsGenerator'; import { ARRAY_STRING, NULL_STRING, UNDEFINED_STRING } from '../utils/consts'; -import { PropertiesGenerator } from './PropertiesGenerator'; import { TypeSerializer } from '../utils/TypeSerializer'; +import { PropertiesGenerator } from './PropertiesGenerator'; export class ObjectGenerator { private nameService = new NameService(); From e5f5367def7870076a363c5215f306ba8501b321 Mon Sep 17 00:00:00 2001 From: Andrey Klimenko Date: Fri, 24 May 2024 09:06:37 +0200 Subject: [PATCH 7/7] update providing --- .../services/ModelMappingService.spec.ts | 4 ++- src/generators/ModelsGenerator.ts | 13 ++++---- .../models-generator/IdentitiesGenerator.ts | 6 ++-- .../models-generator/InterfacesGenerator.ts | 2 +- .../models-generator/ObjectGenerator.ts | 8 +++-- .../models-generator/PropertiesGenerator.ts | 5 ++- .../models-generator/UnionsGenerator.ts | 5 +-- src/gengen/GenGenCodeGenInjector.ts | 32 +++++++++++++++++-- src/services/ModelMappingService.ts | 6 ++-- .../NameService.ts} | 0 10 files changed, 57 insertions(+), 24 deletions(-) rename src/{swagger/nameService.ts => services/NameService.ts} (100%) diff --git a/__tests__/services/ModelMappingService.spec.ts b/__tests__/services/ModelMappingService.spec.ts index bd98ccd..deea4a2 100644 --- a/__tests__/services/ModelMappingService.spec.ts +++ b/__tests__/services/ModelMappingService.spec.ts @@ -1,6 +1,7 @@ import { MockOpenAPIService } from '../../__mocks__/MockOpenAPIService'; import { defaultOptions } from '../../src/options'; import { ModelMappingService } from '../../src/services/ModelMappingService'; +import { NameService } from '../../src/services/NameService'; import { TypesService } from '../../src/services/TypesService'; import { OpenAPITypesGuard } from '../../src/swagger/OpenAPITypesGuard'; import { OpenAPI3SchemaContainer } from '../../src/swagger/v3/schemas/schema'; @@ -11,8 +12,9 @@ describe('ModelMappingService tests', () => { beforeEach(() => { const guard = new OpenAPITypesGuard(); + const nameService = new NameService(); const openAPIService = new MockOpenAPIService(guard); - service = new ModelMappingService(openAPIService, guard, new TypesService(guard, defaultOptions)); + service = new ModelMappingService(openAPIService, guard, new TypesService(guard, defaultOptions), nameService); }); describe('toModelsContainer', () => { diff --git a/src/generators/ModelsGenerator.ts b/src/generators/ModelsGenerator.ts index 770b3d8..4415043 100644 --- a/src/generators/ModelsGenerator.ts +++ b/src/generators/ModelsGenerator.ts @@ -14,14 +14,15 @@ export const TO_DTO_METHOD = 'toDTO'; export const FROM_DTO_METHOD = 'fromDTO'; export class ModelsGenerator { - private interfaceGenerator = new InterfacesGenerator(); - private unionGenerator = new UnionGenerator(); - private identitiesGenerator = new IdentitiesGenerator(this.settings); - private objectGenerator = new ObjectGenerator(); - private pathBuilder = new PathBuilder(); - constructor(private settings: IOptions) {} + constructor( + private settings: IOptions, + private readonly interfaceGenerator: InterfacesGenerator, + private readonly unionGenerator: UnionGenerator, + private readonly identitiesGenerator: IdentitiesGenerator, + private readonly objectGenerator: ObjectGenerator + ) {} public getModelsCodeStructure(models: IModelsContainer): StatementStructures[] { return [ diff --git a/src/generators/models-generator/IdentitiesGenerator.ts b/src/generators/models-generator/IdentitiesGenerator.ts index e1a3aa8..b497eb9 100644 --- a/src/generators/models-generator/IdentitiesGenerator.ts +++ b/src/generators/models-generator/IdentitiesGenerator.ts @@ -16,9 +16,11 @@ import { TypeSerializer } from '../utils/TypeSerializer'; import { PropertiesGenerator } from './PropertiesGenerator'; export class IdentitiesGenerator { - private propertiesGenerator = new PropertiesGenerator(); + constructor( + private readonly propertiesGenerator: PropertiesGenerator, + private readonly settings: IOptions + ) {} - constructor(private settings: IOptions) {} public getIdentities(identities: IIdentityModel[], interfaces: InterfaceModel[]): StatementStructures[] { return identities.map( (z): ClassDeclarationStructure => ({ diff --git a/src/generators/models-generator/InterfacesGenerator.ts b/src/generators/models-generator/InterfacesGenerator.ts index 1996f49..d54edd2 100644 --- a/src/generators/models-generator/InterfacesGenerator.ts +++ b/src/generators/models-generator/InterfacesGenerator.ts @@ -7,7 +7,7 @@ import { PropertiesGenerator } from './PropertiesGenerator'; type InterfacesStructures = InterfaceDeclarationStructure | TypeAliasDeclarationStructure; export class InterfacesGenerator { - private propertiesGenerator = new PropertiesGenerator(); + constructor(private readonly propertiesGenerator: PropertiesGenerator) {} public getCodeStructure(interfaces: InterfaceModel[], unions: IUnionModel[]): InterfacesStructures[] { const unionStructures = unions.flatMap((curr) => this.propertiesGenerator.getUnionTypeAliases(curr)); diff --git a/src/generators/models-generator/ObjectGenerator.ts b/src/generators/models-generator/ObjectGenerator.ts index 78f2a51..bb334a0 100644 --- a/src/generators/models-generator/ObjectGenerator.ts +++ b/src/generators/models-generator/ObjectGenerator.ts @@ -2,15 +2,17 @@ import { ClassDeclarationStructure, CodeBlockWriter, PropertyDeclarationStructur import { PropertyKind } from '../../models/kinds/PropertyKind'; import { IExtendedObjectModel, IObjectPropertyModel, ObjectModel } from '../../models/ObjectModel'; -import { NameService } from '../../swagger/nameService'; +import { NameService } from '../../services/NameService'; import { FROM_DTO_METHOD, TO_DTO_METHOD } from '../ModelsGenerator'; import { ARRAY_STRING, NULL_STRING, UNDEFINED_STRING } from '../utils/consts'; import { TypeSerializer } from '../utils/TypeSerializer'; import { PropertiesGenerator } from './PropertiesGenerator'; export class ObjectGenerator { - private nameService = new NameService(); - private propertiesGenerator = new PropertiesGenerator(); + constructor( + private readonly nameService: NameService, + private readonly propertiesGenerator: PropertiesGenerator + ) {} public getObjects(objects: ObjectModel[]): ClassDeclarationStructure[] { return objects.map((z) => ({ diff --git a/src/generators/models-generator/PropertiesGenerator.ts b/src/generators/models-generator/PropertiesGenerator.ts index 71d3038..ce43b0c 100644 --- a/src/generators/models-generator/PropertiesGenerator.ts +++ b/src/generators/models-generator/PropertiesGenerator.ts @@ -11,13 +11,12 @@ import { import { IExtendedInterfaceModel, IInterfaceModel, IInterfacePropertyModel } from '../../models/InterfaceModel'; import { IObjectPropertyModel } from '../../models/ObjectModel'; import { IUnionModel } from '../../models/UnionModel'; -import { NameService } from '../../swagger/nameService'; +import { NameService } from '../../services/NameService'; import { lowerFirst } from '../../utils'; import { TypeSerializer } from '../utils/TypeSerializer'; export class PropertiesGenerator { - private nameService = new NameService(); - + constructor(private readonly nameService: NameService) {} public getGuardProperty(name: string): PropertyDeclarationStructure { return { kind: StructureKind.Property, diff --git a/src/generators/models-generator/UnionsGenerator.ts b/src/generators/models-generator/UnionsGenerator.ts index 0188436..fa268f5 100644 --- a/src/generators/models-generator/UnionsGenerator.ts +++ b/src/generators/models-generator/UnionsGenerator.ts @@ -1,11 +1,12 @@ import { ClassDeclarationStructure, CodeBlockWriter, Scope, StructureKind } from 'ts-morph'; import { IUnionModel } from '../../models/UnionModel'; -import { NameService } from '../../swagger/nameService'; +import { NameService } from '../../services/NameService'; import { FROM_DTO_METHOD, TO_DTO_METHOD } from '../ModelsGenerator'; export class UnionGenerator { - private nameService = new NameService(); + constructor(private readonly nameService: NameService) {} + public getUnionObjects(objects: IUnionModel[]): ClassDeclarationStructure[] { return objects.map((z) => ({ kind: StructureKind.Class, diff --git a/src/gengen/GenGenCodeGenInjector.ts b/src/gengen/GenGenCodeGenInjector.ts index 8ca312e..460bd8f 100644 --- a/src/gengen/GenGenCodeGenInjector.ts +++ b/src/gengen/GenGenCodeGenInjector.ts @@ -16,6 +16,12 @@ import { UriBuilder } from '../services/UriBuilder'; import { OpenAPIService } from '../swagger/OpenAPIService'; import { OpenAPITypesGuard } from '../swagger/OpenAPITypesGuard'; import { IOpenAPI3 } from '../swagger/v3/open-api'; +import { NameService } from '../services/NameService'; +import { InterfacesGenerator } from '../generators/models-generator/InterfacesGenerator'; +import { UnionGenerator } from '../generators/models-generator/UnionsGenerator'; +import { IdentitiesGenerator } from '../generators/models-generator/IdentitiesGenerator'; +import { ObjectGenerator } from '../generators/models-generator/ObjectGenerator'; +import { PropertiesGenerator } from '../generators/models-generator/PropertiesGenerator'; export abstract class EndpointsToken { public abstract getEndpoints(): Promise> | Set; @@ -61,7 +67,17 @@ export class GenGenCodeGenInjector { ) .provide(OpenAPITypesGuard) .provide(EndpointNameResolver) - .provide(ModelsGenerator, () => new ModelsGenerator(this.options)) + .provide( + ModelsGenerator, + (x) => + new ModelsGenerator( + this.options, + x.get(InterfacesGenerator), + x.get(UnionGenerator), + x.get(IdentitiesGenerator), + x.get(ObjectGenerator) + ) + ) .provide(UriBuilder) .provide(ServicesMethodGeneratorToken, (x) => new AngularServicesMethodGenerator(x.get(UriBuilder))) .provide(AliasResolver, () => new AliasResolver(this.options)) @@ -85,10 +101,20 @@ export class GenGenCodeGenInjector { ) .provide( ModelMappingService, - (x) => new ModelMappingService(x.get(OpenAPIService), x.get(OpenAPITypesGuard), x.get(TypesService)) + (x) => new ModelMappingService(x.get(OpenAPIService), x.get(OpenAPITypesGuard), x.get(TypesService), x.get(NameService)) ) .provide(TypesService, (x) => new TypesService(x.get(OpenAPITypesGuard), this.options)) .provide(OpenAPIService, (x) => new OpenAPIService(this.spec, x.get(OpenAPITypesGuard))) + + .provide(InterfacesGenerator, (x) => new InterfacesGenerator(x.get(PropertiesGenerator))) + .provide(ObjectGenerator, (x) => new ObjectGenerator(x.get(NameService), x.get(PropertiesGenerator))) + .provide(UnionGenerator, (x) => new UnionGenerator(x.get(NameService))) + .provide(IdentitiesGenerator, (x) => new IdentitiesGenerator(x.get(PropertiesGenerator), this.options)) + .provide(PropertiesGenerator, (x) => new PropertiesGenerator(x.get(NameService))) + .provide(NameService) ); - constructor(private options: IOptions, private spec: IOpenAPI3) {} + constructor( + private options: IOptions, + private spec: IOpenAPI3 + ) {} } diff --git a/src/services/ModelMappingService.ts b/src/services/ModelMappingService.ts index 771828e..9713166 100644 --- a/src/services/ModelMappingService.ts +++ b/src/services/ModelMappingService.ts @@ -5,7 +5,7 @@ import { PropertyKind } from '../models/kinds/PropertyKind'; import { IModelsContainer } from '../models/ModelsContainer'; import { IExtendedObjectModel, IObjectModel, IObjectPropertyModel, ObjectModel } from '../models/ObjectModel'; import { IUnionModel } from '../models/UnionModel'; -import { NameService } from '../swagger/nameService'; +import { NameService } from './NameService'; import { OpenAPIService } from '../swagger/OpenAPIService'; import { OpenAPITypesGuard } from '../swagger/OpenAPITypesGuard'; import { IOpenAPI3Reference } from '../swagger/v3/reference'; @@ -23,12 +23,12 @@ const IGNORE_PROPERTIES = ['startRow', 'rowCount']; export class ModelMappingService { public additionalEnums: IEnumModel[] = []; public unions: IUnionModel[] = []; - private nameService = new NameService(); constructor( private readonly openAPIService: OpenAPIService, private readonly typesGuard: OpenAPITypesGuard, - private readonly typesService: TypesService + private readonly typesService: TypesService, + private readonly nameService: NameService ) {} public toModelsContainer(schemas: OpenAPI3SchemaContainer): IModelsContainer { diff --git a/src/swagger/nameService.ts b/src/services/NameService.ts similarity index 100% rename from src/swagger/nameService.ts rename to src/services/NameService.ts