UNPKG

@nestia/sdk

Version:

Nestia SDK and Swagger generator

302 lines 19.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); 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 __typia_transform__assertGuard = __importStar(require("typia/lib/internal/_assertGuard.js")); const openapi_1 = require("@samchon/openapi"); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const tstl_1 = require("tstl"); const typia_1 = __importDefault(require("typia")); const JsonSchemasProgrammer_1 = require("typia/lib/programmers/json/JsonSchemasProgrammer"); const FileRetriever_1 = require("../utils/FileRetriever"); const SwaggerOperationComposer_1 = require("./internal/SwaggerOperationComposer"); var SwaggerGenerator; (function (SwaggerGenerator) { SwaggerGenerator.generate = (app) => __awaiter(this, void 0, void 0, function* () { // 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 directory = path_1.default.dirname(parsed.dir); if (fs_1.default.existsSync(directory) === false) try { yield fs_1.default.promises.mkdir(directory); } catch (_a) { } if (fs_1.default.existsSync(directory) === false) throw new Error(`Error on NestiaApplication.swagger(): failed to create output directory: ${directory}`); const location = !!parsed.ext ? path_1.default.resolve(config.output) : path_1.default.join(path_1.default.resolve(config.output), "swagger.json"); // COMPOSE SWAGGER DOCUMENT const document = SwaggerGenerator.compose({ config, routes: app.routes.filter((route) => route.protocol === "http"), document: yield SwaggerGenerator.initialize(config), }); const specified = config.openapi === "2.0" ? openapi_1.OpenApi.downgrade(document, config.openapi) : config.openapi === "3.0" ? openapi_1.OpenApi.downgrade(document, config.openapi) : document; 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")); const metadatas = routes .map((r) => [ r.success.metadata, ...r.parameters.map((p) => p.metadata), ...Object.values(r.exceptions).map((e) => e.metadata), ]) .flat() .filter((m) => m.size() !== 0); // COMPOSE JSON SCHEMAS const json = JsonSchemasProgrammer_1.JsonSchemasProgrammer.write({ version: "3.1", metadatas, }); 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, _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 = (() => { const _io0 = input => (undefined === input.name || "string" === typeof input.name) && (undefined === input.version || "string" === typeof input.version) && (undefined === input.description || "string" === typeof input.description) && (null !== input.license && (undefined === input.license || "string" === typeof input.license || "object" === typeof input.license && null !== input.license && _io1(input.license))); const _io1 = input => "string" === typeof input.type && ("string" === typeof input.url && (/\/|:/.test(input.url) && /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i.test(input.url))); const _ao0 = (input, _path, _exceptionable = true) => (undefined === input.name || "string" === typeof input.name || __typia_transform__assertGuard._assertGuard(_exceptionable, { method: "typia.json.assertParse", path: _path + ".name", expected: "(string | undefined)", value: input.name }, _errorFactory)) && (undefined === input.version || "string" === typeof input.version || __typia_transform__assertGuard._assertGuard(_exceptionable, { method: "typia.json.assertParse", path: _path + ".version", expected: "(string | undefined)", value: input.version }, _errorFactory)) && (undefined === input.description || "string" === typeof input.description || __typia_transform__assertGuard._assertGuard(_exceptionable, { method: "typia.json.assertParse", path: _path + ".description", expected: "(string | undefined)", value: input.description }, _errorFactory)) && ((null !== input.license || __typia_transform__assertGuard._assertGuard(_exceptionable, { method: "typia.json.assertParse", path: _path + ".license", expected: "(__type.o1 | string | undefined)", value: input.license }, _errorFactory)) && (undefined === input.license || "string" === typeof input.license || ("object" === typeof input.license && null !== input.license || __typia_transform__assertGuard._assertGuard(_exceptionable, { method: "typia.json.assertParse", path: _path + ".license", expected: "(__type.o1 | string | undefined)", value: input.license }, _errorFactory)) && _ao1(input.license, _path + ".license", true && _exceptionable) || __typia_transform__assertGuard._assertGuard(_exceptionable, { method: "typia.json.assertParse", path: _path + ".license", expected: "(__type.o1 | string | undefined)", value: input.license }, _errorFactory))); const _ao1 = (input, _path, _exceptionable = true) => ("string" === typeof input.type || __typia_transform__assertGuard._assertGuard(_exceptionable, { method: "typia.json.assertParse", path: _path + ".type", expected: "string", value: input.type }, _errorFactory)) && ("string" === typeof input.url && (/\/|:/.test(input.url) && /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i.test(input.url) || __typia_transform__assertGuard._assertGuard(_exceptionable, { method: "typia.json.assertParse", path: _path + ".url", expected: "string & Format<\"uri\">", value: input.url }, _errorFactory)) || __typia_transform__assertGuard._assertGuard(_exceptionable, { method: "typia.json.assertParse", path: _path + ".url", expected: "(string & Format<\"uri\">)", value: input.url }, _errorFactory)); const __is = input => "object" === typeof input && null !== input && false === Array.isArray(input) && _io0(input); let _errorFactory; const __assert = (input, errorFactory) => { if (false === __is(input)) { _errorFactory = errorFactory; ((input, _path, _exceptionable = true) => ("object" === typeof input && null !== input && false === Array.isArray(input) || __typia_transform__assertGuard._assertGuard(true, { method: "typia.json.assertParse", path: _path + "", expected: "__type", value: input }, _errorFactory)) && _ao0(input, _path + "", true) || __typia_transform__assertGuard._assertGuard(true, { method: "typia.json.assertParse", path: _path + "", expected: "__type", value: input }, _errorFactory))(input, "$input", true); } return input; }; return (input, errorFactory) => __assert(JSON.parse(input), errorFactory); })()(content); return { title: data.name, version: data.version, description: data.description, license: 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.1.0", servers: (_a = config.servers) !== null && _a !== void 0 ? _a : [ { url: "https://github.com/samchon/nestia", description: "insert your server url", }, ], info: Object.assign(Object.assign({}, ((_b = config.info) !== null && _b !== void 0 ? _b : {})), { version: (_f = (_d = (_c = config.info) === null || _c === void 0 ? void 0 : _c.version) !== null && _d !== void 0 ? _d : (_e = (yield pack.get())) === null || _e === void 0 ? void 0 : _e.version) !== null && _f !== void 0 ? _f : "0.1.0", title: (_k = (_h = (_g = config.info) === null || _g === void 0 ? void 0 : _g.title) !== null && _h !== void 0 ? _h : (_j = (yield pack.get())) === null || _j === void 0 ? void 0 : _j.title) !== null && _k !== void 0 ? _k : "Swagger Documents", description: (_p = (_m = (_l = config.info) === null || _l === void 0 ? void 0 : _l.description) !== null && _m !== void 0 ? _m : (_o = (yield pack.get())) === null || _o === void 0 ? void 0 : _o.description) !== null && _p !== void 0 ? _p : "Generated by nestia - https://github.com/samchon/nestia", license: (_r = (_q = config.info) === null || _q === void 0 ? void 0 : _q.license) !== null && _r !== void 0 ? _r : (_s = (yield pack.get())) === null || _s === void 0 ? void 0 : _s.license }), paths: {}, components: { schemas: {}, securitySchemes: config.security, }, tags: (_t = config.tags) !== null && _t !== void 0 ? _t : [], "x-samchon-emended": true, }; }); 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; const filtered = route.parameters.filter((param) => param.category === "param"); for (const param of filtered) str = str.replace(`:${param.field}`, `{${param.field}}`); return str; }; })(SwaggerGenerator || (exports.SwaggerGenerator = SwaggerGenerator = {})); //# sourceMappingURL=SwaggerGenerator.js.map