File

src/lib/validators/is-complex.ts

Index

Properties

Properties

alphaNumeric
alphaNumeric: string
Type : string
digit
digit: string
Type : string
lowercase
lowercase: string
Type : string
special
special: string
Type : string
uppercase
uppercase: string
Type : string
upperLower
upperLower: string
Type : string
import {
  AbstractControl,
  ValidationErrors,
} from '@angular/forms';
import { IsString } from './is-string';
import { isNumber } from './is-number';

export interface ComplexityOptions {
  uppercase?: number;
  special?: number;
  digit?: number;
  lowercase?: number;
  upperLower?: number;
  alphaNumeric?: number;
  min?: number,
  max?: number,
  range?: number,
  exact?: number,
}

interface RegexOptions {
  uppercase: string;
  special: string;
  digit: string;
  lowercase: string;
  upperLower: string;
  alphaNumeric: string;
}

const regexOptions: RegexOptions = {
  uppercase: '.*[A-Z]',
  special: '.*[^A-Za-z0-9]',
  digit: '.*[0-9]',
  lowercase: '.*[a-z]',
  upperLower: '.*[a-zA-Z]',
  alphaNumeric: '.*[a-zA-Z0-9]',
};

interface LengthOptions {
  min: string,
  max: string,
  range: string,
  exact: string,
  no_limit: string;
}

const lengthOptions: LengthOptions = {
  min: '.{n,}',
  max: '.{0,n}',
  range: '.{min,max}',
  exact: '.{n}',
  no_limit: '.*',
};

type Discriminate<U, K extends PropertyKey> =
  U extends any
    ? K extends keyof U ? U : U & Record<K, unknown>
    : never;

function inOperator<K extends PropertyKey, T extends object>(k: K, o: T): o is Discriminate<T, K> {
  return k in o;
}

function create(options: ComplexityOptions): string {
  let regex = '^';
  for (const [ key, value ] of Object.entries(regexOptions)) {
    if (inOperator(key, options) && isNumber(options[key])) {
      regex += '(?=' + value.repeat(options[key]) + ')';
    }
  }
  if (isNumber(options.min) && isNumber(options.max)) {
    regex += lengthOptions.range.replace('min', options.min.toFixed(0)).replace('max', options.max.toFixed(0));
  } else if (isNumber(options.max)) {
    regex += lengthOptions.max.replace('n', options.max.toFixed(0));
  } else if (isNumber(options.min)) {
    regex += lengthOptions.min.replace('n', options.min.toFixed(0));
  } else if (isNumber(options.exact)) {
    regex += lengthOptions.exact.replace('n', options.exact.toFixed(0));
  } else {
    regex += lengthOptions.no_limit;
  }
  regex += '$';
  return regex;
}

function check(str: string, regexStringOrOptions: string | ComplexityOptions) {
  let regexString: string;
  if (typeof regexStringOrOptions === 'object') {
    regexString = create(regexStringOrOptions);
  } else {
    regexString = regexStringOrOptions;
  }
  return new RegExp(regexString).test(str);
}

function checkError(str: string, options: ComplexityOptions) {
  const returnObject: Record<string, boolean> = {};
  for (const [ key, value ] of Object.entries(options)) {
    returnObject[key] = check(str, { [key]: value });
  }
  return returnObject;
}

export function IsComplex({
                            message,
                            options,
                          }: { message?: string, options: ComplexityOptions }) {
  return (control: AbstractControl): ValidationErrors | null => {
    if (control.value === null) {
      return null;
    }
    const isNotString = IsString({ message })(control);
    if (isNotString !== null) {
      return isNotString;
    }
    const errors = checkError(control.value, options);
    if (Object.keys(errors).length && Object.values(errors).some(item => !item)) {
      return {
        isComplex: {
          expected: options,
          actual: errors,
          message,
        },
      };
    }
    return null;
  };
}

results matching ""

    No results matching ""