@nestia/sdk
Version:
Nestia SDK and Swagger generator
230 lines • 12.1 kB
JavaScript
;
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