Typ

by JeyDotC

JavaScript

const ValueKind = {
  Parameter: 0,
  ReturnValue: 1,
}

class TypeCheckError extends Error {
  constructor({
    kind,
    index,
    expectedTypeName
  }) {
    super(`${kind === ValueKind.Parameter ? `Parameter ${index}` : 'Return value'} must be an instance of ${expectedTypeName}`);
  }
}

class TypeCheck {
  constructor(name, implementation) {
    this.implementation = implementation;
    this.name = name;
  }
  perform(value) {
    this.implementation(value);
  }
}

const Any = new TypeCheck("Any", () => {});

const Void = Any;
Void.name = 'Void';

function Optional(Type) {
  return new TypeCheck(`Optional<${Type.name}>`, (entry) => {
    if (entry.value === undefined || entry.value === null) {
      return;
    }
    typeCheckFactory(Type).perform(entry);
  });
}

function OneOf(...types) {
  const expectedTypeName = `OneOf<${types.map(t => t.name).join(', ')}>`;
  return new TypeCheck(expectedTypeName, (entry) => {
    const didAnyValidate = types.some((T) => {
      try {
        typeCheckFactory(T).perform(entry);
        return true;
      } catch (e) {
        return false;
      }
    });
    if (!didAnyValidate) {
      const {
        index,
        kind,
      } = entry;
      throw new TypeCheckError({
        index,
        kind,
        expectedTypeName
      });
    }
  });
}

function PromiseOf(Type) {
  return new TypeCheck(`Promise<${Type.name}>`, (entry) => {
    if (entry.value instanceof Promise) {
      entry.value.then((value) => {
        typeCheckFactory(Type).perform({
          ...entry,
          value
        });
        return value;
      });
    }
    InstanceOf(Promise).perform(entry);
  });
}

function InstanceOf(Type) {
  return new TypeCheck(Type.name, ({
    index,
    value,
    kind,
  }) => {
    if (!(value instanceof Type)) {
      throw new TypeCheckError({
        index,
        kind,
        expectedTypeName: Type.name
      });
    }
  })
}

const InstanceOfStringCheck = new TypeCheck('string', ({
  index,
 ...