UNPKG

@tsed/common

Version:
292 lines • 9.85 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PlatformHandler = void 0; const tslib_1 = require("tslib"); const core_1 = require("@tsed/core"); const di_1 = require("@tsed/di"); const mvc_1 = require("../../mvc"); const PlatformResponseFilter_1 = require("../../platform-response-filter/services/PlatformResponseFilter"); const HandlerContext_1 = require("../domain/HandlerContext"); const ParamValidationError_1 = require("../errors/ParamValidationError"); const createHandlerMetadata_1 = require("../utils/createHandlerMetadata"); const renderView_1 = require("../utils/renderView"); const setResponseHeaders_1 = require("../utils/setResponseHeaders"); function shouldBeSent(data) { return Buffer.isBuffer(data) || core_1.isBoolean(data) || core_1.isNumber(data) || core_1.isString(data) || data === null; } function shouldBeSerialized(data) { return !(core_1.isStream(data) || shouldBeSent(data) || data === undefined); } /** * Platform Handler abstraction layer. Wrap original class method to a pure platform handler (Express, Koa, etc...). * @platform */ let PlatformHandler = class PlatformHandler { constructor(injector) { this.injector = injector; } /** * Create a native middleware based on the given metadata and return an instance of HandlerContext * @param input * @param options */ createHandler(input, options = {}) { const metadata = this.createHandlerMetadata(input, options); this.sortPipes(metadata); return this.createRawHandler(metadata); } createCustomHandler(provider, propertyKey) { const metadata = new mvc_1.HandlerMetadata({ token: provider.provide, target: provider.useClass, type: mvc_1.HandlerType.CUSTOM, propertyKey }); this.sortPipes(metadata); return this.createRawHandler(metadata); } /** * Create handler metadata * @param obj * @param routeOptions */ createHandlerMetadata(obj, routeOptions = {}) { return createHandlerMetadata_1.createHandlerMetadata(this.injector, obj, routeOptions); } /** * Get argument from parameter medata or handler context. * @param type * @param h */ getArg(type, h) { const { $ctx, $ctx: { request, response } } = h; switch (type) { case mvc_1.ParamTypes.NODE_RESPONSE: return $ctx.getRes(); case mvc_1.ParamTypes.NODE_REQUEST: return $ctx.getReq(); case mvc_1.ParamTypes.FILES: return h.getRequest().files; case mvc_1.ParamTypes.RESPONSE: return h.getResponse(); case mvc_1.ParamTypes.REQUEST: return h.getRequest(); case mvc_1.ParamTypes.PLATFORM_RESPONSE: return response; case mvc_1.ParamTypes.PLATFORM_REQUEST: return request; case mvc_1.ParamTypes.NEXT_FN: return h.next; case mvc_1.ParamTypes.ERR: return h.err; case mvc_1.ParamTypes.$CTX: // tsed ctx return $ctx; case mvc_1.ParamTypes.ENDPOINT_INFO: return $ctx.endpoint; case mvc_1.ParamTypes.RESPONSE_DATA: return $ctx.data; case mvc_1.ParamTypes.BODY: return request.body; case mvc_1.ParamTypes.QUERY: return request.query; case mvc_1.ParamTypes.PATH: return request.params; case mvc_1.ParamTypes.HEADER: return request.headers; case mvc_1.ParamTypes.COOKIES: return request.cookies; case mvc_1.ParamTypes.SESSION: return request.session; case mvc_1.ParamTypes.LOCALS: return response.locals; default: return h.request; } } async onCtxRequest(requestOptions) { const { metadata, $ctx } = requestOptions; await metadata.handler($ctx); return this.next(requestOptions); } /** * Call handler when a request his handle * @param requestOptions */ async onRequest(requestOptions) { const { metadata, $ctx, err } = requestOptions; try { const h = new HandlerContext_1.HandlerContext({ $ctx, metadata, args: [], err }); h.args = await this.getArgs(h); await h.callHandler(); if (h.status === HandlerContext_1.HandlerContextStatus.RESOLVED) { // Can be canceled by the handler itself return await this.onSuccess($ctx.data, requestOptions); } } catch (er) { return this.onError(er, requestOptions); } } async onError(er, requestOptions) { const { next, $ctx } = requestOptions; $ctx.data = er; if (!next) { throw er; } return !$ctx.response.isHeadersSent() && next && next(er); } /** * Manage success scenario * @param data * @param requestOptions * @protected */ async onSuccess(data, requestOptions) { const { metadata, $ctx, next } = requestOptions; if ($ctx.request.isAborted() || $ctx.response.isDone()) { return; } // set headers each times that an endpoint is called if (metadata.type === mvc_1.HandlerType.ENDPOINT) { this.setHeaders($ctx); } // call returned middleware if (core_1.isFunction(data) && !core_1.isStream(data)) { return this.callReturnedMiddleware(data, $ctx, next); } if (metadata.isFinal()) { return this.flush(data, $ctx); } return this.next(requestOptions); } /** * Call the returned middleware by the handler. * @param middleware * @param ctx * @param next * @protected */ callReturnedMiddleware(middleware, ctx, next) { return middleware(ctx.getRequest(), ctx.getResponse(), next); } /** * Send the response to the consumer. * @param data * @param ctx * @protected */ async flush(data, ctx) { const { response, endpoint } = ctx; if (endpoint.view) { data = await this.render(data, ctx); } else if (shouldBeSerialized(data)) { data = this.injector.get(mvc_1.ConverterService).serialize(data, { ...endpoint.getResponseOptions(), endpoint: true }); } if (!response.isDone()) { const responseFilter = this.injector.get(PlatformResponseFilter_1.PlatformResponseFilter); response.body(responseFilter.transform(data, ctx)); } } /** * Render the view if the endpoint has a configured view. * @param data * @param ctx * @protected */ async render(data, ctx) { return renderView_1.renderView(data, ctx); } /** * create Raw handler * @param metadata */ createRawHandler(metadata) { switch (metadata.type) { case mvc_1.HandlerType.CUSTOM: return (ctx) => this.onRequest({ metadata, $ctx: ctx }); case mvc_1.HandlerType.RAW_ERR_FN: case mvc_1.HandlerType.RAW_FN: return metadata.handler; default: case mvc_1.HandlerType.ENDPOINT: case mvc_1.HandlerType.MIDDLEWARE: return (request, response, next) => this.onRequest({ $ctx: request.$ctx, next, metadata }); } } /** * Set response headers * @param ctx * @protected */ setHeaders(ctx) { return setResponseHeaders_1.setResponseHeaders(ctx); } next(requestOptions) { const { $ctx, next } = requestOptions; return !$ctx.response.isDone() && next && next(); } /** * Return arguments to call handler * @param h */ async getArgs(h) { const { metadata: { parameters } } = h; return Promise.all(parameters.map((param) => this.mapArg(param, h))); } /** * Sort pipes before calling it * @param metadata */ sortPipes(metadata) { if (!metadata.injectable) { return; } const get = (pipe) => { return this.injector.getProvider(pipe).priority || 0; }; metadata.parameters.forEach((param) => { return (param.pipes = param.pipes.sort((p1, p2) => { return get(p1) < get(p2) ? -1 : get(p1) > get(p2) ? 1 : 0; })); }); } /** * Map argument by calling pipe. * @param metadata * @param h */ async mapArg(metadata, h) { const { injector } = h; const value = this.getArg(metadata.paramType, h); // istanbul ignore next const handleError = async (cb) => { try { return await cb(); } catch (er) { throw ParamValidationError_1.ParamValidationError.from(metadata, er); } }; return metadata.pipes.reduce(async (value, pipe) => { value = await value; return handleError(() => injector.get(pipe).transform(value, metadata)); }, value); } }; PlatformHandler = tslib_1.__decorate([ di_1.Injectable({ scope: di_1.ProviderScope.SINGLETON }), tslib_1.__metadata("design:paramtypes", [di_1.InjectorService]) ], PlatformHandler); exports.PlatformHandler = PlatformHandler; //# sourceMappingURL=PlatformHandler.js.map