"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.forConstraints = exports.forConstraint = exports.isConstraints = exports.isConstraint = void 0;
const lodash_1 = __importDefault(require("lodash"));
const strings_1 = require("../utils/strings");
const logger_1 = __importDefault(require("../logger"));
function isStringArray(arr) {
    return lodash_1.default.isArray(arr) && lodash_1.default.every(arr, (val) => lodash_1.default.isString(val));
}
function isConstraint(val) {
    if (!lodash_1.default.isObject(val)) {
        return false;
    }
    const constraint = val;
    if (!("type" in constraint) || !lodash_1.default.isString(constraint.type)) {
        return false;
    }
    if ("optional" in constraint
        && !lodash_1.default.isUndefined(constraint.optional)
        && !lodash_1.default.isBoolean(constraint.optional)) {
        return false;
    }
    if ("allowed" in constraint
        && !lodash_1.default.isUndefined(constraint.allowed)
        && !isStringArray(constraint.allowed)) {
        return false;
    }
    if ("min" in constraint
        && !lodash_1.default.isUndefined(constraint.min)
        && !lodash_1.default.isNumber(constraint.min)) {
        return false;
    }
    if ("max" in constraint
        && !lodash_1.default.isUndefined(constraint.max)
        && !lodash_1.default.isNumber(constraint.max)) {
        return false;
    }
    // noinspection RedundantIfStatementJS
    if ("regex" in constraint
        && !lodash_1.default.isUndefined(constraint.regex)
        && !lodash_1.default.isRegExp(constraint.regex)) {
        return false;
    }
    return true;
}
exports.isConstraint = isConstraint;
function isConstraints(constraints) {
    if (!lodash_1.default.isObject(constraints)) {
        return false;
    }
    return lodash_1.default.every(constraints, (constraint, key) => lodash_1.default.isString(key) && isConstraint(constraint));
}
exports.isConstraints = isConstraints;
// TODO: sanitize input for further processing as specified by constraints (correct types, trimming, etc.)
function isValidBoolean(value) {
    return lodash_1.default.isBoolean(value) || value === 'true' || value === 'false';
}
function isValidNumber(constraint, value) {
    if (lodash_1.default.isString(value)) {
        value = strings_1.parseInteger(value);
    }
    if (!lodash_1.default.isNumber(value)) {
        return false;
    }
    if (lodash_1.default.isNaN(value) || !lodash_1.default.isFinite(value)) {
        return false;
    }
    if (lodash_1.default.isNumber(constraint.min) && value < constraint.min) {
        return false;
    }
    // noinspection RedundantIfStatementJS
    if (lodash_1.default.isNumber(constraint.max) && value > constraint.max) {
        return false;
    }
    return true;
}
function isValidEnum(constraint, value) {
    if (!lodash_1.default.isString(value)) {
        return false;
    }
    return lodash_1.default.indexOf(constraint.allowed, value) >= 0;
}
function isValidString(constraint, value) {
    if (!constraint.regex) {
        throw new Error("String constraints must have regex set: " + constraint);
    }
    if (!lodash_1.default.isString(value)) {
        return false;
    }
    const trimmed = value.trim();
    return (trimmed === '' && constraint.optional) || constraint.regex.test(trimmed);
}
function isValid(constraint, acceptUndefined, value) {
    if (value === undefined) {
        return acceptUndefined || constraint.optional === true;
    }
    switch (constraint.type) {
        case 'boolean':
            return isValidBoolean(value);
        case 'number':
            return isValidNumber(constraint, value);
        case 'enum':
            return isValidEnum(constraint, value);
        case 'string':
            return isValidString(constraint, value);
    }
    logger_1.default.tag('validation').error('No validation method for constraint type: {}', constraint.type);
    return false;
}
function areValid(constraints, acceptUndefined, values) {
    const fields = new Set(Object.keys(constraints));
    for (const field of fields) {
        if (!isValid(constraints[field], acceptUndefined, values[field])) {
            return false;
        }
    }
    for (const field of Object.keys(values)) {
        if (!fields.has(field)) {
            logger_1.default.tag('validation').error('Validation failed: No constraint for field: {}', field);
            return false;
        }
    }
    return true;
}
function forConstraint(constraint, acceptUndefined) {
    return ((value) => isValid(constraint, acceptUndefined, value));
}
exports.forConstraint = forConstraint;
function forConstraints(constraints, acceptUndefined) {
    return ((values) => areValid(constraints, acceptUndefined, values));
}
exports.forConstraints = forConstraints;
//# sourceMappingURL=validator.js.map