sequelize/sequelize

View on GitHub
src/utils/validator-extras.ts

Summary

Maintainability
A
1 hr
Test Coverage
import _ from 'lodash';
import Validator from 'validator';
import moment from 'moment';

type ValidatorFn = (arg0: string) => boolean;
type RegExpValidatorFn = (str: string, pattern: RegExp, modifiers: string) => boolean;

interface ValidatorCustom extends ValidatorJS.ValidatorStatic {
  extend: <T extends Function>(name: string, fn: T) => void;
  contains: (str: string, elem: string) => boolean;
  is: RegExpValidatorFn;
  isDate: ValidatorFn;
  isDecimal: ValidatorFn;
  isIPv4: ValidatorFn;
  isIPv6: ValidatorFn;
  isImmutable: (...args: any[]) => boolean;
  isNull: ValidatorFn;
  isUrl: ValidatorFn;
  len: (str: string, min: number, max: number) => boolean;
  max: (str: string, val: number) => boolean;
  min: (str: string, val: number) => boolean;
  not: RegExpValidatorFn;
  notContains: (str: string, elem: string) => boolean;
  notEmpty: ValidatorFn;
  notIn: (str: string, values: string[]) => boolean;
  notNull: ValidatorFn;
  notRegex: RegExpValidatorFn;
  regex: RegExpValidatorFn;
}

const clonedValidator = _.cloneDeep(Validator);

export const extensions = {
  extend<T extends Function>(name: string, fn: T): void {
    // eslint-disable-next-line
    // @ts-ignore
    this[name] = fn;
  },
  notEmpty(str: string): boolean {
    return !str.match(/^[\s\t\r\n]*$/);
  },
  len(str: string, min: number, max: number): boolean {
    return clonedValidator.isLength(str, min, max);
  },
  isUrl(str: string): boolean {
    return clonedValidator.isURL(str);
  },
  isIPv6(str: string): boolean {
    return clonedValidator.isIP(str, 6);
  },
  isIPv4(str: string): boolean {
    return clonedValidator.isIP(str, 4);
  },
  notIn(str: string, values: string[]): boolean {
    return !clonedValidator.isIn(str, values);
  },
  regex(str: string, pattern: RegExp, modifiers: string): boolean {
    str += '';
    if (Object.prototype.toString.call(pattern).slice(8, -1) !== 'RegExp') {
      pattern = new RegExp(pattern, modifiers);
    }
    const result = str.match(pattern);
    return result ? result.length > 0 : false;
  },
  notRegex(str: string, pattern: RegExp, modifiers: string): boolean {
    return !this.regex(str, pattern, modifiers);
  },
  isDecimal(str: string): boolean {
    return str !== '' && !!str.match(/^(?:-?(?:[0-9]+))?(?:\.[0-9]*)?(?:[eE][+-]?(?:[0-9]+))?$/);
  },
  min(str: string, val: number): boolean {
    const number = parseFloat(str);
    return isNaN(number) || number >= val;
  },
  max(str: string, val: number): boolean {
    const number = parseFloat(str);
    return isNaN(number) || number <= val;
  },
  not(str: string, pattern: RegExp, modifiers: string): boolean {
    return this.notRegex(str, pattern, modifiers);
  },
  contains(str: string, elem: string): boolean {
    return !!elem && str.includes(elem);
  },
  notContains(str: string, elem: string): boolean {
    return !this.contains(str, elem);
  },
  is(str: string, pattern: RegExp, modifiers: string): boolean {
    return this.regex(str, pattern, modifiers);
  }
};

const extraValidator = {
  // instance based validators
  isImmutable(
    value: unknown,
    validatorArgs: unknown[],
    field: string,
    modelInstance: {
      isNewRecord: boolean;
      getDataValue: (args0: string) => unknown;
      previous: (args0: string) => unknown;
    }
  ) {
    return modelInstance.isNewRecord || modelInstance.getDataValue(field) === modelInstance.previous(field);
  },

  // extra validators
  notNull(val: string) {
    return val !== null && val !== undefined;
  },

  // map isNull to isEmpty
  // https://github.com/chriso/validator.js/commit/e33d38a26ee2f9666b319adb67c7fc0d3dea7125
  isNull: clonedValidator.isEmpty,

  // isDate removed in 7.0.0
  // https://github.com/chriso/validator.js/commit/095509fc707a4dc0e99f85131df1176ad6389fc9
  isDate(dateString: string) {
    // avoid http://momentjs.com/guides/#/warnings/js-date/
    // by doing a preliminary check on `dateString`
    const parsed = Date.parse(dateString);
    if (isNaN(parsed)) {
      // fail if we can't parse it
      return false;
    }
    // otherwise convert to ISO 8601 as moment prefers
    // http://momentjs.com/docs/#/parsing/string/
    const date = new Date(parsed);
    return moment(date.toISOString()).isValid();
  }
};

export const validator: ValidatorCustom = { ...clonedValidator, ...extensions, ...extraValidator };