valerie

Simple JS object validator

Usage no npm install needed!

<script type="module">
  import valerie from 'https://cdn.skypack.dev/valerie';
</script>

README

valerie Build Status devDependency Status devDependency Status

Simple javascript object validator

The goal of this project is to provide a simple, intuitive, extensible, independent, and isomorphic javascript object validation library.

What makes Valerie any different from other validation libs?

  • No dependencies
  • Very lightweight
  • Easy to use both server- and browser-side
  • Validation rules are standard functions (keep it simple)
  • Supports async/promise-based validation rules
  • Custom rules are super easy to make
  • Custom error messages for all built-in rules
  • Source uses ES6/7 features (transpiled to ES5 for browsers)
  • Full tests and linting

Usage

Import the function and built-in rules

import createValidator from 'valerie';
import { number, oneOf } from 'valerie/rules';
import { range, required } from 'valerie/rules/extended';

Compose the validation schema for our object

const schema = {
  id: [required('id is required'), number('id must be a number')],
  name: {
    first: required('first name is required'),
    last: required('last name is required')
  },
  age: range(0, 123, 'must be a normal human age'),
  favoriteColor: oneOf(['blue', 'red', 'yellow'], 'must be a primary color')
};

Create the object validator, which is a function taking a single object parameter, and returns a Promise which resolves to an array containing errors. The array will be empty if there are no errors.

const validate = createValidator(schema);

Validate


const input = {
  id: 10,
  name: {
    first: 'foo'
  },
  age: 99,
  favoriteColor: 'potato'
};

// ES7
const errors = await validate(input);

// ES6
validate(input).then(errors => {
  // tell the user about the errors!
})

// ES5
validate(input).then(function(errors) {
  // tell the user about the errors!
});

/*
[
  {
    property: 'name.last',
    message: 'last name is required'
  },
  {
    property: 'favoriteColor',
    message: 'must be a primary color'
  }
]
*/

// or just get the first error
const errors = await validate(input, 1);

/*
[
  {
    property: 'name.last',
    message: 'last name is required'
  }
]
*/

Rules

Validation rules are just simple functions that take a single input, the value to validate, and return undefined if valid, or an error message if not.

The built-in rule objects exported from valerie/rules are functions that take a set of options and return the rule function itself.

Example:

import { is } from 'valerie/rules';

const isTrue = is(true, 'value must be true');

isTrue(false); // 'value must be true';
isTrue(true); // undefined

Simple rules

The simple rules are largely based on the fundamental javascript operations.

Equality rules

is(target, [message = "is"])

Tests if a value is strictly equal (===) to a target value.

  • target: what the validated value should equal
  • message: optional custom error message
const isBar = equalTo('bar', 'foo must be bar');

const validate = createValidator({ foo: isBar });

const errors = await validate({ foo: 'bar' });

equalTo(target, [message = "equalTo"])

Tests if a value is loosely equal (==) to a target value.

  • target: what the validated value should equal
  • message: optional custom error message
const isBar = equalTo('bar', 'foo must be bar');

const validate = createValidator({ foo: isBar });

const errors = await validate({ foo: new String('bar') });

Numeric rules

number([message = "number"])

Tests if a value is a number (!isNaN).

  • message: optional custom error message
const isNumber = number(foo 'must be a number');

const validate = createValidator({ foo: isNumber });

const errors = await validate({ foo: Math.PI });

greaterThan(target, [message = "greaterThan"])

Tests if a value is greater than (>) a target value.

  • target: what the validated value should be greater than
  • message: optional custom error message
const isPositive = greaterThan(0, 'foo must be positive');

const validate = createValidator({ foo: isPositive });

const errors = await validate({ foo: 1 });

lessThan(target, [message = "lessThan"])

Tests if a value is less than (<) a target value.

  • target: what the validated value should be less than
  • message: optional custom error message
const isNegative = lessThan(0, 'foo must be negative');

const validate = createValidator({ foo: isNegative });

const errors = await validate({ foo: -1 });

Array rules

array([message = "array"])

Tests if a value is an array (Array.isArray).

  • message: optional custom error message
const isArray = array('foo must be an array');

const validate = createValidator({ foo: isArray });

const errors = await validate({ foo: ['bar', 'baz'] });

contains(item, [message = "contains"])

Tests if a value contains (indexOf) an item.

  • item: item that value should contain
  • message: optional custom error message
const containsBar = contains('bar', 'foo must contain bar');

const validate = createValidator({ foo: containsBar });

