"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HttpWorkflowListener = exports.HttpWorkflowListenerOptions = void 0;
const tslib_1 = require("tslib");
function __assignType(fn, args) {
    fn.__type = args;
    return fn;
}
const core_1 = require("@deepkit/core");
const event_1 = require("@deepkit/event");
const http_1 = require("@deepkit/http");
/*@ts-ignore*/
var { __ΩLoggerInterface } = require('@deepkit/logger');
const type_1 = require("@deepkit/type");
const helpers_1 = require("../telemetry/otel/helpers");
class HttpWorkflowListenerOptions {
    constructor() {
        this.logRequestBodyOnError = process.env.NODE_ENV !== 'production';
    }
}
exports.HttpWorkflowListenerOptions = HttpWorkflowListenerOptions;
HttpWorkflowListenerOptions.__type = ['logRequestBodyOnError', function () { return process.env.NODE_ENV !== 'production'; }, 'HttpWorkflowListenerOptions', ')3!>"5w#'];
class HttpWorkflowListener {
    constructor(router, logger, options) {
        this.router = router;
        this.logger = logger;
        this.options = options;
    }
    // this is a copy of the default onRoute, with the timeouts removed, because
    // we sometimes use middleware to easily express various security requirements, and allowing a timeout
    // during a time of delayed response due to any number of factors (such as DB load) is a security risk
    // having a priority of 99 and dispatching a "next" ensures the default handler doesn't do anything
    async onRouteOverride(event) {
        if (event.sent)
            return;
        if (event.hasNext())
            return;
        try {
            const resolved = this.router.resolveRequest(event.request);
            if (resolved) {
                event.request.uploadedFiles = resolved.uploadedFiles;
                if (resolved.middlewares) {
                    const middlewares = resolved.middlewares(event.injectorContext);
                    if (middlewares.length) {
                        await (0, core_1.asyncOperation)(__assignType(async (resolve, reject) => {
                            function finish() {
                                resolve(undefined);
                            }
                            finish.__type = ['finish', 'P"/!'];
                            event.response.once('finish', finish);
                            let i = -1;
                            async function next() {
                                i++;
                                if (i >= middlewares.length) {
                                    event.response.off('finish', finish);
                                    resolve(undefined);
                                    return;
                                }
                                try {
                                    // eslint-disable-next-line @typescript-eslint/no-explicit-any
                                    await middlewares[i].fn(event.request, event.response, __assignType((error) => {
                                        if (error) {
                                            event.response.off('finish', finish);
                                            reject(error);
                                        }
                                        else {
                                            next();
                                        }
                                    }, ['error', '', 'P"2!8"/"']));
                                }
                                catch (error) {
                                    reject(error);
                                }
                            }
                            next.__type = ['next', 'P"/!'];
                            await next();
                        }, ['resolve', 'reject', '', 'P"2!"2""/#']));
                    }
                }
                event.routeFound(resolved.routeConfig, resolved.parameters);
            }
        }
        catch (error) {
            this.logger.error('Could not resolve request', error);
            event.notFound();
        }
    }
    // the default behavior for parameter resolver and controllers encountering HttpError
    // is to return a JSONResponse with the error message in the "message" field. we want to use
    // the "error" field, so override them both here
    onParametersFailed(event) {
        if (event.error instanceof type_1.ValidationError) {
            this.logger.warn('Request parameters error', {
                type: 'validation',
                err: event.error.message,
                requestBody: this.getRequestBodyForErrorLogging(event.request)
            });
            // JSON parse errors should be treated as 400s
        }
        else if (event.error instanceof SyntaxError && event.error.message.includes('JSON')) {
            this.logger.warn('Request parameters error ', { type: 'json', requestBody: this.getRequestBodyForErrorLogging(event.request) });
            event.error = new http_1.HttpBadRequestError('Failed to parse JSON');
            // all other errors should include the request body as error context
        }
        else if (!(event.error instanceof http_1.HttpError)) {
            this.decorateErrorWithRequest(event.error, event.request);
        }
        return this.handleHttpErrorFromEvent(event);
    }
    onControllerError(event) {
        // ValidationErrors should only render as HTTP 400 when it's in the query or body, which is handled by
        // parameters processing. validation errors within the controller body are internal errors and should be treated
        // as 500s
        if (event.error instanceof type_1.ValidationError) {
            const originalError = event.error;
            event.error = new Error('Validation failure within controller');
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            event.error.cause = originalError;
        }
        if (event.sent && event.response.statusCode < 400) {
            if (event.response.chunkedEncoding) {
                event.response.write('\n\nINTERNAL SERVER ERROR\n\n', () => {
                    event.response.destroy();
                });
            }
            else {
                // not safe to write more data if we're not using chunked encoding
                event.response.destroy();
            }
            this.logger.error('Controller error after response sent', event.error);
            return;
        }
        this.handleHttpErrorFromEvent(event);
        if (!event.sent) {
            this.decorateErrorWithRequest(event.error, event.request);
        }
    }
    handleHttpErrorFromEvent(event) {
        if (event.error instanceof http_1.HttpError) {
            (0, helpers_1.setSpanAttributes)({ 'error.message': event.error.message });
            this.logger.warn('Request processing error', { 'error.message': event.error.message });
            event.stopImmediatePropagation();
            event.send(new http_1.JSONResponse({
                error: event.error.message
            }, event.error.httpCode).disableAutoSerializing());
        }
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    decorateErrorWithRequest(error, request) {
        error.context = {
            ...error.context,
            requestBody: this.getRequestBodyForErrorLogging(request)
        };
    }
    getRequestBodyForErrorLogging(request) {
        if (!this.options.logRequestBodyOnError)
            return undefined;
        return request.body?.toString().slice(0, 4096);
    }
}
exports.HttpWorkflowListener = HttpWorkflowListener;
HttpWorkflowListener.__type = [() => http_1.HttpRouter, 'router', () => __ΩLoggerInterface, 'logger', () => HttpWorkflowListenerOptions, 'options', 'constructor', () => http_1.httpWorkflow.onRoute.event, 'event', 'onRouteOverride', () => http_1.httpWorkflow.onParametersFailed.event, 'onParametersFailed', () => http_1.httpWorkflow.onControllerError.event, 'onControllerError', () => http_1.httpWorkflow.onParametersFailed.event, () => http_1.httpWorkflow.onControllerError.event, 'handleHttpErrorFromEvent', 'error', () => http_1.HttpRequest, 'request', 'decorateErrorWithRequest', () => http_1.HttpRequest, 'getRequestBodyForErrorLogging', 'HttpWorkflowListener', 'PP7!2"<n#2$<P7%2&<"0\'Pi(2)"0*Pi+2)"0,Pi-2)"0.PPi/i0J2)"01;P"22P7324"05;PP7624"07;5w8'];
tslib_1.__decorate([
    event_1.eventDispatcher.listen(http_1.httpWorkflow.onRoute, 99),
    tslib_1.__metadata("design:type", Function),
    tslib_1.__metadata("design:paramtypes", [Object]),
    tslib_1.__metadata("design:returntype", Promise)
], HttpWorkflowListener.prototype, "onRouteOverride", null);
tslib_1.__decorate([
    event_1.eventDispatcher.listen(http_1.httpWorkflow.onParametersFailed),
    tslib_1.__metadata("design:type", Function),
    tslib_1.__metadata("design:paramtypes", [Object]),
    tslib_1.__metadata("design:returntype", void 0)
], HttpWorkflowListener.prototype, "onParametersFailed", null);
tslib_1.__decorate([
    event_1.eventDispatcher.listen(http_1.httpWorkflow.onControllerError),
    tslib_1.__metadata("design:type", Function),
    tslib_1.__metadata("design:paramtypes", [Object]),
    tslib_1.__metadata("design:returntype", void 0)
], HttpWorkflowListener.prototype, "onControllerError", null);
//# sourceMappingURL=workflow.js.map