Learn about our RFC process, Open RFC meetings & more.Join in the discussion! »

straints

0.1.0 • Public • Published

Straints

The JSON Object Validator

What is this?

Straints is JSON object validator that can be used both in node and in the browser. Validation constraints are declaratively defined in a single JSON or YAML file.

For example:

create_user:
  constrain:
    name:
      { method: "isNull", flip: true }
    email:
      { method: "isNull", flip: true }
      { method: "isEmail" }

With the above (YAML) configuration, executing a Straints validation with a 'create_user' context will ensure that a JSON object has non-null name and email properties and that email is in a valid format.

In essence, the goal of this project is to provide an easy way to maintain validations used across a web application in a single place.

Features

  • Manage site-wide validations in a single place.
  • Use the same validation configuration in node and in the browser
  • Reusable constraint configurations
  • Reusable validation contexts/schemas
  • Validate nested objects or nested arrays of objects

NOTE
Validation tests are not in the scope of this project, and Straints uses validator.js as its validator by default. You can also configure an alternative validator (See Validators section).

Awesome. How do I get it?

npm

npm install straints

or, as a dependency in your project

npm install straints --save

bower

bower install straints

or, as a dependency in your project

bower install straints --save

What's the Setup?

For Node/Express

var straints = require('straints');

straints.configure({ ... });

app.use(straints.usage);

With no configuration options specified Straints will look for a validation.json file at the root of your project.

The app.use() call simply adds the Straints instance to the request object. So, in your controllers you can do

req.straints.validate(req.body, "create_user", function(results)
{
    if (results.isValid())
    {
        // all validations passed
        ...
    }    
});

To get at the 'global' instance provided through straints.usage, use

straints.getInstance()

You can also create a new Straints instance if you need to (and pass config options as necessary).

straints.newInstance({ ... })

For the Browser

You will find the js for the browser in the /dist folder.

<script src="/path/to/straints/straints.min.js"></script>

Or, if you wish to use the default validator:

<script src="/path/to/straints/straints-with-validator.min.js"></script>

Then you can do

<script>
    var instance = StraintsFactory.configure({ ... }).getInstance();    
</script> 

With no configuration options specified Straints will simply try to request /validation.json.

Note that The methods described in For Node/Express are available on StraintsFactory.

How does all this work?

Configuration Options

Here are the config options listed with their defaults.

  • load ('[project-root|http-root]/validation.json')
    Specify the validation configuration data here. Here are the options:

    1. (string)
      In a browser this is assumed to be a URL and an XHR GET is attempted. Otherwise, it will assume this is a filename.
    2. (function)
      Must be of the form
    { load: function(callback) { callback(vcdata); } }

    You could also load a file like this

    { load: straints.fileLoader(filename) }

    or a URL like this

    { load: straints.httpLoader(url) }
    1. (object)
      The JSON VC data itself.
  • datatype ('json')
    The type of data being loaded, when load is a string or function ('yaml' or 'json')

  • validator (validator)
    Specify the test method implementation to use here (See Validators section).

  • useStrings (true)
    Set to true to have values converted to strings prior to validation (necessary for validator.js).

  • log (null)
    There is some logging available. Specify the log function here.

Validation Configuration (VC)

Let's start with an example:

person:
  constrain:
    name: [ is.notNull ]
    email: [ is.notNull, isEmail ]
basketball:
  player:
    include: person
    constrain:
      position: [ is.basketballPosition ]
  team:
    nested:
      coach:
        include: person
      players:
        nested:
          ____:
            include: basketball.player
    constrain:
      name: [ is.notNull ]
      coach: [ is.notNull ]
      players: [ is.notNull ]
is:
  { name: notNull, method: isNull, flip: true }
  { name: basketballPosition, method: isIn, params: [ [ point, guard, forward, water ] ] }

Note that there are some 'reserved' words in the VC. These are constrain, include, nested, and ____(four underscores). More on these later. The sections below will refer to the example above.

Contexts

A 'context' generally defines how a JSON object will be validated. If a path level in the VC has a constrain, include, or nested child, then it can be used as a context.

In the above our contexts are:

  • person
  • basketball.player
  • basketball.team
  • basketball.team.nested.coach
  • basketball.team.nested.players
  • basketball.team.nested.players.____
Constrain

The context child constrain defines constraints by property name. These constraints will be required if the context is included in the validation session.

For instance, the 'person' context requires that:

  • 'name' is not null
  • 'email' is not null
  • 'email' is a valid email address
Include

A context can include other contexts by listing their names in an array or comma-delimited string.

