UNPKG

tsoa-custom-decorators

Version:

Build swagger-compliant REST APIs using TypeScript and Node

225 lines (195 loc) 7.31 kB
import { Metadata, ArrayType, EnumerateType, Parameter, Property } from '../metadataGeneration/metadataGenerator'; import { RoutesConfig } from './../config'; import * as fs from 'fs'; import * as handlebars from 'handlebars'; import * as path from 'path'; import * as tsfmt from 'typescript-formatter'; import * as handlebarsHelpers from 'handlebars-helpers'; export class RouteGenerator { constructor(private readonly metadata: Metadata, private readonly options: RoutesConfig) { } public GenerateRoutes(middlewareTemplate: string, pathTransformer: (path: string) => string) { const fileName = `${this.options.routesDir}/routes.ts`; const content = this.buildContent(middlewareTemplate, pathTransformer); return new Promise<void>((resolve, reject) => { tsfmt.processString(fileName, content, { editorconfig: true, replace: true, tsconfig: true, tsfmt: true, tslint: true, verify: true, vscode: true } as any) .then(result => { fs.writeFile(fileName, result.dest, (err) => { if (err) { reject(err); } else { resolve(); } }); } ); }); } public GenerateCustomRoutes(template: string, pathTransformer: (path: string) => string) { let file: string; fs.readFile(path.join(template), (err, data) => { if (err) { throw err; } file = data.toString(); return this.GenerateRoutes(file, pathTransformer); }); } private buildContent(middlewareTemplate: string, pathTransformer: (path: string) => string) { handlebars.registerHelper('json', function (context: any) { return JSON.stringify(context); }); handlebarsHelpers.comparison({ handlebars: handlebars }); const routesTemplate = handlebars.compile(middlewareTemplate, { noEscape: true }); const authenticationModule = this.options.authenticationModule ? this.getRelativeImportPath(this.options.authenticationModule) : undefined; const iocModule = this.options.iocModule ? this.getRelativeImportPath(this.options.iocModule) : undefined; // If we're working locally then tsoa won't exist as an importable module. // So, when in testing mode we reference the module by path instead. const env = process.env.NODE_ENV; let canImportByAlias = true; if (env === 'test') { canImportByAlias = false; } return routesTemplate({ authenticationModule, basePath: this.options.basePath === '/' ? '' : this.options.basePath, canImportByAlias: canImportByAlias, controllers: this.metadata.Controllers.map(controller => { return { actions: controller.methods.map(method => { const parameters: { [name: string]: PropertySchema } = {}; method.parameters.forEach(parameter => { parameters[parameter.parameterName] = this.getParameterSchema(parameter); }); return { method: method.method.toLowerCase(), name: method.name, parameters, path: pathTransformer(method.path), security: method.security }; }), modulePath: this.getRelativeImportPath(controller.location), name: controller.name, path: controller.path }; }), environment: process.env, iocModule, models: this.getModels(), useSecurity: this.metadata.Controllers.some( controller => controller.methods.some(methods => methods.security !== undefined) ) }); } private getModels(): TemplateModel[] { return Object.keys(this.metadata.ReferenceTypes).map(key => { const referenceType = this.metadata.ReferenceTypes[key]; const properties: { [name: string]: PropertySchema } = {}; referenceType.properties.map(property => { properties[property.name] = this.getPropertySchema(property); }); const templateModel: TemplateModel = { name: key, properties }; if (referenceType.additionalProperties && referenceType.additionalProperties.length) { templateModel.additionalProperties = referenceType.additionalProperties.map(property => this.getTemplateAdditionalProperty(property)); } return templateModel; }); } private getRelativeImportPath(fileLocation: string) { fileLocation = fileLocation.replace('.ts', ''); return `./${path.relative(this.options.routesDir, fileLocation).replace(/\\/g, '/')}`; } private getPropertySchema(source: Property): PropertySchema { const templateProperty: PropertySchema = { required: source.required, typeName: source.type.typeName }; const arrayType = source.type as ArrayType; if (arrayType.elementType) { const arraySchema: ArraySchema = { typeName: arrayType.elementType.typeName }; const arrayEnumType = arrayType.elementType as EnumerateType; if (arrayEnumType.enumMembers) { arraySchema.enumMembers = arrayEnumType.enumMembers; } templateProperty.array = arraySchema; } const enumType = source.type as EnumerateType; if (enumType.enumMembers) { templateProperty.enumMembers = enumType.enumMembers; } return templateProperty; } private getTemplateAdditionalProperty(source: Property): TemplateAdditionalProperty { const templateAdditionalProperty: TemplateAdditionalProperty = { typeName: source.type.typeName }; return templateAdditionalProperty; } private getParameterSchema(parameter: Parameter): ParameterSchema { const parameterSchema: ParameterSchema = { in: parameter.in, name: parameter.name, required: parameter.required, typeName: parameter.type.typeName }; const arrayType = parameter.type as ArrayType; if (arrayType.elementType) { const tempArrayType: ArraySchema = { typeName: arrayType.elementType.typeName }; const arrayEnumType = arrayType.elementType as EnumerateType; if (arrayEnumType.enumMembers) { tempArrayType.enumMembers = arrayEnumType.enumMembers; } parameterSchema.array = tempArrayType; } const enumType = parameter.type as EnumerateType; if (enumType.enumMembers) { parameterSchema.enumMembers = enumType.enumMembers; } return parameterSchema; } } interface TemplateModel { name: string; properties: { [name: string]: PropertySchema }; additionalProperties?: TemplateAdditionalProperty[]; } interface PropertySchema { typeName: string; required: boolean; array?: ArraySchema; request?: boolean; enumMembers?: string[]; } interface TemplateAdditionalProperty { typeName: string; } export interface ArraySchema { typeName: string; enumMembers?: string[]; } export interface ParameterSchema { name: string; in: string; typeName: string; required: boolean; array?: ArraySchema; request?: boolean; enumMembers?: string[]; }