diff --git a/.snapshot/all/models.ts b/.snapshot/all/models.ts index 732690a..9ec7290 100644 --- a/.snapshot/all/models.ts +++ b/.snapshot/all/models.ts @@ -8,10 +8,16 @@ export enum ProductStatus { UnderTheOrder = 1 } -export interface ICategory { +interface ICategoryBaseInterface { name: $types.TypeOrUndefinedNullable; } +export type ICategory = ICategoryBaseInterface & ICategoryBase; + +export interface ICategoryBase { + id: $types.TypeOrUndefinedNullable; +} + export interface IProduct { category: $types.TypeOrUndefinedNullable; colors: $types.TypeOrUndefined; @@ -35,24 +41,44 @@ export class ProductIdentityDTO { this.id = new Guid(id); } - public static toDTO(id: Guid): IProductIdentityDTO { + public static toDTO(id: Guid): ICategoryBase { return { id: id.toString() }; } } export class Category { + public id: $types.TypeOrUndefinedNullable = undefined; public name: $types.TypeOrUndefinedNullable = undefined; private __category!: string; public static toDTO(model: Partial): ICategory { return { name: model.name, + id: model.id, }; } public static fromDTO(dto: ICategory): Category { const model = new Category(); model.name = dto.name; + model.id = dto.id; + return model; + } +} + +export class CategoryBase { + public id: $types.TypeOrUndefinedNullable = undefined; + private __categoryBase!: string; + + public static toDTO(model: Partial): ICategoryBase { + return { + id: model.id, + }; + } + + public static fromDTO(dto: ICategoryBase): CategoryBase { + const model = new CategoryBase(); + model.id = dto.id; return model; } } 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..09ccfd8 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,14 @@ export class ModelMappingService { } if (this.typesGuard.isOneOf(schema.items)) { property = this.getReferenceProperty(name, first(schema.items.oneOf)); + + const firstObj = first(schema.items.oneOf); + const schemaKey = this.openAPIService.getSchemaKey(firstObj); + property = { + ...property, + typeAlias: schemaKey + 'Union', + dtoTypeAlias: schemaKey + ' UnionClass' + }; } if (property) { @@ -143,62 +156,46 @@ 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 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..722353a 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,8 +242,39 @@ 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.isOneOf(schema.items)) { + refs.push(...schema.items.oneOf); + } + if (this.typesGuard.isAllOf(schema)) { + refs.push(...schema.allOf); + } if (this.typesGuard.isCollection(schema) && this.typesGuard.isReference(schema.items)) { refs.push(schema.items); } else if (this.typesGuard.isReference(schema)) { diff --git a/src/swagger/OpenAPITypesGuard.ts b/src/swagger/OpenAPITypesGuard.ts index c039167..8916fb5 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,6 +39,10 @@ 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); } 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..c456277 100644 --- a/swagger.json +++ b/swagger.json @@ -641,8 +641,23 @@ "schemas": { "Category": { "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/CategoryBase" + } + ], "properties": { - "name": { + "name": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "CategoryBase": { + "type": "object", + "properties": { + "id": { "type": "string", "nullable": true }