Fast, compiled, eval-free data validator/transformer

Overview

spectypes

npm build publish codecov Type Coverage Libraries.io dependency status for latest release npm

Fast, compiled, eval-free data validator/transformer


Features

  • really fast, can be even faster than ajv
  • detailed errors, failure will result into explicit error message(s) and path to invalid data
  • extensively tested, each release undergoes more than 900 fast-check powered tests
  • precise types, accurately infers all types and provides readable compile-time error messages
  • browser friendly, uses babel to compile validators, so no eval or new Function involved
  • easily extensible, custom validators are created by mixing existing ones

Getting started

  1. There are two packages to install - spectypes, which contains type definitions and small set of runtime helpers and babel-plugin-spectypes, which parses and compiles validators into functions:

    npm i spectypes
    npm i babel-plugin-spectypes -D
    
  2. Add babel-plugin-spectypes to plugins section in your babel config:

    "plugins": [
    +  "babel-plugin-spectypes"
    ]

Example

import { array, number } from 'spectypes'

const check = array(number)

expect(check([1, 2, 3])).toEqual({
  tag: 'success',
  success: [1, 2, 3] // readonly number[]
})

expect(check({ 0: 1 })).toEqual({
  tag: 'failure',
  failure: {
    value: { 0: 1 }, // unknown
    errors: [{ issue: 'not an array', path: [] }]
  }
})

expect(check([1, 2, '3', false])).toEqual({
  tag: 'failure',
  failure: {
    value: [1, 2, '3', false], // unknown
    errors: [
      { issue: 'not a number', path: [2] },
      { issue: 'not a number', path: [3] }
    ]
  }
})
Transformed code
import * as _spectypes from 'spectypes';

const check = (value) => {
  let err

  if (!Array.isArray(value)) {
    ;(err = err || []).push({
      issue: 'not an array',
      path: []
    })
  } else {
    for (let index = 0; index < value.length; index++) {
      const value_index = value[index]

      if (typeof value_index !== 'number') {
        ;(err = err || []).push({
          issue: 'not a number',
          path: [index]
        })
      }
    }
  }

  return err
    ? { tag: 'failure', failure: { value, errors: err } }
    : { tag: 'success', success: value }
}

...

Reference

Primitive validators

Complex validators

Utilities

Primitive validators

boolean

Validates a boolean value

import { boolean } from 'spectypes'

const check = boolean

expect(check(true)).toEqual({
  tag: 'success',
  success: true
})

expect(check('false')).toEqual({
  tag: 'failure',
  failure: {
    value: 'false',
    errors: [{ issue: 'not a boolean', path: [] }]
  }
})
Transformed code
import * as _spectypes from 'spectypes'

const check = (value) => {
  let err

  if (typeof value !== 'boolean') {
    ;(err = err || []).push({
      issue: 'not a boolean',
      path: []
    })
  }

  return err
    ? { tag: 'failure', failure: { value, errors: err } }
    : { tag: 'success', success: value }
}

literal

Creates a literal validator spec. literalcan validate strings, numbers, booleans, undefined and null. literal(undefined) is treated specially when used as a property validator inside object or struct.

import { literal } from 'spectypes'

const check = literal('test')

expect(check('test')).toEqual({
  tag: 'success',
  success: 'test'
})

expect(check('temp')).toEqual({
  tag: 'failure',
  failure: {
    value: 'temp',
    errors: [{ issue: "not a 'test' string literal", path: [] }]
  }
})
Transformed code
import * as _spectypes from 'spectypes'

const check = (value) => {
  let err

  if (value !== 'test') {
    ;(err = err || []).push({
      issue: "not a '" + 'test' + "' string literal",
      path: []
    })
  }

  return err
    ? { tag: 'failure', failure: { value, errors: err } }
    : { tag: 'success', success: value }
}

nullish

Transformer spec, that accepts undefined and null values and maps them to undefined. nullish is treated specially when used as a property validator inside object or struct.

import { nullish } from 'spectypes'

const check = nullish

expect(check(undefined)).toEqual({
  tag: 'success'
  success: undefined
})

expect(check(null)).toEqual({
  tag: 'success'
  success: undefined
})

expect(check(123)).toEqual({
  tag: 'failure',
  failure: {
    value: 'temp',
    errors: [{ issue: "not 'null' or 'undefined'", path: [] }]
  }
})
Transformed code
import * as _spectypes from 'spectypes'

const check = (value) => {
  let err, result

  if (value !== null && value !== undefined) {
    ;(err = err || []).push({
      issue: "not 'null' or 'undefined'",
      path: []
    })
  }

  return err
    ? { tag: 'failure', failure: { value, errors: err } }
    : { tag: 'success', success: result }
}

number

Validates a number value.

import { number } from 'spectypes'

const check = number

expect(check(0)).toEqual({
  tag: 'success',
  success: 0
})

expect(check({})).toEqual({
  tag: 'failure',
  failure: {
    value: {},
    errors: [{ issue: 'not a number', path: [] }]
  }
})
Transformed code
import * as _spectypes from 'spectypes'

const check = (value) => {
  let err

  if (typeof value !== 'number') {
    ;(err = err || []).push({
      issue: 'not a number',
      path: []
    })
  }

  return err
    ? { tag: 'failure', failure: { value, errors: err } }
    : { tag: 'success', success: value }
}

