dino-express
Version:
DinO enabled REST framework based on express
253 lines • 10.1 kB
JavaScript
"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