Spire Labs

Simple, dependency-free filtering and validation tool for Node.js.

Whitelister allows you to simultaneously filter and validate a wide range of data via its default Promise interface or using its .sync method. For example, you can validate and filter:

  • a single value
  • an array, its properties, and its contents
  • an object and its key/value pairs
All code is happily hosted on Github, including tests.

Requires Node.js 6 or higher.

Latest Release: 0.0.5

Build Status

Introduction

The most common way to use whitelister is to validate and filter incoming parameters.

const rules = {
  username: { type: 'string', minLength: 3, maxLength: 30 },
  email: 'email',
};

const params = {
  username: 'david',
  email: 'david@spirelabs.co',
  admin: true,
};

return whitelister(rules, params);
// => { username: 'david', email: 'david@spirelabs.co' }
      
Another important way to use whitelister is to filter the data you output.
const rules = {
  users: {
    type: 'array',
    attributes: {
      id: 'integer',
      username: 'string',
    },
  },
};

const params = {
  users: [
    { id: 1, username: 'david', email: 'david@spirelabs.co', password: 'P@ssw0rd' },
    { id: 2, username: 'rob', email: 'rob@spirelabs.co', password: '0H(@nAda' },
  ],
};

return whitelister(rules, params);
// => [{ id: 1, username: 'david' }, { id: 2, username: 'rob' }]
      
Finally, whitelister truly blurs the line between filtering and validation. When a value fails validation, whitelister throws an error.
const rules = {
  user: {
    type: 'object',
    required: true,
    attributes: {
      email: { type: 'email', required: true },
      password: { type: 'string', required: true },
    },
  },
};

const params = {
  user: { email: 'david@spirelabs.co' },
};

return whitelister(rules, params)
.catch((e) => {
  console.log(e);
  // => WhitelistError { errors: [{ field: 'password', message: 'is required' }], message: 'password is required'}
});
      

Installation

npm install whitelister@latest
yarn add whitelister@latest

Configuration

Whitelister is ready to use. However, when producing error messages, whitelister provides the entire path of the offending property. For those who depend on only the name of the offending property and not its path, you can set the following configuration immediately after requiring whitelister:

const whitelister = require('whitelister');
whitelister.setConfig({ nestedNames: false });

API

Whitelister's main function returns a Promise by default.

Whitelister(rules, params)

rules object | string required
params any
const whitelister = require('whitelister');

const rules = {
  page: { type: 'integer', default: 1, min: 1 },
  per_page: { type: 'integer', default: 25, min: 1, max: 100 },
};

const params = {
  page: 1,
  per_page: 25,
};

return whitelister(rules, params)
.then((filteredParams) => {
  console.log(filteredParams);
})
.catch(whitelister.errors.WhitelistError, (err) => {
  console.log(err.toJSON());
});

Whitelister also offers a synchronous interface.

Whitelister.sync(rules, params)

rules object | string required
params any
const whitelister = require('whitelister');

const rules = {
  page: { type: 'integer', default: 1, min: 1 },
  per_page: { type: 'integer', default: 25, min: 1, max: 100 },
};

const params = {
  page: 1,
  per_page: 25,
};

let filteredParams;
try {
  filteredParams = whitelister.sync(rules, params);
} catch (e) {
  if (e instanceof whitelister.errors.WhitelistError) {
    console.log(e.toJSON());
  } else {
    // do something else
  }
}

console.log(filteredParams);

Rules

Rules refer to the options you can pass to whitelister instructing it how to filter and validate input. Some rules apply to all types while other rules are specific to one or more types.

Rules are usually an object, for example:

const rules = {
  user_id: { type: 'integer' },
  email: { type: 'email', required: true },
};
If you only need to pass a type rule, use shorthand:
const rules = {
  user_id: 'integer',
  email: { type: 'email', required: true },
};
Finally, you can pass a "custom" rule in the form of a function:
const rules = {
  user_id: { type: 'integer' },
  email: (val) => {
    if (val === 'david@spire.me') {
      return 'david@spirelabs.co';
    }
    return val;
  },
};

type

string required

The type of value to be filtered.

const rules = {
  user_id: { type: 'integer' },
};

// or shorthand
const rules = { user_id: 'integer' };
types: all

required

boolean [default: false]

Throws an error if the value is undefined. Also throws an error if the value is null unless allowNull is true.

const rules = {
  user_id: { required: true },
};
types: all

allowNull

boolean [default: false]

Allows a null value through. Undefined values will be ignored. For example, a user wants to remove their email address on update.

const rules = {
  email: { type: 'email', allowNull: true },
};
types: all

default

any

Set the property to a default value if the value is undefined. For example, setting a default page number for pagination.

const rules = {
  page: { type: 'integer', default: 1 },
};
types: all

acceptedValues

array

Expect the value of the property to be one of the accepted values. For example, a member's role can only be changed to one of N values.

const rules = {
  role: { type: 'string', acceptedValues: ['admin', 'member', 'owner'] },
};
types: all

filterWith

function [must return true or false]

