UNPKG

@nestia/core

Version:

Super-fast validation decorators of NestJS

284 lines 14 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.WebSocketAdaptor = void 0; /// <reference path="../typings/get-function-location.d.ts" /> const common_1 = require("@nestjs/common"); const constants_1 = require("@nestjs/common/constants"); const get_function_location_1 = __importDefault(require("get-function-location")); const path_1 = __importDefault(require("path")); const path_parser_1 = require("path-parser"); const tgrid_1 = require("tgrid"); const typia_1 = __importDefault(require("typia")); const ws_1 = __importDefault(require("ws")); const ArrayUtil_1 = require("../utils/ArrayUtil"); const VersioningStrategy_1 = require("../utils/VersioningStrategy"); class WebSocketAdaptor { static upgrade(app) { return __awaiter(this, void 0, void 0, function* () { return new this(app, yield visitApplication(app)); }); } constructor(app, operations) { this.close = () => __awaiter(this, void 0, void 0, function* () { return new Promise((resolve) => { this.http.off("close", this.close); this.http.off("upgrade", this.handleUpgrade); this.ws.close(() => resolve()); }); }); this.handleUpgrade = (request, duplex, head) => { this.ws.handleUpgrade(request, duplex, head, (client, request) => tgrid_1.WebSocketAcceptor.upgrade(request, client, (acceptor) => __awaiter(this, void 0, void 0, function* () { const path = (() => { const index = acceptor.path.indexOf("?"); return index === -1 ? acceptor.path : acceptor.path.slice(0, index); })(); for (const op of this.operators) { const params = op.parser.test(path); if (params !== null) try { yield op.handler({ params, acceptor }); } catch (error) { if (acceptor.state === 1 /* WebSocketAcceptor.State.OPEN */ || acceptor.state === 0 /* WebSocketAcceptor.State.ACCEPTING */) yield acceptor.reject(1008, error instanceof Error ? JSON.stringify(Object.assign({}, error)) : "unknown error"); } finally { return; } } yield acceptor.reject(1002, `WebSocket API not found`); }))); }; this.operators = operations; this.ws = new ws_1.default.Server({ noServer: true }); this.http = app.getHttpServer(); this.http.on("close", this.close); this.http.on("upgrade", this.handleUpgrade); } } exports.WebSocketAdaptor = WebSocketAdaptor; const visitApplication = (app) => __awaiter(void 0, void 0, void 0, function* () { var _a, _b, _c; const operators = []; const errors = []; const config = { globalPrefix: typeof ((_a = app.config) === null || _a === void 0 ? void 0 : _a.globalPrefix) === "string" ? app.config.globalPrefix : undefined, versioning: (() => { var _a; const versioning = (_a = app.config) === null || _a === void 0 ? void 0 : _a.versioningOptions; return versioning === undefined || versioning.type !== common_1.VersioningType.URI ? undefined : { prefix: versioning.prefix === undefined || versioning.prefix === false ? "v" : versioning.prefix, defaultVersion: versioning.defaultVersion, }; })(), }; const container = app.container; const modules = [...container.getModules().values()].filter((m) => { var _a; return !!((_a = m.controllers) === null || _a === void 0 ? void 0 : _a.size); }); for (const m of modules) { const modulePrefix = (_c = (_b = Reflect.getMetadata(constants_1.MODULE_PATH + container.getModules().applicationId, m.metatype)) !== null && _b !== void 0 ? _b : Reflect.getMetadata(constants_1.MODULE_PATH, m.metatype)) !== null && _c !== void 0 ? _c : ""; for (const controller of m.controllers.values()) yield visitController({ config, errors, operators, controller, modulePrefix, }); } if (errors.length) throw new Error([ `WebSocketAdaptor: ${errors.length} error(s) found:`, ``, ...errors.map((e) => [ ` - controller: ${e.name}`, ` methods:`, ...e.methods.map((m) => [ ` - name: ${m.name}`, ` file: ${m.source}:${m.line}:${m.column}`, ` reasons:`, ...m.messages.map((msg) => ` - ${msg .split("\n") .map((str, i) => (i == 0 ? str : ` ${str}`)) .join("\n")}`), ].join("\n")), ].join("\n")), ].join("\n")); return operators; }); const visitController = (props) => __awaiter(void 0, void 0, void 0, function* () { if (ArrayUtil_1.ArrayUtil.has(Reflect.getMetadataKeys(props.controller.metatype), constants_1.PATH_METADATA, constants_1.HOST_METADATA, constants_1.SCOPE_OPTIONS_METADATA) === false) return; const methodErrors = []; const controller = { name: props.controller.name, instance: props.controller.instance, constructor: props.controller.metatype, prototype: Object.getPrototypeOf(props.controller.instance), prefixes: (() => { const value = Reflect.getMetadata(constants_1.PATH_METADATA, props.controller.metatype); if (typeof value === "string") return [value]; else if (value.length === 0) return [""]; else return value; })(), versions: props.config.versioning ? VersioningStrategy_1.VersioningStrategy.cast(Reflect.getMetadata(constants_1.VERSION_METADATA, props.controller.metatype)) : undefined, modulePrefix: props.modulePrefix, }; for (const mk of getOwnPropertyNames(controller.prototype).filter((key) => key !== "constructor" && typeof controller.prototype[key] === "function")) { const errorMessages = []; visitMethod({ config: props.config, operators: props.operators, controller, method: { key: mk, value: controller.prototype[mk], }, report: (msg) => errorMessages.push(msg), }); if (errorMessages.length) { const file = yield (0, get_function_location_1.default)(controller.prototype[mk]); methodErrors.push(Object.assign(Object.assign({ name: mk, messages: errorMessages }, file), { source: path_1.default.relative(process.cwd(), file.source.replace("file:///", "")) })); } } if (methodErrors.length) props.errors.push({ name: controller.name, methods: methodErrors, }); }); const visitMethod = (props) => { var _a, _b, _c; const route = Reflect.getMetadata("nestia/WebSocketRoute", props.method.value); if ((() => { const _io0 = input => Array.isArray(input.paths) && input.paths.every(elem => "string" === typeof elem); return input => "object" === typeof input && null !== input && _io0(input); })()(route) === false) return; const parameters = ((_a = Reflect.getMetadata("nestia/WebSocketRoute/Parameters", props.controller.prototype, props.method.key)) !== null && _a !== void 0 ? _a : []).sort((a, b) => a.index - b.index); // acceptor must be if (parameters.some((p) => p.category === "acceptor") === false) return props.report("@WebSocketRoute.Acceptor() decorated parameter must be."); // length of parameters must be fulfilled if (parameters.length !== props.method.value.length) return props.report([ "Every parameters must be one of below:", " - @WebSocketRoute.Acceptor()", " - @WebSocketRoute.Driver()", " - @WebSocketRoute.Header()", " - @WebSocketRoute.Param()", " - @WebSocketRoute.Query()", ].join("\n")); const versions = VersioningStrategy_1.VersioningStrategy.merge(props.config.versioning)([ ...((_b = props.controller.versions) !== null && _b !== void 0 ? _b : []), ...VersioningStrategy_1.VersioningStrategy.cast(Reflect.getMetadata(constants_1.VERSION_METADATA, props.method.value)), ]); for (const v of versions) for (const cp of wrapPaths(props.controller.prefixes)) for (const mp of wrapPaths(route.paths)) { const parser = new path_parser_1.Path("/" + [ (_c = props.config.globalPrefix) !== null && _c !== void 0 ? _c : "", v, props.controller.modulePrefix, cp, mp, ] .filter((str) => !!str.length) .join("/") .split("/") .filter((str) => str.length) .join("/")); const pathParams = parameters.filter((p) => p.category === "param"); if (parser.params.length !== pathParams.length) { props.report([ `Path "${parser}" must have same number of parameters with @WebSocketRoute.Param()`, ` - path: ${JSON.stringify(parser.params)}`, ` - arguments: ${JSON.stringify(pathParams.map((p) => p.field))}`, ].join("\n")); continue; } const meet = pathParams .map((p) => { const has = parser.params.includes(p.field); if (has === false) props.report(`Path "${parser}" must have parameter "${p.field}" with @WebSocketRoute.Param()`); return has; }) .every((b) => b); if (meet === false) continue; props.operators.push({ parser, handler: (input) => __awaiter(void 0, void 0, void 0, function* () { const args = []; try { for (const p of parameters) if (p.category === "acceptor") args.push(input.acceptor); else if (p.category === "driver") args.push(input.acceptor.getDriver()); else if (p.category === "header") { const error = p.validate(input.acceptor.header); if (error !== null) throw error; args.push(input.acceptor.header); } else if (p.category === "param") args.push(p.assert(input.params[p.field])); else if (p.category === "query") { const query = p.validate(new URLSearchParams(input.acceptor.path.indexOf("?") !== -1 ? input.acceptor.path.split("?")[1] : "")); if (query instanceof Error) throw query; args.push(query); } } catch (exp) { yield input.acceptor.reject(1003, exp instanceof Error ? JSON.stringify(Object.assign({}, exp)) : "unknown error"); return; } yield props.method.value.call(props.controller.instance, ...args); }), }); } }; const wrapPaths = (value) => (value.length === 0 ? [""] : value); const getOwnPropertyNames = (prototype) => { const result = new Set(); const iterate = (m) => { if (m === null) return; for (const k of Object.getOwnPropertyNames(m)) result.add(k); iterate(Object.getPrototypeOf(m)); }; iterate(prototype); return Array.from(result); }; //# sourceMappingURL=WebSocketAdaptor.js.map