In the above example, the 'basketball.player' context includes the 'person' context. This means that all of the configuration for the 'person' context now also applies to 'basketball.player' as well.

Nested

If you need to validate an object that includes another object, you can use the nested context area. This works similarly to constrain except that you are specifying new contexts for each property rather than constraints.

In the above example, a 'basketball.team' object must have a 'coach', as specified by the constrain section of that context. But, we also have

nested:
  coach:
    include: person

This means that we want to validate the 'coach' property as an object, and so we specify a context for that property.

The Quadruple Underscore

While nested alone works well for single objects, what if you want to validate a list of objects?

Here's the nested 'players' part of the 'basketball.team' context.

nested:
  players:
    nested:
      ____:
        include: basketball.player

Nest again inside of 'players' to get at the objects in the list. Then you can use the ____ value to apply the context to every element of the array.

Why the double nesting? Remember that a JS Array is similar to an Object, except that it uses numbers as keys. Thus an array is effectively an object of objects.

In case you were just wondering, yes, this trick will work on objects of objects as well. It is worth remembering, however, that ____ will apply its contextual validation rules to EVERY object found as a property value of the parent object.

Constraints

Constraints can be defined anywhere in the VC, but wherever they appear they must be in an array. There are 3 ways to specify a constraint in the VC.

1. Constraint Methods

The simplest way, is to specify the name of a test method in the validator implementation.

  - isEmail

Note that if the test method requires parameters you will need to build a constraint object instead.

2. Constraint Object

This is an actual definition of a constraint. Its unique identifier is the path to it in the VC.

{ name: notNull, method: isNull, flip: true }

Define constraint objects using these attributes:

  • method (string)
    The method on the validator object that will be called.

  • name (string)
    Identifies the constraint within the list. If not specified, method will be used. If more than one constraint have the same identifier within the same list only the first one loaded will be used.

  • params (array)
    An array of additional parameters to be passed to method on validation (for a single parameter brackets are not needed).

  • flip (boolean)
    Set to true to reverse the boolean value returned by method.

3. Constraint References

Lastly, you can include an individual constraint by specifying the path to it in the VC.

shoes:
  constrain:
    size:
      - is.notNull
is:
  { name: notNull, method: isNull, flip: true }

You can also include an entire array of constraints.

shoes:
  constrain:
    size: [ sizes ]
sizes:
  { name: isNotNull, method: isNull, flip: true }
  { method: isIn, params: [ [ small, medium, large ] ] }

NOTE
Be aware of potential conflicts here. If a constraint array and a validator method have the same name, the latter will take precedence.

The Quadruple Underscore (again)

The ____ can also be used in the realm of constraints.

constrain:
  ____:
    - is.notNumeric
  email:
    - isEmail
  address:
    - isAddress

Here, the ____ applies its constraints to all properties of the object. This means that, 'email' and 'address' (and EVERY other field) on the object must not be numeric.

Running a Validation

straints.validate(target, contexts, complete, validate)
  • target (object)
    The object to be validated

  • contexts (string or array)
    One or more contexts to be validated against

  • complete (function(results))
    Called when validation has completed

    • results (object)
      See Validation Results section below.
  • validate (boolean <= function(property, target, constraint, result))
    Called for every constraint test during the validation session

    • property (string)
      name of the property that was evaludated
    • target (object)
      the object under validation
    • constraint (object)
      validation test that was run
    • result (boolean)
      validation result
    • returns (boolean) the final validation result

Validation Results

Here's the rundown on the information available in the validation results object.

  • target
    The JS or JSON object that validation was run against.

  • contexts
    The contexts specified for the validation session.

  • tried
    For each property, the identifiers of the executed constraint tests on the property.

  • failed
    For each property, the identifiers of the failed constraint tests on the property.

  • constraints
    The constraints involved in the validation session indexed by their identifiers.

  • isValid
    A convenience function to determine whether or not all validations passed.

Validators

Straints is bundled with validator.js and uses it by default (i.e., if no validator is provided in the configuration).

You may use any other validator implementation you wish. Just note that Straints will pass the test value as the first argument to the test method, followed by any parameters.

What Else?

Updates

Check out the changelog.

Tests

Under construction, but what there is of it can be invoked with

node test

Feedback

For bugs or feature suggestions please use the issue tracker.

Bugs will be fixed as quickly as possible.

License

MIT

Finally

Happy Validating!

Install

npm i [email protected]

Version

0.1.0

License

MIT

Last publish

Collaborators

  • avatar