UNPKG

@nestia/sdk

Version:

Nestia SDK and Swagger generator

230 lines 12.1 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; 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 utils_1 = require("@typia/utils"); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const tstl_1 = require("tstl"); const legacy_1 = require("../internal/legacy"); const FileRetriever_1 = require("../utils/FileRetriever"); const SdkHttpParameterProgrammer_1 = require("./internal/SdkHttpParameterProgrammer"); const SwaggerOperationComposer_1 = require("./internal/SwaggerOperationComposer"); const SwaggerReadonlyArrayEmender_1 = require("./internal/SwaggerReadonlyArrayEmender"); var SwaggerGenerator; (function (SwaggerGenerator) { SwaggerGenerator.generate = (app) => __awaiter(this, void 0, void 0, function* () { var _a; // GET CONFIGURATION console.log("Generating Swagger Document"); if (app.project.config.swagger === undefined) throw new Error("Swagger configuration is not defined."); const config = app.project.config.swagger; // TARGET LOCATION const parsed = path_1.default.parse(config.output); const location = !!parsed.ext ? path_1.default.resolve(config.output) : path_1.default.join(path_1.default.resolve(config.output), "swagger.json"); const directory = path_1.default.dirname(location); if (fs_1.default.existsSync(directory) === false) try { yield fs_1.default.promises.mkdir(directory, { recursive: true }); } catch (_b) { } if (fs_1.default.existsSync(directory) === false) throw new Error(`Error on NestiaApplication.swagger(): failed to create output directory: ${directory}`); // COMPOSE SWAGGER DOCUMENT const document = SwaggerGenerator.compose({ config, routes: app.routes.filter((route) => route.protocol === "http"), document: yield SwaggerGenerator.initialize(config), }); const specified = ((_a = config.openapi) !== null && _a !== void 0 ? _a : "3.2") === "3.2" ? document : utils_1.OpenApiConverter.downgradeDocument(document, config.openapi); yield fs_1.default.promises.writeFile(location, !config.beautify ? JSON.stringify(specified) : JSON.stringify(specified, null, typeof config.beautify === "number" ? config.beautify : 2), "utf8"); }); SwaggerGenerator.compose = (props) => { var _a; var _b; // GATHER METADATA const routes = props.routes.filter((r) => r.jsDocTags.every((tag) => tag.name !== "internal" && tag.name !== "hidden") && isSwaggerExcluded(r) === false); const metadatas = routes .map((r) => [ r.success.metadata, ...SdkHttpParameterProgrammer_1.SdkHttpParameterProgrammer.getAll(r).map((p) => p.metadata), ...Object.values(r.exceptions).map((e) => e.metadata), ]) .flat() .filter((m) => (0, legacy_1.sizeOf)(m) !== 0); // COMPOSE JSON SCHEMAS const json = legacy_1.JsonSchemasProgrammer.writeSchemas({ version: "3.1", metadatas, }); json.schemas.forEach((schema, i) => SwaggerReadonlyArrayEmender_1.SwaggerReadonlyArrayEmender.emend({ components: json.components, schema, metadata: metadatas[i], })); const dict = new WeakMap(); json.schemas.forEach((schema, i) => dict.set(metadatas[i], schema)); const schema = (metadata) => dict.get(metadata); // COMPOSE DOCUMENT const document = props.document; (_a = (_b = document.components).schemas) !== null && _a !== void 0 ? _a : (_b.schemas = {}); Object.assign(document.components.schemas, json.components.schemas); fillPaths(Object.assign(Object.assign({}, props), { routes, schema, document })); return document; }; SwaggerGenerator.initialize = (config) => __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d, _e, _f, _g, _h; var _j, _k, _l, _m, _o, _p, _q, _r, _s, _t; const pack = new tstl_1.Singleton(() => __awaiter(this, void 0, void 0, function* () { const location = yield FileRetriever_1.FileRetriever.file("package.json")(process.cwd()); if (location === null) return null; try { const content = yield fs_1.default.promises.readFile(location, "utf8"); const data = JSON.parse(content); return { title: typeof data.name === "string" ? data.name : undefined, version: typeof data.version === "string" ? data.version : undefined, description: typeof data.description === "string" ? data.description : undefined, license: isLicense(data.license) ? typeof data.license === "string" ? { name: data.license } : typeof data.license === "object" ? { name: data.license.type, url: data.license.url, } : undefined : undefined, }; } catch (_a) { return null; } })); return { openapi: "3.2.0", servers: (_j = config.servers) !== null && _j !== void 0 ? _j : [ { url: "https://github.com/samchon/nestia", description: "insert your server url", }, ], info: Object.assign(Object.assign({}, ((_k = config.info) !== null && _k !== void 0 ? _k : {})), { version: (_m = (_l = (_a = config.info) === null || _a === void 0 ? void 0 : _a.version) !== null && _l !== void 0 ? _l : (_b = (yield pack.get())) === null || _b === void 0 ? void 0 : _b.version) !== null && _m !== void 0 ? _m : "0.1.0", title: (_p = (_o = (_c = config.info) === null || _c === void 0 ? void 0 : _c.title) !== null && _o !== void 0 ? _o : (_d = (yield pack.get())) === null || _d === void 0 ? void 0 : _d.title) !== null && _p !== void 0 ? _p : "Swagger Documents", description: (_r = (_q = (_e = config.info) === null || _e === void 0 ? void 0 : _e.description) !== null && _q !== void 0 ? _q : (_f = (yield pack.get())) === null || _f === void 0 ? void 0 : _f.description) !== null && _r !== void 0 ? _r : "Generated by nestia - https://github.com/samchon/nestia", license: (_s = (_g = config.info) === null || _g === void 0 ? void 0 : _g.license) !== null && _s !== void 0 ? _s : (_h = (yield pack.get())) === null || _h === void 0 ? void 0 : _h.license }), paths: {}, components: { schemas: {}, securitySchemes: config.security, }, tags: (_t = config.tags) !== null && _t !== void 0 ? _t : [], "x-typia-emended-v12": true, }; }); const isLicense = (input) => typeof input === "string" || (typeof input === "object" && input !== null && Array.isArray(input) === false && typeof input.type === "string" && typeof input.url === "string"); const fillPaths = (props) => { var _a, _b; var _c, _d; // SWAGGER CUSTOMIZER const customizers = []; const neighbor = { at: new tstl_1.Singleton(() => { var _a, _b; const functor = new Map(); for (const r of props.routes) { const method = r.method.toLowerCase(); const path = getPath(r); const operation = (_b = (_a = props.document.paths) === null || _a === void 0 ? void 0 : _a[path]) === null || _b === void 0 ? void 0 : _b[method]; if (operation === undefined) continue; functor.set(r.function, { method, path, route: operation, }); } return functor; }), get: new tstl_1.Singleton(() => (key) => { var _a, _b; const method = key.method.toLowerCase(); const path = "/" + key.path .split("/") .filter((str) => !!str.length) .map((str) => str.startsWith(":") ? `{${str.substring(1)}}` : str) .join("/"); return (_b = (_a = props.document.paths) === null || _a === void 0 ? void 0 : _a[path]) === null || _b === void 0 ? void 0 : _b[method]; }), }; // COMPOSE OPERATIONS for (const r of props.routes) { const operation = SwaggerOperationComposer_1.SwaggerOperationComposer.compose(Object.assign(Object.assign({}, props), { route: r })); const path = getPath(r); (_a = (_c = props.document).paths) !== null && _a !== void 0 ? _a : (_c.paths = {}); (_b = (_d = props.document.paths)[path]) !== null && _b !== void 0 ? _b : (_d[path] = {}); props.document.paths[path][r.method.toLowerCase()] = operation; const closure = Reflect.getMetadata("nestia/SwaggerCustomizer", r.controller.class.prototype, r.name); if (closure !== undefined) { const array = Array.isArray(closure) ? closure : [closure]; customizers.push(() => { for (const closure of array) closure({ swagger: props.document, method: r.method, path, route: operation, at: (func) => neighbor.at.get().get(func), get: (accessor) => neighbor.get.get()(accessor), }); }); } } // DO CUSTOMIZE for (const fn of customizers) fn(); }; const getPath = (route) => { let str = route.path; for (const param of route.pathParameters) str = str.replace(`:${param.field}`, `{${param.field}}`); return str; }; const isSwaggerExcluded = (route) => { const controller = Reflect.getMetadata("swagger/apiExcludeController", route.controller.class); if (Array.isArray(controller) && controller[0] === true) return true; const endpoint = Reflect.getMetadata("swagger/apiExcludeEndpoint", route.function); return (endpoint !== undefined && (typeof endpoint !== "object" || endpoint === null || endpoint.disable !== false)); }; })(SwaggerGenerator || (exports.SwaggerGenerator = SwaggerGenerator = {})); //# sourceMappingURL=SwaggerGenerator.js.map