restifyx.js
Version:
Advanced API endpoint handler system with automatic documentation
231 lines • 10.2 kB
JavaScript
"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