import validator from 'validator';
import { PhoneNumberUtil } from 'google-libphonenumber';

/**
 * This metadata contains validation rules.
 */
class ValidationMetadata {
    // -------------------------------------------------------------------------
    // Constructor
    // -------------------------------------------------------------------------
    constructor(args) {
        /**
         * Validation groups used for this validation.
         */
        this.groups = [];
        /**
         * Indicates if validation must be performed always, no matter of validation groups used.
         */
        this.always = false;
        /**
         * Specifies if validated value is an array and each of its item must be validated.
         */
        this.each = false;
        /*
         * A transient set of data passed through to the validation result for response mapping
         */
        this.context = undefined;
        this.type = args.type;
        this.target = args.target;
        this.propertyName = args.propertyName;
        this.constraints = args.constraints;
        this.constraintCls = args.constraintCls;
        this.validationTypeOptions = args.validationTypeOptions;
        if (args.validationOptions) {
            this.message = args.validationOptions.message;
            this.groups = args.validationOptions.groups;
            this.always = args.validationOptions.always;
            this.each = args.validationOptions.each;
            this.context = args.validationOptions.context;
        }
    }
}

/**
 * Used to transform validation schemas to validation metadatas.
 */
class ValidationSchemaToMetadataTransformer {
    transform(schema) {
        const metadatas = [];
        Object.keys(schema.properties).forEach(property => {
            schema.properties[property].forEach(validation => {
                const validationOptions = {
                    message: validation.message,
                    groups: validation.groups,
                    always: validation.always,
                    each: validation.each
                };
                const args = {
                    type: validation.type,
                    target: schema.name,
                    propertyName: property,
                    constraints: validation.constraints,
                    validationTypeOptions: validation.options,
                    validationOptions: validationOptions
                };
                metadatas.push(new ValidationMetadata(args));
            });
        });
        return metadatas;
    }
}

/**
 * Gets metadata storage.
 * Metadata storage follows the best practices and stores metadata in a global variable.
 */
function getMetadataStorage() {
    if (typeof window !== "undefined") {
        window.global = window;
    }
    if (!global.classValidatorMetadataStorage)
        global.classValidatorMetadataStorage = new MetadataStorage();
    return global.classValidatorMetadataStorage;
}
/**
 * Storage all metadatas.
 */
class MetadataStorage {
    constructor() {
        // -------------------------------------------------------------------------
        // Private properties
        // -------------------------------------------------------------------------
        this.validationMetadatas = [];
        this.constraintMetadatas = [];
    }
    get hasValidationMetaData() {
        return !!this.validationMetadatas.length;
    }
    // -------------------------------------------------------------------------
    // Public Methods
    // -------------------------------------------------------------------------
    /**
     * Adds a new validation metadata.
     */
    addValidationSchema(schema) {
        const validationMetadatas = new ValidationSchemaToMetadataTransformer().transform(schema);
        validationMetadatas.forEach(validationMetadata => this.addValidationMetadata(validationMetadata));
    }
    /**
     * Adds a new validation metadata.
     */
    addValidationMetadata(metadata) {
        this.validationMetadatas.push(metadata);
    }
    /**
     * Adds a new constraint metadata.
     */
    addConstraintMetadata(metadata) {
        this.constraintMetadatas.push(metadata);
    }
    /**
     * Groups metadata by their property names.
     */
    groupByPropertyName(metadata) {
        const grouped = {};
        metadata.forEach(metadata => {
            if (!grouped[metadata.propertyName])
                grouped[metadata.propertyName] = [];
            grouped[metadata.propertyName].push(metadata);
        });
        return grouped;
    }
    /**
     * Gets all validation metadatas for the given object with the given groups.
     */
    getTargetValidationMetadatas(targetConstructor, targetSchema, groups) {
        // get directly related to a target metadatas
        const originalMetadatas = this.validationMetadatas.filter(metadata => {
            if (metadata.target !== targetConstructor && metadata.target !== targetSchema)
                return false;
            if (metadata.always)
                return true;
            if (groups && groups.length > 0)
                return metadata.groups && !!metadata.groups.find(group => groups.indexOf(group) !== -1);
            return true;
        });
        // get metadatas for inherited classes
        const inheritedMetadatas = this.validationMetadatas.filter(metadata => {
            // if target is a string it's means we validate agains a schema, and there is no inheritance support for schemas
            if (typeof metadata.target === "string")
                return false;
            if (metadata.target === targetConstructor)
                return false;
            if (metadata.target instanceof Function &&
                !(targetConstructor.prototype instanceof metadata.target))
                return false;
            if (metadata.always)
                return true;
            if (groups && groups.length > 0)
                return metadata.groups && !!metadata.groups.find(group => groups.indexOf(group) !== -1);
            return true;
        });
        // filter out duplicate metadatas, prefer original metadatas instead of inherited metadatas
        const uniqueInheritedMetadatas = inheritedMetadatas.filter(inheritedMetadata => {
            return !originalMetadatas.find(originalMetadata => {
                return originalMetadata.propertyName === inheritedMetadata.propertyName &&
                    originalMetadata.type === inheritedMetadata.type;
            });
        });
        return originalMetadatas.concat(uniqueInheritedMetadatas);
    }
    /**
     * Gets all validator constraints for the given object.
     */
    getTargetValidatorConstraints(target) {
        return this.constraintMetadatas.filter(metadata => metadata.target === target);
    }
}

/**
 * Validation error description.
 */
class ValidationError {
    /**
     *
     * @param shouldDecorate decorate the message with ANSI formatter escape codes for better readability
     * @param hasParent true when the error is a child of an another one
     * @param parentPath path as string to the parent of this property
     */
    toString(shouldDecorate = false, hasParent = false, parentPath = ``) {
        const boldStart = shouldDecorate ? `\x1b[1m` : ``;
        const boldEnd = shouldDecorate ? `\x1b[22m` : ``;
        const propConstraintFailed = (propertyName) => ` - property ${boldStart}${parentPath}${propertyName}${boldEnd} has failed the following constraints: ${boldStart}${Object.keys(this.constraints).join(`, `)}${boldEnd} \n`;
        if (!hasParent) {
            return `An instance of ${boldStart}${this.target ? this.target.constructor.name : "an object"}${boldEnd} has failed the validation:\n` +
                (this.constraints ? propConstraintFailed(this.property) : ``) +
                this.children
                    .map(childError => childError.toString(shouldDecorate, true, this.property))
                    .join(``);
        }
        else {
            // we format numbers as array indexes for better readability.
            const formattedProperty = Number.isInteger(+this.property) ? `[${this.property}]` : `${parentPath ? `.` : ``}${this.property}`;
            if (this.constraints) {
                return propConstraintFailed(formattedProperty);
            }
            else {
                return this.children
                    .map(childError => childError.toString(shouldDecorate, true, `${parentPath}${formattedProperty}`))
                    .join(``);
            }
        }
    }
}

/**
 * Validation types.
 */
class ValidationTypes {
    /**
     * Checks if validation type is valid.
     */
    static isValid(type) {
        return type !== "isValid" &&
            type !== "getMessage" &&
            Object.keys(this).map(key => this[key]).indexOf(type) !== -1;
    }
}
/* system */
ValidationTypes.CUSTOM_VALIDATION = "customValidation"; // done
ValidationTypes.NESTED_VALIDATION = "nestedValidation"; // done
ValidationTypes.PROMISE_VALIDATION = "promiseValidation"; // done
ValidationTypes.CONDITIONAL_VALIDATION = "conditionalValidation"; // done
ValidationTypes.WHITELIST = "whitelistValidation"; // done
ValidationTypes.IS_DEFINED = "isDefined"; // done

class ValidationUtils {
    static replaceMessageSpecialTokens(message, validationArguments) {
        let messageString;
        if (message instanceof Function) {
            messageString = message(validationArguments);
        }
        else if (typeof message === "string") {
            messageString = message;
        }
        if (messageString && validationArguments.constraints instanceof Array) {
            validationArguments.constraints.forEach((constraint, index) => {
                messageString = messageString.replace(new RegExp(`\\$constraint${index + 1}`, "g"), constraint);
            });
        }
        if (messageString && validationArguments.value !== undefined && validationArguments.value !== null && typeof validationArguments.value === "string")
            messageString = messageString.replace(/\$value/g, validationArguments.value);
        if (messageString)
            messageString = messageString.replace(/\$property/g, validationArguments.property);
        if (messageString)
            messageString = messageString.replace(/\$target/g, validationArguments.targetName);
        return messageString;
    }
}

// https://github.com/TylorS/typed-is-promise/blob/abf1514e1b6961adfc75765476b0debb96b2c3ae/src/index.ts
function isPromise(p) {
    return p !== null && typeof p === "object" && typeof p.then === "function";
}
/**
 * Convert Map, Set to Array
 */
function convertToArray(val) {
    if (val instanceof Map) {
        return Array.from(val.values());
    }
    return Array.isArray(val) ? val : Array.from(val);
}

/**
 * Executes validation over given object.
 */