const errors = await validate({ foo: ['bar'] });

oneOf(options, [message = "oneOf"])

Tests if a value is equal to (===) an item in an Array.

  • options: array of items to check against
  • message: optional custom error message
const isPrimaryColor = oneOf(['red', 'blue', 'yellow'], 'foo must be a primary color');

const validate = createValidator({ foo: isPrimaryColor });

const errors = await validate({ foo: 'blue' });

Type rules

isInstanceOf(type, [message = "isInstanceOf"])

Tests if a value is an instance of a class (instanceof).

  • type: what the validated value should be an instance of
  • message: optional custom error message
const isBar = isInstanceOf(Bar, 'foo must be bar');

const validate = createValidator({ foo: isBar });

const errors = await validate({ foo: new Bar() });

isTypeOf(type, [message = "isTypeOf"])

Tests if a value is of a given type (typeof).

  • type: what the validated value should be a type of
  • message: optional custom error message
const isString = isTypeOf(Bar, 'foo must be a string');

const validate = createValidator({ foo: isString });

const errors = await validate({ foo: 'bar' });

hasProperty(property, [message = "hasProperty"])

Tests if an object has a child property (hasOwnProperty).

  • property: name of the property
  • message: optional custom error message
const hasBar = hasProperty('bar', 'foo must have bar property');

const validate = createValidator({ foo: hasBar });

const errors = await validate({
    foo: {
        bar: true
    }
});

Logical operators

These rules take one or more rules as input and return new, compoud rule.

async and(rules, [message = "and"])

Tests if a value is valid against all rules within an Array.

  • rules: array of rules to validate against
  • message: optional custom error message
const isArrayContainingBar = and([array(), contains('bar')], 'foo must be an array containing "bar"');

const validate = createValidator({ foo: isArrayContainingBar });

const errors = await validate({ foo: ['bar', 'baz', 'qux'] );

async or(rules, [message = "or"])

Tests if a value is is valid against at least one rule within an Array of rules.

  • rules: array of rules to validate against
  • message: optional custom error message
const isNumberOrX = or([number(), equals('x')], 'foo must be a number or the letter "x"');

const validate = createValidator({ foo: isNumberOrX });

const errors = await validate({ foo: 'x' );

async not(rule, [message = "not"])

Tests if a value is not valid against rule.

  • rule: rule to validate against
  • message: optional custom error message
const isNotNumber = not(number(), 'foo must not be a number');

const validate = createValidator({ foo: isNotNumber });

const errors = await validate({ foo: 'bar' );

Other rules

regex(pattern, [message = "regex"])

Tests if a value matches a regex (.match)

  • pattern: regexp (RegExp or /pattern/)
  • message: optional custom error message
const isEMail = regex(/^\S+@\S+$/, 'foo must be an email address');

const validate = createValidator({ foo: isEMail });

const errors = await validate({ foo: 'bar@baz.com' });

Extended Rules

Extended rules use the simple rules to form more complex logic

async range(min, max, [message = "range"])

Tests if a value is between two values. Generally want to use with number. Depends on and, or, greaterThan, lessThan, and equalTo.

  • min: minimum value, inclusive
  • max: maximum value, inclusive
  • message: optional custom error message
const isNumber = number('foo must be a number');
const isHumanAge = range(0, 123, 'foo must be a human age');

const validate = createValidator({ foo: [isNumber, isHumanAge] });

const errors = await validate({ foo: 100 });

async required([message = "required"])

Tests if a value exists (not undefined, not an empty string, not an empty array). Depends on and, defined, and notEmpty.

  • message: optional custom error message
const isRequired = required('foo is required');

const validate = createValidator({ foo: isRequired });

const errors = await validate({ foo: 'bar' );

Custom Rules

Custom rules are easy to implement. They're simply functions that take a single value and return an error message for failure, and undefined for passing.

const isEven = value => {
  if (value % 2 !== 0) return 'value must be even';
};

isEven(4); // undefined
isEven(5); // value must be even

const validate = createValidator({
  foo: isEven
});

const errors = await validate({
  foo: 5
});

/* 
[
  {
    property: 'foo',
    message: 'value must be even'
  }
]
*/

Built-in rules use currying to allow options and custom error messages to be set. You can follow this technique like so:

const divisibleBy = (divisor, message = 'divisibleBy') => {
  return value => {
    if (value % divisor !== 0) return message;
  }
};

const isDivisibleBy3 = divisibleBy(3, 'value must divisibly by 3');

Check out the other rules for more examples.

TODO: