travajs

vanilla javascript validation library

Trava stands for TRAnsform and VAlidate. Inspired by struct.

The main goal of the library is to provide highly extendable and customizable way of JavaScript entities validation. Validation often goes along with parsing or transforming values. So coercion feature is included by design into Trava.

Features

Install

Install from npm:

npm install trava

And import or require:

import Trava from 'trava';

or use CDN:

<script src="https://unpkg.com/trava"></script>

For modern browsers es201X builds are available (trava.es.js and trava.es.min.js).

Getting Started

Let's imaging the most obvious and simple validator. Probably it will look like:
function validate (value) {
  // any checking...
  if (!check(value)) return false;
  return true;
}
But in real scenarios we'd also like to get some error details, e.g.:
function validate (value) {
  if (!check1(value)) return 'ERROR_1';
  if (!check2(value)) return 'ERROR_2';
  return true;
}

We may stop here and probably you don't need any library to do validation this way, may be just some primitives for common cases. But there is one more feature we might want to get and when Trava could help. While using JSON it's often needed to parse or convert values after validation. Code for parsing looks pretty similar to validation: its just validate being replaced with parse. In Trava these steps are united together. To support both validation and transformation we need to distinguish error from transformed value returned from validator. Probably we could use js Error, but it works only with string messages. Fortunately Trava has own ValidationError to support complex errors.

So Trava validator looks like:

function validate (value) {
  if (!check1(value)) return new Trava.ValidationError({ code: 401 });
  if (!check2(value)) return new Trava.ValidationError({ code: 405 });
  return parseOrTransform(value);  // apply some parse or transform
}

Then use validator:

const result = validate(data);
if (result instanceof Trava.ValidationError) {
  // note using `data` property to extract error data
  console.log('This is error!', result.data);
} else {
  const goodTransformedValue = result;
}

That's all you have to know to start using Trava. It is very simple. The second advantage why to use Trava is a collection of helpful operators out of the box. See examples to learn how to use them.

Operators

Despite operators itself are just functions, it's convinient to use Trava to build validators or validate data directly:

import Trava from 'trava';
let validator = Trava(validators);
let values = validator(data);

// or validate directly
values = Trava(validators, data);

Compose

Compose is used to combine several validators into one. E.g.:

const validator1 = n => n < 10 ? n : new ValidationError('BAD NUMBER');
const validator2 = ...;
const validator3 = ...;
const composedValidator = Trava.Compose([validator1, validator2, validator3]);
// then `composedValidator` could be used just like simple validator

Validation goes consequently from left to right. When an error occurs, validation stops and an error is returned immediately.

When used inside other operators explicit call Trava.Compose could be omitted. E.g.:

const composedValidator = Trava.Required(Trava.Compose([v1, v2, v3]));
// is same as
const composedValidator = Trava.Required([v1, v2, v3]);

Required

Required is a guard to check if a value is defined (!== undefined):

const validator = ...;
const requiredValidator = Trava.Required(validator);

let value;
console.log(requiredValidator(value)); // ValidationError('Value is required')

value = 'any';
console.log(requiredValidator(value)); // `Required` is bypassed

Custom error message can be set by providing it as a second argument:

const requiredValidator = Trava.Required(validator, 'My custom error message');

Or set the default for all validators:

Trava.Required.ErrorMessage = 'My default required error';

Error message also could be a function which should return error data and will be called with same arguments as validator when error occurs.

const validator = Trava.Check(v => v > 0, v => `${v} is not positive number!`);

Optional

Optional checks if value is not defined then returns value or default value which can be provided as a second argument:

const optionalValidator = Trava.Optional(validator, 'default value');

Nullable

Nullable is just like Optional except it also checks if value is not null.

Check

Check is helper to reuse validators which return boolean:
const myExistingValidator = (v) => v < 10;
const travaValidator = Trava.Check(myExistingValidator);

console.log(travaValidator(20)); // ValidationError('Incorrect value')

Custom error message can be set by providing it as a second argument:

const checkValidator = Trava.Check(n => Boolean(n), 'My custom error message');

Or use the following to set the default one:

Trava.Check.ErrorMessage = 'My default check error';

Enum

Enum checks if value exists in enum:
const enumValidator = Trava.Enum(['a', 'b']);
console.log(enumValidator('c')); // ValidationError('Incorrect value')

Just like Check it accepts an error message as a second argument.

Const

Const checks if value equals:
const constValidator = Trava.Const('a');
console.log(constValidator('c')); // ValidationError('Incorrect value')

Just like Check it accepts an error message as a second argument.

Each

Each is usefull for validating uniform data structures (so far works only with arrays). Errors are aggregated in object by keys (or indices):

const elementValidator = n => n < 10 ? n : new ValidationError('BAD NUMBER');
const arrayValidator = Trava.Each(elementValidator);

console.log(arrayValidator([1, 15, 7, 5, 20]));
// ValidationError({2: 'BAD NUMBER', 4: 'BAD NUMBER'})
Note: Values of Each are wrapped with Required by default, use Optional otherwise.

Keys

Keys is used to validate Objects:

const objectValidator = Trava.Keys({
  a: Trava.Check(a => a >= 0),
  b: Trava.Required(Trava.Check(b => b.startsWith('nice'), 'HEY, think positive!')),
});

console.log(objectValidator({
  a: -1,
  b: 'bad wrong error'
}));
// ValidationError({
//   a: 'Incorrect value'.
//   b: 'HEY, think positive!',
// })

When used inside other operators explicit call Trava.Keys could be omitted. E.g.:

const objectValidator = Trava.Required({
  a: Trava.Check(a => a >= 0),
  b: Trava.Check(b => b < 0),
});
Note: Values of Keys are wrapped with Required by default, use Optional otherwise.

Some

Some is like Compose but tries to find first non error.

const someValidator = Trava.Some([
  Trava.Check(isString, 'Not a string'),
  Trava.Check(isNumber, 'Not a number'),
]);

console.log(someValidator(1));   // 1
console.log(someValidator('a')); // 'a'
console.log(someValidator({}));  // ValidationError('Not a number') <-- latest error

Examples

const t = require('trava');
const { Required, Optional, Each, Enum, Check, Keys, Some, ValidationError } = t;

const isString = s => typeof s === 'string';
const isEmail = s => /^\S+@\S+\.\S+$/.test(s);

const validateForm = t({
  username: Check(un => isString(un) && /^\w{3,30}$/.test(un), 'CUSTOM ERROR: INVALID USERNAME!'), // required by default
  password: Optional(Check(pwd => isString(pwd) && pwd.length >= 6)),
  access_token: Optional(Some([isString, Number.isInteger]), 'default token'),
  birthyear: Optional(Check(b => Number.isInteger(b) && 1900 <= b && b <= 2018)),
  email: Optional(Check(isEmail)),
  contacts: Optional(Each({
    name: Enum(['phone', 'email']),
    value: Check(isString),
  }))
});

const values = validateForm({
  username: 'HelloTrava',
  password: 'secretoversecret',
  birthyear: 1990,
});
if (values instanceof ValidationError) {
  console.error('FORM ERRORS', values.data);
} else {
  console.log('FORM VALUES', values);
}