string

Validates a string value.

import { string } from 'spectypes'

const check = string

expect(check('')).toEqual({
  tag: 'success',
  success: ''
})

expect(check(null)).toEqual({
  tag: 'failure',
  failure: {
    value: null,
    errors: [{ issue: 'not a string', path: [] }]
  }
})
Transformed code
import * as _spectypes from 'spectypes'

const check = (value) => {
  let err

  if (typeof value !== 'string') {
    ;(err = err || []).push({
      issue: 'not a string',
      path: []
    })
  }

  return err
    ? { tag: 'failure', failure: { value, errors: err } }
    : { tag: 'success', success: value }
}

unknown

Empty validator spec. unknown is treated specially when used as a property validator inside object or struct.

import { unknown } from 'spectypes'

const check = unknown

expect(check('anything')).toEqual({
  tag: 'success',
  success: 'anything'
})
Transformed code
import * as _spectypes from 'spectypes'

const check = (value) => {
  let err
  return err
    ? { tag: 'failure', failure: { value, errors: err } }
    : { tag: 'success', success: value }
}

Complex validators

array

Creates an array validator spec. Takes a spec to validate each item of an array.

See example


filter

Can be used only as an argument for array and record to create filtered transformer specs. Filtering happens after each item or key validation. Takes a spec to validate each item or key of a collection and filter predicate.

import { array, number, filter } from 'spectypes'

const check = array(filter(number, (x) => x > 1))

expect(check([1, 2, 3])).toEqual({
  tag: 'success',
  success: [2, 3]
})

expect(check([1, 2, null])).toEqual({
  tag: 'failure',
  failure: {
    value: [1, 2, null],
    errors: [{ issue: 'not a number', path: [2] }]
  }
})
Transformed code
import * as _spectypes from 'spectypes'

const _filter = (x) => x > 1

const check = (value) => {
  let err, result
  result = []

  if (!Array.isArray(value)) {
    ;(err = err || []).push({
      issue: 'not an array',
      path: []
    })
  } else {
    let filterindex = 0

    for (let index = 0; index < value.length; index++) {
      const value_index = value[index]

      if (typeof value_index !== 'number') {
        ;(err = err || []).push({
          issue: 'not a number',
          path: [index]
        })
      }

      if (!err && _filter(value_index)) {
        result[filterindex++] = value_index
      }
    }
  }

  return err
    ? { tag: 'failure', failure: { value, errors: err } }
    : { tag: 'success', success: result }
}

Type predicate will be taken into account if provided

import { array, string, filter } from 'spectypes'

const check = array(filter(string, (x): x is 'test' => x === 'test'))

expect(check(['hello', 'test', 'world'])).toEqual({
  tag: 'success',
  success: ['test'] // readonly 'test'[]
})

limit

Creates a spec with custom constraint. Takes a basis spec and a function to perform additinal validation.

import { number, limit } from 'spectypes'

const check = limit(number, (x) => x > 1)

expect(check(5)).toEqual({
  tag: 'success',
  success: 5
})

expect(check(-5)).toEqual({
  tag: 'failure',
  failure: {
    value: -5,
    errors: [{ issue: 'does not fit the limit', path: [] }]
  }
})

expect(check('5')).toEqual({
  tag: 'failure',
  failure: {
    value: '5',
    errors: [{ issue: 'not a number', path: [] }]
  }
})
Transformed code
import * as _spectypes from 'spectypes'

const _limit = (x) => x > 1

const check = (value) => {
  let err

  if (typeof value !== 'number') {
    ;(err = err || []).push({
      issue: 'not a number',
      path: []
    })
  } else if (!_limit(value)) {
    ;(err = err || []).push({
      issue: 'does not fit the limit',
      path: []
    })
  }

  return err
    ? { tag: 'failure', failure: { value, errors: err } }
    : { tag: 'success', success: value }
}

Type predicate will be taken into account if provided

import { array, string, limit } from 'spectypes'

const check = array(limit(string, (x): x is 'test' => x === 'test'))

expect(check(['test', 'test', 'test'])).toEqual({
  tag: 'success',
  success: ['test', 'test', 'test'] // readonly 'test'[]
})

map

Creates a spec that transforms the result of successful validation. Takes basis spec and mapping function.

import { number, map } from 'spectypes'

const check = map(number, (x) => x + 1)

expect(check(10)).toEqual({
  tag: 'success',
  success: 11
})

expect(check(undefined)).toEqual({
  tag: 'failure',
  failure: {
    value: undefined,
    errors: [{ issue: 'not a number', path: [] }]
  }
})
Transformed code
import * as _spectypes from 'spectypes'

const _map = (x) => x + 1

const check = (value) => {
  let err, result

  if (typeof value !== 'number') {
    ;(err = err || []).push({
      issue: 'not a number',
      path: []
    })
  } else {
    result = _map(value)
  }

  return err
    ? { tag: 'failure', failure: { value, errors: err } }
    : { tag: 'success', success: result }
}

merge

Can combine tuple with array or object with record into single spec.

import { tuple, array, string, boolean, merge } from 'spectypes'

const check = merge(tuple(string, string), array(boolean))

