UNPKG

restifyx.js

Version:

Advanced API endpoint handler system with automatic documentation

231 lines 10.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SwaggerGenerator = void 0; const swagger_jsdoc_1 = __importDefault(require("swagger-jsdoc")); const swagger_ui_express_1 = __importDefault(require("swagger-ui-express")); const logger_1 = require("../utils/logger"); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const js_yaml_1 = __importDefault(require("js-yaml")); class SwaggerGenerator { constructor(config) { this.logger = (0, logger_1.getLogger)(); this.config = config; } /** * Set up Swagger documentation for the API * * @param app - Express application instance * @param endpoints - Array of endpoint metadata */ setup(app, endpoints) { if (!this.config.enabled) { this.logger.info("Swagger documentation is disabled"); return; } let swaggerSpec; try { if (this.config.specPath && fs_1.default.existsSync(path_1.default.resolve(process.cwd(), this.config.specPath))) { try { const specContent = fs_1.default.readFileSync(path_1.default.resolve(process.cwd(), this.config.specPath), "utf8"); swaggerSpec = JSON.parse(specContent); this.logger.info(`Loaded Swagger spec from file: ${this.config.specPath}`); } catch (error) { this.logger.error(`Failed to load Swagger spec from file: ${error instanceof Error ? error.message : String(error)}`); swaggerSpec = this.generateSwaggerSpec(endpoints); } } else { swaggerSpec = this.generateSwaggerSpec(endpoints); } this.logger.debug(`Generated Swagger spec: ${JSON.stringify(swaggerSpec, null, 2)}`); if (!swaggerSpec.paths || Object.keys(swaggerSpec.paths).length === 0) { this.logger.warn("No paths defined in Swagger spec. Check that endpoints are properly registered."); } const uiOptions = { explorer: true, ...(this.config.uiOptions || {}), }; app.use(this.config.path, swagger_ui_express_1.default.serve, swagger_ui_express_1.default.setup(swaggerSpec, uiOptions)); app.get("/swagger.json", (req, res) => { res.setHeader("Content-Type", "application/json"); res.send(swaggerSpec); }); if (this.config.serveYaml) { app.get("/swagger.yaml", (req, res) => { res.setHeader("Content-Type", "text/yaml"); res.send(js_yaml_1.default.dump(swaggerSpec)); }); } this.logger.info(`Swagger documentation set up at ${this.config.path}`); this.logger.debug(`API Server URL: ${this.getServerUrl()}`); } catch (error) { this.logger.error(`Failed to set up Swagger: ${error instanceof Error ? error.message : String(error)}`); this.logger.error(`Stack trace: ${error instanceof Error ? error.stack : "No stack trace available"}`); } } getServerUrl() { const protocol = this.config.servers?.[0]?.url || "/"; return protocol; } generateSwaggerSpec(endpoints) { const info = { title: this.config.title || "API Documentation", version: this.config.version || "1.0.0", description: this.config.description || "API Documentation generated by RestifyX.js", }; if (this.config.contact && this.config.contact.name) { info.contact = { ...this.config.contact, name: this.config.contact.name ?? "", url: this.config.contact.url, email: this.config.contact.email, }; } else if (this.config.contact) { info.contact = this.config.contact; } if (this.config.license && this.config.license.name) { info.license = { ...this.config.license, name: this.config.license.name ?? "", url: this.config.license.url, }; } const options = { definition: { openapi: "3.0.0", info, servers: this.config.servers || [ { url: "/", description: "Current server", }, ], components: { securitySchemes: this.config.securitySchemes || {}, schemas: this.config.schemas || {}, }, tags: this.config.tags || [], ...this.config.options, }, apis: this.config.includeDocumentation ? [this.config.includeDocumentation] : [], }; try { const swaggerSpec = (0, swagger_jsdoc_1.default)(options); const paths = this.generateSwaggerPaths(endpoints); swaggerSpec.paths = paths; return swaggerSpec; } catch (error) { this.logger.error(`Error generating Swagger spec: ${error instanceof Error ? error.message : String(error)}`); throw error; } } generateSwaggerPaths(endpoints) { const paths = {}; try { if (!endpoints || endpoints.length === 0) { this.logger.warn("No endpoints found to generate Swagger documentation"); return {}; } this.logger.debug(`Generating Swagger paths for ${endpoints.length} endpoints`); endpoints.forEach((endpoint) => { if (!endpoint.path) { this.logger.warn(`Skipping endpoint without path: ${endpoint.name || "Unnamed endpoint"}`); return; } const swaggerPath = endpoint.path.replace(/:([^/]+)/g, "{$1}"); if (!paths[swaggerPath]) { paths[swaggerPath] = {}; } const method = endpoint.method.toLowerCase(); this.logger.debug(`Adding ${method.toUpperCase()} ${swaggerPath} to Swagger docs`); const parameters = endpoint.parameters || []; if (parameters.length === 0) { const pathParams = endpoint.path.match(/:([^/]+)/g); if (pathParams) { pathParams.forEach((param) => { const paramName = param.substring(1); parameters.push({ name: paramName, in: "path", required: true, schema: { type: "string", }, description: `Path parameter: ${paramName}`, }); }); } } paths[swaggerPath][method] = { tags: endpoint.tags || ["default"], summary: endpoint.name || `${method.toUpperCase()} ${swaggerPath}`, description: endpoint.description || "", operationId: endpoint.operationId || `${method}${swaggerPath.replace(/[^a-zA-Z0-9]/g, "")}`, parameters: parameters, security: endpoint.security || undefined, responses: endpoint.responses || { "200": { description: "Successful operation", }, "400": { description: "Bad request", }, "500": { description: "Internal server error", }, }, deprecated: endpoint.deprecated || false, }; if (["post", "put", "patch"].includes(method) && endpoint.requestBody) { paths[swaggerPath][method].requestBody = endpoint.requestBody; } else if (["post", "put", "patch"].includes(method) && !endpoint.requestBody) { paths[swaggerPath][method].requestBody = { content: { "application/json": { schema: { type: "object", }, }, }, }; } }); this.logger.debug(`Generated Swagger paths for ${Object.keys(paths).length} unique paths`); return paths; } catch (error) { this.logger.error(`Error generating Swagger paths: ${error instanceof Error ? error.message : String(error)}`); return {}; } } /** * Export the Swagger specification to a file * * @param filePath - Path to the output file * @param endpoints - Array of endpoint metadata * @returns True if export was successful, false otherwise */ exportToFile(filePath, endpoints) { try { const swaggerSpec = this.generateSwaggerSpec(endpoints); fs_1.default.writeFileSync(path_1.default.resolve(process.cwd(), filePath), JSON.stringify(swaggerSpec, null, 2), "utf8"); this.logger.info(`Swagger specification exported to ${filePath}`); return true; } catch (error) { this.logger.error(`Failed to export Swagger specification: ${error instanceof Error ? error.message : String(error)}`); return false; } } } exports.SwaggerGenerator = SwaggerGenerator; //# sourceMappingURL=SwaggerGenerator.js.map