purity

Purity is a JSON data validation and transformation tool for web browsers and node.js

Usage no npm install needed!

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

README

Purity

Purity is a JSON data validation and transformation tool for web browsers and node.js

It can be installed via:

  • npm: npm install purity
  • bower: bower install purity

Purity, Inspired by mongoose, exposes a schema-based validation API allowing you to apply constraints and transformations to the data in your application.

For usage with express, please see the express-purity package on npm.

Quickstart Example

Most of the action happens through the Schema object.

import { Schema } from 'purity';

The following schema expects:

  • title - A non empty string.
  • tags - a comma separated string of tags which will be lowercased and split into an array.
  • comments - An array of objects with properties:
    • likes - a non negative number which defaults to 0.
    • content - a non empty string not exceeding 255 characters.
let toLowercase = v => v.toLowerCase();
let split = v => v.split(',');

const blogPostSchema = Schema({
  title: { $type: String, $required: true },
  tags: { $type: String, $transform: [toLowercase, split] },
  comments: [{
    likes: { $type: Number, $default: 0, $gte: 0 },
    content: { $type: String, $required: true }
  }]
});

Validate some data against the schema, using the callback api...

let dirtyData = {
  title: 'Stuff about lions',
  tags: 'Lions',
  comments: [
    { likes: 123, content: 'lions r cool' },
    { content: 'w/e' }
  ]
};

blogPostSchema.validate(dirtyData, (err, res) => {
  assert.equal(err, null);
  assert.equal(res, {
    title: 'Stuff about lions',
    tags: ['lions'],
    comments: [
      { likes: 123, content: 'lions r cool' },
      { likes: 0, content: 'w/e' }
    ]
  })
})

Or the promise api...

let dirtyData = {
  title: 'Spiders',
  tags: 'HAIRY,LEGS',
  comments: [{ content: 'creepy' }]
};

blogPostSchema.validate(dirtyData)
  .catch(handleError)
  .then(handleSuccess);

Errors have properties to help you make meaningful messages:

let dirtyData = {
  comments: [{ content: 'this one will fail' }]
};

blogPostSchema.validate(dirtyData, function (err, res) {
  assert(err instanceof purity.ValidationError);
  let message = `The blog ${err.path} is ${err.type}`;
  // message -> 'The blog title is missing'
})

API reference

purity.Types

An object containing the aliases of built in types

  • Types.Any - a relaxed type with no formal type checks
  • Types.Boolean
  • Types.String
  • Types.Number
  • Types.Date

purity.Schema(definiton [, options])

Create a re-usable schema to validate data against.

Arguments
  • definition A definition (usually an object) representing the expected data. Detailing type and value constraints, with optional defaults and transformations. See detailed description below for more information.
  • options - schema options
    • options.cast {Boolean} - when true, purity will attempt to cast all properties to their expected types before applying constraints.
Returns

A new Schema instance.


Schema definition

The schema definition is a representation of the constraints and transformations you would like to apply to your data.

Basics

The definition is plain Obejct with the following format:

const definition = {
  /**
   * $type is the only required field.
   * It can be one of purity.Types or an alias for a custom type.
   */
  $type: String,

  /**
   * $required flag defaults to false.
   * Setting this to true will generate errors when
   * the property is null|undefined or an empty string.
   */
  $required: true,

  /**
   * $cast flag determines whether purity will attempt to cast
   * the property to it's declared $type. this overrides schema
   * level cast options and defaults to false.
   */
  $cast: true,

  /**
   * $default can be a static value or a function which yields
   * a default value when the property is null|undefined or
   * an empty string.
   */
  $default: Date.now,

  /**
   * $transform is a mapping function or array of mapping functions
   * which will transform the data.
   */
  $transform: [v => v + 1, v => v - 1, v => 'unnecessary']

  /**
   * More options are available for the built in purity.Types
   * and for custom types which declare constraints
   */
};

const schema = Schema(definiton);

However if you only wish to enforce a type, the following is sufficient:

const schema = Schema(String);

constraints

The following additional constraints can be applied per type:

purity.Types.String