class ValidationExecutor {
    // -------------------------------------------------------------------------
    // Constructor
    // -------------------------------------------------------------------------
    constructor(validator, validatorOptions) {
        this.validator = validator;
        this.validatorOptions = validatorOptions;
        // -------------------------------------------------------------------------
        // Properties
        // -------------------------------------------------------------------------
        this.awaitingPromises = [];
        this.ignoreAsyncValidations = false;
        // -------------------------------------------------------------------------
        // Private Properties
        // -------------------------------------------------------------------------
        this.metadataStorage = getMetadataStorage();
    }
    // -------------------------------------------------------------------------
    // Public Methods
    // -------------------------------------------------------------------------
    execute(object, targetSchema, validationErrors) {
        /**
         * If there is no metadata registered it means possibly the dependencies are not flatterned and
         * more than one instance is used.
         *
         * TODO: This needs proper handling, forcing to use the same container or some other proper solution.
         */
        if (!this.metadataStorage.hasValidationMetaData) {
            console.warn(`No metadata found. There is more than once class-validator version installed probably. You need to flatten your dependencies.`);
        }
        const groups = this.validatorOptions ? this.validatorOptions.groups : undefined;
        const targetMetadatas = this.metadataStorage.getTargetValidationMetadatas(object.constructor, targetSchema, groups);
        const groupedMetadatas = this.metadataStorage.groupByPropertyName(targetMetadatas);
        if (this.validatorOptions && this.validatorOptions.forbidUnknownValues && !targetMetadatas.length) {
            const validationError = new ValidationError();
            if (!this.validatorOptions ||
                !this.validatorOptions.validationError ||
                this.validatorOptions.validationError.target === undefined ||
                this.validatorOptions.validationError.target === true)
                validationError.target = object;
            validationError.value = undefined;
            validationError.property = undefined;
            validationError.children = [];
            validationError.constraints = { unknownValue: "an unknown value was passed to the validate function" };
            validationErrors.push(validationError);
            return;
        }
        if (this.validatorOptions && this.validatorOptions.whitelist)
            this.whitelist(object, groupedMetadatas, validationErrors);
        // General validation
        Object.keys(groupedMetadatas).forEach(propertyName => {
            const value = object[propertyName];
            const definedMetadatas = groupedMetadatas[propertyName].filter(metadata => metadata.type === ValidationTypes.IS_DEFINED);
            const metadatas = groupedMetadatas[propertyName].filter(metadata => metadata.type !== ValidationTypes.IS_DEFINED && metadata.type !== ValidationTypes.WHITELIST);
            if (value instanceof Promise && metadatas.find(metadata => metadata.type === ValidationTypes.PROMISE_VALIDATION)) {
                this.awaitingPromises.push(value.then((resolvedValue) => {
                    this.performValidations(object, resolvedValue, propertyName, definedMetadatas, metadatas, validationErrors);
                }));
            }
            else {
                this.performValidations(object, value, propertyName, definedMetadatas, metadatas, validationErrors);
            }
        });
    }
    whitelist(object, groupedMetadatas, validationErrors) {
        let notAllowedProperties = [];
        Object.keys(object).forEach(propertyName => {
            // does this property have no metadata?
            if (!groupedMetadatas[propertyName] || groupedMetadatas[propertyName].length === 0)
                notAllowedProperties.push(propertyName);
        });
        if (notAllowedProperties.length > 0) {
            if (this.validatorOptions && this.validatorOptions.forbidNonWhitelisted) {
                // throw errors
                notAllowedProperties.forEach(property => {
                    const validationError = this.generateValidationError(object, object[property], property);
                    validationError.constraints = { [ValidationTypes.WHITELIST]: `property ${property} should not exist` };
                    validationError.children = undefined;
                    validationErrors.push(validationError);
                });
            }
            else {
                // strip non allowed properties
                notAllowedProperties.forEach(property => delete object[property]);
            }
        }
    }
    stripEmptyErrors(errors) {
        return errors.filter(error => {
            if (error.children) {
                error.children = this.stripEmptyErrors(error.children);
            }
            if (Object.keys(error.constraints).length === 0) {
                if (error.children.length === 0) {
                    return false;
                }
                else {
                    delete error.constraints;
                }
            }
            return true;
        });
    }
    // -------------------------------------------------------------------------
    // Private Methods
    // -------------------------------------------------------------------------
    performValidations(object, value, propertyName, definedMetadatas, metadatas, validationErrors) {
        const customValidationMetadatas = metadatas.filter(metadata => metadata.type === ValidationTypes.CUSTOM_VALIDATION);
        const nestedValidationMetadatas = metadatas.filter(metadata => metadata.type === ValidationTypes.NESTED_VALIDATION);
        const conditionalValidationMetadatas = metadatas.filter(metadata => metadata.type === ValidationTypes.CONDITIONAL_VALIDATION);
        const validationError = this.generateValidationError(object, value, propertyName);
        validationErrors.push(validationError);
        const canValidate = this.conditionalValidations(object, value, conditionalValidationMetadatas);
        if (!canValidate) {
            return;
        }
        // handle IS_DEFINED validation type the special way - it should work no matter skipUndefinedProperties/skipMissingProperties is set or not
        this.customValidations(object, value, definedMetadatas, validationError);
        this.mapContexts(object, value, definedMetadatas, validationError);
        if (value === undefined && this.validatorOptions && this.validatorOptions.skipUndefinedProperties === true) {
            return;
        }
        if (value === null && this.validatorOptions && this.validatorOptions.skipNullProperties === true) {
            return;
        }
        if ((value === null || value === undefined) && this.validatorOptions && this.validatorOptions.skipMissingProperties === true) {
            return;
        }
        this.customValidations(object, value, customValidationMetadatas, validationError);
        this.nestedValidations(value, nestedValidationMetadatas, validationError.children);
        this.mapContexts(object, value, metadatas, validationError);
        this.mapContexts(object, value, customValidationMetadatas, validationError);
    }
    generateValidationError(object, value, propertyName) {
        const validationError = new ValidationError();
        if (!this.validatorOptions ||
            !this.validatorOptions.validationError ||
            this.validatorOptions.validationError.target === undefined ||
            this.validatorOptions.validationError.target === true)
            validationError.target = object;
        if (!this.validatorOptions ||
            !this.validatorOptions.validationError ||
            this.validatorOptions.validationError.value === undefined ||
            this.validatorOptions.validationError.value === true)
            validationError.value = value;
        validationError.property = propertyName;
        validationError.children = [];
        validationError.constraints = {};
        return validationError;
    }
    conditionalValidations(object, value, metadatas) {
        return metadatas
            .map(metadata => metadata.constraints[0](object, value))
            .reduce((resultA, resultB) => resultA && resultB, true);
    }
    customValidations(object, value, metadatas, error) {
        metadatas.forEach(metadata => {
            this.metadataStorage
                .getTargetValidatorConstraints(metadata.constraintCls)
                .forEach(customConstraintMetadata => {
                if (customConstraintMetadata.async && this.ignoreAsyncValidations)
                    return;
                const validationArguments = {
                    targetName: object.constructor ? object.constructor.name : undefined,
                    property: metadata.propertyName,
                    object: object,
                    value: value,
                    constraints: metadata.constraints
                };
                if (!metadata.each || !(value instanceof Array || value instanceof Set || value instanceof Map)) {
                    const validatedValue = customConstraintMetadata.instance.validate(value, validationArguments);
                    if (isPromise(validatedValue)) {
                        const promise = validatedValue.then(isValid => {
                            if (!isValid) {
                                const [type, message] = this.createValidationError(object, value, metadata, customConstraintMetadata);
                                error.constraints[type] = message;
                                if (metadata.context) {
                                    if (!error.contexts) {
                                        error.contexts = {};
                                    }
                                    error.contexts[type] = Object.assign((error.contexts[type] || {}), metadata.context);
                                }
                            }
                        });
                        this.awaitingPromises.push(promise);
                    }
                    else {
                        if (!validatedValue) {
                            const [type, message] = this.createValidationError(object, value, metadata, customConstraintMetadata);
                            error.constraints[type] = message;
                        }
                    }
                    return;
                }
                // convert set and map into array
                const arrayValue = convertToArray(value);
                // Validation needs to be applied to each array item
                const validatedSubValues = arrayValue.map((subValue) => customConstraintMetadata.instance.validate(subValue, validationArguments));
                const validationIsAsync = validatedSubValues
                    .some((validatedSubValue) => isPromise(validatedSubValue));
                if (validationIsAsync) {
                    // Wrap plain values (if any) in promises, so that all are async
                    const asyncValidatedSubValues = validatedSubValues
                        .map((validatedSubValue) => isPromise(validatedSubValue) ? validatedSubValue : Promise.resolve(validatedSubValue));
                    const asyncValidationIsFinishedPromise = Promise.all(asyncValidatedSubValues)
                        .then((flatValidatedValues) => {
                        const validationResult = flatValidatedValues.every((isValid) => isValid);
                        if (!validationResult) {
                            const [type, message] = this.createValidationError(object, value, metadata, customConstraintMetadata);
                            error.constraints[type] = message;
                            if (metadata.context) {
                                if (!error.contexts) {
                                    error.contexts = {};
                                }
                                error.contexts[type] = Object.assign((error.contexts[type] || {}), metadata.context);
                            }
                        }
                    });
                    this.awaitingPromises.push(asyncValidationIsFinishedPromise);
                    return;
                }
                const validationResult = validatedSubValues.every((isValid) => isValid);
                if (!validationResult) {
                    const [type, message] = this.createValidationError(object, value, metadata, customConstraintMetadata);
                    error.constraints[type] = message;
                }
            });
        });
    }
    nestedValidations(value, metadatas, errors) {
        if (value === void 0) {
            return;
        }
        metadatas.forEach(metadata => {
            if (metadata.type !== ValidationTypes.NESTED_VALIDATION &&
                metadata.type !== ValidationTypes.PROMISE_VALIDATION) {
                return;
            }
            if (value instanceof Array || value instanceof Set || value instanceof Map) {
                // Treats Set as an array - as index of Set value is value itself and it is common case to have Object as value
                const arrayLikeValue = value instanceof Set ? Array.from(value) : value;
                arrayLikeValue.forEach((subValue, index) => {
                    this.performValidations(value, subValue, index.toString(), [], metadatas, errors);
                });
            }
            else if (value instanceof Object) {
                const targetSchema = typeof metadata.target === "string" ? metadata.target : metadata.target.name;
                this.execute(value, targetSchema, errors);
            }
            else {
                const error = new ValidationError();
                error.value = value;
                error.property = metadata.propertyName;
                error.target = metadata.target;
                const [type, message] = this.createValidationError(metadata.target, value, metadata);
                error.constraints = {
                    [type]: message
                };
                errors.push(error);
            }
        });
    }
    mapContexts(object, value, metadatas, error) {
        return metadatas
            .forEach(metadata => {
            if (metadata.context) {
                let customConstraint;
                if (metadata.type === ValidationTypes.CUSTOM_VALIDATION) {
                    const customConstraints = this.metadataStorage.getTargetValidatorConstraints(metadata.constraintCls);
                    customConstraint = customConstraints[0];
                }
                const type = this.getConstraintType(metadata, customConstraint);
                if (error.constraints[type]) {
                    if (!error.contexts) {
                        error.contexts = {};
                    }
                    error.contexts[type] = Object.assign((error.contexts[type] || {}), metadata.context);
                }
            }
        });
    }
    createValidationError(object, value, metadata, customValidatorMetadata) {
        const targetName = object.constructor ? object.constructor.name : undefined;
        const type = this.getConstraintType(metadata, customValidatorMetadata);
        const validationArguments = {
            targetName: targetName,
            property: metadata.propertyName,
            object: object,
            value: value,
            constraints: metadata.constraints
        };
        let message = metadata.message || "";
        if (!metadata.message &&
            (!this.validatorOptions || (this.validatorOptions && !this.validatorOptions.dismissDefaultMessages))) {
            if (customValidatorMetadata && customValidatorMetadata.instance.defaultMessage instanceof Function) {
                message = customValidatorMetadata.instance.defaultMessage(validationArguments);
            }
        }
        const messageString = ValidationUtils.replaceMessageSpecialTokens(message, validationArguments);
        return [type, messageString];
    }
    getConstraintType(metadata, customValidatorMetadata) {
        const type = customValidatorMetadata && customValidatorMetadata.name ? customValidatorMetadata.name : metadata.type;
        return type;
    }
}

/**
 * Validator performs validation of the given object based on its metadata.
 */
class Validator {
    // -------------------------------------------------------------------------
    // Private Properties
    // -------------------------------------------------------------------------
    /**
     * Performs validation of the given object based on decorators or validation schema.
     * Common method for `validateOrReject` and `validate` methods.
     */
    coreValidate(objectOrSchemaName, objectOrValidationOptions, maybeValidatorOptions) {
        const object = typeof objectOrSchemaName === "string" ? objectOrValidationOptions : objectOrSchemaName;
        const options = typeof objectOrSchemaName === "string" ? maybeValidatorOptions : objectOrValidationOptions;
        const schema = typeof objectOrSchemaName === "string" ? objectOrSchemaName : undefined;
        const executor = new ValidationExecutor(this, options);
        const validationErrors = [];
        executor.execute(object, schema, validationErrors);
        return Promise.all(executor.awaitingPromises).then(() => {
            return executor.stripEmptyErrors(validationErrors);
        });
    }
    /**
     * Performs validation of the given object based on decorators or validation schema.
     */
    validate(objectOrSchemaName, objectOrValidationOptions, maybeValidatorOptions) {
        return this.coreValidate(objectOrSchemaName, objectOrValidationOptions, maybeValidatorOptions);
    }
    /**
     * Performs validation of the given object based on decorators or validation schema and reject on error.
     */
    async validateOrReject(objectOrSchemaName, objectOrValidationOptions, maybeValidatorOptions) {
        const errors = await this.coreValidate(objectOrSchemaName, objectOrValidationOptions, maybeValidatorOptions);
        if (errors.length)
            return Promise.reject(errors);
    }
    /**
     * Performs validation of the given object based on decorators or validation schema.
     */
    validateSync(objectOrSchemaName, objectOrValidationOptions, maybeValidatorOptions) {
        const object = typeof objectOrSchemaName === "string" ? objectOrValidationOptions : objectOrSchemaName;
        const options = typeof objectOrSchemaName === "string" ? maybeValidatorOptions : objectOrValidationOptions;
        const schema = typeof objectOrSchemaName === "string" ? objectOrSchemaName : undefined;
        const executor = new ValidationExecutor(this, options);
        executor.ignoreAsyncValidations = true;
        const validationErrors = [];
        executor.execute(object, schema, validationErrors);
        return executor.stripEmptyErrors(validationErrors);
    }
}

/**
 * Container to be used by this library for inversion control. If container was not implicitly set then by default
 * container simply creates a new instance of the given class.
 */
const defaultContainer = new (class {
    constructor() {
        this.instances = [];
    }
    get(someClass) {
        let instance = this.instances.find(instance => instance.type === someClass);
        if (!instance) {
            instance = { type: someClass, object: new someClass() };
            this.instances.push(instance);
        }
        return instance.object;
    }
})();
let userContainer;
let userContainerOptions;
/**
 * Sets container to be used by this library.
 */
function useContainer(iocContainer, options) {
    userContainer = iocContainer;
    userContainerOptions = options;
}
/**
 * Gets the IOC container used by this library.
 */
function getFromContainer(someClass) {
    if (userContainer) {
        try {
            const instance = userContainer.get(someClass);
            if (instance)
                return instance;
            if (!userContainerOptions || !userContainerOptions.fallback)
                return instance;
        }
        catch (error) {
            if (!userContainerOptions || !userContainerOptions.fallbackOnErrors)
                throw error;
        }
    }
    return defaultContainer.get(someClass);
}

/**
 * If object has both allowed and not allowed properties a validation error will be thrown.
 */
function Allow(validationOptions) {
    return function (object, propertyName) {
        const args = {
            type: ValidationTypes.WHITELIST,
            target: object.constructor,
            propertyName: propertyName,
            validationOptions: validationOptions
        };
        getMetadataStorage().addValidationMetadata(new ValidationMetadata(args));
    };
}

/**
 * This metadata interface contains information for custom validators.
 */
class ConstraintMetadata {
    // -------------------------------------------------------------------------
    // Constructor
    // -------------------------------------------------------------------------
    constructor(target, name, async = false) {
        this.target = target;
        this.name = name;
        this.async = async;
    }
    // -------------------------------------------------------------------------
    // Accessors
    // -------------------------------------------------------------------------
    /**
     * Instance of the target custom validation class which performs validation.
     */
    get instance() {
        return getFromContainer(this.target);
    }
}

/**
 * Registers a custom validation decorator.
 */
function registerDecorator(options) {
    let constraintCls;
    if (options.validator instanceof Function) {
        constraintCls = options.validator;
        const constraintClasses = getFromContainer(MetadataStorage).getTargetValidatorConstraints(options.validator);
        if (constraintClasses.length > 1) {
            throw `More than one implementation of ValidatorConstraintInterface found for validator on: ${options.target}:${options.propertyName}`;
        }
    }
    else {
        const validator = options.validator;
        constraintCls = class CustomConstraint {
            validate(value, validationArguments) {
                return validator.validate(value, validationArguments);
            }
            defaultMessage(validationArguments) {
                if (validator.defaultMessage) {
                    return validator.defaultMessage(validationArguments);
                }
                return "";
            }
        };
        getMetadataStorage().addConstraintMetadata(new ConstraintMetadata(constraintCls, options.name, options.async));
    }
    const validationMetadataArgs = {
        type: options.name && ValidationTypes.isValid(options.name) ? options.name : ValidationTypes.CUSTOM_VALIDATION,
        target: options.target,
        propertyName: options.propertyName,
        validationOptions: options.options,
        constraintCls: constraintCls,
        constraints: options.constraints
    };
    getMetadataStorage().addValidationMetadata(new ValidationMetadata(validationMetadataArgs));
}

