UNPKG

@miracledevs/paradigm-express-webapi

Version:

An easy to use MVC WebApi implementation over Express.

200 lines 11 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.ApiRouter = void 0; const express_1 = __importDefault(require("express")); const http_context_1 = require("./shared/http-context"); const http_method_1 = require("./shared/http-method"); const logger_1 = require("./logging/logger"); const controller_type_collection_1 = require("./decorators/controller-type-collection"); const action_type_collection_1 = require("./decorators/action-type-collection"); const action_url_1 = require("./decorators/action-url"); const paradigm_web_di_1 = require("@miracledevs/paradigm-web-di"); const object_type_1 = require("@miracledevs/paradigm-web-di/object-type"); const routing_context_1 = require("./shared/routing-context"); class ApiRouter { constructor(logger, injector) { this._logger = logger !== null && logger !== void 0 ? logger : new logger_1.Logger(); this._injector = injector !== null && injector !== void 0 ? injector : paradigm_web_di_1.DependencyCollection.globalCollection.buildContainer(); this._globalFilters = []; this._routers = new Map(); this._ignoreClosedResponseOnFilters = false; } ignoreClosedResponseOnFilters() { this._ignoreClosedResponseOnFilters = true; } registerGlobalFilter(filter) { this._globalFilters.push(filter); } registerGlobalFilters(filters) { for (const filter of filters) { this._globalFilters.push(filter); } } registerRoutes(application) { for (const controllerType of controller_type_collection_1.ControllerTypeCollection.globalInstance.getControllers()) { for (const actionType of action_type_collection_1.ActionTypeCollection.globalInstance.getForController(controllerType.type.name)) { const routingContext = new routing_context_1.RoutingContext(controllerType, actionType); const route = this.mergeRoute(routingContext); const router = this.getRouter(controllerType, application); const method = this.getMethod(actionType, router); method.call(application, route, (request, response) => __awaiter(this, void 0, void 0, function* () { const httpContext = new http_context_1.HttpContext(request, response); yield this.callAction(httpContext, routingContext); })); this._logger.debug(`Mapping route ${http_method_1.HttpMethod[actionType.descriptor.method]} ${route} to '${routingContext}'.`); } } } getRouter(controllerType, application) { let controllerRoute = controllerType.descriptor.route || ""; if (controllerRoute.endsWith("/")) controllerRoute = controllerRoute.substr(0, controllerRoute.length - 1); if (!this._routers.has(controllerRoute)) { const router = express_1.default.Router(); this._routers.set(controllerRoute, router); application.use(controllerRoute, router); return router; } return this._routers.get(controllerRoute); } callAction(httpContext, routingContext) { var _a, _b; return __awaiter(this, void 0, void 0, function* () { // create a new scoped injector const injector = this._injector.createScopedInjector(ApiRouter.ThreadScope); // join all the filters. const filters = this._globalFilters.concat((_a = routingContext.controllerType.descriptor.filters) !== null && _a !== void 0 ? _a : [], (_b = routingContext.actionType.descriptor.filters) !== null && _b !== void 0 ? _b : []); // resolve the filter instances const filterInstances = filters.map(x => injector.resolve(x)); try { this._logger.debug(`Request received '${httpContext.request.url}'`); // check if the response is still alive. this.checkResponse(httpContext, routingContext); // try to instantiate the controller. const controllerInstance = this.createControllerInstance(routingContext, injector); // try to retrieve the method. const actionMethod = this.getActionMethod(routingContext, controllerInstance); // sets the http context on the controller. controllerInstance.setHttpContext(httpContext); // execute before filters. yield this.executeFilters(filterInstances, httpContext, (f) => __awaiter(this, void 0, void 0, function* () { if (f.beforeExecute) yield f.beforeExecute(httpContext, routingContext); })); // reverses the array to execute filters in the in-to-out order instead of out-to-in that we used for the before events. filterInstances.reverse(); // execute the action itself. const result = yield this.executeMethod(controllerInstance, actionMethod, routingContext, httpContext); // execute the after filters. yield this.executeFilters(filterInstances, httpContext, (f) => __awaiter(this, void 0, void 0, function* () { if (f.afterExecute) yield f.afterExecute(httpContext, routingContext); })); // finish the request if wasn't finished already this.finishRequest(httpContext, result); // log the resulting operation. this._logger.debug(`Action returned with code [${httpContext.response.statusCode}].`); } catch (error) { // log the exception. this._logger.error(error.message); // execute the after filters. yield this.executeFilters(filterInstances, httpContext, (f) => __awaiter(this, void 0, void 0, function* () { if (f.onError) yield f.onError(httpContext, routingContext, error); })); if (!httpContext.closed) { // close with error. httpContext.response.status(500).send(error.message); } } }); } checkResponse(httpContext, routingContext) { if (httpContext.closed) { this._logger.debug(`The response is already closed, the action '${routingContext}' won't be called.`); return; } this._logger.debug(`The action '${routingContext}' will be executed.`); } createControllerInstance(routingContext, injector) { return injector.resolve(routingContext.controllerType.type); } getActionMethod(routingContext, controllerInstance) { return routingContext.actionType.getExecutableMethod(controllerInstance); } executeMethod(controllerInstance, actionMethod, routingContext, httpContext) { return __awaiter(this, void 0, void 0, function* () { const methodArgs = []; if (httpContext.closed) return; if (routingContext.actionType.descriptor.fromBody) methodArgs.push(httpContext.request.body); const parameters = this.getParametersArray(routingContext.actionType, httpContext.request); return yield actionMethod.apply(controllerInstance, methodArgs.concat(parameters)); }); } finishRequest(httpContext, result) { if (httpContext.closed) return; httpContext.response.status(200).send(result || {}); } mergeRoute(routingContext) { var controllerRoute = routingContext.controllerType.descriptor.route || ""; var actionRoute = routingContext.actionType.descriptor.route || ""; return `${controllerRoute}${!controllerRoute.endsWith("/") && !actionRoute.startsWith("/") ? "/" : ""}${actionRoute}`; } getMethod(actionType, router) { switch (actionType.descriptor.method) { case http_method_1.HttpMethod.GET: return router.get; case http_method_1.HttpMethod.POST: return router.post; case http_method_1.HttpMethod.PUT: return router.put; case http_method_1.HttpMethod.DELETE: return router.delete; } } executeFilters(filterInstances, httpContext, action) { return __awaiter(this, void 0, void 0, function* () { if (!filterInstances || filterInstances.length === 0) return; for (const filterInstance of filterInstances) { if (httpContext.closed && !this._ignoreClosedResponseOnFilters) break; yield action(filterInstance); } }); } getParametersArray(actionType, request) { return actionType.actionUrl.parameters.map((routeParameter, index) => { const parameter = (routeParameter.parameterType === action_url_1.RouteParameterType.Segment ? request.params[routeParameter.name] : request.query[routeParameter.name]); switch (actionType.parameters[index]) { case Number: return parseFloat(parameter); case Boolean: return parameter.toLowerCase() === 'true' || parameter.toLowerCase() === 'yes' || parameter.toLowerCase() === '1'; case Date: return new Date(Date.parse(parameter)); case String: return parameter; default: throw new Error(`The parameter '${routeParameter.name}' is of type '${(0, object_type_1.getObjectTypeName)(actionType.parameters[index])}'. Only Number, String, Date or Boolean are allowed for route or query string parameters.`); } }); } } exports.ApiRouter = ApiRouter; ApiRouter.ThreadScope = 'thread'; //# sourceMappingURL=api-router.js.map