{
  // {Number} assert a minimum string length
  $minlength: 123,

  // {Number} assert a maximum string length
  $maxlength: 456,

  // {Number} assert an exact string length
  $fixedwidth: 789,

  // {RegExp} assert a regex .test()
  $regex: /(?:lions|tigers|bears|)+\soh my$/i
}

purity.Types.Number

{
  // {Number} assert greater than
  $gt: 1,

  // {Number} assert greater than || equal
  $gte: 2,

  // {Number} assert less than
  $lt: 3,

  // {Number} assert less than || equal
  $lte: 4,

  // {Number} assert equal
  $eq: 5,

  // {Number} assert not equal
  $neq: (act, opt) => act !== opt
}

purity.Types.Date

{
  // {Date|Number} assert greater than
  $gt: new Date(),

  // {Date|Number} assert less than
  $lt: Date.now()
}
Arrays

To declare an array, simply wrap the definition in square brackets [].

const schema = Schema([{ $type: Date, $required: true }]);
Nested Objects

For nested objects ...nest them

const schema = Schema({
  id: Number,
  some: {
    deeply: {
      nested: {
        data: { $type: Boolean, $default: true }
      }
    }
  }
});

purity.createDataType(options)

Declare a data custom data type to be used in a schema.

Arguments

options {Object} - with properties:

  • required:
    • aliases
  • optional:
    • check
    • cast
    • constraints

options.aliases {Any|Any[]} - Identifier(s) fot this data type. The alias(es) declared here will be looked up when used to alias a $type in a schema definition.

options.check {Function (value)} - a function which is passed the value of the property currently being processed and should return a Boolean indicating whether or not it is valid. By default this returns true.

options.cast {Function (value)} - a function which maps the value of the property currently being processed to your custom data type. Only invoked when the schema level cast or type level $cast flag is set. By default this is an identity transformation v => v.

options.constraints {Object} - an object mapping type specific constraint names to predicate functions which enforce them.

Important notes: options.check is invoked before any casting is applied. The incoming value will always be a primitive javascript value. Use this opportunity to reject data that cannot be casted.

Examples

Creating a data type

Here is an example of how you might create a data type to handle mongodb ObjectIds.

For those who don't know, an ObjectID is simply a 24 character hex string.


import { ObjectID } from 'mongodb';

purity.createDataType({
  // give the type some aliases so we
  // can refer to it in a schema definition
  aliases: [ObjectID, 'objectid'],

  // implement a quick and dirty check
  // to see if it's a valid ObjectID
  check: v => typeof v === 'string' && /[a-zA-Z0-9]{24}/.test(v),

  // provide a method to cast incoming data
  cast: v => new ObjectID(v),

  // A contrived example of adding constraints
  // we're going to check if the ObjectID
  // ends with a particualr sequence
  constraints: {
    endsWith: (value, option) => {
      // value is the incoming data.
      // option is the character we're looking for
      return value.toString().endsWith(option);
    }
  }
});

Now let's use it in a schema

const schema = Schema(ObejctID, { cast: true });

schema.validate('abcdef0123456789abcdef01', function (e, r) {
  assert.equal(r, ObejctID(abcdef0123456789abcdef01));
});

using the endsWith constraint we defined...

// we can use the other alias we provided as well
const schema = Schema({ $type: 'objectid', endsWith: '02', $cast: true });

schema.validate('abcdef0123456789abcdef01', function (e, r) {
  // the endsWith constraint causes an error
  assert(e instanceof purity.ValidationError);
});

Bugs and Features

Please log any issues or feature requests on github's issue tracker.

Version history

  • 0.x - initial release
  • 1.0.0
    • Breaking changes, please consult the new api to migrate.
    • removed array options
    • removed many mutations in favour of sequential mapping functions
    • removed cleanse alias for Schema#validate
    • added Date type
    • added ability to validate primitives and arrays as root level data

Todo:

  • Travis CI
  • browser testing
  • improve docs
  • customisation around error messages
  • add some support for array options
    • unique
    • option to ignore errors and remove elements instead
    • constraints on array length