Skip to content

Commit

Permalink
Reduce generated code (#125)
Browse files Browse the repository at this point in the history
feat: utils relative path
  • Loading branch information
aleshchev authored Oct 3, 2023
1 parent 38a227d commit 36882b9
Show file tree
Hide file tree
Showing 11 changed files with 100 additions and 44 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ gengen g --all
| **configOutput** | Output directory using in 'Generate a part of API' scenario | string | ./.generated |
| **aliasName** | Specify prefix for generated filenames. [more info](#aliasName) | string | |
| **withRequestOptions** | Allows to pass http request options to generated methods. [more info](#withRequestOptions) | boolean | false |
| **utilsRelativePath** | Relative path to utils files. It may be useful when you have multiple generation sources | string | |
| **unstrictId** | Disable converting 'id' properties to strong Guid type. [more info](#unstrictId) | boolean | false |
| |
Expand Down
30 changes: 30 additions & 0 deletions __tests__/services/PathBuilder.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { MethodKind } from '../../src/models/kinds/MethodKind';
import { MethodOperation } from '../../src/models/kinds/MethodOperation';
import { ParameterPlace } from '../../src/models/kinds/ParameterPlace';
import { PropertyKind } from '../../src/models/kinds/PropertyKind';
import { IMethodModel } from '../../src/models/method-parameter/IMethodModel';
import { PathBuilder } from '../../src/services/PathBuilder';

describe('UriBuilder tests', () => {
let pathBuilder: PathBuilder;
beforeEach(() => (pathBuilder = new PathBuilder()));

describe('normalizePath', () => {
describe('return correct path', () => {
test('path with double slash', () => {
// Arrange
const firstPart = './folder1/folder2/';
const secondPart = '/folder3/folder4/';
const path = `${firstPart}/${secondPart}`;

const expected = './folder1/folder2/folder3/folder4';

// Act
const result = pathBuilder.normalizePath(path);

// Assert
expect(result).toEqual(expected);
});
});
});
});
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@luxbss/gengen",
"version": "1.0.0",
"version": "1.1.0",
"description": "Tool for generating models and Angular services based on OpenAPIs and Swagger's JSON",
"bin": {
"gengen": "./bin/index.js"
Expand Down
1 change: 1 addition & 0 deletions src/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ program
.option('--url <string>')
.option('--output <string>')
.option('--configOutput <string>')
.option('--utilsRelativePath <string>')
.option('--all')
.option('--withRequestOptions')
.option('--unstrictId')
Expand Down
11 changes: 7 additions & 4 deletions src/generators/ModelsGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ 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 { InterfacesGenerator } from './models-generator/InterfacesGenerator';
import { TypeSerializer } from './utils/TypeSerializer';
Expand All @@ -26,6 +27,7 @@ const FROM_DTO_METHOD = 'fromDTO';

export class ModelsGenerator {
private interfaceGenerator = new InterfacesGenerator();
private pathBuilder = new PathBuilder();

constructor(private settings: IOptions) {}

Expand All @@ -40,26 +42,27 @@ export class ModelsGenerator {
}

private getImports(): ImportDeclarationStructure[] {
const path = this.pathBuilder.normalizePath(`./${this.settings.utilsRelativePath}`);
const imports: ImportDeclarationStructure[] = [
{
kind: StructureKind.ImportDeclaration,
moduleSpecifier: './Guid',
moduleSpecifier: `${path}/Guid`,
namedImports: [{ name: 'Guid' }]
},
{
kind: StructureKind.ImportDeclaration,
moduleSpecifier: './date-converters',
moduleSpecifier: `${path}/date-converters`,
namedImports: [{ name: 'toDateIn' }, { name: 'toDateOut' }]
},
{
kind: StructureKind.ImportDeclaration,
moduleSpecifier: './types',
moduleSpecifier: `${path}/types`,
namespaceImport: TYPES_NAMESPACE,
isTypeOnly: true
}
];

return this.settings.unstrictId ? imports.filter((x) => x.moduleSpecifier !== './Guid') : imports;
return this.settings.unstrictId ? imports.filter((x) => !x.moduleSpecifier.includes('Guid')) : imports;
}

private getEnums(enums: IEnumModel[]): StatementStructures[] {
Expand Down
18 changes: 11 additions & 7 deletions src/generators/angular/AngularServicesGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { IServiceModel } from '../../models/ServiceModel';
import { MethodKind } from '../../models/kinds/MethodKind';
import { IOptions } from '../../options';
import { AliasResolver } from '../../services/AliasResolver';
import { PathBuilder } from '../../services/PathBuilder';
import { MAPPERS_NAMESPACE, MODELS_NAMESPACE, TYPES_NAMESPACE } from '../utils/consts';
import { HTTP_REQUEST_OPTIONS } from './AngularServicesMethodGenerator';

Expand All @@ -22,6 +23,8 @@ const GET_BASE_PATH_FUNCTION_NAME = 'getBasePath';
const HTTP_CLIENT_VARIABLE_NAME = 'http';

export class AngularServicesGenerator {
private pathBuilder = new PathBuilder();

constructor(
protected aliasResolver: AliasResolver,
protected methodGenerator: ServicesMethodGeneratorToken,
Expand All @@ -33,6 +36,7 @@ export class AngularServicesGenerator {
}

protected getImports(): ImportDeclarationStructure[] {
const path = this.pathBuilder.normalizePath(`./${this.settings.utilsRelativePath}`);
const imports: ImportDeclarationStructure[] = [
{
kind: StructureKind.ImportDeclaration,
Expand All @@ -51,34 +55,34 @@ export class AngularServicesGenerator {
},
{
kind: StructureKind.ImportDeclaration,
moduleSpecifier: './Guid',
moduleSpecifier: `${path}/Guid`,
namedImports: [{ name: 'Guid' }]
},
{
kind: StructureKind.ImportDeclaration,
moduleSpecifier: './base-http.service',
moduleSpecifier: `${path}/base-http.service`,
namedImports: this.settings.withRequestOptions
? [{ name: BASE_SERVICE }, { name: HTTP_REQUEST_OPTIONS }]
: [{ name: BASE_SERVICE }]
},
{
kind: StructureKind.ImportDeclaration,
moduleSpecifier: './download.service',
moduleSpecifier: `${path}/download.service`,
namedImports: [{ name: DOWNLOAD_SERVICE }, { name: 'IDownloadResult' }]
},
{
kind: StructureKind.ImportDeclaration,
moduleSpecifier: './utils',
moduleSpecifier: `${path}/utils`,
namedImports: [{ name: GET_BASE_PATH_FUNCTION_NAME }]
},
{
kind: StructureKind.ImportDeclaration,
moduleSpecifier: './mappers',
moduleSpecifier: `${path}/mappers`,
namespaceImport: MAPPERS_NAMESPACE
},
{
kind: StructureKind.ImportDeclaration,
moduleSpecifier: './types',
moduleSpecifier: `${path}/types`,
namespaceImport: TYPES_NAMESPACE,
isTypeOnly: true
},
Expand All @@ -89,7 +93,7 @@ export class AngularServicesGenerator {
}
];

return this.settings.unstrictId ? imports.filter((x) => x.moduleSpecifier !== './Guid') : imports;
return this.settings.unstrictId ? imports.filter((x) => !x.moduleSpecifier.includes('Guid')) : imports;
}

protected getServices(services: IServiceModel[]): ClassDeclarationStructure[] {
Expand Down
15 changes: 10 additions & 5 deletions src/gengen/GenGenCodeGen.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { promises } from 'fs';
import { existsSync, mkdirSync, promises } from 'fs';
import { resolve } from 'path';
import { Project, StatementStructures } from 'ts-morph';
import { IOptions, generatorsOptions } from '../options';
import { PathBuilder } from '../services/PathBuilder';
import { IOpenAPI3 } from '../swagger/v3/open-api';
import { GenGenCodeGenInjector } from './GenGenCodeGenInjector';

Expand Down Expand Up @@ -54,17 +55,21 @@ export class GenGenCodeGen {
}

protected copyLibs(settings: IOptions): void {
const output = settings.output;
promises.copyFile(resolve(__dirname, '../../libs/types.ts'), `${output}/types.ts`);
const output = new PathBuilder().normalizePath(`${settings.output}/${settings.utilsRelativePath}`);

if (!settings.unstrictId) {
promises.copyFile(resolve(__dirname, '../../libs/Guid.ts'), `${output}/Guid.ts`);
if (!existsSync(output)) {
mkdirSync(output, { recursive: true });
}

promises.copyFile(resolve(__dirname, '../../libs/types.ts'), `${output}/types.ts`);
promises.copyFile(resolve(__dirname, '../../libs/utils.ts'), `${output}/utils.ts`);
promises.copyFile(resolve(__dirname, '../../libs/mappers.ts'), `${output}/mappers.ts`);
promises.copyFile(resolve(__dirname, '../../libs/date-converters.ts'), `${output}/date-converters.ts`);
promises.copyFile(resolve(__dirname, '../../libs/base-http.service.ts'), `${output}/base-http.service.ts`);
promises.copyFile(resolve(__dirname, '../../libs/download.service.ts'), `${output}/download.service.ts`);

if (!settings.unstrictId) {
promises.copyFile(resolve(__dirname, '../../libs/Guid.ts'), `${output}/Guid.ts`);
}
}
}
2 changes: 2 additions & 0 deletions src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { IndentationText, ModuleKind, NewLineKind, ProjectOptions, QuoteKind, Sc
export const defaultOptions: IOptions = {
configOutput: './.generated',
output: './src/generated',
utilsRelativePath: '',
url: 'https://localhost:5001/swagger/v1/swagger.json',
withRequestOptions: false,
unstrictId: false
Expand All @@ -17,6 +18,7 @@ export interface IOptions {
aliasName?: string;
withRequestOptions: boolean;
unstrictId: boolean;
utilsRelativePath: string;
}

export const pathOptions = {
Expand Down
52 changes: 27 additions & 25 deletions src/services/EndpointsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ export interface IEndpointInfo {
}

export class EndpointsService {
constructor(
private readonly openAPIService: OpenAPIService,
private readonly endpointNameResolver: EndpointNameResolver) {}
constructor(private readonly openAPIService: OpenAPIService, private readonly endpointNameResolver: EndpointNameResolver) {}

public getActionsGroupedByController(): Record<string, Record<string, string>> {
const result: Record<string, Record<string, string>> = {};
Expand All @@ -30,9 +28,10 @@ export class EndpointsService {
.forEach((key) => {
result[key] = {};
controllers[key]
.flatMap(endpointInfo => endpointInfo.actions).sort(sortBy(action => action.name))
.forEach(action => {
const info = controllers[key].find(endpointInfo => endpointInfo.actions.includes(action));
.flatMap((endpointInfo) => endpointInfo.actions)
.sort(sortBy((action) => action.name))
.forEach((action) => {
const info = controllers[key].find((endpointInfo) => endpointInfo.actions.includes(action));
if (info) {
result[key][action.name] = info.origin;
}
Expand All @@ -44,42 +43,38 @@ export class EndpointsService {

public getEndpoints(): Set<string> {
const controllers = this.getControllers();
const actions = Object.values(controllers).reduce<string[]>(
(store, endpoints) => store.concat(endpoints.map((z) => z.origin)),
[]
);
const actions = Object.values(controllers).reduce<string[]>((store, endpoints) => store.concat(endpoints.map((z) => z.origin)), []);

return new Set(actions.sort());
}

public parse(endpoint: string): IEndpointInfo | undefined {
const controller = first(this.openAPIService.getTagsByEndpoint(endpoint));
if (!controller) {
const rawGroupName = first(this.openAPIService.getTagsByEndpoint(endpoint));
if (!rawGroupName) {
return undefined;
}

const controllerStartIndex = endpoint.indexOf(controller);
if (controllerStartIndex < 0) {
const groupNameStartIndex = endpoint.indexOf(rawGroupName);
if (groupNameStartIndex < 0) {
return undefined;
}

const groupName = this.normalizeGroupName(rawGroupName);
const methods = this.openAPIService.getOperationsByEndpoint(endpoint);
const rawAction = endpoint.slice(controllerStartIndex + controller.length + pathOptions.separator.length);
const rawEndpointName = endpoint.slice(groupNameStartIndex + rawGroupName.length + pathOptions.separator.length);

return {
name: controller,
name: groupName,
origin: endpoint,
relativePath: endpoint.slice(0, controllerStartIndex) + controller,
actions: methods.map(z => {
const name = rawAction
? this.endpointNameResolver.generateNameByPath(rawAction)
: this.endpointNameResolver.generateNameDefault(controller);
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: rawAction
name: `${methods.length > 1 ? `${MethodOperation[z.method].toLocaleLowerCase()}${upperFirst(name)}` : name}`,
origin: rawEndpointName
};
})
};
Expand All @@ -104,4 +99,11 @@ export class EndpointsService {
return store;
}, {});
}

private normalizeGroupName(name: string): string {
return name
.split('-')
.map((x) => upperFirst(x))
.join('');
}
}
8 changes: 8 additions & 0 deletions src/services/PathBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export class PathBuilder {
public normalizePath(path: string): string {
return path
.split('/')
.filter((x) => x)
.join('/');
}
}

0 comments on commit 36882b9

Please sign in to comment.