expect(check(['hello', 'world', true])).toEqual({
  tag: 'success',
  success: ['hello', 'world', true]
})

expect(check(['hello', 'world', '!'])).toEqual({
  tag: 'failure',
  failure: {
    value: ['hello', 'world', '!'],
    errors: [{ issue: 'not a string', path: [2] }]
  }
})

expect(check(['hello'])).toEqual({
  tag: 'failure',
  failure: {
    value: ['hello'],
    errors: [{ issue: 'length is less than 2', path: [] }]
  }
})
Transformed code
import * as _spectypes from 'spectypes'

const check = (value) => {
  let err

  if (!Array.isArray(value)) {
    ;(err = err || []).push({
      issue: 'not an array',
      path: []
    })
  } else if (value.length < 2) {
    ;(err = err || []).push({
      issue: 'length is less than ' + 2,
      path: []
    })
  } else {
    const value_$30_ = value[0]

    if (typeof value_$30_ !== 'string') {
      ;(err = err || []).push({
        issue: 'not a string',
        path: [0]
      })
    }

    const value_$31_ = value[1]

    if (typeof value_$31_ !== 'string') {
      ;(err = err || []).push({
        issue: 'not a string',
        path: [1]
      })
    }

    for (let index = 2; index < value.length; index++) {
      const value_index = value[index]

      if (typeof value_index !== 'boolean') {
        ;(err = err || []).push({
          issue: 'not a boolean',
          path: [index]
        })
      }
    }
  }

  return err
    ? { tag: 'failure', failure: { value, errors: err } }
    : { tag: 'success', success: value }
}
import { object, record, number, string, boolean, merge } from 'spectypes'

const check = merge(object({ x: number }), record(string, boolean))

expect(check({ x: 123, y: true })).toEqual({
  tag: 'success',
  success: { x: 123, y: true }
})

expect(check({ x: true, y: 123 })).toEqual({
  tag: 'failure',
  failure: {
    value: { x: true, y: 123 },
    errors: [
      { issue: 'not a number', path: ['x'] },
      { issue: 'not a boolean', path: ['y'] }
    ]
  }
})
Transformed code
import * as _spectypes from 'spectypes'

const check = (value) => {
  let err

  if (!(typeof value === 'object' && value !== null && !Array.isArray(value))) {
    ;(err = err || []).push({
      issue: 'not an object',
      path: []
    })
  } else {
    for (let i = 0; i < _spectypes.bannedKeys.length; i++) {
      const ban = _spectypes.bannedKeys[i]

      if (Object.prototype.hasOwnProperty.call(value, ban)) {
        ;(err = err || []).push({
          issue: "includes banned '" + ban + "' key",
          path: []
        })
      }
    }

    const value_x = value.x

    if (typeof value_x !== 'number') {
      ;(err = err || []).push({
        issue: 'not a number',
        path: ['x']
      })
    }

    for (const key in value) {
      if (!(key === 'x')) {
        const value_key = value[key]

        if (typeof value_key !== 'boolean') {
          ;(err = err || []).push({
            issue: 'not a boolean',
            path: [key]
          })
        }
      }
    }
  }

  return err
    ? { tag: 'failure', failure: { value, errors: err } }
    : { tag: 'success', success: value }
}

object

Creates an object validator spec. Validation will fail if validated object has a property set different from the one specified. Takes an object with specs to validate object properties. literal(undefined), nullish and unknown are treated specially when used as a property validator inside object.

import { object, number, string, boolean } from 'spectypes'

const check = object({ x: number, y: string, z: boolean })

expect(check({ x: 1, y: '2', z: false })).toEqual({
  tag: 'success',
  success: { x: 1, y: '2', z: false }
})

expect(check({ x: 1, y: '2', z: false, xyz: [] })).toEqual({
  tag: 'failure',
  failure: {
    value: { x: 1, y: '2', z: false, xyz: [] },
    errors: [{ issue: 'excess key - xyz', path: [] }]
  }
})

expect(check({})).toEqual({
  tag: 'failure',
  failure: {
    value: {},
    errors: [
      { issue: 'not a number', path: ['x'] },
      { issue: 'not a string', path: ['y'] },
      { issue: 'not a boolean', path: ['z'] }
    ]
  }
})

expect(check([])).toEqual({
  tag: 'failure',
  failure: {
    value: [],
    errors: [{ issue: 'not an object', path: [] }]
  }
})
Transformed code
import * as _spectypes from 'spectypes'

const check = (value) => {
  let err

  if (!(typeof value === 'object' && value !== null && !Array.isArray(value))) {
    ;(err = err || []).push({
      issue: 'not an object',
      path: []
    })
  } else {
    const value_x = value.x

    if (typeof value_x !== 'number') {
      ;(err = err || []).push({
        issue: 'not a number',
        path: ['x']
      })
    }

    const value_y = value.y

    if (typeof value_y !== 'string') {
      ;(err = err || []).push({
        issue: 'not a string',
        path: ['y']
      })
    }

    const value_z = value.z

    if (typeof value_z !== 'boolean') {
      ;(err = err || []).push({
        issue: 'not a boolean',
        path: ['z']
      })
    }

    for (const key in value) {
      if (!(key === 'x' || key === 'y' || key === 'z')) {
        ;(err = err || []).push({
          issue: 'excess key - ' + key,
          path: []
        })
      }
    }
  }

  return err
    ? { tag: 'failure', failure: { value, errors: err } }
    : { tag: 'success', success: value }
}

