Skip to content

andrejewski/trent

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Trent

Type specifications

npm install trent

npm Build Status Greenkeeper badge

This library provides runtime type specifications with a focus on:

  • A small, native-feeling syntax for defining specifications
  • Good error messages

Usage

import {createSpec} from 'trent'

const Matrix = createSpec(() => [[Number]])

const errors = Matrix.getErrors([
  [1, 2, 3],
  [4, 5, 'wrong']
])

console.log(errors)
/* => [ Error('Value[1][2] must be of type "number"') ] */

Documentation

createSpec(builder: builtInChecks -> Check): Spec

Create a Spec using the builder function which receives the built-in checks described below. The builder must return a valid check or an error throws. The Spec object returned has a method getErrors(value) which returns an array of type errors for value.

Checks


Spec

Check that a provided value matches the Spec.

import {createSpec} from 'trent'
import assert from 'assert'

const Num = createSpec(() => Number)
const Matrix = createSpec(() => [[Num]])

const errors = Matrix.getErrors([[1, 2, 3], [4, 5, 6]])
assert(errors.length === 0)

Constructor

Check that a provided value is an instanceof Constructor, or typeof if the constructor is for a primitive such as Number or String.

import {createSpec} from 'trent'
import assert from 'assert'

const Num = createSpec(() => Number)
const errors = Num.getErrors(8)
assert(errors.length === 0)

Note: Any Constructor will work if root[Constructor.name] === Constructor where root is the window in browsers and global in Node.


Array [check]

Check that a provided value is an array where every element passes the check.

import {createSpec} from 'trent'
import assert from 'assert'

const Numbers = createSpec(() => [Number])
const errors = Numbers.getErrors([8])
assert(errors.length === 0)

Object {key: check, ..., keyN: checkN}

Check that a provided value is an object where every key value passes its check.

import {createSpec} from 'trent'
import assert from 'assert'

const Point = createSpec(() => ({x: Number, y: Number}))
const errors = Point.getErrors({x: 8, y: 8})
assert(errors.length === 0)

is(value : any)

Check that a provided value must strictly equal (===) the value.

import {createSpec} from 'trent'
import assert from 'assert'

const Eight = createSpec(({is}) => is(8))
const errors = Eight.getErrors(8)
assert(errors.length === 0)

or(checks : [Check])

Check that a provided value matches at least one of the checks.

import {createSpec} from 'trent'
import assert from 'assert'

const NumberOrString = createSpec(({or}) => or([Number, String]))
const errors = NumberOrString.getErrors(8)
assert(errors.length === 0)

and(checks : [Check])

Check that a provided value matches at every one of the checks.

import {createSpec} from 'trent'
import assert from 'assert'

const Eight = createSpec(({and, is}) => and([Number, is(8)]))
const errors = Eight.getErrors(8)
assert(errors.length === 0)

not(check : Check)

Check that a provided value does not pass the check.

import {createSpec} from 'trent'
import assert from 'assert'

const Eight = createSpec(({not, is}) => not(is(9)))
const errors = Eight.getErrors(8)
assert(errors.length === 0)

maybe(check : Check)

Check that a provided value matches check or is null or undefined.

import {createSpec} from 'trent'
import assert from 'assert'

const Eight = createSpec(({maybe, is}) => maybe(is(8)))
assert(Eight.getErrors(8).length === 0)
assert(Eight.getErrors(null).length === 0)
assert(Eight.getErrors(undefined).length === 0)

tuple(checks : [Check])

Check that a provided value is an array with length equal to checks.length and the first element passes the first check and so on.

import {createSpec} from 'trent'
import assert from 'assert'

const Eight = createSpec(({tuple, is}) => tuple([is(8), is(8)]))
assert(Eight.getErrors([8, 8]).length === 0)

nullable(check : Check)

Check that a provided value matches check or is null.

import {createSpec} from 'trent'
import assert from 'assert'

const Eight = createSpec(({nullable, is}) => nullable(is(8)))
assert(Eight.getErrors(8).length === 0)
assert(Eight.getErrors(null).length === 0)

voidable(check : Check)

Check that a provided value matches check or is undefined.

import {createSpec} from 'trent'
import assert from 'assert'

const Eight = createSpec(({voidable, is}) => voidable(is(8)))
assert(Eight.getErrors(8).length === 0)
assert(Eight.getErrors(undefined).length === 0)

Using checkCustomCheck({descriptor: String, isValid: Function})

Create a check where descriptor fits the sentence {subject} must be {descriptor} for error messages and isValid(value: any) returns whether the value passes the check.

import {createSpec, createCustomCheck} from 'trent'
import assert from 'assert'

const lowercase = createCustomCheck({
  descriptor: 'lowercase',
  isValid (x) {
    return typeof x === 'string' && x.toLowerCase() === x
  }
})

const Code = createSpec(() => lowercase)
assert(Code.getErrors('8').length === 0)

Note: Custom checks cannot nest checks within them. In a tree structure analogy, custom checks must be leaves. The reason for this limitation is the complexity of creating good error messages.

createDependentSpecs(builder : builtInChecks -> {Check}) {Spec}

Create a collection of Specs which are dependent and/or recursive.

For example, we have two types Foo and Bar which both can contain each other. We can try to write this system with createSpec:

import {createSpec} from 'trent'
var Foo = createSpec(() => ({barList: [Bar]}))
var Bar = createSpec(() => ({fooList: [Foo]}))

Creating Foo with undefined Bar will not work. We need createDependentSpecs to enable a "late-binding" where order does not matter. We use the ref built-in check available to createDependentSpecs to reference Specs.

import {createDependentSpecs} from 'trent'
import assert from 'assert'
const {Foo} = createDependentSpecs(({ref}) => ({
  Foo: {barList: [ref('Bar')]},
  Bar: {fooList: [ref('Foo')]}
}))

assert(Foo.getErrors({
  barList: [{
    fooList: [{
      barList: [{
        fooList: []
      }]
    }]
  }]
}).length === 0)

Real-life example

I maintain the HTML parser Himalaya which follows a strict specification for its output. The output contains Nodes which can have children Nodes, so we need a recursive type.

import {
  createSpec,
  createDependentSpecs
} from 'trent'

// I pull this out to show that you can
const Text = createSpec(({is}) => ({
  type: is('text'),
  content: String
}))

export const {Node} = createDependentSpecs(({is, or, ref, nullable}) => ({
  Node: or([
    ref('Element'),
    ref('Comment'),
    Text
  ]),
  Element: {
    type: is('element'),
    tagName: String,
    children: [ref('Node')],
    attributes: [{
      key: String,
      value: nullable(String)
    }]
  },
  Comment: {
    type: is('comment'),
    content: String
  }
}))

About

Type specifications

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published