UNPKG

@foal/core

Version:

Full-featured Node.js framework, with no complexity

160 lines (159 loc) 7.11 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getMethods = getMethods; exports.makeControllerRoutes = makeControllerRoutes; const openapi_1 = require("../openapi"); const utils_1 = require("./utils"); /** * Recursively get the property names of an object and its prototypes. * * @param {(object|null)} obj - The object. * @returns {string[]} The property names. */ function getMethods(obj) { if (obj === Object.prototype) { return []; } return Object.getOwnPropertyNames(obj).concat(getMethods(Object.getPrototypeOf(obj))); } function normalizePath(path) { return (path.startsWith('/') ? path : `/${path}`) .replace(/\:\w*/g, $1 => `{${$1.slice(1)}}`); } function throwErrorIfDuplicatePaths(paths) { const originalPaths = []; const convertedPaths = []; for (const path in paths) { const convertedPath = path.replace(/{.*?}/g, () => '#'); const index = convertedPaths.indexOf(convertedPath); if (index !== -1) { throw new Error('[OpenAPI] Templated paths with the same hierarchy but different templated names' + ' MUST NOT exist as they are identical.' + `\n Path 1: ${originalPaths[index]}` + `\n Path 2: ${path}`); } originalPaths.push(path); convertedPaths.push(convertedPath); } } function getPath(controllerClass, propertyKey) { return (0, utils_1.getMetadata)('path', controllerClass, propertyKey) || ''; } function getHooks(controllerClass, controller, propertyKey) { return ((0, utils_1.getMetadata)('hooks', controllerClass, propertyKey) || []) .map(hook => hook.bind(controller)); } /** * Recursively create the routes of a controller and its subcontrollers from the * controller class definition. * * @export * @param {string} parentPath - Prefix of all the route paths. * @param {HookFunction[]} parentHooks - First hooks of all the route hooks. * @param {Class} controllerClass - The controller class. * @param {ServiceManager} services - The application services. * @returns {Route[]} The created routes. */ function* makeControllerRoutes(controllerClass, services, openapi = false, documentControllers) { // FoalTS stores as well the controllers in the service manager. const controller = services.get(controllerClass); const controllerPath = getPath(controllerClass); const controllerHooks = getHooks(controllerClass, controller); /* OpenAPI */ const info = (0, openapi_1.getApiInfo)(controllerClass); // Check if the controller is inside an OpenAPI api. If not, components, operations and tags are discarded. openapi = !!info || openapi; /* OpenAPI */ const controllerComponents = openapi ? (0, openapi_1.getApiComponents)(controllerClass, controller) : {}; const controllerOperation = openapi ? (0, openapi_1.getApiCompleteOperation)(controllerClass, controller) : { responses: {} }; const controllerTags = openapi ? (0, openapi_1.getApiTags)(controllerClass) : undefined; if (openapi && info) { delete controllerOperation.servers; delete controllerOperation.externalDocs; delete controllerOperation.security; } /* OpenAPI */ const openApi = services.get(openapi_1.OpenApi); let document; /* OpenAPI */ if (info) { document = { info: typeof info === 'function' ? info(controller) : info, openapi: '3.0.0', paths: {} }; const operation = (0, openapi_1.getApiCompleteOperation)(controllerClass, controller); if (operation.servers) { document.servers = operation.servers; } if (operation.security) { document.security = operation.security; } if (operation.externalDocs) { document.externalDocs = operation.externalDocs; } document.components = controllerComponents; document.tags = controllerTags; } documentControllers = document ? [] : documentControllers; if (documentControllers) { documentControllers.push(controller); } function processRoute(route, components, operation, tags) { /* OpenAPI */ if (document) { const normalizedPath = normalizePath(route.path); document.paths[normalizedPath] = { ...document.paths[normalizedPath], // Potentially undefined [route.httpMethod.toLowerCase()]: (0, openapi_1.mergeOperations)(controllerOperation, operation) }; document.tags = Array.from(new Set((0, openapi_1.mergeTags)(document.tags, tags))); document.components = (0, openapi_1.mergeComponents)(document.components || {}, components); } return { // OpenAPI components: openapi ? (0, openapi_1.mergeComponents)(controllerComponents, components) : {}, // OpenAPI operation: openapi ? (0, openapi_1.mergeOperations)(controllerOperation, operation) : { responses: {} }, route: { controller: route.controller, hooks: controllerHooks.concat(route.hooks), httpMethod: route.httpMethod, path: (0, utils_1.join)(controllerPath, route.path), propertyKey: route.propertyKey, }, // OpenAPI tags: openapi ? (0, openapi_1.mergeTags)(controllerTags, tags) : undefined }; } for (const controllerClass of controller.subControllers || []) { for (const { route, components, operation, tags } of makeControllerRoutes(controllerClass, services, openapi, documentControllers)) { yield processRoute(route, components, operation, tags); } } for (const propertyKey of getMethods(controllerClass.prototype)) { const httpMethod = (0, utils_1.getMetadata)('httpMethod', controllerClass, propertyKey); if (!httpMethod) { continue; } const path = getPath(controllerClass, propertyKey); const hooks = getHooks(controllerClass, controller, propertyKey); const route = { controller, hooks, httpMethod, path, propertyKey }; /* OpenAPI */ const components = openapi ? (0, openapi_1.getApiComponents)(controllerClass, controller, propertyKey) : {}; const operation = openapi ? (0, openapi_1.getApiCompleteOperation)(controllerClass, controller, propertyKey) : { responses: {} }; const tags = openapi ? (0, openapi_1.getApiTags)(controllerClass, propertyKey) : undefined; yield processRoute(route, components, operation, tags); } /* OpenAPI */ if (document) { if (document.components && Object.keys(document.components).length === 0) { delete document.components; } if (document.tags && Object.keys(document.tags).length === 0) { delete document.tags; } throwErrorIfDuplicatePaths(document.paths); openApi.addDocument(controllerClass, document, documentControllers); } }