optional

Creates an optional object property validator spec. Can be used only inside object and struct arguments. Will not produce any validation errors if property equals undefined or is not present in the validated object.

import { optional, struct, number } from 'spectypes'

const check = struct({ x: optional(number) })

expect(check({ x: 5 })).toEqual({
  tag: 'success',
  success: { x: 5 }
})

expect(check({ x: undefined })).toEqual({
  tag: 'success',
  success: { x: undefined }
})

expect(check({})).toEqual({
  tag: 'success',
  success: {}
})

expect(check({ x: 'x' })).toEqual({
  tag: 'failure',
  failure: {
    value: { x: 'x' },
    errors: [{ issue: 'not a number', path: ['x'] }]
  }
})
Transformed code
import * as _spectypes from 'spectypes'

const check = (value) => {
  let err, result
  result = {}

  if (!(typeof value === 'object' && value !== null && !Array.isArray(value))) {
    ;(err = err || []).push({
      issue: 'not an object',
      path: []
    })
  } else {
    const value_x = value.x

    if ('x' in value) {
      if (value_x !== undefined) {
        if (typeof value_x !== 'number') {
          ;(err = err || []).push({
            issue: 'not a number',
            path: ['x']
          })
        }
      }

      result.x = value_x
    }
  }

  return err
    ? { tag: 'failure', failure: { value, errors: err } }
    : { tag: 'success', success: result }
}

record

Creates a record validator spec. This validator is protected from prototype pollution and validation will fail if validated object contains properties that override Object.proptotype methods. This function has two signatures - one takes a spec to validate each key of a record and a spec to validate each item, another takes only item spec and treats all keys as strings. Key spec can be a string, template, string literal or union of these specs.

import { record, boolean } from 'spectypes'

const check = record(boolean)

expect(check({ foo: false, bar: true })).toEqual({
  tag: 'success',
  success: { foo: false, bar: true }
})

expect(check(true)).toEqual({
  tag: 'failure',
  failure: {
    value: true,
    errors: [{ issue: 'not an object', path: [] }]
  }
})

expect(check({ toString: true })).toEqual({
  tag: 'failure',
  failure: {
    value: { toString: true },
    errors: [{ issue: "includes banned 'toString' key", path: [] }]
  }
})
Transformed code
import * as _spectypes from 'spectypes'

const check = (value) => {
  let err

  if (!(typeof value === 'object' && value !== null && !Array.isArray(value))) {
    ;(err = err || []).push({
      issue: 'not an object',
      path: []
    })
  } else {
    for (let i = 0; i < _spectypes.bannedKeys.length; i++) {
      const ban = _spectypes.bannedKeys[i]

      if (Object.prototype.hasOwnProperty.call(value, ban)) {
        ;(err = err || []).push({
          issue: "includes banned '" + ban + "' key",
          path: []
        })
      }
    }

    for (const key in value) {
      const value_key = value[key]

      if (typeof value_key !== 'boolean') {
        ;(err = err || []).push({
          issue: 'not a boolean',
          path: [key]
        })
      }
    }
  }

  return err
    ? { tag: 'failure', failure: { value, errors: err } }
    : { tag: 'success', success: value }
}

struct

Creates an object transformer spec. All properties of validated object that are not present in passed param will be removed from the result of successful validation. Takes an object with specs to validate object properties. literal(undefined), nullish and unknown are treated specially when used as a property validator inside struct.

import { struct, number, string, boolean } from 'spectypes'

const check = struct({ x: number, y: string, z: boolean })

expect(check({ x: 1, y: '2', z: false })).toEqual({
  tag: 'success',
  success: { x: 1, y: '2', z: false }
})

expect(check({ x: 1, y: '2', z: false, xyz: [] })).toEqual({
  tag: 'success',
  success: { x: 1, y: '2', z: false }
})

expect(check({})).toEqual({
  tag: 'failure',
  failure: {
    value: {},
    errors: [
      { issue: 'not a number', path: ['x'] },
      { issue: 'not a string', path: ['y'] },
      { issue: 'not a boolean', path: ['z'] }
    ]
  }
})

expect(check([])).toEqual({
  tag: 'failure',
  failure: {
    value: [],
    errors: [{ issue: 'not an object', path: [] }]
  }
})
Transformed code
import * as _spectypes from 'spectypes'

const check = (value) => {
  let err, result
  result = {}

  if (!(typeof value === 'object' && value !== null && !Array.isArray(value))) {
    ;(err = err || []).push({
      issue: 'not an object',
      path: []
    })
  } else {
    const value_x = value.x

    if (typeof value_x !== 'number') {
      ;(err = err || []).push({
        issue: 'not a number',
        path: ['x']
      })
    }

    result.x = value_x
    const value_y = value.y

    if (typeof value_y !== 'string') {
      ;(err = err || []).push({
        issue: 'not a string',
        path: ['y']
      })
    }

    result.y = value_y
    const value_z = value.z

    if (typeof value_z !== 'boolean') {
      ;(err = err || []).push({
        issue: 'not a boolean',
        path: ['z']
      })
    }

    result.z = value_z
  }

  return err
    ? { tag: 'failure', failure: { value, errors: err } }
    : { tag: 'success', success: result }
}

