@miracledevs/paradigm-express-webapi
Version:
An easy to use MVC WebApi implementation over Express.
200 lines • 11 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.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