diff --git a/.snapshot/all/services.ts b/.snapshot/all/services.ts index 8906229..7dacf3d 100644 --- a/.snapshot/all/services.ts +++ b/.snapshot/all/services.ts @@ -84,6 +84,20 @@ export class ProductService extends DownloadFileService { ).pipe($mappers.mapCollection($models.Product)); } + public itemsGetById(parameter: string): Observable<$models.Product[]> { + return this.post<$models.IProduct[]>( + `Items/GetById`, + parameter, + ).pipe($mappers.mapCollection($models.Product)); + } + + public itemsGetByIds(parameter: string[]): Observable<$models.Product[]> { + return this.post<$models.IProduct[]>( + `Items/GetByIds`, + parameter, + ).pipe($mappers.mapCollection($models.Product)); + } + public product(): Observable<$models.Product[]> { return this.get<$models.IProduct[]>( `Product`, diff --git a/.snapshot/withRequestOptions/services.ts b/.snapshot/withRequestOptions/services.ts index 74abda8..d045750 100644 --- a/.snapshot/withRequestOptions/services.ts +++ b/.snapshot/withRequestOptions/services.ts @@ -90,6 +90,22 @@ export class ProductService extends DownloadFileService { ).pipe($mappers.mapCollection($models.Product)); } + public itemsGetById(parameter: string, options?: $types.TypeOrUndefined): Observable<$models.Product[]> { + return this.post<$models.IProduct[]>( + `Items/GetById`, + parameter, + options, + ).pipe($mappers.mapCollection($models.Product)); + } + + public itemsGetByIds(parameter: string[], options?: $types.TypeOrUndefined): Observable<$models.Product[]> { + return this.post<$models.IProduct[]>( + `Items/GetByIds`, + parameter, + options, + ).pipe($mappers.mapCollection($models.Product)); + } + public product(options?: $types.TypeOrUndefined): Observable<$models.Product[]> { return this.get<$models.IProduct[]>( `Product`, diff --git a/package-lock.json b/package-lock.json index cc2282d..bd4cb4b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@luxbss/gengen", - "version": "1.0.0-rc.12", + "version": "1.0.0-rc.13", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@luxbss/gengen", - "version": "1.0.0-rc.12", + "version": "1.0.0-rc.13", "license": "MIT", "dependencies": { "commander": "9.0.0", diff --git a/package.json b/package.json index 9d9721d..0495d79 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@luxbss/gengen", - "version": "1.0.0-rc.12", + "version": "1.0.0-rc.13", "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/angular/AngularServicesMethodGenerator.ts b/src/generators/angular/AngularServicesMethodGenerator.ts index 76380da..48a3c79 100644 --- a/src/generators/angular/AngularServicesMethodGenerator.ts +++ b/src/generators/angular/AngularServicesMethodGenerator.ts @@ -8,7 +8,7 @@ import { IBodyParameter } from '../../models/method-parameter/IBodyParameter'; import { IMethodModel } from '../../models/method-parameter/IMethodModel'; import { IPathParameter } from '../../models/method-parameter/IPathParameter'; import { IQueryParameter } from '../../models/method-parameter/IQueryParameter'; -import { IReturnType } from '../../models/method-parameter/IReturnType'; +import { ITypeInfo } from '../../models/method-parameter/ITypeInfo'; import { UriBuilder } from '../../services/UriBuilder'; import { MAPPERS_NAMESPACE, MODELS_NAMESPACE, UNDEFINED_STRING } from '../utils/consts'; import { TypeSerializer } from '../utils/TypeSerializer'; @@ -84,7 +84,7 @@ export class AngularServicesMethodGenerator { return statement; } - protected getReturnTypeName(returnType: IReturnType | undefined, targetType: string | undefined): string { + protected getReturnTypeName(returnType: ITypeInfo | undefined, targetType: string | undefined): string { if (!returnType || !targetType) { return 'void'; } @@ -107,14 +107,14 @@ export class AngularServicesMethodGenerator { }).toString(); } - protected needPipe(returnType: IReturnType | undefined): returnType is IReturnType { + protected needPipe(returnType: ITypeInfo | undefined): returnType is ITypeInfo { if (!returnType) { return false; } return [PropertyKind.Object, PropertyKind.Identity, PropertyKind.Guid, PropertyKind.Date].includes(returnType.type.kind); } - protected createPipe(returnType: IReturnType): string { + protected createPipe(returnType: ITypeInfo): string { if (returnType.type.kind === PropertyKind.Guid) { return `${MAPPERS_NAMESPACE}.mapGuid()`; } diff --git a/src/models/method-parameter/IMethodModel.ts b/src/models/method-parameter/IMethodModel.ts index 3828cbf..adb5238 100644 --- a/src/models/method-parameter/IMethodModel.ts +++ b/src/models/method-parameter/IMethodModel.ts @@ -3,7 +3,7 @@ import { MethodOperation } from '../kinds/MethodOperation'; import { IBodyParameter } from './IBodyParameter'; import { IPathParameter } from './IPathParameter'; import { IQueryParameter } from './IQueryParameter'; -import { IReturnType } from './IReturnType'; +import { ITypeInfo } from './ITypeInfo'; export type MethodParameter = IPathParameter | IQueryParameter | IBodyParameter; @@ -12,6 +12,6 @@ export interface IMethodModel { operation: MethodOperation; name: string; parameters: MethodParameter[]; - returnType: IReturnType | undefined; + returnType: ITypeInfo | undefined; originUri: string; } diff --git a/src/models/method-parameter/IReturnType.ts b/src/models/method-parameter/ITypeInfo.ts similarity index 77% rename from src/models/method-parameter/IReturnType.ts rename to src/models/method-parameter/ITypeInfo.ts index 8b96c5d..618a382 100644 --- a/src/models/method-parameter/IReturnType.ts +++ b/src/models/method-parameter/ITypeInfo.ts @@ -1,6 +1,6 @@ import { IType } from '../TypeModel'; -export interface IReturnType { +export interface ITypeInfo { isCollection: boolean; type: IType; isModel: boolean; diff --git a/src/models/method-parameter/MethodParameterModelBase.ts b/src/models/method-parameter/MethodParameterModelBase.ts index 0d947f8..7616573 100644 --- a/src/models/method-parameter/MethodParameterModelBase.ts +++ b/src/models/method-parameter/MethodParameterModelBase.ts @@ -32,8 +32,8 @@ export abstract class MethodParameterModelBase implements IParameter { if (this.typesGuard.isSimple(this.model.schema)) { this.setupSimple(this.model.schema); return; - } - + } + if (this.typesGuard.isEnum(this.openAPIService.getRefSchema(this.model.schema))) { this.setupRef(this.model.schema); return; diff --git a/src/services/EndpointsConfigReader.ts b/src/services/EndpointsConfigReader.ts index ec7621d..6f301b3 100644 --- a/src/services/EndpointsConfigReader.ts +++ b/src/services/EndpointsConfigReader.ts @@ -1,7 +1,8 @@ +import { EndpointsToken } from '../gengen/GenGenCodeGenInjector'; import { configOptions, IOptions } from '../options'; -export class EndpointsConfigReader { - constructor(private readonly options: IOptions) { } +export class EndpointsConfigReader implements EndpointsToken { + constructor(private readonly options: IOptions) {} public async getEndpoints(): Promise> { require('ts-node').register({ diff --git a/src/services/ModelFinder.ts b/src/services/ModelFinder.ts new file mode 100644 index 0000000..04f8ed7 --- /dev/null +++ b/src/services/ModelFinder.ts @@ -0,0 +1,30 @@ +import { PropertyKind } from '../models/kinds/PropertyKind'; +import { IModelsContainer } from '../models/ModelsContainer'; +import { OpenAPIService } from '../swagger/OpenAPIService'; +import { IOpenAPI3Reference } from '../swagger/v3/reference'; +import { IModel } from './ServiceMappingService'; + +export class ModelFinder { + public constructor(private readonly openAPIService: OpenAPIService, private readonly models: IModelsContainer) {} + + public find(ref: IOpenAPI3Reference): IModel | undefined { + const name = this.openAPIService.getSchemaKey(ref); + + const objectModel = this.models.objects.find((x) => x.name === name); + if (objectModel) { + return { kind: PropertyKind.Object, name, dtoType: objectModel.dtoType }; + } + + const identityModel = this.models.identities.find((x) => x.name === name); + if (identityModel) { + return { kind: PropertyKind.Identity, name, dtoType: identityModel.dtoType }; + } + + const enumModel = this.models.enums.find((x) => x.name === name); + if (enumModel) { + return { kind: PropertyKind.Enum, name, dtoType: name }; + } + + return undefined; + } +} diff --git a/src/services/ServiceMappingService.ts b/src/services/ServiceMappingService.ts index 4797858..f9ad10d 100644 --- a/src/services/ServiceMappingService.ts +++ b/src/services/ServiceMappingService.ts @@ -7,7 +7,7 @@ import { IBodyParameter } from '../models/method-parameter/IBodyParameter'; import { IMethodModel } from '../models/method-parameter/IMethodModel'; import { IPathParameter } from '../models/method-parameter/IPathParameter'; import { IQueryParameter } from '../models/method-parameter/IQueryParameter'; -import { IReturnType } from '../models/method-parameter/IReturnType'; +import { ITypeInfo } from '../models/method-parameter/ITypeInfo'; import { PathMethodParameterModel } from '../models/method-parameter/PathMethodParameterModel'; import { QueryMethodParameterModel } from '../models/method-parameter/QueryMethodParameterModel'; import { IModelsContainer } from '../models/ModelsContainer'; @@ -23,9 +23,10 @@ import { OpenAPI3Schema } from '../swagger/v3/schemas/schema'; import { first, lowerFirst, sortBy } from '../utils'; import { EndpointNameResolver } from './EndpointNameResolver'; import { EndpointsService, IEndpointInfo } from './EndpointsService'; +import { ModelFinder } from './ModelFinder'; import { TypesService } from './TypesService'; -interface IModel { +export interface IModel { name: string; dtoType: string; kind: PropertyKind; @@ -101,16 +102,17 @@ export class ServiceMappingService { models: IModelsContainer, originUri: string ): IMethodModel { + const modelFinder = new ModelFinder(this.openAPIService, models); const model: IMethodModel = { kind: this.hasDownloadResponse(operation) ? MethodKind.Download : MethodKind.Default, name: lowerFirst(actionName), operation: method, parameters: this.getUriParameters(operation.parameters), - returnType: this.getReturnType(operation.responses[200].content?.['application/json']?.schema, models), + returnType: this.getTypeInfo(operation.responses[200].content?.['application/json']?.schema, modelFinder), originUri }; - const bodyParameter = this.getBodyParameter(operation.requestBody?.content['application/json']?.schema, models); + const bodyParameter = this.getBodyParameter(operation.requestBody?.content['application/json']?.schema, modelFinder); if (bodyParameter) { model.parameters.push(bodyParameter); } @@ -175,32 +177,24 @@ export class ServiceMappingService { private getBodyParameter( schema: IOpenAPI3ArraySchema | IOpenAPI3Reference | undefined, - models: IModelsContainer + modelFinder: ModelFinder ): IBodyParameter | undefined { - let model: IModel | undefined; - let isCollection = false; - if (this.typesGuard.isReference(schema)) { - model = this.findModel(models, schema); - } else if (this.typesGuard.isCollection(schema) && this.typesGuard.isReference(schema.items)) { - isCollection = true; - model = this.findModel(models, schema.items); - } - - if (!model) { + const typeInfo = this.getTypeInfo(schema, modelFinder); + if (!typeInfo) { return undefined; } return { - name: lowerFirst(model.name), - place: ParameterPlace.Body, + dtoType: typeInfo.type.dtoType, + isCollection: typeInfo.isCollection, + isModel: typeInfo.isModel, optional: false, - dtoType: model.dtoType, - isCollection, - isModel: true + place: ParameterPlace.Body, + name: typeInfo.isModel ? lowerFirst(typeInfo.type.type) : 'parameter' }; } - private getReturnType(schema: OpenAPI3Schema | undefined, models: IModelsContainer): IReturnType | undefined { + private getTypeInfo(schema: OpenAPI3Schema | undefined, modelFinder: ModelFinder): ITypeInfo | undefined { let model: IModel | undefined; let isCollection = false; @@ -209,7 +203,7 @@ export class ServiceMappingService { } if (this.typesGuard.isReference(schema)) { - model = this.findModel(models, schema); + model = modelFinder.find(schema); } else if (this.typesGuard.isCollection(schema)) { isCollection = true; @@ -218,7 +212,7 @@ export class ServiceMappingService { } if (this.typesGuard.isReference(schema.items)) { - model = this.findModel(models, schema.items); + model = modelFinder.find(schema.items); } } @@ -232,25 +226,4 @@ export class ServiceMappingService { private hasDownloadResponse(operation: IOpenAPI3Operation): boolean { return Boolean(operation.responses[200].content?.['application/octet-stream']); } - - private findModel(models: IModelsContainer, ref: IOpenAPI3Reference): IModel | undefined { - const name = this.openAPIService.getSchemaKey(ref); - - const objectModel = models.objects.find((z) => z.name === name); - if (objectModel) { - return { kind: PropertyKind.Object, name, dtoType: objectModel.dtoType }; - } - - const identityModel = models.identities.find((z) => z.name === name); - if (identityModel) { - return { kind: PropertyKind.Identity, name, dtoType: identityModel.dtoType }; - } - - const enumModel = models.enums.find((z) => z.name === name); - if (enumModel) { - return { kind: PropertyKind.Enum, name, dtoType: name }; - } - - return undefined; - } } diff --git a/src/swagger/OpenAPIService.ts b/src/swagger/OpenAPIService.ts index 907c2f4..2458895 100644 --- a/src/swagger/OpenAPIService.ts +++ b/src/swagger/OpenAPIService.ts @@ -26,6 +26,10 @@ export class OpenAPIService { } } + public getSpec(): IOpenAPI3 { + return this.spec; + } + public getEndpoints(): string[] { if (!this.spec.paths) { return []; @@ -55,7 +59,7 @@ export class OpenAPIService { return store; } - const refs = operations.flatMap((z) => this.getReferencesByOperation(z.operation)); + const refs = operations.flatMap((z) => this.getRefsByOperation(z.operation)); return { ...store, ...this.getSchemasByRefs(refs) }; }, {}); } @@ -82,7 +86,7 @@ export class OpenAPIService { } const refs: IOpenAPI3Reference[] = []; - Object.values(modelSchema.properties).forEach((propertySchema) => { + Object.values(modelSchema.properties || {}).forEach((propertySchema) => { refs.push(...this.getRefsFromSchema(propertySchema)); }); @@ -101,7 +105,7 @@ export class OpenAPIService { return store; } - store[first(operations).key] = operations.map((x) => ({ method: x.method, operation: x.operation })); + store[first(operations).key] = operations; return store; }, {}); @@ -156,7 +160,7 @@ export class OpenAPIService { return first(this.spec.openapi.split('.')); } - private getReferencesByOperation(operation: IOpenAPI3Operation): IOpenAPI3Reference[] { + private getRefsByOperation(operation: IOpenAPI3Operation): IOpenAPI3Reference[] { const refs: IOpenAPI3Reference[] = []; operation.parameters?.forEach((z) => { @@ -172,27 +176,49 @@ export class OpenAPIService { ]; } - private getReferencesByObject(object: IOpenAPI3ObjectSchema, objectRef: IOpenAPI3Reference): IOpenAPI3Reference[] { - let refs: IOpenAPI3Reference[] = []; + /** + * @description Finds all refs from all objects and properties of a given refs + */ + private expandRefs(refs: IOpenAPI3Reference[], refKeys = new Set()): IOpenAPI3Reference[] { + const collectedRefs: IOpenAPI3Reference[] = []; - Object.values(object.properties || []).forEach((z) => { - let propertyRefs: IOpenAPI3Reference[] = []; - if (this.typesGuard.isCollection(z) && this.typesGuard.isReference(z.items)) { - propertyRefs.push(z.items); - } else if (this.typesGuard.isReference(z)) { - propertyRefs.push(z); - } else if (this.typesGuard.isAllOf(z)) { - propertyRefs = z.allOf; + refs.forEach((ref) => { + if (refKeys.has(ref.$ref)) { + return; } - propertyRefs - .filter((z) => z.$ref !== objectRef.$ref) + collectedRefs.push(ref); + refKeys.add(ref.$ref); + + const schema = this.getSchemaByRef(ref); + if (this.typesGuard.isObject(schema)) { + const refsFromObject = this.getRefsByObject(schema, ref); + const expanded = this.expandRefs(refsFromObject, refKeys); + collectedRefs.push(...expanded); + } + }); + + return collectedRefs; + } + + /** + * @description Gets refs from object schema only one level down + */ + private getRefsByObject( + object: IOpenAPI3ObjectSchema, + 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); - const schema = this.getRefSchema(ref); - if (this.typesGuard.isObject(schema)) { - refs = refs.concat(this.getReferencesByObject(schema, objectRef)); + if (this.typesGuard.isObject(property)) { + this.getRefsByObject(property, objectRef, refs); } }); }); @@ -206,32 +232,24 @@ export class OpenAPIService { refs.push(schema.items); } else if (this.typesGuard.isReference(schema)) { refs.push(schema); + } else if (this.typesGuard.isAllOf(schema)) { + refs.push(...schema.allOf); } return refs; } private getSchemasByRefs(refs: IOpenAPI3Reference[]): OpenAPI3SchemaContainer { - const keys = new Set(); - - refs.forEach((ref) => { - const schemaKey = this.getSchemaKey(ref); - if (keys.has(schemaKey)) { - return; - } - - keys.add(schemaKey); - - const schema = this.spec.components.schemas[schemaKey]; - if (this.typesGuard.isObject(schema)) { - this.getReferencesByObject(schema, ref).forEach((x) => { - keys.add(this.getSchemaKey(x)); - }); - } - }); + return this.expandRefs(refs) + .map((x) => this.getSchemaKey(x)) + .reduce((store, key) => { + store[key] = this.spec.components.schemas[key]; + return store; + }, {}); + } - return [...keys].reduce((store, key) => { - store[key] = this.spec.components.schemas[key]; - return store; - }, {}); + private getSchemaByRef(ref: IOpenAPI3Reference): IOpenAPI3ObjectSchema | IOpenAPI3EnumSchema | undefined { + const schemaKey = this.getSchemaKey(ref); + const schema = this.spec.components.schemas[schemaKey]; + return schema; } } diff --git a/swagger.json b/swagger.json index 2dd3fad..ca95cc1 100644 --- a/swagger.json +++ b/swagger.json @@ -5,6 +5,105 @@ "version": "1.0" }, "paths": { + "/api/v1/Product/Items/GetById": { + "post": { + "tags": [ + "Product" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "string", + "format": "uuid" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Product" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Product" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Product" + } + } + } + } + } + } + } + }, + "/api/v1/Product/Items/GetByIds": { + "post": { + "tags": [ + "Product" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Product" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Product" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Product" + } + } + } + } + } + } + } + }, "/Product/getByCustomer/{customer}/type/{type}": { "get": { "tags": [ @@ -267,7 +366,6 @@ } } }, - "/Product/SearchProducts": { "get": { "tags": [