template

Creates a template string validator spec. Takes number, string, boolean, literal specs and their unions to validate parts of the validated string.

import { template, literal, number, string, boolean } from 'spectypes'

const check = template(literal('test'), string, number, boolean)

expect(check('test___123false')).toEqual({
  tag: 'success',
  success: 'test___123false'
})

expect(check('test___false')).toEqual({
  tag: 'failure',
  failure: {
    value: 'test___false',
    errors: [{ issue: 'template literal mismatch', path: [] }]
  }
})
Transformed code
import * as _spectypes from 'spectypes'

const _template = new RegExp(
  '^' +
    _spectypes.escape('test') +
    _spectypes.stringTest +
    _spectypes.numberTest +
    _spectypes.booleanTest +
    '$'
)

const check = (value) => {
  let err

  if (typeof value !== 'string') {
    ;(err = err || []).push({
      issue: 'not a string',
      path: []
    })
  } else if (!_template.test(value)) {
    ;(err = err || []).push({
      issue: 'template literal mismatch',
      path: []
    })
  }

  return err
    ? { tag: 'failure', failure: { value, errors: err } }
    : { tag: 'success', success: value }
}

tuple

Creates a tuple validator spec. Takes specs to validate tuple parts.

import { tuple, number, string, boolean } from 'spectypes'

const check = tuple(number, string, boolean)

expect(check([1, '2', false])).toEqual({
  tag: 'success',
  success: [1, '2', false]
})

expect(check([])).toEqual({
  tag: 'failure',
  failure: {
    value: [],
    errors: [{ issue: 'length is not 3', path: [] }]
  }
})

expect(check([1, '2', false, 1000])).toEqual({
  tag: 'failure',
  failure: {
    value: [1, '2', false, 1000],
    errors: [{ issue: 'length is not 3', path: [] }]
  }
})

expect(check(['1', '2', 'false'])).toEqual({
  tag: 'failure',
  failure: {
    value: ['1', '2', 'false'],
    errors: [
      { issue: 'not a number', path: [0] },
      { issue: 'not a boolean', path: [2] }
    ]
  }
})
Transformed code
import * as _spectypes from 'spectypes'

const check = (value) => {
  let err

  if (!Array.isArray(value)) {
    ;(err = err || []).push({
      issue: 'not an array',
      path: []
    })
  } else if (value.length !== 3) {
    ;(err = err || []).push({
      issue: 'length is not ' + 3,
      path: []
    })
  } else {
    const value_$30_ = value[0]

    if (typeof value_$30_ !== 'number') {
      ;(err = err || []).push({
        issue: 'not a number',
        path: [0]
      })
    }

    const value_$31_ = value[1]

    if (typeof value_$31_ !== 'string') {
      ;(err = err || []).push({
        issue: 'not a string',
        path: [1]
      })
    }

    const value_$32_ = value[2]

    if (typeof value_$32_ !== 'boolean') {
      ;(err = err || []).push({
        issue: 'not a boolean',
        path: [2]
      })
    }
  }

  return err
    ? { tag: 'failure', failure: { value, errors: err } }
    : { tag: 'success', success: value }
}

union

Creates a union validator spec. Takes specs to validate union cases.

import { union, number, string, boolean } from 'spectypes'

const check = union(number, string, boolean)

expect(check('temp')).toEqual({
  tag: 'success',
  success: 'temp'
})

expect(check(true)).toEqual({
  tag: 'success',
  success: true
})

expect(check(null)).toEqual({
  tag: 'failure',
  failure: {
    value: null,
    errors: [
      { issue: 'union case #0 mismatch: not a number', path: [] },
      { issue: 'union case #1 mismatch: not a string', path: [] },
      { issue: 'union case #2 mismatch: not a boolean', path: [] }
    ]
  }
})
Transformed code
import * as _spectypes from 'spectypes'

const check = (value) => {
  let err

  if (!(typeof value === 'number' || typeof value === 'string' || typeof value === 'boolean')) {
    if (typeof value !== 'number') {
      ;(err = err || []).push({
        issue: 'union case #0 mismatch: not a number',
        path: []
      })
    }

    if (typeof value !== 'string') {
      ;(err = err || []).push({
        issue: 'union case #1 mismatch: not a string',
        path: []
      })
    }

    if (typeof value !== 'boolean') {
      ;(err = err || []).push({
        issue: 'union case #2 mismatch: not a boolean',
        path: []
      })
    }
  }

  return err
    ? { tag: 'failure', failure: { value, errors: err } }
    : { tag: 'success', success: value }
}

Utilities

transformer

Spec that tells babel plugin to generate a wrapper for an external transformer spec. Any spec containing struct, nullish, map, filter and transformer specs will create and return new object on successful validation. Such spec has to be wrapped with transformer when used inside another spec.

import { array, transformer, map, number } from 'spectypes'

const negated = map(number, (x) => -x)
const check = array(transformer(negated))

// Incorrect usage !!!
// const negated = transformer(map(number, (x) => -x))
// const check = array(negated)

expect(check([1, 2, -3])).toEqual({
  tag: 'success',
  success: [-1, -2, 3]
})

