From 9c6f386b2b428e09ee4c2342babae8a621b2d14b Mon Sep 17 00:00:00 2001 From: Leshchev Artem Date: Wed, 4 Oct 2023 14:41:09 +0200 Subject: [PATCH] feat: Compatibility to support REST endpoints (#127) --- .snapshot/all/services.ts | 12 ++--- .snapshot/withRequestOptions/services.ts | 14 ++--- .../services/EndpointNameResolver.spec.ts | 54 +++++++++++++------ package-lock.json | 4 +- package.json | 2 +- src/services/EndpointNameResolver.ts | 28 +++++----- src/services/EndpointsService.ts | 15 ++---- src/swagger/OpenAPIService.ts | 15 ++++-- 8 files changed, 84 insertions(+), 60 deletions(-) diff --git a/.snapshot/all/services.ts b/.snapshot/all/services.ts index 2630e96..c20896b 100644 --- a/.snapshot/all/services.ts +++ b/.snapshot/all/services.ts @@ -72,6 +72,12 @@ export class ProductService extends DownloadFileService { ).pipe($mappers.mapSingle($models.Product)); } + public getProduct(): Observable<$models.Product[]> { + return this.get<$models.IProduct[]>( + ``, + ).pipe($mappers.mapCollection($models.Product)); + } + public getProducts(): Observable<$models.Product[]> { return this.get<$models.IProduct[]>( `GetProducts`, @@ -104,12 +110,6 @@ export class ProductService extends DownloadFileService { ).pipe($mappers.mapCollection($models.Product)); } - public productDefault(): Observable<$models.Product[]> { - return this.get<$models.IProduct[]>( - ``, - ).pipe($mappers.mapCollection($models.Product)); - } - public searchProducts(name: string): Observable<$models.Product[]> { return this.get<$models.IProduct[]>( `SearchProducts?name=${encodeURIComponent(name)}`, diff --git a/.snapshot/withRequestOptions/services.ts b/.snapshot/withRequestOptions/services.ts index 1ee6d25..422752e 100644 --- a/.snapshot/withRequestOptions/services.ts +++ b/.snapshot/withRequestOptions/services.ts @@ -76,6 +76,13 @@ export class ProductService extends DownloadFileService { ).pipe($mappers.mapSingle($models.Product)); } + public getProduct(options?: $types.TypeOrUndefined): Observable<$models.Product[]> { + return this.get<$models.IProduct[]>( + ``, + options, + ).pipe($mappers.mapCollection($models.Product)); + } + public getProducts(options?: $types.TypeOrUndefined): Observable<$models.Product[]> { return this.get<$models.IProduct[]>( `GetProducts`, @@ -113,13 +120,6 @@ export class ProductService extends DownloadFileService { ).pipe($mappers.mapCollection($models.Product)); } - public productDefault(options?: $types.TypeOrUndefined): Observable<$models.Product[]> { - return this.get<$models.IProduct[]>( - ``, - options, - ).pipe($mappers.mapCollection($models.Product)); - } - public searchProducts(name: string, options?: $types.TypeOrUndefined): Observable<$models.Product[]> { return this.get<$models.IProduct[]>( `SearchProducts?name=${encodeURIComponent(name)}`, diff --git a/__tests__/services/EndpointNameResolver.spec.ts b/__tests__/services/EndpointNameResolver.spec.ts index 06759bd..ff0ad52 100644 --- a/__tests__/services/EndpointNameResolver.spec.ts +++ b/__tests__/services/EndpointNameResolver.spec.ts @@ -12,10 +12,12 @@ describe('EndpointNameResolver tests', () => { }; } + let service: EndpointNameResolver; + beforeEach(() => (service = new EndpointNameResolver())); + describe('checkDuplicates', () => { test('store with duplicates', () => { // Arrange - const service = new EndpointNameResolver(); const origin = '/api/v1/Product/Product'; const infos = [ toEndpointInfo({ actions: [{ name: 'product', origin: '' }], origin }), @@ -23,45 +25,63 @@ describe('EndpointNameResolver tests', () => { ]; // Assert - expect(() => { service.checkDuplicates(infos) }).toThrowError(new Error(`Duplicate by path: '${origin}' was detected. Please, rename your endpoints`)); + expect(() => { + service.checkDuplicates(infos); + }).toThrowError(new Error(`Duplicate by path: '${origin}' was detected. Please, rename your endpoints`)); }); }); - describe('generateNameByPath', () => { - test('short path', () => { + describe('generateName', () => { + test('empty', () => { // Arrange - const service = new EndpointNameResolver(); + const group = 'Product'; + const verb = 'get'; + const endpoint = ''; // Act - const result = service.generateNameByPath('SearchProducts') + const result = service.generateName(group, endpoint, verb, 1); // Assert - expect(result).toEqual('searchProducts'); + expect(result).toEqual('getProduct'); + }); + + test('only one query parameter', () => { + // Arrange + const group = 'Product'; + const verb = 'get'; + const endpoint = '{id}'; + + // Act + const result = service.generateName(group, endpoint, verb, 1); + + // Assert + expect(result).toEqual('getProductById'); }); test('long path', () => { // Arrange - const service = new EndpointNameResolver(); + const group = 'Product'; + const verb = 'get'; + const endpoint = 'customer/{customer}/type/{type}'; // Act - const result = service.generateNameByPath('getByCustomer/{customer}/type/{type}') + const result = service.generateName(group, endpoint, verb, 1); // Assert - expect(result).toEqual('getByCustomerType'); + expect(result).toEqual('customerType'); }); - }); - describe('generateNameDefault', () => { - test('name', () => { + test('multiple verbs', () => { // Arrange - const service = new EndpointNameResolver(); - const name = 'product'; + const group = 'Product'; + const verb = 'get'; + const endpoint = 'customer/{customer}/type/{type}'; // Act - const result = service.generateNameDefault(name); + const result = service.generateName(group, endpoint, verb, 2); // Assert - expect(result).toEqual(`${name}Default`); + expect(result).toEqual('getCustomerType'); }); }); }); diff --git a/package-lock.json b/package-lock.json index 187f6a5..e4742e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@luxbss/gengen", - "version": "1.1.1", + "version": "1.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@luxbss/gengen", - "version": "1.1.1", + "version": "1.2.0", "license": "MIT", "dependencies": { "commander": "9.0.0", diff --git a/package.json b/package.json index aed31b8..3073101 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@luxbss/gengen", - "version": "1.1.1", + "version": "1.2.0", "description": "Tool for generating models and Angular services based on OpenAPIs and Swagger's JSON", "bin": { "gengen": "./bin/index.js" diff --git a/src/services/EndpointNameResolver.ts b/src/services/EndpointNameResolver.ts index 83d9e95..113c06c 100644 --- a/src/services/EndpointNameResolver.ts +++ b/src/services/EndpointNameResolver.ts @@ -11,11 +11,11 @@ interface IEndpointInfoItem { export class EndpointNameResolver { public checkDuplicates(endpointInfos: IEndpointInfo[]): void { const infos = endpointInfos.reduce((arr, z) => { - arr.push(...z.actions.map(x => ({ name: z.name, origin: z.origin, action: x }))) + arr.push(...z.actions.map((x) => ({ name: z.name, origin: z.origin, action: x }))); return arr; }, []); - infos.forEach(z => { + infos.forEach((z) => { const duplicates = this.findDuplicates(z, infos); if (duplicates.length > 1) { const duplicate = first(duplicates); @@ -24,21 +24,25 @@ export class EndpointNameResolver { }); } - public generateNameByPath(path: string): string { - return path - .split(pathOptions.separator) - .filter((z) => z && !this.queryParameterRegExp.test(z)) - .map((z, i) => i ? upperFirst(z) : lowerFirst(z)) + public generateName(groupName: string, enpoint: string, verb: string, verbCount: number): string { + if (!enpoint) { + return `${verb}${groupName}`; + } + const parts = enpoint.split(pathOptions.separator).filter((x) => x); + if (parts.length === 1 && this.queryParameterRegExp.test(parts[0])) { + return `${verb}${groupName}By${upperFirst(parts[0].substring(1, parts[0].length - 1))}`; + } + + const name = parts + .filter((x) => !this.queryParameterRegExp.test(x)) + .map((x, i) => (i ? upperFirst(x) : lowerFirst(x))) .join(''); - } - public generateNameDefault(name: string): string { - return lowerFirst(`${name}Default`); + return verbCount > 1 ? `${verb}${upperFirst(name)}` : name; } - private findDuplicates(info: IEndpointInfoItem, infos: IEndpointInfoItem[]): IEndpointInfoItem[] { - return infos.filter(x => info.action.name === x.action.name && info.name === x.name); + return infos.filter((x) => info.action.name === x.action.name && info.name === x.name); } private queryParameterRegExp = new RegExp('^{(.*)}$'); diff --git a/src/services/EndpointsService.ts b/src/services/EndpointsService.ts index c4d126e..30725f2 100644 --- a/src/services/EndpointsService.ts +++ b/src/services/EndpointsService.ts @@ -1,4 +1,3 @@ -import { MethodOperation } from '../models/kinds/MethodOperation'; import { pathOptions } from '../options'; import { OpenAPIService } from '../swagger/OpenAPIService'; import { first, sortBy, upperFirst } from '../utils'; @@ -67,16 +66,10 @@ export class EndpointsService { name: groupName, origin: endpoint, relativePath: endpoint.slice(0, groupNameStartIndex) + rawGroupName, - actions: methods.map((z) => { - const name = rawEndpointName - ? this.endpointNameResolver.generateNameByPath(rawEndpointName) - : this.endpointNameResolver.generateNameDefault(groupName); - - return { - name: `${methods.length > 1 ? `${MethodOperation[z.method].toLocaleLowerCase()}${upperFirst(name)}` : name}`, - origin: rawEndpointName - }; - }) + actions: methods.map((z) => ({ + name: this.endpointNameResolver.generateName(groupName, rawEndpointName, z.methodName, methods.length), + origin: rawEndpointName + })) }; } diff --git a/src/swagger/OpenAPIService.ts b/src/swagger/OpenAPIService.ts index 2458895..35e5478 100644 --- a/src/swagger/OpenAPIService.ts +++ b/src/swagger/OpenAPIService.ts @@ -14,6 +14,7 @@ interface IOperation { key: string; operation: IOpenAPI3Operation; method: MethodOperation; + methodName: string; } export type IOpenAPI3Operations = { [key: string]: { method: MethodOperation; operation: IOpenAPI3Operation }[] }; @@ -133,21 +134,27 @@ export class OpenAPIService { const [name, pathItem] = path; return Object.keys(pathItem).reduce((operations, method) => { + const operation: Partial = { key: name, methodName: method }; switch (method) { case 'get': - operations.push({ key: name, operation: pathItem.get, method: MethodOperation.GET } as IOperation); + operation.method = MethodOperation.GET; + operation.operation = pathItem.get; break; case 'post': - operations.push({ key: name, operation: pathItem.post, method: MethodOperation.POST } as IOperation); + operation.method = MethodOperation.POST; + operation.operation = pathItem.post; break; case 'put': - operations.push({ key: name, operation: pathItem.put, method: MethodOperation.PUT } as IOperation); + operation.method = MethodOperation.PUT; + operation.operation = pathItem.put; break; case 'delete': - operations.push({ key: name, operation: pathItem.delete, method: MethodOperation.DELETE } as IOperation); + operation.method = MethodOperation.DELETE; + operation.operation = pathItem.delete; break; } + operations.push(operation as IOperation); return operations; }, []); }