Valivar

Mostly derived from eivindfjeldstad/validate; Valivar provides dynamic schemas for modern javascript and typescript validation and sanitization. In Valivar you can use catch-all keys $ and * to define any array or object respectively, letting you define a single schema that can apply consistent rules for growing and changing datasets.


version NPM Deno deno doc Build Status Codecov dependencies

Install

Node.js or Browser

npm install valivar

Deno

import valivar from "https://deno.land/x/valivar/mod.ts"

Import in HTML

<script type="text/javascript" src="dist/valivar.js"></script>

From CDN

<script type="text/javascript" src="https://unpkg.com/valivar"></script>

Examples

For a full list see the examples directory (coming)

Play with it

CodePen (suggested)

JSFiddle

Reademe rewrite in progress…

Usage

Define a schema and call .validate() with the object you want to validate. The .validate() function returns an array of validation errors.

import { Schema } from 'valivar'

const user = new Schema({
  username: {
    type: String,
    required: true,
    length: { min: 3, max: 32 }
  },
  pets: [{
    name: {
      type: String
      required: true
    },
    animal: {
      type: String
      enum: ['cat', 'dog', 'cow']
    }
  }],
  address: {
    street: {
      type: String,
      required: true
    },
    city: {
      type: String,
      required: true
    }
    zip: {
      type: String,
      match: /^[0-9]+$/,
      required: true
    }
  }
})

const errors = user.validate(obj)

Each error has a .path, describing the full path of the property that failed validation, and a .message describing the error.

errors[0].path //=> 'address.street'
errors[0].message //=> 'address.street is required.'

Custom error messages

You can override the default error messages by passing an object to Schema.message().

const post = new Schema({
  title: { required: true }
})

post.message({
  required: (path) => `${path} can not be empty.`
})

const [error] = post.validate({})
assert(error.message = 'title can not be empty.')

It is also possible to define messages for individual properties:

const post = new Schema({
  title: {
    required: true,
    message: 'Title is required.'
  }
})

And for individual validators:

const post = new Schema({
  title: {
    type: String,
    required: true,
    message: {
      type: 'Title must be a string.',
      required: 'Title is required.'
    }
  }
})

Nesting

Objects and arrays can be nested as deep as you want:

const event = new Schema({
  title: {
    type: String,
    required: true
  },
  participants: [{
    name: String,
    email: {
      type: String,
      required: true
    },
    things: [{
      name: String,
      amount: Number
    }]
  }]
})

Arrays can be defined implicitly, like in the above example, or explicitly:

const post = new Schema({
  keywords: {
    type: Array,
    each: { type: String }
  }
})

Array elements can also be defined individually:

const user = new Schema({
  something: {
    type: Array,
    elements: [
      { type: Number },
      { type: String }
    ]
  }
})

Nesting also works with schemas:

const user = new Schema({
  name: {
    type: String,
    required: true
  },
  email: {
    type: String,
    required: true
  }
})

const post = new Schema({
  title: {
    type: String,
    required: true
  },
  content: {
    type: String,
    required: true
  },
  author: user
})

If you think it should work, it probably works.

Naming conflicts

Validate will naively assume that a nested object where all property names are validators is not a nested object.

const schema = new Schema({
  pet: {
    type: {
      required: true,
      type: String,
      enum: ['cat', 'dog']
    }
  }
});

In this example, the pet.type property will be interpreted as a type rule, and the validations will not work as intended. To work around this we could use the slightly more verbose properties rule:

const schema = new Schema({
  pet: {
    properties: {
      type: {
        required: true,
        type: String,
        enum: ['cat', 'dog']
      }
    }
  }
});

In this case the type property of pets.properties will be interpreted as a nested property, and the validations will work as intended.

Custom validators

Custom validators can be defined by passing an object with named validators to .use:

const hexColor = val => /^#[0-9a-fA-F]$/.test(val)

const car = new Schema({
  color: {
    type: String,
    use: { hexColor }
  }
})

Define a custom error message for the validator:

car.message({
  hexColor: path => `${path} must be a valid color.`
})

Custom types

Pass a constructor to .type to validate against a custom type:

class Car {}

const user = new Schema({
  car: { type: Car }
})

Chainable API

If you want to avoid constructing large objects, you can add paths to a schema by using the chainable API:

const user = new Schema()

user
  .path('username').type(String).required()
  .path('address.zip').type(String).required()

Array elements can be defined by using $ as a placeholder for indices:

const user = new Schema()
user.path('pets.$').type(String)

This is equivalent to writing

const user = new Schema({ pets: [{ type: String }]})

Typecasting

Values can be automatically typecast before validation. To enable typecasting, pass an options object to the Schema constructor with typecast set to true.

const user = new Schema(definition, { typecast: true })

You can override this setting by passing an option to .validate().

user.validate(obj, { typecast: false })

To typecast custom types, you can register a typecaster:

class Car {}

const user = new Schema({
  car: { type: Car }
})

user.typecaster({
  Car: (val) => new Car(val)
})

Property stripping

By default, all values not defined in the schema will be stripped from the object. Set .strip = false on the options object to disable this behavior. This will likely be changed in a future version.

Strict mode

When strict mode is enabled, properties that are not defined in the schema will trigger a validation error. Set .strict = true on the options object to enable strict mode.

API