A custom filter to run along with any other specified rules. If the filter returns true, the value is allowed through. If the filter returns false, the value is filtered, or if `required` is true, an error is thrown. For example, checking a date.

const rules = {
  date: {
    type: 'string',
    filterWith: (val) => {
      if (val) {
        return moment(val).isBefore(moment(), 'day');
      }
      return true;
    },
  },
};
types: all

preTransform

function [must return the value to be validated]

Mutate the value of the property before validating it. The value returned by preTransform is the value allowed through after validation. For example, some enumerable.

const rules = {
  state: {
    type: 'integer',
    preTransform: (val) => {
      if (typeof val === 'string') {
        return states[val];
      }
      return val;
    },
  },
};
types: all

postTransform

function [should return the value you want to receive after validation]

Used to replace, mutate, or otherwise change the value recieved after validation is successful. For example, modifying a date.

const rules = {
  date: {
    type: 'string',
    postTransform: val => moment(val).format('ggggww'),
  },
};
types: all

min

integer

Minimum acceptable value. For example, page.

const rules = {
  page: { type: 'integer', min: 1 },
};
types: float, integer

max

integer

Maximum acceptable value. For example, per_page.

const rules = {
  per_page: { type: 'integer', max: 100 },
};
types: float, integer

minLength

integer

Minimum acceptable length of an array or string. For example, elements.

const rules = {
  elements: { array: 'string', minLength: 1 },
};
types: array, string

maxLength

integer

Maximum acceptable length of an array or string. For example, a username.

const rules = {
  username: { type: 'string', maxLength: 30 },
};
types: all

requireOneOf

array

Require that an object have at least one of the following keys. For example, username or email.

const rules = {
  user: {
    type: 'object',
    attributes: { email: 'email', username: 'string' },
    requireOneOf: ['email', 'username'],
  },
};
types: object

attributes

object

Require an array's elements to meet the given rules, or provide rules for an object's properties. For example, nested associations.

const rules = {
  user: {
    // object
    type: 'object',
    attributes: {
      email: 'email',
      address: {
        type: 'object',
        attributes: {
          city: 'string',
          state: 'string',
        },
      },
      // array
      image_urls: {
        type: 'array',
        attributes: {
          type: 'string',
        },
      },
    },
  },,
};
types: array, object

Types

A type tells whitelister what to allow through its filter (or validate against). Different types support varying rules for whitelister to validate against.

Types can be passed as a rule:

const rules = {
  user_id: { type: 'integer' },
};
Or shorthand when alone:
const rules = {
  user_id: 'integer',
};
Currently, whitelister doesn't support "custom" types, but it is being discussed.

array

Accepted values: Arrays and strings. Strings are split using arry.split(',').

Special rules: minLength maxLength attributes

boolean

Accepted values: Booleans (true, false), integers (1, 0), and strings ("t", "f", "true", "false"). Integers are converted to strings before validation. Values are not case sensitive.

Special rules: none

email

Accepted values: Strings.

Special rules: minLength maxLength

float

Accepted values: Numbers and strings. Whitelister tries to parse strings into floats.

Special rules: min max

integer

Accepted values: Numbers and strings. Whitelister tries to parse strings into integers.

Special rules: min max

null

Accepted values: null

Special rules: none

object

Accepted values: Objects.

Special rules: requireOneOf attributes

string

Accepted values: Strings. Empty strings such as "" are considered invalid when required is set to true.

Special rules: minLength maxLength

Errors

Whitelister uses custom error classes internally. All custom error classes are exposed directly by Whitelister for your convenience.

BaseError

A base error that all other Whitelister errors inherit from. Doing so allows all Whitelister errors to be caught at a single point.

ArgumentError

Inherits from BaseError

Thrown when the arguments provided to Whitelister are not acceptable.

const rules = ['integer'];
const params = { per_page: 25 };
return whitelister(rules, params);
// => ArgumentError { message: 'rules is not an object or string' }

FieldError

Inherits from BaseError

Thrown when a validation fails or a rule cannot be met and collected to root WhitelistError.

const rules = {
  page: { type: 'integer', required: true },
  per_page: { type: 'integer', default: 100 },
};
const params = { per_page: 25 };
return whitelister(rules, params);
// => FieldError { field: 'page', message: 'is required' }

WhitelistError

Inherits from BaseError

Thrown when overall whitelisting fails. Aggregates FieldErrors and nested WhitelistErrors into its errors property.

const rules = {
  user_id: { type: 'integer', required: true },
  page: { type: 'integer', required: true },
  per_page: { type: 'integer', default: 100 },
};
const params = { per_page: 25 };
return whitelister(rules, params);
// => WhitelistError { errors: [{ field: 'page', message: 'is required' }, { field: 'user_id', message: 'is required' }], message: 'page is required; user_id is required' }

Usage Examples

At Spire Labs, we first used Whitelister for filtering and validating incoming parameters. You can find a simplified examples of that and others in examples. To use the examples, clone the repo and run $ node examples/[filename].js - e.g. $ node examples/express-app.js.