Lambda - sosroInSpace/wordpress_wiki GitHub Wiki

/*
  check.js
*/

class SchemaError extends Error {
  constructor(message, key) {
    super(key ? `${key}: ${message}` : message);
    this.name = 'SchemaError';
    this.key = key != null ? key : null;
  }
}

function sErr (message, key) {
  throw new SchemaError(message, key);
}

function check (arg, func, key) {
  func(arg, key);
}

function match (arg, func, key) {
  try {
    func(arg, key);
    return true;
  } catch (err) {
    return false;
  }
}

function getError (arg, func, key) {
  try {
    func(arg, key);
    return '';
  } catch (err) {
    return err.message;
  }
}

/*
  Basic Types.
*/

const IsString = (x, key) => {
  if (typeof x !== 'string') {
    sErr('must be a String', key);
  }
};

const IsObject = (x, key) => {
  if (!x || x.constructor !== Object) {
    sErr('must be an Object', key);
  }
};

const IsArray = (x, key) => {
  if (!Array.isArray(x)) {
    sErr('must be an Array', key);
  }
};

const IsNumber = (x, key) => {
  if (typeof x !== 'number') {
    sErr('must be a Number', key);
  }
};

const IsBoolean = (x, key) => {
  if (typeof x !== 'boolean') {
    sErr('must be a Boolean', key);
  }
};

const IsInteger = (x, key) => {
  if (!Number.isInteger(x)) {
    sErr('must be an Integer', key);
  }
};

const IsNull = (x, key) => {
  if (x !== null) {
    sErr('must be Null', key);
  }
}

const IsUndefined = (x, key) => {
  if (x !== undefined) {
    sErr('must be Undefined', key);
  }
};

const IsFunction = (x, key) => {
  if (typeof x !== 'function') {
    sErr('must be a Function', key);
  }
}

const IsAny = (x, key) => {};

/*
  More complicated types.
*/

const IsRegExpOf = (regExp) => (x, key) => {
  IsString(x, key);
  if (!regExp.test(x)) {
    sErr(`must match ${regExp}`, key);
  }
};

const IsStringOfLength = (min, max) => (x, key) => {
  IsString(x, key);
  if (max === undefined) {
    max = min;
  }
  if (x.length < min || x.length > max) {
    const rangeText = max !== min ? `${min} - ${max}` : min;
    sErr(`must be String of length ${rangeText}`, key);
  }
};

const IsObjectWith = (obj) => (x, key) => {
  IsObject(x, key);
  for (let [k, func] of Object.entries(obj)) {
    func(x[k], key ? `${key}.${k}` : k);
  }
};

const IsObjectExact = (obj) => (x, key) => {
  IsObject(x, key);
  for (let k of Object.keys(x)) {
    if (!obj[k]) {
      sErr('is illegal key', key ? `${key}.${k}` : k);
    }
  }
  IsObjectWith(obj)(x, key);
};

const IsArrayOfLength = (min, max) => (x, key) => {
  IsArray(x, key);
  if (max === undefined) {
    max = min;  // If max is undefined, use min.
  }
  if (x.length < min || x.length > max) {
    const rangeText = max !== min ? `${min} - ${max}` : min;
    sErr(`must be Array of length ${rangeText}`, key);
  }
};

const IsArrayOf = (func) => (x, key) => {
  IsArray(x, key);
  for (let i = 0; i < x.length; i++) {
    func(x[i], key ? `${key}.${i}` : i);
  }
};

const IsArrayOfLengthWith = (min, max, func) => (x, key) => {
  if (func === undefined) {
    func = max;  // If func is undefined, assume min = max.
    max = undefined;
  }
  IsArrayOfLength(min, max)(x, key);
  IsArrayOf(func)(x, key);
};

// Based on: https://github.com/manishsaraan/email-validator/blob/master/index.js
// NOTE: We need international emails to work, so we can't be picky with this.
const IsEmail = (x, key) => {
  IsStringOfLength(0, 256)(x, key);
  
  const [account, address] = x.split('@');
  if (account.length === 0 || account.length > 64) {
    sErr('Invalid email address', key);
  }
  if (address.length === 0) {
    sErr('Invalid email address', key);
  }
  
  const domainParts = address.split('.');
  if (domainParts.some(part => part.length > 63)) {
    sErr('Invalid email address', key);
  }
};

/*
  Utility types.
*/

const IsMaybe = (func) => (x, key) => {
  if (x != null) {
    func(x, key);
  }
};

const IsOptional = (func) => (x, key) => {
  if (x !== undefined) {
    func(x, key);
  }
};

const IsOneOf = (...funcs) => (x, key) => {
  const errors = [];
  for (let func of funcs) {
    try {
      func(x, key);
      return;
    } catch (err) {
      errors.push(err.message);
    }
  }
  sErr(`Failed checks: (${errors.join(', ')})`, key);
};

const IsCustom = (func) => (x, key) => {
  func(x, key);
};

module.exports = {
  check,
  IsString,
  IsObject,
  IsArray,
  IsNumber,
  IsBoolean,
  IsInteger,
  IsNull,
  IsUndefined,
  IsFunction,
  IsAny,
  IsRegExpOf,
  IsStringOfLength,
  IsObjectWith,
  IsObjectExact,
  IsArrayOfLength,
  IsArrayOf,
  IsArrayOfLengthWith,
  IsEmail,
  IsMaybe,
  IsOptional,
  IsOneOf,
  IsCustom
};