expect(check([1, 2, 'abc'])).toEqual({
  tag: 'failure',
  failure: {
    value: [1, 2, 'abc'],
    errors: [{ issue: 'not a number', path: [2] }]
  }
})
Transformed code
import * as _spectypes from 'spectypes'

const _map = (x) => -x

const negated = (value) => {
  let err, result

  if (typeof value !== 'number') {
    ;(err = err || []).push({
      issue: 'not a number',
      path: []
    })
  } else {
    result = _map(value)
  }

  return err
    ? { tag: 'failure', failure: { value, errors: err } }
    : { tag: 'success', success: result }
}

const check = (value) => {
  let err, result
  result = []

  if (!Array.isArray(value)) {
    ;(err = err || []).push({
      issue: 'not an array',
      path: []
    })
  } else {
    for (let index = 0; index < value.length; index++) {
      let result_index
      const value_index = value[index]
      const ext_value_index0 = negated(value_index)

      if (ext_value_index0.tag === 'failure') {
        ;(err = err || []).push(
          ...ext_value_index0.failure.errors.map((fail) => ({
            issue: '' + fail.issue,
            path: [index, ...fail.path]
          }))
        )
      } else {
        result_index = ext_value_index0.success
      }

      result[index] = result_index
    }
  }

  return err
    ? { tag: 'failure', failure: { value, errors: err } }
    : { tag: 'success', success: result }
}

validator

Spec that tells babel plugin to generate a wrapper for an external validator spec. Any spec not containing struct, nullish, map, filter and transformer specs on successful validation will return validated object. Such spec has to be wrapped with validator when used inside another spec.

import { array, validator, limit, number } from 'spectypes'

const positive = limit(number, (x) => x >= 0)
const check = array(validator(positive))

// Incorrect usage !!!
// const positive = validator(limit(number, (x) => x >= 0))
// const check = array(positive)

expect(check([0, 1, 2])).toEqual({
  tag: 'success',
  success: [0, 1, 2]
})

expect(check([-1, -2, -3])).toEqual({
  tag: 'failure',
  failure: {
    value: [-1, -2, -3],
    errors: [
      { issue: 'does not fit the limit', path: [0] },
      { issue: 'does not fit the limit', path: [1] },
      { issue: 'does not fit the limit', path: [2] }
    ]
  }
})
Transformed code
import * as _spectypes from 'spectypes'

const _limit = (x) => x >= 0

const positive = (value) => {
  let err

  if (typeof value !== 'number') {
    ;(err = err || []).push({
      issue: 'not a number',
      path: []
    })
  } else if (!_limit(value)) {
    ;(err = err || []).push({
      issue: 'does not fit the limit',
      path: []
    })
  }

  return err
    ? { tag: 'failure', failure: { value, errors: err } }
    : { tag: 'success', success: value }
}

const check = (value) => {
  let err

  if (!Array.isArray(value)) {
    ;(err = err || []).push({
      issue: 'not an array',
      path: []
    })
  } else {
    for (let index = 0; index < value.length; index++) {
      const value_index = value[index]
      const ext_value_index0 = positive(value_index)

      if (ext_value_index0.tag === 'failure') {
        ;(err = err || []).push(
          ...ext_value_index0.failure.errors.map((fail) => ({
            issue: '' + fail.issue,
            path: [index, ...fail.path]
          }))
        )
      }
    }
  }

  return err
    ? { tag: 'failure', failure: { value, errors: err } }
    : { tag: 'success', success: value }
}

lazy

Creates a spec to validate a value with recursive type. But data that recursively references itself is not supported. LazyTransformerSpec type should be used when spec contains struct, nullish, map, filter and transformer specs, and LazyValidatorSpec otherwise.

import { lazy, string, object, array, validator, LazyValidatorSpec } from 'spectypes'

type Person = {
  readonly name: string
  readonly likes: readonly Person[]
}

const person: LazyValidatorSpec<Person> = lazy(() =>
  object({ name: string, likes: array(validator(person)) })
)

expect(person({ name: 'Bob', likes: [{ name: 'Alice', likes: [] }] })).toEqual({
  tag: 'success',
  { name: 'Bob', likes: [{ name: 'Alice', likes: [] }] }
})

expect(person({ name: 'Alice', likes: [{ name: 'Bob', likes: 'cats' }] })).toEqual({
  tag: 'failure',
  failure: {
    value: { name: 'Alice', likes: [{ name: 'Bob', likes: 'cats' }] },
    errors: [{ issue: 'not an array', path: ['likes', 0, 'likes'] }]
  }
})
Transformed code
import * as _spectypes from 'spectypes'

