UNPKG

@namecheap/tsoa-cli

Version:

Build swagger-compliant REST APIs using TypeScript and Node

237 lines 11.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RouteGenerator = void 0; const fs = require("fs"); const handlebars = require("handlebars"); const path = require("path"); const tsoa_runtime_1 = require("@namecheap/tsoa-runtime"); const fs_1 = require("../utils/fs"); const internalTypeGuards_1 = require("../utils/internalTypeGuards"); const pathUtils_1 = require("./../utils/pathUtils"); class RouteGenerator { constructor(metadata, options) { this.metadata = metadata; this.options = options; } async GenerateRoutes(middlewareTemplate, pathTransformer) { if (!fs.lstatSync(this.options.routesDir).isDirectory()) { throw new Error(`routesDir should be a directory`); } else if (this.options.routesFileName !== undefined && !this.options.routesFileName.endsWith('.ts')) { throw new Error(`routesFileName should have a '.ts' extension`); } const fileName = `${this.options.routesDir}/${this.options.routesFileName || 'routes.ts'}`; const content = this.buildContent(middlewareTemplate, pathTransformer); if (this.options.noWriteIfUnchanged) { if (await (0, fs_1.fsExists)(fileName)) { const existingContent = (await (0, fs_1.fsReadFile)(fileName)).toString(); if (content === existingContent) { return; } } } await (0, fs_1.fsWriteFile)(fileName, content); } async GenerateCustomRoutes(template, pathTransformer) { const data = await (0, fs_1.fsReadFile)(path.join(template)); const file = data.toString(); return await this.GenerateRoutes(file, pathTransformer); } buildContent(middlewareTemplate, pathTransformer) { handlebars.registerHelper('json', (context) => { return JSON.stringify(context); }); const additionalPropsHelper = (additionalProperties) => { if (additionalProperties) { // Then the model for this type explicitly allows additional properties and thus we should assign that return JSON.stringify(additionalProperties); } else if (this.options.noImplicitAdditionalProperties === 'silently-remove-extras') { return JSON.stringify(false); } else if (this.options.noImplicitAdditionalProperties === 'throw-on-extras') { return JSON.stringify(false); } else if (this.options.noImplicitAdditionalProperties === 'ignore') { return JSON.stringify(true); } else { return (0, tsoa_runtime_1.assertNever)(this.options.noImplicitAdditionalProperties); } }; handlebars.registerHelper('additionalPropsHelper', additionalPropsHelper); 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; // Left in for backwards compatibility, previously if we're working locally then tsoa runtime code wasn't an importable module but now it is. const canImportByAlias = true; const normalisedBasePath = (0, pathUtils_1.normalisePath)(this.options.basePath, '/'); return routesTemplate({ authenticationModule, basePath: normalisedBasePath, canImportByAlias, controllers: this.metadata.controllers.map(controller => { const normalisedControllerPath = pathTransformer((0, pathUtils_1.normalisePath)(controller.path, '/')); return { actions: controller.methods.map(method => { const parameterObjs = {}; method.parameters.forEach(parameter => { parameterObjs[parameter.parameterName] = this.buildParameterSchema(parameter); }); const normalisedMethodPath = pathTransformer((0, pathUtils_1.normalisePath)(method.path, '/')); const normalisedFullPath = (0, pathUtils_1.normalisePath)(`${normalisedBasePath}${normalisedControllerPath}${normalisedMethodPath}`, '/', '', false); const uploadFileParameter = method.parameters.find(parameter => parameter.type.dataType === 'file'); const uploadFilesParameter = method.parameters.find(parameter => parameter.type.dataType === 'array' && parameter.type.elementType.dataType === 'file'); return { fullPath: normalisedFullPath, method: method.method.toLowerCase(), name: method.name, parameters: parameterObjs, path: normalisedMethodPath, uploadFile: !!uploadFileParameter, uploadFileName: uploadFileParameter === null || uploadFileParameter === void 0 ? void 0 : uploadFileParameter.name, uploadFiles: !!uploadFilesParameter, uploadFilesName: uploadFilesParameter === null || uploadFilesParameter === void 0 ? void 0 : uploadFilesParameter.name, security: method.security, successStatus: method.successStatus ? method.successStatus : 'undefined', }; }), modulePath: this.getRelativeImportPath(controller.location), name: controller.name, path: normalisedControllerPath, }; }), environment: process.env, iocModule, minimalSwaggerConfig: { noImplicitAdditionalProperties: this.options.noImplicitAdditionalProperties }, models: this.buildModels(), useFileUploads: this.metadata.controllers.some(controller => controller.methods.some(method => !!method.parameters.find(parameter => { if (parameter.type.dataType === 'file') { return true; } else if (parameter.type.dataType === 'array' && parameter.type.elementType.dataType === 'file') { return true; } return false; }))), multerOpts: this.options.multerOpts, useSecurity: this.metadata.controllers.some(controller => controller.methods.some(method => !!method.security.length)), esm: this.options.esm, }); } buildModels() { const models = {}; Object.keys(this.metadata.referenceTypeMap).forEach(name => { const referenceType = this.metadata.referenceTypeMap[name]; let model; if (referenceType.dataType === 'refEnum') { const refEnumModel = { dataType: 'refEnum', enums: referenceType.enums, }; model = refEnumModel; } else if (referenceType.dataType === 'refObject') { const propertySchemaDictionary = {}; referenceType.properties.forEach(property => { propertySchemaDictionary[property.name] = this.buildPropertySchema(property); }); const refObjModel = { dataType: 'refObject', properties: propertySchemaDictionary, }; if (referenceType.additionalProperties) { refObjModel.additionalProperties = this.buildProperty(referenceType.additionalProperties); } else if (this.options.noImplicitAdditionalProperties !== 'ignore') { refObjModel.additionalProperties = false; } else { // Since Swagger allows "excess properties" (to use a TypeScript term) by default refObjModel.additionalProperties = true; } model = refObjModel; } else if (referenceType.dataType === 'refAlias') { const refType = { dataType: 'refAlias', type: { ...this.buildProperty(referenceType.type), validators: referenceType.validators, default: referenceType.default, }, }; model = refType; } else { model = (0, tsoa_runtime_1.assertNever)(referenceType); } models[name] = model; }); return models; } getRelativeImportPath(fileLocation) { fileLocation = fileLocation.replace(/.ts$/, ''); // no ts extension in import return `./${path.relative(this.options.routesDir, fileLocation).replace(/\\/g, '/')}${this.options.esm ? '.js' : ''}`; } buildPropertySchema(source) { const propertySchema = this.buildProperty(source.type); propertySchema.default = source.default; propertySchema.required = source.required ? true : undefined; if (Object.keys(source.validators).length > 0) { propertySchema.validators = source.validators; } return propertySchema; } buildParameterSchema(source) { const property = this.buildProperty(source.type); const parameter = { default: source.default, in: source.in, name: source.name, required: source.required ? true : undefined, }; const parameterSchema = Object.assign(parameter, property); if (Object.keys(source.validators).length > 0) { parameterSchema.validators = source.validators; } return parameterSchema; } buildProperty(type) { const schema = { dataType: type.dataType, }; if ((0, internalTypeGuards_1.isRefType)(type)) { schema.dataType = undefined; schema.ref = type.refName; } if (type.dataType === 'array') { const arrayType = type; if ((0, internalTypeGuards_1.isRefType)(arrayType.elementType)) { schema.array = { dataType: arrayType.elementType.dataType, ref: arrayType.elementType.refName, }; } else { schema.array = this.buildProperty(arrayType.elementType); } } if (type.dataType === 'enum') { schema.enums = type.enums; } if (type.dataType === 'union' || type.dataType === 'intersection') { schema.subSchemas = type.types.map(type => this.buildProperty(type)); } if (type.dataType === 'nestedObjectLiteral') { const objLiteral = type; schema.nestedProperties = objLiteral.properties.reduce((acc, prop) => { return { ...acc, [prop.name]: this.buildPropertySchema(prop) }; }, {}); schema.additionalProperties = objLiteral.additionalProperties && this.buildProperty(objLiteral.additionalProperties); } return schema; } } exports.RouteGenerator = RouteGenerator; //# sourceMappingURL=routeGenerator.js.map