"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.errorCatchingMiddleware = exports.validatorMiddleware = exports.InputParameters = void 0;
const crypto_1 = require("crypto");
const ioredis_1 = require("ioredis");
const cache_1 = require("../cache");
const metrics_1 = require("../metrics");
const util_1 = require("../metrics/util");
const util_2 = require("../util");
const error_1 = require("./error");
const input_params_1 = require("./input-params");
var input_params_2 = require("./input-params");
Object.defineProperty(exports, "InputParameters", { enumerable: true, get: function () { return input_params_2.InputParameters; } });
const errorCatcherLogger = (0, util_2.makeLogger)('ErrorCatchingMiddleware');
const validatorMiddleware = (adapter) => (rawRequest, reply, done) => {
    const req = rawRequest;
    if (req.headers['content-type'] !== 'application/json') {
        throw new error_1.AdapterInputError({
            message: 'Content type not "application/json", returning 400',
            statusCode: 400,
        });
    }
    // We can restrict usage of the raw request body everywhere in the framework
    // by setting its type to EmptyBody, and we cast here (and only here)
    const requestBody = req.body;
    // Assign empty object to data if it does not exist in the request
    requestBody.data = requestBody.data ?? {};
    // Make endpoints case insensitive
    const endpointParam = requestBody.endpoint?.toLowerCase() ||
        requestBody.data?.endpoint?.toLowerCase() ||
        adapter.defaultEndpoint;
    if (!endpointParam) {
        throw new error_1.AdapterInputError({
            message: `Request body does not specify an endpoint, and there is no default endpoint configured for this adapter.`,
            statusCode: 400,
        });
    }
    const endpoint = adapter.endpointsMap[endpointParam];
    if (!endpoint) {
        throw new error_1.AdapterInputError({
            message: `Adapter does not have a "${endpointParam}" endpoint.`,
            statusCode: 404,
        });
    }
    // Validate the incoming data and normalize
    (0, input_params_1.validateOverrides)(requestBody.data);
    const validatedData = endpoint.inputParameters.validateInput(requestBody.data);
    req.requestContext = {
        cacheKey: '',
        data: validatedData,
        endpointName: endpoint.name,
    };
    // We do it afterwards so the custom routers can have a request with a requestContext fulfilled (sans transportName, ofc)
    req.requestContext.transportName = endpoint.getTransportNameForRequest(req, adapter.config.settings);
    // Custom input validation defined in the EA
    const error = endpoint.customInputValidation && endpoint.customInputValidation(req, adapter.config.settings);
    if (error) {
        throw error;
    }
    // Run any request transforms that might have been defined in the adapter.
    // This is the last time modifications are supposed to happen to the request.
    endpoint.runRequestTransforms(req, adapter.config.settings);
    if (adapter.config.settings.METRICS_ENABLED &&
        adapter.config.settings.EXPERIMENTAL_METRICS_ENABLED) {
        // Add metrics meta which includes feedId to the request
        // Perform after overrides to maintain consistent Feed IDs across the same adapter
        const metrics = (0, util_1.getMetricsMeta)({
            inputParameters: endpoint.inputParameters,
            adapterSettings: adapter.config.settings,
        }, validatedData);
        req.requestContext = { ...req.requestContext, meta: { metrics } };
    }
    // Now that all the transformations have been applied, all that's left is calculating the cache key
    if (endpoint.cacheKeyGenerator) {
        let cacheKey;
        cacheKey = endpoint.cacheKeyGenerator(req.requestContext.data);
        if (cacheKey.length > adapter.config.settings.MAX_COMMON_KEY_SIZE) {
            errorCatcherLogger.warn(`Generated custom cache key for adapter request is bigger than the MAX_COMMON_KEY_SIZE and will be truncated`);
            const shasum = (0, crypto_1.createHash)('sha1');
            shasum.update(cacheKey);
            cacheKey = shasum.digest('base64');
        }
        const cachePrefix = adapter.config.settings.CACHE_PREFIX
            ? `${adapter.config.settings.CACHE_PREFIX}-`
            : '';
        req.requestContext.cacheKey = `${cachePrefix}${cacheKey}`;
    }
    else {
        const transportName = req.requestContext.transportName;
        req.requestContext.cacheKey = (0, cache_1.calculateCacheKey)({
            data: req.requestContext.data,
            adapterName: adapter.name,
            endpointName: endpoint.name,
            transportName,
            adapterSettings: adapter.config.settings,
        });
    }
    done();
};
exports.validatorMiddleware = validatorMiddleware;
const errorCatchingMiddleware = (err, req, res) => {
    // Add adapter or generic error to request meta for metrics use
    // There's a chance we have no request context if there was an error during input validation,
    // but we still want to include error in meta for metrics
    if (req.requestContext) {
        req.requestContext.meta = { ...req.requestContext?.meta, error: err };
    }
    else {
        const errorLabel = 'inputValidationError';
        req.requestContext = {
            cacheKey: errorLabel,
            data: undefined,
            endpointName: errorLabel,
            transportName: errorLabel,
            meta: { error: err },
        };
    }
    // Add the request context to the error so that we can check things like incoming params, endpoint, etc
    const errorWithContext = {
        ...req.requestContext,
        error: {
            name: err.name,
            stack: err.stack,
            message: err.message,
        },
        reqBody: req.body,
    };
    if (err instanceof error_1.AdapterTimeoutError) {
        // AdapterTimeoutError are somewhat expected when the adapter doesn't find a response in the cache within the specified polling interval
        // This is common on startup so logging these errors as debug to help alleviate logs getting flooded in the beginning
        (0, util_2.censorLogs)(() => errorCatcherLogger.debug(errorWithContext));
        res.status(err.statusCode).send(err.toJSONResponse());
    }
    else if (err instanceof error_1.AdapterError) {
        // We want to log these as warn, because although they are to be expected, NOPs should
        // Only use "correct" job specs and therefore not hit adapters with invalid requests.
        (0, util_2.censorLogs)(() => errorCatcherLogger.warn(errorWithContext));
        res.status(err.statusCode).send(err.toJSONResponse());
    }
    else if (err instanceof ioredis_1.ReplyError) {
        // Native ioredis error
        (0, util_2.censorLogs)(() => errorCatcherLogger.warn(errorWithContext));
        const replyError = err;
        if (process.env['METRICS_ENABLED']) {
            (0, metrics_1.recordRedisCommandMetric)(metrics_1.CMD_SENT_STATUS.FAIL, replyError.command?.name);
        }
        res.status(500).send(replyError.message || 'There was an unexpected error with the Redis cache');
    }
    else if (err.name === 'FastifyError') {
        (0, util_2.censorLogs)(() => errorCatcherLogger.error(errorWithContext));
        res.status(err.statusCode).send(err.message);
    }
    else {
        (0, util_2.censorLogs)(() => errorCatcherLogger.error(errorWithContext));
        res.status(500).send(err.message || 'There was an unexpected error in the adapter.');
    }
};
exports.errorCatchingMiddleware = errorCatchingMiddleware;
//# sourceMappingURL=index.js.map