const person = (value) => {
  let err

  if (!(typeof value === 'object' && value !== null && !Array.isArray(value))) {
    ;(err = err || []).push({
      issue: 'not an object',
      path: []
    })
  } else {
    const value_name = value.name

    if (typeof value_name !== 'string') {
      ;(err = err || []).push({
        issue: 'not a string',
        path: ['name']
      })
    }

    const value_likes = value.likes

    if (!Array.isArray(value_likes)) {
      ;(err = err || []).push({
        issue: 'not an array',
        path: ['likes']
      })
    } else {
      for (let index_likes = 0; index_likes < value_likes.length; index_likes++) {
        const value_likes_index_likes = value_likes[index_likes]
        const ext_value_likes_index_likes0 = person(value_likes_index_likes)

        if (ext_value_likes_index_likes0.tag === 'failure') {
          ;(err = err || []).push(
            ...ext_value_likes_index_likes0.failure.errors.map((fail) => ({
              issue: '' + fail.issue,
              path: ['likes', index_likes, ...fail.path]
            }))
          )
        }
      }
    }

    for (const key in value) {
      if (!(key === 'name' || key === 'likes')) {
        ;(err = err || []).push({
          issue: 'excess key - ' + key,
          path: []
        })
      }
    }
  }

  return err
    ? { tag: 'failure', failure: { value, errors: err } }
    : { tag: 'success', success: value }
}

writable

Creates an empty validator that removes readonly modifiers from the result of validation

import { object, number, string, boolean, writable } from 'spectypes'

const check = writable(object({ x: number, y: string, z: boolean }))

expect(check({ x: 1, y: '2', z: true })).toEqual({
  tag: 'success',
  success: { x: 1, y: '2', z: true } // { x: number, y: string, z: true }
})

Spectype

Type to infer success value

import { object, number, string, boolean, Spectype } from 'spectypes'

const check = object({ x: number, y: string, z: boolean })

// { readonly x: number; readonly y: string; readonly z: boolean }
type Value = Spectype<typeof check>

Misc

How does the plugin understand what code to transform?

Plugin searches for named imports like import { ... } from 'spectypes' or const { ... } = require('spectypes')and gets all imported identifiers (aliases also supported). All variable declarations which include these identifiers will be converted into validating functions.

Special cases

  • When literal(undefined) or unknown is used as a property validator inside object or struct and that property is not present in the validated object the validation will fail.
  • When nullish is used as a property validator inside object or struct and that property is not present in the validated object the result will still contain that property set to undefined.

Result handling

Validators return their results as 'success or failure' wrapped values and does not throw any exceptions (other than those thrown by the functions passed to map, limit or filter). This library does not include any functions to process validation results, but a compatible handy package exists - ts-railway

Custom validators

There is no specific APIs to create custom validators, usually just unknown, map and limit are enough to create a validator for arbitrary data. For example, lets create a validator that checks if some value is a representation of a date and converts that value to Date object:

import { unknown, map, limit } from 'spectypes'

const check = map(
  limit(unknown, (x) => !isNaN(Date.parse(x))),
  (x) => new Date(x)
)

const date = new Date('Sun Apr 24 2022 12:51:57')

expect(check('Sun Apr 24 2022 12:51:57')).toEqual({
  tag: 'success',
  success: date
})

expect(check([1, 2, 'abc'])).toEqual({
  tag: 'failure',
  failure: {
    value: [1, 2, 'abc'],
    errors: [{ issue: 'does not fit the limit', path: [] }]
  }
})
Transformed code
import * as _spectypes from 'spectypes'

const _map = (x) => new Date(x)

const _limit = (x) => !isNaN(Date.parse(x))

const check = (value) => {
  let err, result

  if (!_limit(value)) {
    ;(err = err || []).push({
      issue: 'does not fit the limit',
      path: []
    })
  } else {
    result = _map(value)
  }

  return err
    ? { tag: 'failure', failure: { value, errors: err } }
    : { tag: 'success', success: result }
}

How is it tested?

Having 100% of the code covered with tests reflects only the coverage of generative code, not the generated one. It says little about the amount of potential bugs in this package. Because of that most of the test cases are randomly generated. When testing valid data validation it will generate spectypes validator and corresponding fast-check arbitrary, then validator will ensure that values provided by arbitrary are valid. When testing invalid data validation it will also generate an expected error, then validator will ensure that values provided by arbitrary are invalid and lead to expected error.

You might also like...

What does the Cosmos Hub validator set looks like without ICF delegations?

This is a Next.js project bootstrapped with create-next-app. Getting Started First, run the development server: npm run dev # or yarn dev Open http://

Sep 2, 2022

This is the code repository of the official mun testnet validator node source code.

How to join Munchain network Infrastructure **Recommended configuration:** - Number of CPUs: 4 - Memory: 16GB - OS: Ubuntu 22.04 LTS - Allow all incom

Dec 15, 2022

DiscordEmailVerifier - Quickly verify emails on your tokens for completely free using mail.tm's api.

DiscordEmailVerifier Quickly verify emails on your tokens for completely free using mail.tm's api. /* ❗ No, this code doesn't verify the email for you

Jun 7, 2022

The most powerful data validation library for JS

joi The most powerful schema description language and data validator for JavaScript. Installation npm install joi Visit the joi.dev Developer Portal f

Jan 4, 2023

A simple and composable way to validate data in JavaScript (and TypeScript).

A simple and composable way to validate data in JavaScript (and TypeScript).

A simple and composable way to validate data in JavaScript (and TypeScript). Usage • Why? • Principles • Demo • Examples • Documentation Superstruct m

Jan 9, 2023

GUI for editing, visualizing, and manipulating JSON data

GUI for editing, visualizing, and manipulating JSON data

JSON-Splora JSON-Splora is a GUI for editing, visualizing, and manipulating JSON data with jq or JavaScript. Design Built with Electron Editor and out

