Newline Proliferating Maniac

    straints

    0.2.1 • 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 front and back end validations in a single place
    • Reusable validation contexts/schemas
    • Reusable constraint configurations
    • Validate a property against another property
    • Validate nested objects or nested arrays of objects
    • Conditional validation (if/then/else)

    NOTE
    straints itself does not include validation test methods. It bundles and uses alidate 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 [--save]
    

    npm version

    bower

    bower install straints [--save]
    

    Bower version

    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.min.js"></script>

    Or, if you wish to use the default validator:

    <script src="/path/to/straints-alidate.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 (false)
      Set to true to have values converted to strings prior to validation.

    • 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 ] ] }

    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.nested.____
    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.

    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.

    Conditional inclusion at the context level is also available.

    For instance:

    include:
      { if: [ greatShooter, greatPasser ], then: starter, else: benchwarmer }

    If you had team tryouts and a person object validated as 'greatShooter' and 'greatPasser', then you might want to try to validate as 'starter' as well. Otherwise, 'benchwarmer' may be more appropriate.

    Here are the properties for an include condition object:

    • name (string)
      If you wish to reference the condition elsewhere in the VC this makes it much easier.

    • if (array,string)
      The contexts that will be executed to determine the control flow. On success, then contexts will be included if provided. On failure, else contexts will be included if provided.

    • then (array,string)
      The contexts that will be included when the if condition succeeds. If there is no if then inclusion is automatic.

    • else (array,string)
      The contexts that will be included when the if condition fails. If there is no if then this is ignored.

    NOTE
    Context validation for if is entirely separate from the current validation session and is not included in result data nor does it generate callbacks.

    Nested

    If you need to validate an object that includes another object, you can use the nested context property. 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? Think of a JS Array like a JS Object, except that it uses numbers as keys. So, in this case, the array is effectively an object of objects.

    In case you were just wondering, yes, this trick will work on an object of objects as well. It is worth remembering, though, 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 Objects

    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:

    • name (string)
      If you wish to reference the constraint elsewhere in the VC this makes it much easier.

    • method (array, string)
      An array or comma-delimited string of validator method(s) or constraint reference(s) that will be executed. When more than one test is specified, they will be executed in the order given unless and until short-circuited by logic.

    • params (array)
      An array of additional parameters to be passed to all validator methods specified in method on validation. It is best to use this only when one test method has been specified in method.

    • logic (string)
      Determines how test method results are aggregated when multiple tests are specified in method. The valid values are

      • and (default)
        All tests must return true. Short circuits at the first test returning false.
      • or
        At least one test must return true. Short circuits at the first test returning true.
      • nor
        All tests must return false. Short circuits at the first test returning true.
      • nand
        At least one test must return false. Short circuits at the first test returning false.
      • xnor
        All tests must return true or false. Short circuits at the first test not being equal to the previous test.
      • xor
        All tests must not return true or false. Short circuits at the first test not being equal to the previous test.
    • flip (boolean)
      Set to true to reverse the boolean value ultimately returned by method.

    Parameter Dependencies

    Suppose you want to use data from the target object when defining params.

    For example, an email and its confirmation:

    constrain:
      email:
        - isEmail
      emailConfirmation:
        { method: isEqual, params: t.email }

    Nifty, eh?

    The target objects are exposed like so:

    • s - session target
      This is the full object being validated, the one passed to straints.validate().

    • t - current target
      This is the current object being validated. It will be the same as s, unless the current object is nested.

    Only dot-notation and alphanumeric (plus underscores) property names are supported when specifying a parameter dependency in this manner.

    To do something more advanced you can use %{ ... } interpolation syntax.

    { method: isEqual, params: "%{t['email']}" }
    3. Constraint References

    Finally, 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: isValueIn, 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

    As far as straints is concerned, a validator is just a JS object with test methods. These methods accept the test value as the first argument and can be passed additional arguments as necessary.

    straints is bundled with and uses alidate as its validator.

    You can use something else if you like.

    straints.configure({ validator: myTestMethodObject });

    What Else?

    Updates

    Check out the changelog.

    Tests

    npm 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 straints@0.2.1

    Version

    0.2.1

    License

    MIT

    Last publish

    Collaborators

    • captison