function buildMessage(impl, validationOptions) {
    return (validationArguments) => {
        const eachPrefix = validationOptions && validationOptions.each
            ? "each value in "
            : "";
        return impl(eachPrefix, validationArguments);
    };
}
function ValidateBy(options, validationOptions) {
    return function (object, propertyName) {
        registerDecorator({
            name: options.name,
            target: object.constructor,
            propertyName: propertyName,
            options: validationOptions,
            constraints: options.constraints,
            validator: options.validator
        });
    };
}

// isDefined is (yet) a special case
const IS_DEFINED = ValidationTypes.IS_DEFINED;
/**
 * Checks if value is defined (!== undefined, !== null).
 */
function isDefined(value) {
    return value !== undefined && value !== null;
}
/**
 * Checks if value is defined (!== undefined, !== null).
 */
function IsDefined(validationOptions) {
    return ValidateBy({
        name: IS_DEFINED,
        validator: {
            validate: (value) => isDefined(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property should not be null or undefined", validationOptions)
        }
    }, validationOptions);
}

/**
 * Checks if value is missing and if so, ignores all validators.
 */
function IsOptional(validationOptions) {
    return function (object, propertyName) {
        const args = {
            type: ValidationTypes.CONDITIONAL_VALIDATION,
            target: object.constructor,
            propertyName: propertyName,
            constraints: [(object, value) => {
                    return object[propertyName] !== null && object[propertyName] !== undefined;
                }],
            validationOptions: validationOptions
        };
        getMetadataStorage().addValidationMetadata(new ValidationMetadata(args));
    };
}

/**
 * Registers custom validator class.
 */
function ValidatorConstraint(options) {
    return function (target) {
        const isAsync = options && options.async ? true : false;
        let name = options && options.name ? options.name : "";
        if (!name) {
            name = target.name;
            if (!name) // generate name if it was not given
                name = name.replace(/\.?([A-Z]+)/g, (x, y) => "_" + y.toLowerCase()).replace(/^_/, "");
        }
        const metadata = new ConstraintMetadata(target, name, isAsync);
        getMetadataStorage().addConstraintMetadata(metadata);
    };
}
function Validate(constraintClass, constraintsOrValidationOptions, maybeValidationOptions) {
    return function (object, propertyName) {
        const args = {
            type: ValidationTypes.CUSTOM_VALIDATION,
            target: object.constructor,
            propertyName: propertyName,
            constraintCls: constraintClass,
            constraints: constraintsOrValidationOptions instanceof Array ? constraintsOrValidationOptions : undefined,
            validationOptions: !(constraintsOrValidationOptions instanceof Array) ? constraintsOrValidationOptions : maybeValidationOptions
        };
        getMetadataStorage().addValidationMetadata(new ValidationMetadata(args));
    };
}

/**
 * Objects / object arrays marked with this decorator will also be validated.
 */
function ValidateIf(condition, validationOptions) {
    return function (object, propertyName) {
        const args = {
            type: ValidationTypes.CONDITIONAL_VALIDATION,
            target: object.constructor,
            propertyName: propertyName,
            constraints: [condition],
            validationOptions: validationOptions
        };
        getMetadataStorage().addValidationMetadata(new ValidationMetadata(args));
    };
}

/**
 * Objects / object arrays marked with this decorator will also be validated.
 */
function ValidateNested(validationOptions) {
    const opts = { ...validationOptions };
    const eachPrefix = opts.each ? "each value in " : "";
    opts.message = opts.message || eachPrefix + "nested property $property must be either object or array";
    return function (object, propertyName) {
        const args = {
            type: ValidationTypes.NESTED_VALIDATION,
            target: object.constructor,
            propertyName: propertyName,
            validationOptions: opts,
        };
        getMetadataStorage().addValidationMetadata(new ValidationMetadata(args));
    };
}

/**
 * Resolve promise before validation
 */
function ValidatePromise(validationOptions) {
    return function (object, propertyName) {
        const args = {
            type: ValidationTypes.PROMISE_VALIDATION,
            target: object.constructor,
            propertyName: propertyName,
            validationOptions: validationOptions,
        };
        getMetadataStorage().addValidationMetadata(new ValidationMetadata(args));
    };
}

const IS_LATLONG = "isLatLong";
/**
 * Checks if a value is string in format a "latitude,longitude".
 */
function isLatLong(value) {
    return typeof value === "string" && validator.isLatLong(value);
}
/**
 * Checks if a value is string in format a "latitude,longitude".
 */
function IsLatLong(validationOptions) {
    return ValidateBy({
        name: IS_LATLONG,
        validator: {
            validate: (value, args) => isLatLong(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a latitude,longitude string", validationOptions)
        }
    }, validationOptions);
}

const IS_LATITUDE = "isLatitude";
/**
 * Checks if a given value is a latitude.
 */
function isLatitude(value) {
    return (typeof value === "number" || typeof value === "string") && isLatLong(`${value},0`);
}
/**
 * Checks if a given value is a latitude.
 */
function IsLatitude(validationOptions) {
    return ValidateBy({
        name: IS_LATITUDE,
        validator: {
            validate: (value, args) => isLatitude(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a latitude string or number", validationOptions)
        }
    }, validationOptions);
}

const IS_LONGITUDE = "isLongitude";
/**
 * Checks if a given value is a longitude.
 */
function isLongitude(value) {
    return (typeof value === "number" || typeof value === "string") && isLatLong(`0,${value}`);
}
/**
 * Checks if a given value is a longitude.
 */
function IsLongitude(validationOptions) {
    return ValidateBy({
        name: IS_LONGITUDE,
        validator: {
            validate: (value, args) => isLongitude(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a longitude string or number", validationOptions)
        }
    }, validationOptions);
}

const EQUALS = "equals";
/**
 * Checks if value matches ("===") the comparison.
 */
function equals(value, comparison) {
    return value === comparison;
}
/**
 * Checks if value matches ("===") the comparison.
 */
function Equals(comparison, validationOptions) {
    return ValidateBy({
        name: EQUALS,
        constraints: [comparison],
        validator: {
            validate: (value, args) => equals(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be equal to $constraint1", validationOptions)
        }
    }, validationOptions);
}

const NOT_EQUALS = "notEquals";
/**
 * Checks if value does not match ("!==") the comparison.
 */
function notEquals(value, comparison) {
    return value !== comparison;
}
/**
 * Checks if value does not match ("!==") the comparison.
 */
function NotEquals(comparison, validationOptions) {
    return ValidateBy({
        name: NOT_EQUALS,
        constraints: [comparison],
        validator: {
            validate: (value, args) => notEquals(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property should not be equal to $constraint1", validationOptions)
        }
    }, validationOptions);
}

const IS_EMPTY = "isEmpty";
/**
 * Checks if given value is empty (=== '', === null, === undefined).
 */
function isEmpty(value) {
    return value === "" || value === null || value === undefined;
}
/**
 * Checks if given value is empty (=== '', === null, === undefined).
 */
function IsEmpty(validationOptions) {
    return ValidateBy({
        name: IS_EMPTY,
        validator: {
            validate: (value, args) => isEmpty(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be empty", validationOptions)
        }
    }, validationOptions);
}

const IS_NOT_EMPTY = "isNotEmpty";
/**
 * Checks if given value is not empty (!== '', !== null, !== undefined).
 */
function isNotEmpty(value) {
    return value !== "" && value !== null && value !== undefined;
}
/**
 * Checks if given value is not empty (!== '', !== null, !== undefined).
 */
function IsNotEmpty(validationOptions) {
    return ValidateBy({
        name: IS_NOT_EMPTY,
        validator: {
            validate: (value, args) => isNotEmpty(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property should not be empty", validationOptions)
        }
    }, validationOptions);
}

const IS_IN = "isIn";
/**
 * Checks if given value is in a array of allowed values.
 */
function isIn(value, possibleValues) {
    return !(possibleValues instanceof Array) || possibleValues.some(possibleValue => possibleValue === value);
}
/**
 * Checks if given value is in a array of allowed values.
 */
function IsIn(values, validationOptions) {
    return ValidateBy({
        name: IS_IN,
        constraints: [values],
        validator: {
            validate: (value, args) => isIn(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be one of the following values: $constraint1", validationOptions)
        }
    }, validationOptions);
}

const IS_NOT_IN = "isNotIn";
/**
 * Checks if given value not in a array of allowed values.
 */
function isNotIn(value, possibleValues) {
    return !(possibleValues instanceof Array) || !possibleValues.some(possibleValue => possibleValue === value);
}
/**
 * Checks if given value not in a array of allowed values.
 */
function IsNotIn(values, validationOptions) {
    return ValidateBy({
        name: IS_NOT_IN,
        constraints: [values],
        validator: {
            validate: (value, args) => isNotIn(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property should not be one of the following values: $constraint1", validationOptions)
        }
    }, validationOptions);
}

const IS_DIVISIBLE_BY = "isDivisibleBy";
/**
 * Checks if value is a number that's divisible by another.
 */
function isDivisibleBy(value, num) {
    return typeof value === "number" &&
        typeof num === "number" &&
        validator.isDivisibleBy(String(value), num);
}
/**
 * Checks if value is a number that's divisible by another.
 */
function IsDivisibleBy(num, validationOptions) {
    return ValidateBy({
        name: IS_DIVISIBLE_BY,
        constraints: [num],
        validator: {
            validate: (value, args) => isDivisibleBy(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be divisible by $constraint1", validationOptions)
        }
    }, validationOptions);
}

const IS_POSITIVE = "isPositive";
/**
 * Checks if the value is a positive number greater than zero.
 */
function isPositive(value) {
    return typeof value === "number" && value > 0;
}
/**
 * Checks if the value is a positive number greater than zero.
 */
function IsPositive(validationOptions) {
    return ValidateBy({
        name: IS_POSITIVE,
        validator: {
            validate: (value, args) => isPositive(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a positive number", validationOptions)
        }
    }, validationOptions);
}

const IS_NEGATIVE = "isNegative";
/**
 * Checks if the value is a negative number smaller than zero.
 */
function isNegative(value) {
    return typeof value === "number" && value < 0;
}
/**
 * Checks if the value is a negative number smaller than zero.
 */
function IsNegative(validationOptions) {
    return ValidateBy({
        name: IS_NEGATIVE,
        validator: {
            validate: (value, args) => isNegative(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a negative number", validationOptions)
        }
    }, validationOptions);
}

const MAX = "max";
/**
 * Checks if the first number is less than or equal to the second.
 */
function max(num, max) {
    return typeof num === "number" && typeof max === "number" && num <= max;
}
/**
 * Checks if the first number is less than or equal to the second.
 */
function Max(maxValue, validationOptions) {
    return ValidateBy({
        name: MAX,
        constraints: [maxValue],
        validator: {
            validate: (value, args) => max(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must not be greater than $constraint1", validationOptions)
        }
    }, validationOptions);
}

const MIN = "min";
/**
 * Checks if the first number is greater than or equal to the second.
 */
function min(num, min) {
    return typeof num === "number" && typeof min === "number" && num >= min;
}
/**
 * Checks if the first number is greater than or equal to the second.
 */
function Min(minValue, validationOptions) {
    return ValidateBy({
        name: MIN,
        constraints: [minValue],
        validator: {
            validate: (value, args) => min(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must not be less than $constraint1", validationOptions)
        }
    }, validationOptions);
}

const MIN_DATE = "minDate";
/**
 * Checks if the value is a date that's after the specified date.
 */
function minDate(date, minDate) {
    return date instanceof Date && date.getTime() >= minDate.getTime();
}
/**
 * Checks if the value is a date that's after the specified date.
 */
function MinDate(date, validationOptions) {
    return ValidateBy({
        name: MIN_DATE,
        constraints: [date],
        validator: {
            validate: (value, args) => minDate(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => "minimal allowed date for " + eachPrefix + "$property is $constraint1", validationOptions)
        }
    }, validationOptions);
}

const MAX_DATE = "maxDate";
/**
* Checks if the value is a date that's before the specified date.
*/
function maxDate(date, maxDate) {
    return date instanceof Date && date.getTime() <= maxDate.getTime();
}
/**
 * Checks if the value is a date that's after the specified date.
 */
function MaxDate(date, validationOptions) {
    return ValidateBy({
        name: MAX_DATE,
        constraints: [date],
        validator: {
            validate: (value, args) => maxDate(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => "maximal allowed date for " + eachPrefix + "$property is $constraint1", validationOptions)
        }
    }, validationOptions);
}

const CONTAINS = "contains";
/**
 * Checks if the string contains the seed.
 * If given value is not a string, then it returns false.
 */
function contains(value, seed) {
    return typeof value === "string" && validator.contains(value, seed);
}
/**
 * Checks if the string contains the seed.
 * If given value is not a string, then it returns false.
 */
function Contains(seed, validationOptions) {
    return ValidateBy({
        name: CONTAINS,
        constraints: [seed],
        validator: {
            validate: (value, args) => contains(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must contain a $constraint1 string", validationOptions)
        }
    }, validationOptions);
}

const NOT_CONTAINS = "notContains";
/**
 * Checks if the string does not contain the seed.
 * If given value is not a string, then it returns false.
 */
function notContains(value, seed) {
    return typeof value === "string" && !validator.contains(value, seed);
}
/**
 * Checks if the string does not contain the seed.
 * If given value is not a string, then it returns false.
 */
function NotContains(seed, validationOptions) {
    return ValidateBy({
        name: NOT_CONTAINS,
        constraints: [seed],
        validator: {
            validate: (value, args) => notContains(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property should not contain a $constraint1 string", validationOptions)
        }
    }, validationOptions);
}

const IS_ALPHA = "isAlpha";
/**
 * Checks if the string contains only letters (a-zA-Z).
 * If given value is not a string, then it returns false.
 */
function isAlpha(value, locale) {
    return typeof value === "string" && validator.isAlpha(value, locale);
}
/**
 * Checks if the string contains only letters (a-zA-Z).
 * If given value is not a string, then it returns false.
 */
function IsAlpha(locale, validationOptions) {
    return ValidateBy({
        name: IS_ALPHA,
        constraints: [locale],
        validator: {
            validate: (value, args) => isAlpha(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must contain only letters (a-zA-Z)", validationOptions)
        }
    }, validationOptions);
}

const IS_ALPHANUMERIC = "isAlphanumeric";
/**
 * Checks if the string contains only letters and numbers.
 * If given value is not a string, then it returns false.
 */
function isAlphanumeric(value, locale) {
    return typeof value === "string" && validator.isAlphanumeric(value, locale);
}
/**
 * Checks if the string contains only letters and numbers.
 * If given value is not a string, then it returns false.
 */
function IsAlphanumeric(locale, validationOptions) {
    return ValidateBy({
        name: IS_ALPHANUMERIC,
        constraints: [locale],
        validator: {
            validate: (value, args) => isAlphanumeric(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must contain only letters and numbers", validationOptions)
        }
    }, validationOptions);
}

const IS_DECIMAL = "isDecimal";
/**
 * Checks if the string is a valid decimal.
 * If given value is not a string, then it returns false.
 */
function isDecimal(value, options) {
    return typeof value === "string" && validator.isDecimal(value, options);
}
/**
 * Checks if the string contains only letters and numbers.
 * If given value is not a string, then it returns false.
 */
function IsDecimal(options, validationOptions) {
    return ValidateBy({
        name: IS_DECIMAL,
        constraints: [options],
        validator: {
            validate: (value, args) => isDecimal(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property is not a valid decimal number.", validationOptions)
        }
    }, validationOptions);
}

const IS_ASCII = "isAscii";
/**
 * Checks if the string contains ASCII chars only.
 * If given value is not a string, then it returns false.
 */
function isAscii(value) {
    return typeof value === "string" && validator.isAscii(value);
}
/**
 * Checks if the string contains ASCII chars only.
 * If given value is not a string, then it returns false.
 */
function IsAscii(validationOptions) {
    return ValidateBy({
        name: IS_ASCII,
        validator: {
            validate: (value, args) => isAscii(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must contain only ASCII characters", validationOptions)
        }
    }, validationOptions);
}

const IS_BASE64 = "isBase64";
/**
 * Checks if a string is base64 encoded.
 * If given value is not a string, then it returns false.
 */
function isBase64(value) {
    return typeof value === "string" && validator.isBase64(value);
}
/**
 * Checks if a string is base64 encoded.
 * If given value is not a string, then it returns false.
 */
function IsBase64(validationOptions) {
    return ValidateBy({
        name: IS_BASE64,
        validator: {
            validate: (value, args) => isBase64(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be base64 encoded", validationOptions)
        }
    }, validationOptions);
}

const IS_BYTE_LENGTH = "isByteLength";
/**
 * Checks if the string's length (in bytes) falls in a range.
 * If given value is not a string, then it returns false.
 */
function isByteLength(value, min, max) {
    return typeof value === "string" && validator.isByteLength(value, { min, max });
}
/**
 * Checks if the string's length (in bytes) falls in a range.
 * If given value is not a string, then it returns false.
 */
function IsByteLength(min, max, validationOptions) {
    return ValidateBy({
        name: IS_BYTE_LENGTH,
        constraints: [min, max],
        validator: {
            validate: (value, args) => isByteLength(value, args.constraints[0], args.constraints[1]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property's byte length must fall into ($constraint1, $constraint2) range", validationOptions)
        }
    }, validationOptions);
}

const IS_CREDIT_CARD = "isCreditCard";
/**
 * Checks if the string is a credit card.
 * If given value is not a string, then it returns false.
 */
function isCreditCard(value) {
    return typeof value === "string" && validator.isCreditCard(value);
}
/**
 * Checks if the string is a credit card.
 * If given value is not a string, then it returns false.
 */
function IsCreditCard(validationOptions) {
    return ValidateBy({
        name: IS_CREDIT_CARD,
        validator: {
            validate: (value, args) => isCreditCard(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a credit card", validationOptions)
        }
    }, validationOptions);
}

const IS_CURRENCY = "isCurrency";
/**
 * Checks if the string is a valid currency amount.
 * If given value is not a string, then it returns false.
 */
function isCurrency(value, options) {
    return typeof value === "string" && validator.isCurrency(value, options);
}
/**
 * Checks if the string is a valid currency amount.
 * If given value is not a string, then it returns false.
 */
function IsCurrency(options, validationOptions) {
    return ValidateBy({
        name: IS_CURRENCY,
        constraints: [options],
        validator: {
            validate: (value, args) => isCurrency(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a currency", validationOptions)
        }
    }, validationOptions);
}

const IS_EMAIL = "isEmail";
/**
 * Checks if the string is an email.
 * If given value is not a string, then it returns false.
 */
function isEmail(value, options) {
    return typeof value === "string" && validator.isEmail(value, options);
}
/**
 * Checks if the string is an email.
 * If given value is not a string, then it returns false.
 */
function IsEmail(options, validationOptions) {
    return ValidateBy({
        name: IS_EMAIL,
        constraints: [options],
        validator: {
            validate: (value, args) => isEmail(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be an email", validationOptions)
        }
    }, validationOptions);
}

const IS_FQDN = "isFqdn";
/**
 * Checks if the string is a fully qualified domain name (e.g. domain.com).
 * If given value is not a string, then it returns false.
 */
function isFQDN(value, options) {
    return typeof value === "string" && validator.isFQDN(value, options);
}
/**
 * Checks if the string is a fully qualified domain name (e.g. domain.com).
 * If given value is not a string, then it returns false.
 */
function IsFQDN(options, validationOptions) {
    return ValidateBy({
        name: IS_FQDN,
        constraints: [options],
        validator: {
            validate: (value, args) => isFQDN(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a valid domain name", validationOptions)
        }
    }, validationOptions);
}

const IS_FULL_WIDTH = "isFullWidth";
/**
 * Checks if the string contains any full-width chars.
 * If given value is not a string, then it returns false.
 */
function isFullWidth(value) {
    return typeof value === "string" && validator.isFullWidth(value);
}
/**
 * Checks if the string contains any full-width chars.
 * If given value is not a string, then it returns false.
 */
function IsFullWidth(validationOptions) {
    return ValidateBy({
        name: IS_FULL_WIDTH,
        validator: {
            validate: (value, args) => isFullWidth(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must contain a full-width characters", validationOptions)
        }
    }, validationOptions);
}

const IS_HALF_WIDTH = "isHalfWidth";
/**
 * Checks if the string contains any half-width chars.
 * If given value is not a string, then it returns false.
 */
function isHalfWidth(value) {
    return typeof value === "string" && validator.isHalfWidth(value);
}
/**
 * Checks if the string contains any full-width chars.
 * If given value is not a string, then it returns false.
 */
function IsHalfWidth(validationOptions) {
    return ValidateBy({
        name: IS_HALF_WIDTH,
        validator: {
            validate: (value, args) => isHalfWidth(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must contain a half-width characters", validationOptions)
        }
    }, validationOptions);
}

const IS_VARIABLE_WIDTH = "isVariableWidth";
/**
 * Checks if the string contains variable-width chars.
 * If given value is not a string, then it returns false.
 */
function isVariableWidth(value) {
    return typeof value === "string" && validator.isVariableWidth(value);
}
/**
 * Checks if the string contains variable-width chars.
 * If given value is not a string, then it returns false.
 */
function IsVariableWidth(validationOptions) {
    return ValidateBy({
        name: IS_VARIABLE_WIDTH,
        validator: {
            validate: (value, args) => isVariableWidth(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must contain a full-width and half-width characters", validationOptions)
        }
    }, validationOptions);
}

const IS_HEX_COLOR = "isHexColor";
/**
 * Checks if the string is a hexadecimal color.
 * If given value is not a string, then it returns false.
 */
function isHexColor(value) {
    return typeof value === "string" && validator.isHexColor(value);
}
/**
 * Checks if the string is a hexadecimal color.
 * If given value is not a string, then it returns false.
 */
function IsHexColor(validationOptions) {
    return ValidateBy({
        name: IS_HEX_COLOR,
        validator: {
            validate: (value, args) => isHexColor(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a hexadecimal color", validationOptions)
        }
    }, validationOptions);
}

const IS_HEXADECIMAL = "isHexadecimal";
/**
 * Checks if the string is a hexadecimal number.
 * If given value is not a string, then it returns false.
 */
function isHexadecimal(value) {
    return typeof value === "string" && validator.isHexadecimal(value);
}
/**
 * Checks if the string is a hexadecimal number.
 * If given value is not a string, then it returns false.
 */
function IsHexadecimal(validationOptions) {
    return ValidateBy({
        name: IS_HEXADECIMAL,
        validator: {
            validate: (value, args) => isHexadecimal(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a hexadecimal number", validationOptions)
        }
    }, validationOptions);
}

function isValidationOptions(val) {
    if (!val) {
        return false;
    }
    return "each" in val
        || "message" in val
        || "groups" in val
        || "always" in val
        || "context" in val;
}

const IS_MAC_ADDRESS = "isMacAddress";
/**
 * Check if the string is a MAC address.
 * If given value is not a string, then it returns false.
 */
function isMACAddress(value, options) {
    return typeof value === "string" && validator.isMACAddress(value, options);
}
function IsMACAddress(optionsOrValidationOptionsArg, validationOptionsArg) {
    const options = !isValidationOptions(optionsOrValidationOptionsArg) ? optionsOrValidationOptionsArg : undefined;
    const validationOptions = isValidationOptions(optionsOrValidationOptionsArg) ? optionsOrValidationOptionsArg : validationOptionsArg;
    return ValidateBy({
        name: IS_MAC_ADDRESS,
        constraints: [options],
        validator: {
            validate: (value, args) => isMACAddress(value, options),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a MAC Address", validationOptions)
        }
    }, validationOptions);
}

const IS_IP = "isIp";
/**
 * Checks if the string is an IP (version 4 or 6).
 * If given value is not a string, then it returns false.
 */
function isIP(value, version) {
    const versionStr = version ? `${version}` : undefined;
    return typeof value === "string" && validator.isIP(value, versionStr);
}
/**
 * Checks if the string is an IP (version 4 or 6).
 * If given value is not a string, then it returns false.
 */
function IsIP(version, validationOptions) {
    return ValidateBy({
        name: IS_IP,
        constraints: [version],
        validator: {
            validate: (value, args) => isIP(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be an ip address", validationOptions)
        }
    }, validationOptions);
}

const IS_PORT = "isPort";
/**
 * Check if the string is a valid port number.
 */
function isPort(value) {
    return typeof value === "string" && validator.isPort(value);
}
/**
 * Check if the string is a valid port number.
 */
function IsPort(validationOptions) {
    return ValidateBy({
        name: IS_PORT,
        validator: {
            validate: (value, args) => isPort(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a port", validationOptions)
        }
    }, validationOptions);
}

const IS_ISBN = "isIsbn";
/**
 * Checks if the string is an ISBN (version 10 or 13).
 * If given value is not a string, then it returns false.
 */
function isISBN(value, version) {
    const versionStr = version ? `${version}` : undefined;
    return typeof value === "string" && validator.isISBN(value, versionStr);
}
/**
 * Checks if the string is an ISBN (version 10 or 13).
 * If given value is not a string, then it returns false.
 */
function IsISBN(version, validationOptions) {
    return ValidateBy({
        name: IS_ISBN,
        constraints: [version],
        validator: {
            validate: (value, args) => isISBN(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be an ISBN", validationOptions)
        }
    }, validationOptions);
}

const IS_ISIN = "isIsin";
/**
 * Checks if the string is an ISIN (stock/security identifier).
 * If given value is not a string, then it returns false.
 */
function isISIN(value) {
    return typeof value === "string" && validator.isISIN(value);
}
/**
 * Checks if the string is an ISIN (stock/security identifier).
 * If given value is not a string, then it returns false.
 */
function IsISIN(validationOptions) {
    return ValidateBy({
        name: IS_ISIN,
        validator: {
            validate: (value, args) => isISIN(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be an ISIN (stock/security identifier)", validationOptions)
        }
    }, validationOptions);
}

const IS_ISO8601 = "isIso8601";
/**
 * Checks if the string is a valid ISO 8601 date.
 * If given value is not a string, then it returns false.
 * Use the option strict = true for additional checks for a valid date, e.g. invalidates dates like 2019-02-29.
 */
function isISO8601(value, options) {
    return typeof value === "string" && validator.isISO8601(value, options);
}
/**
 * Checks if the string is a valid ISO 8601 date.
 * If given value is not a string, then it returns false.
 * Use the option strict = true for additional checks for a valid date, e.g. invalidates dates like 2019-02-29.
 */
function IsISO8601(options, validationOptions) {
    return ValidateBy({
        name: IS_ISO8601,
        constraints: [options],
        validator: {
            validate: (value, args) => isISO8601(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a valid ISO 8601 date string", validationOptions)
        }
    }, validationOptions);
}

const IS_JSON = "isJson";
/**
 * Checks if the string is valid JSON (note: uses JSON.parse).
 * If given value is not a string, then it returns false.
 */
function isJSON(value) {
    return typeof value === "string" && validator.isJSON(value);
}
/**
 * Checks if the string is valid JSON (note: uses JSON.parse).
 * If given value is not a string, then it returns false.
 */
function IsJSON(validationOptions) {
    return ValidateBy({
        name: IS_JSON,
        validator: {
            validate: (value, args) => isJSON(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a json string", validationOptions)
        }
    }, validationOptions);
}

const IS_JWT = "isJwt";
/**
 * Checks if the string is valid JWT token.
 * If given value is not a string, then it returns false.
 */
function isJWT(value) {
    return typeof value === "string" && validator.isJWT(value);
}
/**
 * Checks if the string is valid JWT token.
 * If given value is not a string, then it returns false.
 */
function IsJWT(validationOptions) {
    return ValidateBy({
        name: IS_JWT,
        validator: {
            validate: (value, args) => isJWT(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a jwt string", validationOptions)
        }
    }, validationOptions);
}

const IS_LOWERCASE = "isLowercase";
/**
 * Checks if the string is lowercase.
 * If given value is not a string, then it returns false.
 */
function isLowercase(value) {
    return typeof value === "string" && validator.isLowercase(value);
}
/**
 * Checks if the string is lowercase.
 * If given value is not a string, then it returns false.
 */
function IsLowercase(validationOptions) {
    return ValidateBy({
        name: IS_LOWERCASE,
        validator: {
            validate: (value, args) => isLowercase(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a lowercase string", validationOptions)
        }
    }, validationOptions);
}

const IS_MOBILE_PHONE = "isMobilePhone";
/**
 * Checks if the string is a mobile phone number (locale is either an array of locales (e.g ['sk-SK', 'sr-RS'])
 * OR one of ['am-Am', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-IQ', ar-JO', 'ar-KW', 'ar-SA', 'ar-SY', 'ar-TN', 'be-BY',
 * 'bg-BG', 'bn-BD', 'cs-CZ', 'da-DK', 'de-DE', 'de-AT', 'el-GR', 'en-AU', 'en-CA', 'en-GB', 'en-GG', 'en-GH', 'en-HK',
 * 'en-MO', 'en-IE', 'en-IN', 'en-KE', 'en-MT', 'en-MU', 'en-NG', 'en-NZ', 'en-PK', 'en-RW', 'en-SG', 'en-SL', 'en-UG',
 * 'en-US', 'en-TZ', 'en-ZA', 'en-ZM', 'es-CL', 'es-CR', 'es-EC', 'es-ES', 'es-MX', 'es-PA', 'es-PY', 'es-UY', 'et-EE',
 * 'fa-IR', 'fi-FI', 'fj-FJ', 'fo-FO', 'fr-BE', 'fr-FR', 'fr-GF', 'fr-GP', 'fr-MQ', 'fr-RE', 'he-IL', 'hu-HU', 'id-ID',
 * 'it-IT', 'ja-JP', 'kk-KZ', 'kl-GL', 'ko-KR', 'lt-LT', 'ms-MY', 'nb-NO', 'ne-NP', 'nl-BE', 'nl-NL', 'nn-NO', 'pl-PL',
 * 'pt-BR', 'pt-PT', 'ro-RO', 'ru-RU', 'sl-SI', 'sk-SK', 'sr-RS', 'sv-SE', 'th-TH', 'tr-TR', 'uk-UA', 'vi-VN', 'zh-CN',
 * 'zh-HK', 'zh-MO', 'zh-TW']
 * If given value is not a string, then it returns false.
 */
function isMobilePhone(value, locale, options) {
    return typeof value === "string" && validator.isMobilePhone(value, locale, options);
}
/**
 * Checks if the string is a mobile phone number (locale is either an array of locales (e.g ['sk-SK', 'sr-RS'])
 * OR one of ['am-Am', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-IQ', ar-JO', 'ar-KW', 'ar-SA', 'ar-SY', 'ar-TN', 'be-BY',
 * 'bg-BG', 'bn-BD', 'cs-CZ', 'da-DK', 'de-DE', 'de-AT', 'el-GR', 'en-AU', 'en-CA', 'en-GB', 'en-GG', 'en-GH', 'en-HK',
 * 'en-MO', 'en-IE', 'en-IN', 'en-KE', 'en-MT', 'en-MU', 'en-NG', 'en-NZ', 'en-PK', 'en-RW', 'en-SG', 'en-SL', 'en-UG',
 * 'en-US', 'en-TZ', 'en-ZA', 'en-ZM', 'es-CL', 'es-CR', 'es-EC', 'es-ES', 'es-MX', 'es-PA', 'es-PY', 'es-UY', 'et-EE',
 * 'fa-IR', 'fi-FI', 'fj-FJ', 'fo-FO', 'fr-BE', 'fr-FR', 'fr-GF', 'fr-GP', 'fr-MQ', 'fr-RE', 'he-IL', 'hu-HU', 'id-ID',
 * 'it-IT', 'ja-JP', 'kk-KZ', 'kl-GL', 'ko-KR', 'lt-LT', 'ms-MY', 'nb-NO', 'ne-NP', 'nl-BE', 'nl-NL', 'nn-NO', 'pl-PL',
 * 'pt-BR', 'pt-PT', 'ro-RO', 'ru-RU', 'sl-SI', 'sk-SK', 'sr-RS', 'sv-SE', 'th-TH', 'tr-TR', 'uk-UA', 'vi-VN', 'zh-CN',
 * 'zh-HK', 'zh-MO', 'zh-TW']
 * If given value is not a string, then it returns false.
 */
function IsMobilePhone(locale, options, validationOptions) {
    return ValidateBy({
        name: IS_MOBILE_PHONE,
        constraints: [locale, options],
        validator: {
            validate: (value, args) => isMobilePhone(value, args.constraints[0], args.constraints[1]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a phone number", validationOptions)
        }
    }, validationOptions);
}

const IS_ISO31661_ALPHA_2 = "isISO31661Alpha2";
/**
 * Check if the string is a valid [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) officially assigned country code.
 */
function isISO31661Alpha2(value) {
    return typeof value === "string" && validator.isISO31661Alpha2(value);
}
/**
 * Check if the string is a valid [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) officially assigned country code.
 */
function IsISO31661Alpha2(validationOptions) {
    return ValidateBy({
        name: IS_ISO31661_ALPHA_2,
        validator: {
            validate: (value, args) => isISO31661Alpha2(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a valid ISO31661 Alpha2 code", validationOptions)
        }
    }, validationOptions);
}

const IS_ISO31661_ALPHA_3 = "isISO31661Alpha3";
/**
 * Check if the string is a valid [ISO 3166-1 alpha-3](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3) officially assigned country code.
 */
function isISO31661Alpha3(value) {
    return typeof value === "string" && validator.isISO31661Alpha3(value);
}
/**
 * Check if the string is a valid [ISO 3166-1 alpha-3](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3) officially assigned country code.
 */
function IsISO31661Alpha3(validationOptions) {
    return ValidateBy({
        name: IS_ISO31661_ALPHA_3,
        validator: {
            validate: (value, args) => isISO31661Alpha3(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a valid ISO31661 Alpha3 code", validationOptions)
        }
    }, validationOptions);
}

const IS_MONGO_ID = "isMongoId";
/**
 * Checks if the string is a valid hex-encoded representation of a MongoDB ObjectId.
 * If given value is not a string, then it returns false.
 */
function isMongoId(value) {
    return typeof value === "string" && validator.isMongoId(value);
}
/**
 * Checks if the string is a valid hex-encoded representation of a MongoDB ObjectId.
 * If given value is not a string, then it returns false.
 */
function IsMongoId(validationOptions) {
    return ValidateBy({
        name: IS_MONGO_ID,
        validator: {
            validate: (value, args) => isMongoId(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a mongodb id", validationOptions)
        }
    }, validationOptions);
}

const IS_MULTIBYTE = "isMultibyte";
/**
 * Checks if the string contains one or more multibyte chars.
 * If given value is not a string, then it returns false.
 */
function isMultibyte(value) {
    return typeof value === "string" && validator.isMultibyte(value);
}
/**
 * Checks if the string contains one or more multibyte chars.
 * If given value is not a string, then it returns false.
 */
function IsMultibyte(validationOptions) {
    return ValidateBy({
        name: IS_MULTIBYTE,
        validator: {
            validate: (value, args) => isMultibyte(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must contain one or more multibyte chars", validationOptions)
        }
    }, validationOptions);
}

const IS_SURROGATE_PAIR = "isSurrogatePair";
/**
 * Checks if the string contains any surrogate pairs chars.
 * If given value is not a string, then it returns false.
 */
function isSurrogatePair(value) {
    return typeof value === "string" && validator.isSurrogatePair(value);
}
/**
 * Checks if the string contains any surrogate pairs chars.
 * If given value is not a string, then it returns false.
 */
function IsSurrogatePair(validationOptions) {
    return ValidateBy({
        name: IS_SURROGATE_PAIR,
        validator: {
            validate: (value, args) => isSurrogatePair(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must contain any surrogate pairs chars", validationOptions)
        }
    }, validationOptions);
}

const IS_URL = "isUrl";
/**
 * Checks if the string is an url.
 * If given value is not a string, then it returns false.
 */
function isURL(value, options) {
    return typeof value === "string" && validator.isURL(value, options);
}
/**
 * Checks if the string is an url.
 * If given value is not a string, then it returns false.
 */
function IsUrl(options, validationOptions) {
    return ValidateBy({
        name: IS_URL,
        constraints: [options],
        validator: {
            validate: (value, args) => isURL(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be an URL address", validationOptions)
        }
    }, validationOptions);
}

const IS_UUID = "isUuid";
/**
 * Checks if the string is a UUID (version 3, 4 or 5).
 * If given value is not a string, then it returns false.
 */
function isUUID(value, version) {
    return typeof value === "string" && validator.isUUID(value, version);
}
/**
 * Checks if the string is a UUID (version 3, 4 or 5).
 * If given value is not a string, then it returns false.
 */
function IsUUID(version, validationOptions) {
    return ValidateBy({
        name: IS_UUID,
        constraints: [version],
        validator: {
            validate: (value, args) => isUUID(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be an UUID", validationOptions)
        }
    }, validationOptions);
}

const IS_FIREBASE_PUSH_ID = "IsFirebasePushId";
/**
 * Checks if the string is a Firebase Push Id
 * If given value is not a Firebase Push Id, it returns false
 */
function isFirebasePushId(value) {
    const webSafeRegex = /^[a-zA-Z0-9_-]*$/;
    return typeof value === "string" && value.length === 20 && webSafeRegex.test(value);
}
/**
 * Checks if the string is a Firebase Push Id
 * If given value is not a Firebase Push Id, it returns false
 */
function IsFirebasePushId(validationOptions) {
    return ValidateBy({
        name: IS_FIREBASE_PUSH_ID,
        validator: {
            validate: (value, args) => isFirebasePushId(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a Firebase Push Id", validationOptions)
        }
    }, validationOptions);
}

const IS_UPPERCASE = "isUppercase";
/**
 * Checks if the string is uppercase.
 * If given value is not a string, then it returns false.
 */
function isUppercase(value) {
    return typeof value === "string" && validator.isUppercase(value);
}
/**
 * Checks if the string is uppercase.
 * If given value is not a string, then it returns false.
 */
function IsUppercase(validationOptions) {
    return ValidateBy({
        name: IS_UPPERCASE,
        validator: {
            validate: (value, args) => isUppercase(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be uppercase", validationOptions)
        }
    }, validationOptions);
}

const LENGTH = "length";
/**
 * Checks if the string's length falls in a range. Note: this function takes into account surrogate pairs.
 * If given value is not a string, then it returns false.
 */
function length(value, min, max) {
    return typeof value === "string" && validator.isLength(value, { min, max });
}
/**
 * Checks if the string's length falls in a range. Note: this function takes into account surrogate pairs.
 * If given value is not a string, then it returns false.
 */
function Length(min, max, validationOptions) {
    return ValidateBy({
        name: LENGTH,
        constraints: [min, max],
        validator: {
            validate: (value, args) => length(value, args.constraints[0], args.constraints[1]),
            defaultMessage: buildMessage((eachPrefix, args) => {
                const isMinLength = args.constraints[0] !== null && args.constraints[0] !== undefined;
                const isMaxLength = args.constraints[1] !== null && args.constraints[1] !== undefined;
                if (isMinLength && (!args.value || args.value.length < args.constraints[0])) {
                    return eachPrefix + "$property must be longer than or equal to $constraint1 characters";
                }
                else if (isMaxLength && (args.value.length > args.constraints[1])) {
                    return eachPrefix + "$property must be shorter than or equal to $constraint2 characters";
                }
                return eachPrefix + "$property must be longer than or equal to $constraint1 and shorter than or equal to $constraint2 characters";
            }, validationOptions)
        }
    }, validationOptions);
}

const MAX_LENGTH = "maxLength";
/**
 * Checks if the string's length is not more than given number. Note: this function takes into account surrogate pairs.
 * If given value is not a string, then it returns false.
 */
function maxLength(value, max) {
    return typeof value === "string" && validator.isLength(value, { min: 0, max });
}
/**
 * Checks if the string's length is not more than given number. Note: this function takes into account surrogate pairs.
 * If given value is not a string, then it returns false.
 */
function MaxLength(max, validationOptions) {
    return ValidateBy({
        name: MAX_LENGTH,
        constraints: [max],
        validator: {
            validate: (value, args) => maxLength(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be shorter than or equal to $constraint1 characters", validationOptions)
        }
    }, validationOptions);
}

const MIN_LENGTH = "minLength";
/**
 * Checks if the string's length is not less than given number. Note: this function takes into account surrogate pairs.
 * If given value is not a string, then it returns false.
 */
function minLength(value, min) {
    return typeof value === "string" && validator.isLength(value, { min });
}
/**
 * Checks if the string's length is not less than given number. Note: this function takes into account surrogate pairs.
 * If given value is not a string, then it returns false.
 */
function MinLength(min, validationOptions) {
    return ValidateBy({
        name: MIN_LENGTH,
        constraints: [min],
        validator: {
            validate: (value, args) => minLength(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be longer than or equal to $constraint1 characters", validationOptions)
        }
    }, validationOptions);
}

const MATCHES = "matches";
function matches(value, pattern, modifiers) {
    return typeof value === "string" && validator.matches(value, pattern, modifiers);
}
function Matches(pattern, modifiersOrAnnotationOptions, validationOptions) {
    let modifiers;
    if (modifiersOrAnnotationOptions && modifiersOrAnnotationOptions instanceof Object && !validationOptions) {
        validationOptions = modifiersOrAnnotationOptions;
    }
    else {
        modifiers = modifiersOrAnnotationOptions;
    }
    return ValidateBy({
        name: MATCHES,
        constraints: [pattern, modifiers],
        validator: {
            validate: (value, args) => matches(value, args.constraints[0], args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix, args) => eachPrefix + "$property must match $constraint1 regular expression", validationOptions)
        }
    }, validationOptions);
}

const IS_PHONE_NUMBER = "isPhoneNumber";
/**
 * Checks if the string is a valid phone number.
 * @param value the potential phone number string to test
 * @param {string} region 2 characters uppercase country code (e.g. DE, US, CH).
 * If users must enter the intl. prefix (e.g. +41), then you may pass "ZZ" or null as region.
 * See [google-libphonenumber, metadata.js:countryCodeToRegionCodeMap on github]{@link https://github.com/ruimarinho/google-libphonenumber/blob/1e46138878cff479aafe2ce62175c6c49cb58720/src/metadata.js#L33}
 */
function isPhoneNumber(value, region) {
    const phoneUtil = PhoneNumberUtil.getInstance();
    try {
        const phoneNum = phoneUtil.parseAndKeepRawInput(value, region);
        const result = phoneUtil.isValidNumber(phoneNum);
        return result;
    }
    catch (error) {
        // logging?
        return false;
    }
}
/**
 * Checks if the string is a valid phone number.
 * @param region 2 characters uppercase country code (e.g. DE, US, CH).
 * If users must enter the intl. prefix (e.g. +41), then you may pass "ZZ" or null as region.
 * See [google-libphonenumber, metadata.js:countryCodeToRegionCodeMap on github]{@link https://github.com/ruimarinho/google-libphonenumber/blob/1e46138878cff479aafe2ce62175c6c49cb58720/src/metadata.js#L33}
 */
function IsPhoneNumber(region, validationOptions) {
    return ValidateBy({
        name: IS_PHONE_NUMBER,
        constraints: [region],
        validator: {
            validate: (value, args) => isPhoneNumber(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a valid phone number", validationOptions),
        }
    }, validationOptions);
}

const IS_MILITARY_TIME = "isMilitaryTime";
/**
 * Checks if the string represents a time without a given timezone in the format HH:MM (military)
 * If the given value does not match the pattern HH:MM, then it returns false.
 */
function isMilitaryTime(value) {
    const militaryTimeRegex = /^([01]\d|2[0-3]):?([0-5]\d)$/;
    return typeof value === "string" && validator.matches(value, militaryTimeRegex);
}
/**
 * Checks if the string represents a time without a given timezone in the format HH:MM (military)
 * If the given value does not match the pattern HH:MM, then it returns false.
 */
function IsMilitaryTime(validationOptions) {
    return ValidateBy({
        name: IS_MILITARY_TIME,
        validator: {
            validate: (value, args) => isMilitaryTime(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a valid representation of military time in the format HH:MM", validationOptions)
        }
    }, validationOptions);
}

const IS_HASH = "isHash";
/**
 * Check if the string is a hash of type algorithm.
 * Algorithm is one of ['md4', 'md5', 'sha1', 'sha256', 'sha384', 'sha512', 'ripemd128', 'ripemd160', 'tiger128',
 * 'tiger160', 'tiger192', 'crc32', 'crc32b']
 */
function isHash(value, algorithm) {
    return typeof value === "string" && validator.isHash(value, algorithm);
}
/**
 * Check if the string is a hash of type algorithm.
 * Algorithm is one of ['md4', 'md5', 'sha1', 'sha256', 'sha384', 'sha512', 'ripemd128', 'ripemd160', 'tiger128',
 * 'tiger160', 'tiger192', 'crc32', 'crc32b']
 */
function IsHash(algorithm, validationOptions) {
    return ValidateBy({
        name: IS_HASH,
        constraints: [algorithm],
        validator: {
            validate: (value, args) => isHash(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a hash of type $constraint1", validationOptions)
        }
    }, validationOptions);
}

const IS_ISSN = "isISSN";
/**
 * Checks if the string is a ISSN.
 * If given value is not a string, then it returns false.
 */
function isISSN(value, options) {
    return typeof value === "string" && validator.isISSN(value, options);
}
/**
 * Checks if the string is a ISSN.
 * If given value is not a string, then it returns false.
 */
function IsISSN(options, validationOptions) {
    return ValidateBy({
        name: IS_ISSN,
        constraints: [options],
        validator: {
            validate: (value, args) => isISSN(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a ISSN", validationOptions)
        }
    }, validationOptions);
}

const IS_DATE_STRING = "isDateString";
/**
 * Checks if a given value is a ISOString date.
 */
function isDateString(value) {
    const regex = /^\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|[\+\-][0-2]\d(?:\:[0-5]\d)?)?$/g;
    return typeof value === "string" && regex.test(value);
}
/**
 * Checks if a given value is a ISOString date.
 */
function IsDateString(validationOptions) {
    return ValidateBy({
        name: IS_DATE_STRING,
        validator: {
            validate: (value, args) => isDateString(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a ISOString", validationOptions)
        }
    }, validationOptions);
}

const IS_BOOLEAN_STRING = "isBooleanString";
/**
 * Checks if a string is a boolean.
 * If given value is not a string, then it returns false.
 */
function isBooleanString(value) {
    return typeof value === "string" && validator.isBoolean(value);
}
/**
 * Checks if a string is a boolean.
 * If given value is not a string, then it returns false.
 */
function IsBooleanString(validationOptions) {
    return ValidateBy({
        name: IS_BOOLEAN_STRING,
        validator: {
            validate: (value, args) => isBooleanString(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a boolean string", validationOptions)
        }
    }, validationOptions);
}

const IS_NUMBER_STRING = "isNumberString";
/**
 * Checks if the string is numeric.
 * If given value is not a string, then it returns false.
 */
function isNumberString(value, options) {
    return typeof value === "string" && validator.isNumeric(value, options);
}
/**
 * Checks if the string is numeric.
 * If given value is not a string, then it returns false.
 */
function IsNumberString(options, validationOptions) {
    return ValidateBy({
        name: IS_NUMBER_STRING,
        constraints: [options],
        validator: {
            validate: (value, args) => isNumberString(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a number string", validationOptions)
        }
    }, validationOptions);
}

const IS_BASE32 = "isBase32";
/**
 * Checks if a string is base32 encoded.
 * If given value is not a string, then it returns false.
 */
function isBase32(value) {
    return typeof value === "string" && validator.isBase32(value);
}
/**
 * Check if a string is base32 encoded.
 * If given value is not a string, then it returns false.
 */
function IsBase32(validationOptions) {
    return ValidateBy({
        name: IS_BASE32,
        validator: {
            validate: (value, args) => isBase32(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be base32 encoded", validationOptions)
        }
    }, validationOptions);
}

const IS_BIC = "isBIC";
/**
 * Check if a string is a BIC (Bank Identification Code) or SWIFT code.
 * If given value is not a string, then it returns false.
 */
function isBIC(value) {
    return typeof value === "string" && validator.isBIC(value);
}
/**
 * Check if a string is a BIC (Bank Identification Code) or SWIFT code.
 * If given value is not a string, then it returns false.
 */
function IsBIC(validationOptions) {
    return ValidateBy({
        name: IS_BIC,
        validator: {
            validate: (value, args) => isBIC(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a BIC or SWIFT code", validationOptions)
        }
    }, validationOptions);
}

const IS_BTC_ADDRESS = "isBtcAddress";
/**
 * Check if the string is a valid BTC address.
 * If given value is not a string, then it returns false.
 */
function isBtcAddress(value) {
    return typeof value === "string" && validator.isBtcAddress(value);
}
/**
 * Check if the string is a valid BTC address.
 * If given value is not a string, then it returns false.
 */
function IsBtcAddress(validationOptions) {
    return ValidateBy({
        name: IS_BTC_ADDRESS,
        validator: {
            validate: (value, args) => isBtcAddress(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a BTC address", validationOptions)
        }
    }, validationOptions);
}

const IS_DATA_URI = "isDataURI";
/**
 * Check if the string is a data uri format.
 * If given value is not a string, then it returns false.
 */
function isDataURI(value) {
    return typeof value === "string" && validator.isDataURI(value);
}
/**
 * Check if the string is a data uri format.
 * If given value is not a string, then it returns false.
 */
function IsDataURI(validationOptions) {
    return ValidateBy({
        name: IS_DATA_URI,
        validator: {
            validate: (value, args) => isDataURI(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a data uri format", validationOptions)
        }
    }, validationOptions);
}

const IS_EAN = "isEAN";
/**
 * Check if the string is an EAN (European Article Number).
 * If given value is not a string, then it returns false.
 */
function isEAN(value) {
    return typeof value === "string" && validator.isEAN(value);
}
/**
 * Check if the string is an EAN (European Article Number).
 * If given value is not a string, then it returns false.
 */
function IsEAN(validationOptions) {
    return ValidateBy({
        name: IS_EAN,
        validator: {
            validate: (value, args) => isEAN(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be an EAN (European Article Number)", validationOptions)
        }
    }, validationOptions);
}

const IS_ETHEREUM_ADDRESS = "isEthereumAddress";
/**
 * Check if the string is an Ethereum address using basic regex. Does not validate address checksums.
 * If given value is not a string, then it returns false.
 */
function isEthereumAddress(value) {
    return typeof value === "string" && validator.isEthereumAddress(value);
}
/**
 * Check if the string is an Ethereum address using basic regex. Does not validate address checksums.
 * If given value is not a string, then it returns false.
 */
function IsEthereumAddress(validationOptions) {
    return ValidateBy({
        name: IS_ETHEREUM_ADDRESS,
        validator: {
            validate: (value, args) => isEthereumAddress(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be an Ethereum address", validationOptions)
        }
    }, validationOptions);
}

const IS_HSL = "isHSL";
/**
* Check if the string is an HSL (hue, saturation, lightness, optional alpha) color based on CSS Colors Level 4 specification.
 * Comma-separated format supported. Space-separated format supported with the exception of a few edge cases (ex: hsl(200grad+.1%62%/1)).
 * If given value is not a string, then it returns false.
 */
function isHSL(value) {
    return typeof value === "string" && validator.isHSL(value);
}
/**
 * Check if the string is an HSL (hue, saturation, lightness, optional alpha) color based on CSS Colors Level 4 specification.
 * Comma-separated format supported. Space-separated format supported with the exception of a few edge cases (ex: hsl(200grad+.1%62%/1)).
 * If given value is not a string, then it returns false.
 */
function IsHSL(validationOptions) {
    return ValidateBy({
        name: IS_HSL,
        validator: {
            validate: (value, args) => isHSL(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a HSL color", validationOptions)
        }
    }, validationOptions);
}

const IS_IBAN = "isIBAN";
/**
 * Check if a string is a IBAN (International Bank Account Number).
 * If given value is not a string, then it returns false.
 */
function isIBAN(value) {
    return typeof value === "string" && validator.isIBAN(value);
}
/**
 * Check if a string is a IBAN (International Bank Account Number).
 * If given value is not a string, then it returns false.
 */
function IsIBAN(validationOptions) {
    return ValidateBy({
        name: IS_IBAN,
        validator: {
            validate: (value, args) => isIBAN(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be an IBAN", validationOptions)
        }
    }, validationOptions);
}

const IS_IDENTITY_CARD = "isIdentityCard";
/**
 * Check if the string is a valid identity card code.
 * locale is one of ['ES', 'zh-TW', 'he-IL', 'ar-TN'] OR 'any'. If 'any' is used, function will check if any of the locals match.
 * Defaults to 'any'.
 * If given value is not a string, then it returns false.
 */
function isIdentityCard(value, locale) {
    return typeof value === "string" && validator.isIdentityCard(value, locale);
}
/**
 * Check if the string is a valid identity card code.
 * locale is one of ['ES', 'zh-TW', 'he-IL', 'ar-TN'] OR 'any'. If 'any' is used, function will check if any of the locals match.
 * Defaults to 'any'.
 * If given value is not a string, then it returns false.
 */
function IsIdentityCard(locale, validationOptions) {
    return ValidateBy({
        name: IS_IDENTITY_CARD,
        constraints: [locale],
        validator: {
            validate: (value, args) => isIdentityCard(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a identity card number", validationOptions)
        }
    }, validationOptions);
}

const IS_ISRC = "isISRC";
/**
 * Check if the string is a ISRC.
 * If given value is not a string, then it returns false.
 */
function isISRC(value) {
    return typeof value === "string" && validator.isISRC(value);
}
/**
 * Check if the string is a ISRC.
 * If given value is not a string, then it returns false.
 */
function IsISRC(validationOptions) {
    return ValidateBy({
        name: IS_ISRC,
        validator: {
            validate: (value, args) => isISRC(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be an ISRC", validationOptions)
        }
    }, validationOptions);
}

const IS_LOCALE = "isLocale";
/**
 * Check if the string is a locale.
 * If given value is not a string, then it returns false.
 */
function isLocale(value) {
    return typeof value === "string" && validator.isLocale(value);
}
/**
 * Check if the string is a locale.
 * If given value is not a string, then it returns false.
 */
function IsLocale(validationOptions) {
    return ValidateBy({
        name: IS_LOCALE,
        validator: {
            validate: (value, args) => isLocale(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be locale", validationOptions)
        }
    }, validationOptions);
}

const IS_MAGNET_URI = "isMagnetURI";
/**
 * Check if the string is a magnet uri format.
 * If given value is not a string, then it returns false.
 */
function isMagnetURI(value) {
    return typeof value === "string" && validator.isMagnetURI(value);
}
/**
 * Check if the string is a magnet uri format.
 * If given value is not a string, then it returns false.
 */
function IsMagnetURI(validationOptions) {
    return ValidateBy({
        name: IS_MAGNET_URI,
        validator: {
            validate: (value, args) => isMagnetURI(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be magnet uri format", validationOptions)
        }
    }, validationOptions);
}

const IS_MIME_TYPE = "isMimeType";
/**
 * Check if the string matches to a valid MIME type format
 * If given value is not a string, then it returns false.
 */
function isMimeType(value) {
    return typeof value === "string" && validator.isMimeType(value);
}
/**
 * Check if the string matches to a valid MIME type format
 * If given value is not a string, then it returns false.
 */
function IsMimeType(validationOptions) {
    return ValidateBy({
        name: IS_MIME_TYPE,
        validator: {
            validate: (value, args) => isMimeType(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be MIME type format", validationOptions)
        }
    }, validationOptions);
}

const IS_OCTAL = "isOctal";
/**
 * Check if the string is a valid octal number.
 * If given value is not a string, then it returns false.
 */
function isOctal(value) {
    return typeof value === "string" && validator.isOctal(value);
}
/**
 * Check if the string is a valid octal number.
 * If given value is not a string, then it returns false.
 */
function IsOctal(validationOptions) {
    return ValidateBy({
        name: IS_OCTAL,
        validator: {
            validate: (value, args) => isOctal(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be valid octal number", validationOptions)
        }
    }, validationOptions);
}

const IS_PASSPORT_NUMBER = "isPassportNumber";
/**
 * Check if the string is a valid passport number relative to a specific country code.
 * If given value is not a string, then it returns false.
 */
function isPassportNumber(value, countryCode) {
    return typeof value === "string" && validator.isPassportNumber(value, countryCode);
}
/**
 * Check if the string is a valid passport number relative to a specific country code.
 * If given value is not a string, then it returns false.
 */
function IsPassportNumber(countryCode, validationOptions) {
    return ValidateBy({
        name: IS_PASSPORT_NUMBER,
        constraints: [countryCode],
        validator: {
            validate: (value, args) => isPassportNumber(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be valid passport number", validationOptions)
        }
    }, validationOptions);
}

const IS_POSTAL_CODE = "isPostalCode";
/**
 * Check if the string is a postal code,
 * (locale is one of [ 'AD', 'AT', 'AU', 'BE', 'BG', 'BR', 'CA', 'CH', 'CZ', 'DE', 'DK', 'DZ', 'EE', 'ES', 'FI', 'FR', 'GB', 'GR', 'HR', 'HU', 'ID', 'IE' 'IL', 'IN', 'IR', 'IS', 'IT', 'JP', 'KE', 'LI', 'LT', 'LU', 'LV', 'MT', 'MX', 'NL', 'NO', 'NZ', 'PL', 'PR', 'PT', 'RO', 'RU', 'SA', 'SE', 'SI', 'TN', 'TW', 'UA', 'US', 'ZA', 'ZM' ] OR 'any'. If 'any' is used, function will check if any of the locals match. Locale list is validator.isPostalCodeLocales.).
 * If given value is not a string, then it returns false.
 */
function isPostalCode(value, locale) {
    return typeof value === "string" && validator.isPostalCode(value, locale);
}
/**
 * Check if the string is a postal code,
 * (locale is one of [ 'AD', 'AT', 'AU', 'BE', 'BG', 'BR', 'CA', 'CH', 'CZ', 'DE', 'DK', 'DZ', 'EE', 'ES', 'FI', 'FR', 'GB', 'GR', 'HR', 'HU', 'ID', 'IE' 'IL', 'IN', 'IR', 'IS', 'IT', 'JP', 'KE', 'LI', 'LT', 'LU', 'LV', 'MT', 'MX', 'NL', 'NO', 'NZ', 'PL', 'PR', 'PT', 'RO', 'RU', 'SA', 'SE', 'SI', 'TN', 'TW', 'UA', 'US', 'ZA', 'ZM' ] OR 'any'. If 'any' is used, function will check if any of the locals match. Locale list is validator.isPostalCodeLocales.).
 * If given value is not a string, then it returns false.
 */
function IsPostalCode(locale, validationOptions) {
    return ValidateBy({
        name: IS_POSTAL_CODE,
        constraints: [locale],
        validator: {
            validate: (value, args) => isPostalCode(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a postal code", validationOptions)
        }
    }, validationOptions);
}

const IS_RFC_3339 = "isRFC3339";
/**
 * Check if the string is a valid RFC 3339 date.
 * If given value is not a string, then it returns false.
 */
function isRFC3339(value) {
    return typeof value === "string" && validator.isRFC3339(value);
}
/**
 * Check if the string is a valid RFC 3339 date.
 * If given value is not a string, then it returns false.
 */
function IsRFC3339(validationOptions) {
    return ValidateBy({
        name: IS_RFC_3339,
        validator: {
            validate: (value, args) => isRFC3339(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be RFC 3339 date", validationOptions)
        }
    }, validationOptions);
}

const IS_RGB_COLOR = "isRgbColor";
/**
* Check if the string is a rgb or rgba color.
 * `includePercentValues` defaults to true. If you don't want to allow to set rgb or rgba values with percents, like rgb(5%,5%,5%), or rgba(90%,90%,90%,.3), then set it to false.
 * If given value is not a string, then it returns false.
 */
function isRgbColor(value, includePercentValues) {
    return typeof value === "string" && validator.isRgbColor(value, includePercentValues);
}
/**
 * Check if the string is a rgb or rgba color.
 * `includePercentValues` defaults to true. If you don't want to allow to set rgb or rgba values with percents, like rgb(5%,5%,5%), or rgba(90%,90%,90%,.3), then set it to false.
 * If given value is not a string, then it returns false.
 */
function IsRgbColor(includePercentValues, validationOptions) {
    return ValidateBy({
        name: IS_RGB_COLOR,
        constraints: [includePercentValues],
        validator: {
            validate: (value, args) => isRgbColor(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be RGB color", validationOptions)
        }
    }, validationOptions);
}

const IS_SEM_VER = "isSemVer";
/**
 * Check if the string is a Semantic Versioning Specification (SemVer).
 * If given value is not a string, then it returns false.
 */
function isSemVer(value) {
    return typeof value === "string" && validator.isSemVer(value);
}
/**
 * Check if the string is a Semantic Versioning Specification (SemVer).
 * If given value is not a string, then it returns false.
 */
function IsSemVer(validationOptions) {
    return ValidateBy({
        name: IS_SEM_VER,
        validator: {
            validate: (value, args) => isSemVer(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a Semantic Versioning Specification", validationOptions)
        }
    }, validationOptions);
}

const IS_BOOLEAN = "isBoolean";
/**
 * Checks if a given value is a number.
 */
function isBoolean(value) {
    return value instanceof Boolean || typeof value === "boolean";
}
/**
 * Checks if a value is a number.
 */
function IsBoolean(validationOptions) {
    return ValidateBy({
        name: IS_BOOLEAN,
        validator: {
            validate: (value, args) => isBoolean(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a boolean value", validationOptions)
        }
    }, validationOptions);
}

const IS_DATE = "isDate";
/**
 * Checks if a given value is a number.
 */
function isDate(value) {
    return value instanceof Date && !isNaN(value.getTime());
}
/**
 * Checks if a value is a number.
 */
function IsDate(validationOptions) {
    return ValidateBy({
        name: IS_DATE,
        validator: {
            validate: (value, args) => isDate(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a Date instance", validationOptions)
        }
    }, validationOptions);
}

const IS_NUMBER = "isNumber";
/**
 * Checks if a given value is a number.
 */
function isNumber(value, options = {}) {
    if (typeof value !== "number") {
        return false;
    }
    if (value === Infinity || value === -Infinity) {
        return options.allowInfinity;
    }
    if (Number.isNaN(value)) {
        return options.allowNaN;
    }
    if (options.maxDecimalPlaces !== undefined) {
        let decimalPlaces = 0;
        if ((value % 1) !== 0) {
            decimalPlaces = value.toString().split(".")[1].length;
        }
        if (decimalPlaces > options.maxDecimalPlaces) {
            return false;
        }
    }
    return Number.isFinite(value);
}
/**
 * Checks if a value is a number.
 */
function IsNumber(options = {}, validationOptions) {
    return ValidateBy({
        name: IS_NUMBER,
        constraints: [options],
        validator: {
            validate: (value, args) => isNumber(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a number conforming to the specified constraints", validationOptions)
        }
    }, validationOptions);
}

const IS_ENUM = "isEnum";
/**
 * Checks if a given value is an enum
 */
function isEnum(value, entity) {
    const enumValues = Object.keys(entity)
        .map(k => entity[k]);
    return enumValues.indexOf(value) >= 0;
}
/**
 * Checks if a given value is an enum
 */
function IsEnum(entity, validationOptions) {
    return ValidateBy({
        name: IS_ENUM,
        constraints: [entity],
        validator: {
            validate: (value, args) => isEnum(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a valid enum value", validationOptions)
        }
    }, validationOptions);
}

const IS_INT = "isInt";
/**
 * Checks if value is an integer.
 */
function isInt(val) {
    return typeof val === "number" && Number.isInteger(val);
}
/**
 * Checks if value is an integer.
 */
function IsInt(validationOptions) {
    return ValidateBy({
        name: IS_INT,
        validator: {
            validate: (value, args) => isInt(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be an integer number", validationOptions)
        }
    }, validationOptions);
}

const IS_STRING = "isString";
/**
* Checks if a given value is a real string.
*/
function isString(value) {
    return value instanceof String || typeof value === "string";
}
/**
* Checks if a given value is a real string.
*/
function IsString(validationOptions) {
    return ValidateBy({
        name: IS_STRING,
        validator: {
            validate: (value, args) => isString(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a string", validationOptions)
        }
    }, validationOptions);
}

const IS_ARRAY = "isArray";
/**
 * Checks if a given value is an array
 */
function isArray(value) {
    return value instanceof Array;
}
/**
 * Checks if a given value is an array
 */
function IsArray(validationOptions) {
    return ValidateBy({
        name: IS_ARRAY,
        validator: {
            validate: (value, args) => isArray(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be an array", validationOptions)
        }
    }, validationOptions);
}

const IS_OBJECT = "isObject";
/**
 * Checks if the value is valid Object.
 * Returns false if the value is not an object.
 */
function isObject(value) {
    return value != null && (typeof value === "object" || typeof value === "function") && !Array.isArray(value);
}
/**
 * Checks if the value is valid Object.
 * Returns false if the value is not an object.
 */
function IsObject(validationOptions) {
    return ValidateBy({
        name: IS_OBJECT,
        validator: {
            validate: (value, args) => isObject(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be an object", validationOptions)
        }
    }, validationOptions);
}

const ARRAY_CONTAINS = "arrayContains";
/**
 * Checks if array contains all values from the given array of values.
 * If null or undefined is given then this function returns false.
 */
function arrayContains(array, values) {
    if (!(array instanceof Array))
        return false;
    return values.every(value => array.indexOf(value) !== -1);
}
/**
 * Checks if array contains all values from the given array of values.
 * If null or undefined is given then this function returns false.
 */
function ArrayContains(values, validationOptions) {
    return ValidateBy({
        name: ARRAY_CONTAINS,
        constraints: [values],
        validator: {
            validate: (value, args) => arrayContains(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must contain $constraint1 values", validationOptions)
        }
    }, validationOptions);
}

const ARRAY_NOT_CONTAINS = "arrayNotContains";
/**
 * Checks if array does not contain any of the given values.
 * If null or undefined is given then this function returns false.
 */
function arrayNotContains(array, values) {
    if (!(array instanceof Array))
        return false;
    return values.every(value => array.indexOf(value) === -1);
}
/**
 * Checks if array does not contain any of the given values.
 * If null or undefined is given then this function returns false.
 */
function ArrayNotContains(values, validationOptions) {
    return ValidateBy({
        name: ARRAY_NOT_CONTAINS,
        constraints: [values],
        validator: {
            validate: (value, args) => arrayNotContains(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property should not contain $constraint1 values", validationOptions)
        }
    }, validationOptions);
}

const ARRAY_NOT_EMPTY = "arrayNotEmpty";
/**
 * Checks if given array is not empty.
 * If null or undefined is given then this function returns false.
 */
function arrayNotEmpty(array) {
    return array instanceof Array && array.length > 0;
}
/**
 * Checks if given array is not empty.
 * If null or undefined is given then this function returns false.
 */
function ArrayNotEmpty(validationOptions) {
    return ValidateBy({
        name: ARRAY_NOT_EMPTY,
        validator: {
            validate: (value, args) => arrayNotEmpty(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property should not be empty", validationOptions)
        }
    }, validationOptions);
}

const ARRAY_MIN_SIZE = "arrayMinSize";
/**
 * Checks if array's length is as minimal this number.
 * If null or undefined is given then this function returns false.
 */
function arrayMinSize(array, min) {
    return array instanceof Array && array.length >= min;
}
/**
 * Checks if array's length is as minimal this number.
 * If null or undefined is given then this function returns false.
 */
function ArrayMinSize(min, validationOptions) {
    return ValidateBy({
        name: ARRAY_MIN_SIZE,
        constraints: [min],
        validator: {
            validate: (value, args) => arrayMinSize(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must contain at least $constraint1 elements", validationOptions)
        }
    }, validationOptions);
}

const ARRAY_MAX_SIZE = "arrayMaxSize";
/**
 * Checks if array's length is as maximal this number.
 * If null or undefined is given then this function returns false.
 */
function arrayMaxSize(array, max) {
    return array instanceof Array && array.length <= max;
}
/**
 * Checks if array's length is as maximal this number.
 * If null or undefined is given then this function returns false.
 */
function ArrayMaxSize(max, validationOptions) {
    return ValidateBy({
        name: ARRAY_MAX_SIZE,
        constraints: [max],
        validator: {
            validate: (value, args) => arrayMaxSize(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must contain not more than $constraint1 elements", validationOptions)
        }
    }, validationOptions);
}

const ARRAY_UNIQUE = "arrayUnique";
/**
 * Checks if all array's values are unique. Comparison for objects is reference-based.
 * If null or undefined is given then this function returns false.
 */
function arrayUnique(array) {
    if (!(array instanceof Array))
        return false;
    const uniqueItems = array.filter((a, b, c) => c.indexOf(a) === b);
    return array.length === uniqueItems.length;
}
/**
 * Checks if all array's values are unique. Comparison for objects is reference-based.
 * If null or undefined is given then this function returns false.
 */
function ArrayUnique(validationOptions) {
    return ValidateBy({
        name: ARRAY_UNIQUE,
        validator: {
            validate: (value, args) => arrayUnique(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "All $property's elements must be unique", validationOptions)
        }
    }, validationOptions);
}

const IS_NOT_EMPTY_OBJECT = "isNotEmptyObject";
/**
 * Checks if the value is valid Object & not empty.
 * Returns false if the value is not an object or an empty valid object.
 */
function isNotEmptyObject(value) {
    if (!isObject(value)) {
        return false;
    }
    for (const key in value) {
        if (value.hasOwnProperty(key)) {
            return true;
        }
    }
    return false;
}
/**
 * Checks if the value is valid Object & not empty.
 * Returns false if the value is not an object or an empty valid object.
 */
function IsNotEmptyObject(validationOptions) {
    return ValidateBy({
        name: IS_NOT_EMPTY_OBJECT,
        validator: {
            validate: (value, args) => isNotEmptyObject(value),
            defaultMessage: buildMessage((eachPrefix) => eachPrefix + "$property must be a non-empty object", validationOptions)
        }
    }, validationOptions);
}

const IS_INSTANCE = "isInstance";
/**
 * Checks if the value is an instance of the specified object.
 */
function isInstance(object, targetTypeConstructor) {
    return targetTypeConstructor
        && typeof targetTypeConstructor === "function"
        && object instanceof targetTypeConstructor;
}
/**
 * Checks if the value is an instance of the specified object.
 */
function IsInstance(targetType, validationOptions) {
    return ValidateBy({
        name: IS_INSTANCE,
        constraints: [targetType],
        validator: {
            validate: (value, args) => isInstance(value, args.constraints[0]),
            defaultMessage: buildMessage((eachPrefix, args) => {
                if (args.constraints[0]) {
                    return eachPrefix + `$property must be an instance of ${args.constraints[0].name}`;
                }
                else {
                    return eachPrefix + `${IS_INSTANCE} decorator expects and object as value, but got falsy value.`;
                }
            }, validationOptions)
        }
    }, validationOptions);
}

/**
 * Validates given object by object's decorators or given validation schema.
 */
function validate(schemaNameOrObject, objectOrValidationOptions, maybeValidatorOptions) {
    if (typeof schemaNameOrObject === "string") {
        return getFromContainer(Validator).validate(schemaNameOrObject, objectOrValidationOptions, maybeValidatorOptions);
    }
    else {
        return getFromContainer(Validator).validate(schemaNameOrObject, objectOrValidationOptions);
    }
}
/**
 * Validates given object by object's decorators or given validation schema and reject on error.
 */
function validateOrReject(schemaNameOrObject, objectOrValidationOptions, maybeValidatorOptions) {
    if (typeof schemaNameOrObject === "string") {
        return getFromContainer(Validator).validateOrReject(schemaNameOrObject, objectOrValidationOptions, maybeValidatorOptions);
    }
    else {
        return getFromContainer(Validator).validateOrReject(schemaNameOrObject, objectOrValidationOptions);
    }
}
/**
 * Validates given object by object's decorators or given validation schema.
 * Note that this method completely ignores async validations.
 * If you want to properly perform validation you need to call validate method instead.
 */
function validateSync(schemaNameOrObject, objectOrValidationOptions, maybeValidatorOptions) {
    if (typeof schemaNameOrObject === "string") {
        return getFromContainer(Validator).validateSync(schemaNameOrObject, objectOrValidationOptions, maybeValidatorOptions);
    }
    else {
        return getFromContainer(Validator).validateSync(schemaNameOrObject, objectOrValidationOptions);
    }
}
/**
 * Registers a new validation schema.
 */
function registerSchema(schema) {
    getMetadataStorage().addValidationSchema(schema);
}

export { ARRAY_CONTAINS, ARRAY_MAX_SIZE, ARRAY_MIN_SIZE, ARRAY_NOT_CONTAINS, ARRAY_NOT_EMPTY, ARRAY_UNIQUE, Allow, ArrayContains, ArrayMaxSize, ArrayMinSize, ArrayNotContains, ArrayNotEmpty, ArrayUnique, CONTAINS, Contains, EQUALS, Equals, IS_ALPHA, IS_ALPHANUMERIC, IS_ARRAY, IS_ASCII, IS_BASE32, IS_BASE64, IS_BIC, IS_BOOLEAN, IS_BOOLEAN_STRING, IS_BTC_ADDRESS, IS_BYTE_LENGTH, IS_CREDIT_CARD, IS_CURRENCY, IS_DATA_URI, IS_DATE, IS_DATE_STRING, IS_DECIMAL, IS_DEFINED, IS_DIVISIBLE_BY, IS_EAN, IS_EMAIL, IS_EMPTY, IS_ENUM, IS_ETHEREUM_ADDRESS, IS_FIREBASE_PUSH_ID, IS_FQDN, IS_FULL_WIDTH, IS_HALF_WIDTH, IS_HASH, IS_HEXADECIMAL, IS_HEX_COLOR, IS_HSL, IS_IBAN, IS_IDENTITY_CARD, IS_IN, IS_INSTANCE, IS_INT, IS_IP, IS_ISBN, IS_ISIN, IS_ISO31661_ALPHA_2, IS_ISO31661_ALPHA_3, IS_ISO8601, IS_ISRC, IS_ISSN, IS_JSON, IS_JWT, IS_LATITUDE, IS_LATLONG, IS_LOCALE, IS_LONGITUDE, IS_LOWERCASE, IS_MAC_ADDRESS, IS_MAGNET_URI, IS_MILITARY_TIME, IS_MIME_TYPE, IS_MOBILE_PHONE, IS_MONGO_ID, IS_MULTIBYTE, IS_NEGATIVE, IS_NOT_EMPTY, IS_NOT_EMPTY_OBJECT, IS_NOT_IN, IS_NUMBER, IS_NUMBER_STRING, IS_OBJECT, IS_OCTAL, IS_PASSPORT_NUMBER, IS_PHONE_NUMBER, IS_PORT, IS_POSITIVE, IS_POSTAL_CODE, IS_RFC_3339, IS_RGB_COLOR, IS_SEM_VER, IS_STRING, IS_SURROGATE_PAIR, IS_UPPERCASE, IS_URL, IS_UUID, IS_VARIABLE_WIDTH, IsAlpha, IsAlphanumeric, IsArray, IsAscii, IsBIC, IsBase32, IsBase64, IsBoolean, IsBooleanString, IsBtcAddress, IsByteLength, IsCreditCard, IsCurrency, IsDataURI, IsDate, IsDateString, IsDecimal, IsDefined, IsDivisibleBy, IsEAN, IsEmail, IsEmpty, IsEnum, IsEthereumAddress, IsFQDN, IsFirebasePushId, IsFullWidth, IsHSL, IsHalfWidth, IsHash, IsHexColor, IsHexadecimal, IsIBAN, IsIP, IsISBN, IsISIN, IsISO31661Alpha2, IsISO31661Alpha3, IsISO8601, IsISRC, IsISSN, IsIdentityCard, IsIn, IsInstance, IsInt, IsJSON, IsJWT, IsLatLong, IsLatitude, IsLocale, IsLongitude, IsLowercase, IsMACAddress, IsMagnetURI, IsMilitaryTime, IsMimeType, IsMobilePhone, IsMongoId, IsMultibyte, IsNegative, IsNotEmpty, IsNotEmptyObject, IsNotIn, IsNumber, IsNumberString, IsObject, IsOctal, IsOptional, IsPassportNumber, IsPhoneNumber, IsPort, IsPositive, IsPostalCode, IsRFC3339, IsRgbColor, IsSemVer, IsString, IsSurrogatePair, IsUUID, IsUppercase, IsUrl, IsVariableWidth, LENGTH, Length, MATCHES, MAX, MAX_DATE, MAX_LENGTH, MIN, MIN_DATE, MIN_LENGTH, Matches, Max, MaxDate, MaxLength, MetadataStorage, Min, MinDate, MinLength, NOT_CONTAINS, NOT_EQUALS, NotContains, NotEquals, Validate, ValidateBy, ValidateIf, ValidateNested, ValidatePromise, ValidationError, ValidationTypes, Validator, ValidatorConstraint, arrayContains, arrayMaxSize, arrayMinSize, arrayNotContains, arrayNotEmpty, arrayUnique, buildMessage, contains, equals, getFromContainer, getMetadataStorage, isAlpha, isAlphanumeric, isArray, isAscii, isBIC, isBase32, isBase64, isBoolean, isBooleanString, isBtcAddress, isByteLength, isCreditCard, isCurrency, isDataURI, isDate, isDateString, isDecimal, isDefined, isDivisibleBy, isEAN, isEmail, isEmpty, isEnum, isEthereumAddress, isFQDN, isFirebasePushId, isFullWidth, isHSL, isHalfWidth, isHash, isHexColor, isHexadecimal, isIBAN, isIP, isISBN, isISIN, isISO31661Alpha2, isISO31661Alpha3, isISO8601, isISRC, isISSN, isIdentityCard, isIn, isInstance, isInt, isJSON, isJWT, isLatLong, isLatitude, isLocale, isLongitude, isLowercase, isMACAddress, isMagnetURI, isMilitaryTime, isMimeType, isMobilePhone, isMongoId, isMultibyte, isNegative, isNotEmpty, isNotEmptyObject, isNotIn, isNumber, isNumberString, isObject, isOctal, isPassportNumber, isPhoneNumber, isPort, isPositive, isPostalCode, isRFC3339, isRgbColor, isSemVer, isString, isSurrogatePair, isURL, isUUID, isUppercase, isValidationOptions, isVariableWidth, length, matches, max, maxDate, maxLength, min, minDate, minLength, notContains, notEquals, registerDecorator, registerSchema, useContainer, validate, validateOrReject, validateSync };
//# sourceMappingURL=index.esm.js.map