UNPKG

dino-express

Version:

DinO enabled REST framework based on express

253 lines 10.1 kB
"use strict"; // Copyright 2018 Quirino Brizi [quirino.brizi@gmail.com] // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. Object.defineProperty(exports, "__esModule", { value: true }); exports.RouteHandler = void 0; const dino_core_1 = require("dino-core"); const DecoratorMetadataRegistry_1 = require("./decorators/registry/DecoratorMetadataRegistry"); const ApplicationEvent_1 = require("./events/ApplicationEvent"); const http_exception_1 = require("./exception/http.exception"); const Helper_1 = require("./Helper"); const Interface_1 = require("./Interface"); const request_body_parser_1 = require("./request/request.body.parser"); // const DEFAULT_RESPONSE = { content: { 'application/json': { schema: { type: 'object', properties: { message: { type: 'string' } } } } } }; /** * Handles the route subject to the client request, it does act as a express middleware. * */ class RouteHandler { path; serverType; responseValidator; applicationContext; requestBodyParser; api; environment; eventProducer; requestHandler; constructor(path, api, applicationContext, responseValidator, environment, eventProducer) { this.api = api; this.path = path; this.environment = environment; this.responseValidator = responseValidator; this.applicationContext = applicationContext; this.requestBodyParser = new request_body_parser_1.RequestBodyParser(environment); this.serverType = this.environment.getOrDefault('dino:server:type', 'express'); this.eventProducer = eventProducer; // throws error if operationId is not defined for an API this.validateOperationId(); } /** * Handle the request from the client forwarding the request to the Interface that implement the * method defined on the API operation identifier. * * @param {any} req the express request object * @param {any} res the express response object * @param {Function} next a callback to propagate the handling action to the next middleware. * * @public */ handle(req, res, next) { this.eventProducer.send(ApplicationEvent_1.ApplicationEvent.create('workloadStarted')); this.getHandler(req.method).then((requestHandler) => { const answer = this.doHandleRequest(req, requestHandler); void this.handleApiResponse(answer, res, next); }).catch(e => { dino_core_1.Logger.error(`Unable to complete transaction ${e.message}`); this.handleError(e, res, next); }); } doHandleRequest(req, requestHandler) { const apiHandlerType = Helper_1.Helper.getVariable(this.environment, 'dino:api:handler:type', 'proxy'); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const operationId = this.api.operationId; const operationMetadata = DecoratorMetadataRegistry_1.decoratorMetadataRegistry.getDefinedOperationMetadata(this.path, [ operationId, req.method.toLowerCase() ]); const fcn = operationMetadata !== undefined ? operationMetadata.fcn : operationId; let answer; if (apiHandlerType === 'proxy') { const params = this.collectParameters(req); const body = this.requestBodyParser.parse(req); params.req = req; params.body = body; params.context = Helper_1.Helper.ensureValue(req.context, {}); answer = requestHandler[fcn](params); } else { // propagate the entire express request answer = requestHandler[fcn](req); } return answer; } /** * Collect the parameters defined as part of the OpenAPI definition. * @param {Object} req the express request */ collectParameters(req) { const parameters = Helper_1.Helper.ensureValue(this.api.parameters, []); if (parameters.reduce === undefined) { return []; } return parameters.reduce((acc, param) => { const name = param.name.replace(/-/g, '_'); switch (param.in) { case 'path': acc[name] = Helper_1.Helper.ensureValue(req.params[param.name], param.schema?.default); break; case 'query': acc[name] = Helper_1.Helper.ensureValue(req.query[param.name], param.schema?.default); break; case 'header': acc[name] = Helper_1.Helper.ensureValue(req.headers[param.name], param.schema?.default); break; } return acc; }, {}); } async handleApiResponse(answer, res, next) { if (answer === undefined) { this.sendResponse(answer, res, next); return; } if (Helper_1.Helper.isAPromise(answer)) { await answer .then((response) => { this.sendResponse(response, res, next); }) .catch((error) => { this.handleError(error, res, next); }); } else { this.sendResponse(answer, res, next); } } handleError(error, _response, next) { this.eventProducer.send(ApplicationEvent_1.ApplicationEvent.create('workloadError', { errorName: error?.name, errorMessage: error?.message })); if (!dino_core_1.ObjectHelper.instanceOf(error, http_exception_1.HttpException)) { next(http_exception_1.HttpException.create(500, error?.message ?? 'Unknown error')); } next(error); } sendResponse(answer, response, next) { this.eventProducer.send(ApplicationEvent_1.ApplicationEvent.create('workloadCompleted')); const statusCode = Object.keys(this.api.responses ?? {}).find((sc) => sc.startsWith('2')) ?? '200'; response.status(parseInt(statusCode, 10)); response.setHeader('x-server-type', this.serverType); if (answer === undefined) { next(); return; } this.normaliseResponse(answer, response); if (this.responseValidator !== undefined) { try { this.responseValidator({ body: response.body, headers: response.getHeaders(), statusCode: response.statusCode }); } catch (e) { this.handleError(e, response, next); return; } } next(); } normaliseResponse(answer, response) { let setAnswerAsBody = true; if (typeof answer === 'string') { response.body = answer; } else { const temp = answer; if (dino_core_1.ObjectHelper.isDefined(temp.body)) { response.body = temp.body; setAnswerAsBody = false; } if (dino_core_1.ObjectHelper.isDefined(temp.headers)) { Object.keys(temp.headers ?? {}).forEach((key) => { response.setHeader(key, temp.headers[key]); }); } if (setAnswerAsBody) { response.body = answer; } } } /** * * @param method the request method * @deprecated use getHandler */ async ensureInterface(method) { await this.getHandler(method); } /** * Lookup the handler for the current API request defined either by method or operationId . * @returns {Interface} * * @private */ async getHandler(method) { if (this.requestHandler !== undefined) { return this.requestHandler; } // eslint-disable-next-line @typescript-eslint/no-non-null-assertion let handler; const operationId = this.api.operationId ?? ''; const pathOperations = DecoratorMetadataRegistry_1.decoratorMetadataRegistry.getDefinedPathMetadata(this.path); const hasOperation = pathOperations?.operations.find((operation) => [operationId, method.toLowerCase()].includes(operation.operation.toLowerCase())) !== undefined; if (pathOperations !== undefined && hasOperation) { handler = this.applicationContext.resolve(pathOperations.handler); if (handler !== undefined) { return handler; } } const handlers = this.applicationContext.resolveAll(Interface_1.Interface); DecoratorMetadataRegistry_1.decoratorMetadataRegistry.getDefinedPaths().forEach((interfaceName) => { handlers.push(this.applicationContext.resolve(interfaceName)); }); this.requestHandler = (handlers ?? []).find((handler) => { try { return Reflect.has(handler, operationId); } catch (e) { return false; } }); if (this.requestHandler === undefined) { throw Error(`there are no interface defined with operationId ${operationId}`); } return this.requestHandler; } validateOperationId() { if (this.api.operationId === undefined) { throw new Error('operationId is not defined'); } } /** * Define tge scope as transient so that a new instance will be created for every request * * @static * @public */ static scope() { return 'transient'; } } exports.RouteHandler = RouteHandler; //# sourceMappingURL=RouteHandler.js.map