Dec 25, 2022

Themis is a validation and processing library that helps you always make sure your data is correct.

Dataffy Themis - The advanced validation library Themis is a validation and processing library that helps you always make sure your data is correct. ·

Oct 27, 2022

Vue-input-validator - 🛡️ Highly extensible & customizable input validator for Vue 2

Vue-input-validator - 🛡️ Highly extensible & customizable input validator for Vue 2

🛡️ Vue-input-validator demo! What is this package all about? By using this package, you can create input validators only with the help of a single di

May 26, 2022

🔧 Alternative to 'eval' in JavaScript that is customizable and safer!

🔧 better-eval An alternative to eval() in JavaScript that is customizable and safer! The eval function sucks, and there lacks alternatives that provi

Sep 14, 2022

A maybe slightly safer-ish wrapper around eval Function constructors

evalish A maybe slightly safer-ish wrapper around eval Function constructors Please maybe try something else first.. Please. evalish is a small helper

Sep 6, 2022

A maybe slightly safer-ish wrapper around eval Function constructors

evalish A maybe slightly safer-ish wrapper around eval Function constructors Please maybe try something else first.. Please. evalish is a small helper

Aug 21, 2022

Browser storage interface for IndexedDB, WebSQL, LocalStorage, and in memory data with Schema and data validator.

Client Web Storage Browser storage interface for IndexedDB, WebSQL, LocalStorage, and in memory data with basic Schema and data validation. Installati

Sep 30, 2022

Font-end app to test the transformer model translation from Cape Verdian Creole to English

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: np

Sep 28, 2022

TypeScript Transformer for injection-js

TypeScript Transformer for injection-js TypeScript Transformer for injection-js, inspired by angular-cli. Why need this No more emitDecoratorMetadata

Dec 4, 2022

A JSX transformer with extra hints around interpolations and outer templates.

@ungap/babel-plugin-transform-hinted-jsx This plugin is a follow up of this post and it can be used together with @babel/plugin-transform-react-jsx. A

Nov 12, 2022

Markdown Transformer. Transform markdown files to different formats

Mdtx Inspired by generative programming and weed :). So I was learning Elm language at home usually in the evening and now I am missing all this gener

Jan 2, 2023

A JSONSchema validator that uses code generation to be extremely fast

is-my-json-valid A JSONSchema validator that uses code generation to be extremely fast. It passes the entire JSONSchema v4 test suite except for remot

Dec 31, 2022

Super Fast Complex Object Validator for Javascript(& Typescript).

Super Fast Complex Object Validator for Javascript(& Typescript).

Super Fast Object Validator for Javascript(& Typescript). Safen supports the syntax similar to the type script interface. This makes it easy to create

Nov 25, 2022

Minimal template engine with compiled output for JavaScript.

@fnando/seagull Minimal template engine with compiled output for JavaScript. Installation This package is available as a NPM package. To install it, u

Mar 1, 2022
Releases(v2.0.1)
Owner
null
A JSONSchema validator that uses code generation to be extremely fast

is-my-json-valid A JSONSchema validator that uses code generation to be extremely fast. It passes the entire JSONSchema v4 test suite except for remot

Mathias Buus 948 Dec 31, 2022
Super Fast Complex Object Validator for Javascript(& Typescript).

Super Fast Object Validator for Javascript(& Typescript). Safen supports the syntax similar to the type script interface. This makes it easy to create

Changwan Jun 31 Nov 25, 2022
The fastest JSON schema Validator. Supports JSON Schema draft-04/06/07/2019-09/2020-12 and JSON Type Definition (RFC8927)

Ajv JSON schema validator The fastest JSON validator for Node.js and browser. Supports JSON Schema draft-06/07/2019-09/2020-12 (draft-04 is supported

Ajv JSON schema validator 12k Jan 4, 2023
ForgJs is a javascript lightweight object validator. Go check the Quick start section and start coding with love

Hey every one im really happy that this repo reached this many stars ?? ,but this repo needs your contibution I started to better document the code th

Hamdaoui Oussama 1.7k Dec 21, 2022
Tiny Validator for JSON Schema v4

Tiny Validator (for v4 JSON Schema) Use json-schema draft v4 to validate simple values and complex objects using a rich validation vocabulary (example

Geraint 1.2k Dec 21, 2022
Simple validator for Steuerliche Identifikationsnummer (German personal tax number) according to the official docs (see readme).

simple-de-taxid-validator Important Code of this validator is taken (with small changes like optimization or removing not needed elements) from THIS R

Wojciech 3 Feb 24, 2022
Easy HTML Form Validator

Easy HTML Form Validator

Ali Nazari 314 Dec 26, 2022
A simple environment variables validator for Node.js and web browsers

A simple environment variables validator for Node.js and web browsers

Mathieu Acthernoene 25 Jul 20, 2022
Facile is an HTML form validator that is inspired by Laravel's validation style and is designed for simplicity of use.

Facile is an HTML form validator that is inspired by Laravel's validation style and is designed for simplicity of use.

upjs 314 Dec 26, 2022
Simple password validator made with Javascript 💛

Password Validator Simple password validator made with Javascript ?? Branch history base-code: a complex logic to password validator. In next branches

Lais Frigério 8 Jul 25, 2022