diff --git a/__tests__/swagger/OpenAPIService.spec.ts b/__tests__/swagger/OpenAPIService.spec.ts index 839a81b..aca1d36 100644 --- a/__tests__/swagger/OpenAPIService.spec.ts +++ b/__tests__/swagger/OpenAPIService.spec.ts @@ -12,6 +12,74 @@ describe('OpenAPIService tests', () => { paths: {} }; + describe('getSchemas', () => { + const schema: IOpenAPI3 = { + ...defaultSpec, + openapi: '3.0.1', + components: { + schemas: { + modelName1: { + type: 'object', + properties: { + id: { + type: 'string', + format: 'uuid' + } + } + }, + modelName2: { + type: 'object', + properties: { + id: { + type: 'string', + format: 'uuid' + } + } + }, + modelName3: { + type: 'object', + properties: { + id: { + type: 'string', + format: 'uuid' + }, + prop1: { + $ref: '#/components/schemas/modelName1' + }, + prop2: { + $ref: '#/components/schemas/modelName2' + } + } + } + } + } + }; + + test('should return all models if no whitelist specified', () => { + const service = new OpenAPIService(schema, guard); + const result = service.getSchemas(); + expect(Object.values(result).length).toBe(3); + }); + + test('should return only selected model if it has no ref fields', () => { + const service = new OpenAPIService(schema, guard); + const result = service.getSchemas(['modelName1']); + expect(Object.values(result).length).toBe(1); + }); + + test('should return model and all of ref field models', () => { + const service = new OpenAPIService(schema, guard); + const result = service.getSchemas(['modelName3']); + expect(Object.values(result).length).toBe(3); + }); + + test('should be empty if no model found', () => { + const service = new OpenAPIService(schema, guard); + const result = service.getSchemas(['someOtherModel']); + expect(Object.values(result).length).toBe(0); + }); + }); + describe('ctor', () => { test('old OpenApi version', () => { const spec = { ...defaultSpec, openapi: '1.0.1' }; diff --git a/package-lock.json b/package-lock.json index 7ffbd0b..cc2282d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@luxbss/gengen", - "version": "1.0.0-rc.11", + "version": "1.0.0-rc.12", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@luxbss/gengen", - "version": "1.0.0-rc.11", + "version": "1.0.0-rc.12", "license": "MIT", "dependencies": { "commander": "9.0.0", @@ -4003,9 +4003,9 @@ } }, "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "node_modules/mkdirp": { @@ -8406,9 +8406,9 @@ } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "mkdirp": { diff --git a/package.json b/package.json index 1fd1432..9d9721d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@luxbss/gengen", - "version": "1.0.0-rc.11", + "version": "1.0.0-rc.12", "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 14d7f81..e92de7f 100644 --- a/src/generators/ModelsGenerator.ts +++ b/src/generators/ModelsGenerator.ts @@ -19,7 +19,6 @@ import { lowerFirst } from '../utils'; import { NULL_STRING, TYPES_NAMESPACE, UNDEFINED_STRING } from './utils/consts'; import { InterfacesGenerator } from './models-generator/InterfacesGenerator'; import { TypeSerializer } from './utils/TypeSerializer'; -import { typeOrUndefined } from './utils/typeOrUndefined'; const TO_DTO_METHOD = 'toDTO'; const FROM_DTO_METHOD = 'fromDTO'; @@ -92,6 +91,7 @@ export class ModelsGenerator { 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 && diff --git a/src/generators/models-generator/InterfacesGenerator.ts b/src/generators/models-generator/InterfacesGenerator.ts index 57d6ad6..ad42d24 100644 --- a/src/generators/models-generator/InterfacesGenerator.ts +++ b/src/generators/models-generator/InterfacesGenerator.ts @@ -12,10 +12,14 @@ export class InterfacesGenerator { })); } - private getInterfaceProperty(model: IInterfacePropertyModel): OptionalKind { + protected getInterfaceProperty(model: IInterfacePropertyModel): OptionalKind { return { name: model.name, - type: TypeSerializer.fromInterfaceProperty(model).toString() + type: this.getInterfacePropertyType(model) }; } + + protected getInterfacePropertyType(model: IInterfacePropertyModel): string { + return TypeSerializer.fromInterfaceProperty(model).toString(); + } } diff --git a/src/services/ModelMappingService.ts b/src/services/ModelMappingService.ts index 2146b2c..6c5d539 100644 --- a/src/services/ModelMappingService.ts +++ b/src/services/ModelMappingService.ts @@ -9,8 +9,8 @@ import { OpenAPITypesGuard } from '../swagger/OpenAPITypesGuard'; import { IOpenAPI3Reference } from '../swagger/v3/reference'; import { IOpenAPI3EnumSchema } from '../swagger/v3/schemas/enum-schema'; import { IOpenAPI3GuidSchema } from '../swagger/v3/schemas/guid-schema'; -import { IOpenAPI3ObjectSchema, OpenAPI3ObjectPropertySchema } from '../swagger/v3/schemas/object-schema'; -import { OpenAPI3SchemaContainer, OpenAPI3SimpleSchema } from '../swagger/v3/schemas/schema'; +import { IOpenAPI3ObjectSchema } from '../swagger/v3/schemas/object-schema'; +import { OpenAPI3Schema, OpenAPI3SchemaContainer, OpenAPI3SimpleSchema } from '../swagger/v3/schemas/schema'; import { first, sortBy } from '../utils'; import { TypesService } from './TypesService'; @@ -47,7 +47,7 @@ export class ModelMappingService { } }); } else { - objects.push(this.toObjectModel(schemas, name, schema)); + objects.push(this.toObjectModel(name, schema)); } } }); @@ -72,7 +72,7 @@ export class ModelMappingService { }; } - private toObjectModel(schemas: OpenAPI3SchemaContainer, name: string, schema: IOpenAPI3ObjectSchema): IObjectModel { + private toObjectModel(name: string, schema: IOpenAPI3ObjectSchema): IObjectModel { const model: IObjectModel = { name, dtoType: this.getInterfaceName(name), properties: [] }; if (!schema.properties) { return model; @@ -80,13 +80,13 @@ export class ModelMappingService { Object.entries(schema.properties) .filter(([name]) => !IGNORE_PROPERTIES.includes(name)) - .forEach(([name, propertySchema]) => this.addProperty(schemas, model, name, propertySchema)); + .forEach(([name, propertySchema]) => this.addProperty(model, name, propertySchema)); model.properties = model.properties.sort(sortBy((z) => z.name)); return model; } - private addProperty(schemas: OpenAPI3SchemaContainer, model: IObjectModel, name: string, schema: OpenAPI3ObjectPropertySchema): void { + private addProperty(model: IObjectModel, name: string, schema: OpenAPI3Schema): void { if (this.typesGuard.isSimple(schema)) { model.properties.push(this.getSimpleProperty(name, schema)); return; diff --git a/src/services/ServiceMappingService.ts b/src/services/ServiceMappingService.ts index e86d0d4..4797858 100644 --- a/src/services/ServiceMappingService.ts +++ b/src/services/ServiceMappingService.ts @@ -19,7 +19,7 @@ import { IOpenAPI3Operation } from '../swagger/v3/operation'; import { IOpenAPI3Parameter } from '../swagger/v3/parameter'; import { IOpenAPI3Reference } from '../swagger/v3/reference'; import { IOpenAPI3ArraySchema } from '../swagger/v3/schemas/array-schema'; -import { OpenAPI3ResponseSchema } from '../swagger/v3/schemas/schema'; +import { OpenAPI3Schema } from '../swagger/v3/schemas/schema'; import { first, lowerFirst, sortBy } from '../utils'; import { EndpointNameResolver } from './EndpointNameResolver'; import { EndpointsService, IEndpointInfo } from './EndpointsService'; @@ -38,7 +38,8 @@ export class ServiceMappingService { private readonly typesGuard: OpenAPITypesGuard, private readonly endpointsService: EndpointsService, private readonly endpointNameResolver: EndpointNameResolver, - private readonly settings: IOptions) {} + private readonly settings: IOptions + ) {} public toServiceModels(operations: IOpenAPI3Operations, models: IModelsContainer): IServiceModel[] { const endpointInfos = Object.keys(operations).reduce((infos, endpoint) => { @@ -54,18 +55,18 @@ export class ServiceMappingService { this.endpointNameResolver.checkDuplicates(endpointInfos); const services = Object.entries(operations).reduce((store, [endpoint, model]) => { - const info = endpointInfos.find(z => z.origin === endpoint); + const info = endpointInfos.find((z) => z.origin === endpoint); if (!info) { return store; } const service = store.find((z) => z.name === info.name); - model.forEach(z => { - const action = model.length > 1 ? - info.actions.find(x => x.name.startsWith(MethodOperation[z.method].toLocaleLowerCase())) - : - first(info.actions); + model.forEach((z) => { + const action = + model.length > 1 + ? info.actions.find((x) => x.name.startsWith(MethodOperation[z.method].toLocaleLowerCase())) + : first(info.actions); if (!action) { throw new Error(`Cannot find action in service ${info.name} by method ${z}`); @@ -199,7 +200,7 @@ export class ServiceMappingService { }; } - private getReturnType(schema: OpenAPI3ResponseSchema | undefined, models: IModelsContainer): IReturnType | undefined { + private getReturnType(schema: OpenAPI3Schema | undefined, models: IModelsContainer): IReturnType | undefined { let model: IModel | undefined; let isCollection = false; diff --git a/src/swagger/OpenAPIService.ts b/src/swagger/OpenAPIService.ts index b15deaf..907c2f4 100644 --- a/src/swagger/OpenAPIService.ts +++ b/src/swagger/OpenAPIService.ts @@ -6,7 +6,7 @@ import { IOpenAPI3Operation } from './v3/operation'; import { IOpenAPI3Reference } from './v3/reference'; import { IOpenAPI3EnumSchema } from './v3/schemas/enum-schema'; import { IOpenAPI3ObjectSchema } from './v3/schemas/object-schema'; -import { OpenAPI3ResponseSchema, OpenAPI3SchemaContainer } from './v3/schemas/schema'; +import { OpenAPI3Schema, OpenAPI3SchemaContainer } from './v3/schemas/schema'; const SUPPORTED_VERSION = 3; @@ -56,10 +56,40 @@ export class OpenAPIService { } const refs = operations.flatMap((z) => this.getReferencesByOperation(z.operation)); - return { ...store, ...this.getSchemas(refs) }; + return { ...store, ...this.getSchemasByRefs(refs) }; }, {}); } + public getSchemas(modelNames?: string[]): OpenAPI3SchemaContainer { + if (!modelNames) { + return this.spec.components.schemas; + } + + return modelNames.reduce((store, modelName) => { + const modelSchema = this.spec.components.schemas[modelName]; + + if (!modelSchema) { + return store; + } + + const result = { + ...store, + [modelName]: modelSchema + }; + + if (!this.typesGuard.isObject(modelSchema)) { + return result; + } + + const refs: IOpenAPI3Reference[] = []; + Object.values(modelSchema.properties).forEach((propertySchema) => { + refs.push(...this.getRefsFromSchema(propertySchema)); + }); + + return { ...result, ...this.getSchemasByRefs(refs) }; + }, {}); + } + public getOperationsByEndpoints(endpoints: Set): IOpenAPI3Operations { if (!endpoints?.size) { return {}; @@ -135,10 +165,11 @@ export class OpenAPIService { } }); - this.getSchemaFromContent(refs, operation.requestBody?.content['application/json']?.schema); - this.getSchemaFromContent(refs, operation.responses[200].content?.['application/json']?.schema); - - return refs; + return [ + ...refs, + ...this.getRefsFromSchema(operation.requestBody?.content['application/json']?.schema), + ...this.getRefsFromSchema(operation.responses[200].content?.['application/json']?.schema) + ]; } private getReferencesByObject(object: IOpenAPI3ObjectSchema, objectRef: IOpenAPI3Reference): IOpenAPI3Reference[] { @@ -169,17 +200,18 @@ export class OpenAPIService { return refs; } - private getSchemaFromContent(refs: IOpenAPI3Reference[], schema: OpenAPI3ResponseSchema | undefined): void { + private getRefsFromSchema(schema: OpenAPI3Schema | undefined): IOpenAPI3Reference[] { + const refs: IOpenAPI3Reference[] = []; if (this.typesGuard.isCollection(schema) && this.typesGuard.isReference(schema.items)) { refs.push(schema.items); } else if (this.typesGuard.isReference(schema)) { refs.push(schema); } + return refs; } - private getSchemas(refs: IOpenAPI3Reference[]): OpenAPI3SchemaContainer { + private getSchemasByRefs(refs: IOpenAPI3Reference[]): OpenAPI3SchemaContainer { const keys = new Set(); - const keysFromObjects = new Set(); refs.forEach((ref) => { const schemaKey = this.getSchemaKey(ref); @@ -192,12 +224,12 @@ export class OpenAPIService { const schema = this.spec.components.schemas[schemaKey]; if (this.typesGuard.isObject(schema)) { this.getReferencesByObject(schema, ref).forEach((x) => { - keysFromObjects.add(this.getSchemaKey(x)); + keys.add(this.getSchemaKey(x)); }); } }); - return [...new Set([...keys, ...keysFromObjects])].reduce((store, key) => { + return [...keys].reduce((store, key) => { store[key] = this.spec.components.schemas[key]; return store; }, {}); diff --git a/src/swagger/v3/operation.ts b/src/swagger/v3/operation.ts index 3b157a2..0dd3723 100644 --- a/src/swagger/v3/operation.ts +++ b/src/swagger/v3/operation.ts @@ -3,7 +3,7 @@ import { IOpenAPI3Reference } from './reference'; import { IOpenAPI3ArraySchema } from './schemas/array-schema'; import { IOpenAPI3BinarySchema } from './schemas/binary-schema'; import { IOpenAPI3ObjectSchema } from './schemas/object-schema'; -import { OpenAPI3ResponseSchema } from './schemas/schema'; +import { OpenAPI3Schema } from './schemas/schema'; export interface IOpenAPI3Operation { tags?: string[]; @@ -13,7 +13,7 @@ export interface IOpenAPI3Operation { description?: 'Success'; content?: { 'application/json'?: { - schema: OpenAPI3ResponseSchema; + schema: OpenAPI3Schema; }; 'application/octet-stream'?: { schema: IOpenAPI3BinarySchema; diff --git a/src/swagger/v3/schemas/object-schema.ts b/src/swagger/v3/schemas/object-schema.ts index 0c792b4..2598f8f 100644 --- a/src/swagger/v3/schemas/object-schema.ts +++ b/src/swagger/v3/schemas/object-schema.ts @@ -1,14 +1,9 @@ -import { IOpenAPI3Reference } from '../reference'; -import { IOpenAPI3AllOfSchema } from './all-of-schema'; -import { IOpenAPI3ArraySchema } from './array-schema'; import { IOpenAPI3BaseSchema } from './base-schema'; -import { OpenAPI3SimpleSchema } from './schema'; - -export type OpenAPI3ObjectPropertySchema = OpenAPI3SimpleSchema | IOpenAPI3ArraySchema | IOpenAPI3Reference | IOpenAPI3AllOfSchema; +import { OpenAPI3Schema } from './schema'; export interface IOpenAPI3ObjectSchema extends IOpenAPI3BaseSchema { type: 'object'; properties: { - [key: string]: OpenAPI3ObjectPropertySchema; + [key: string]: OpenAPI3Schema; }; } diff --git a/src/swagger/v3/schemas/schema.ts b/src/swagger/v3/schemas/schema.ts index b81652f..864c7df 100644 --- a/src/swagger/v3/schemas/schema.ts +++ b/src/swagger/v3/schemas/schema.ts @@ -1,4 +1,5 @@ import { IOpenAPI3Reference } from '../reference'; +import { IOpenAPI3AllOfSchema } from './all-of-schema'; import { IOpenAPI3ArraySchema } from './array-schema'; import { IOpenAPI3BooleanSchema } from './boolean-schema'; import { IOpenAPI3DateSchema } from './date-schema'; @@ -15,6 +16,5 @@ export type OpenAPI3SimpleSchema = | IOpenAPI3DateSchema | IOpenAPI3BooleanSchema; -export type OpenAPI3ResponseSchema = IOpenAPI3ArraySchema | OpenAPI3SimpleSchema | IOpenAPI3Reference; - +export type OpenAPI3Schema = IOpenAPI3ArraySchema | OpenAPI3SimpleSchema | IOpenAPI3Reference | IOpenAPI3AllOfSchema; export type OpenAPI3SchemaContainer = { [key: string]: IOpenAPI3ObjectSchema | IOpenAPI3EnumSchema };