Validation Constraints

Constraints can be defined in arrays anywhere in the VSD. They are put to use, however, within the constrain directive or other validation levels within a context or within rules or rule expressions.

Constraint Objects

In the VSD, a constraint object may be defined with some combination of the following properties:

  • name (string)
    A name for the constraint object that makes it easier to reference from elsewhere in the VSD.

  • test (string)
    A rule or rule expression that defines a validation test for a given property of the current validation target.

  • poll (string)
    A rule or rule expression that defines a validation test for all properties of the current validation target. This is ignored if test is given.

  • results (string)
    A rule or rule expression used with poll to evaluate its aggregated validation test results.

  • if (string)
    A rule or rule expression that, when true, allows test or poll to be executed.

  • param (array)
    Additional parameter that is passed to test methods appearing in any rule or rule expression in the constraint.

  • params (array)
    Additional parameters that are applied to test methods appearing in any rule or rule expression in the constraint. This is ignored if param is given.

  • property (string)
    Locks the target property used with any test, poll, or if rule expression.

  • flip (boolean)
    Set to true to reverse the boolean value ultimately returned by test or results.

  • payload (any)
    Arbitrary data to attach to the constraint that will be available in results object via .payload().

Remember that either test or poll is required for a constraint object.

Referencing a Constraint

Constraints are identified by the path to their definition in the VSD.

pizza:
  constrain:
    cheese:
      - { test: itemIn, param: [ mozzarella, provolone, jack ] }
    topping:
      - in.available.toppings
in:
  available:
    - { name: toppings, test: itemIn, param: [ pepperoni, mushroom, olives ] }

This one-topping pizza validation schema has two referenceable constraints.

  • pizza.constrain.cheese.0
  • in.available.toppings

When a constraint has no 'name' specified then it's name is the index of its position in the array where it is defined.

Constraint arrays can also be referenced:

pizza:
  constrain:
    sauce: [ good.name ]
good:
  name:
    - [ exists, lowercase ]
    - { test: not longer, param: 16 }

Here, the named pizza sauce must be a lowercase string of no more than 16 characters. Note here also that constraint arrays can be nested.

Test Method Parameters

Other target object properties can be used as parameters to test methods in a constraint.

constrain:
  email:
    - email
  emailConfirmation:
    - { test: equal, params: $_.email }

The above ensures that 'emailConfirmation' is equal to 'email'.

In param and params, you can reference a property of the target object by prefixing it with a dollar-sign ($).

Additionally, you can specify:

  • _ - current target object
    The current target being validated (same as s unless nested)

  • __ - parent target object
    The parent of the object being validated. Use these with the dot-notation to check higher-level parents.

Aggregate Constraints

A neat feature of straints is the ability to validate against aggregate validation results.

The way this works is through the poll constraint object parameter.

Consider the following VSD.

activity:
  constrain:
    participants: [ not.missing, array ]
  nested:
    participants:
      constrain:
        _:
          - { name: enoughAdults, poll: age:is.atLeast21, results: passCount:atLeast2 }
      nested:
        ____:
          constrain:
            name: [ exists, string ]
            age: [ exists, number ]
is:
  - { name: atLeast21, test: not.less, params: [ 21 ] }
  - { name: atLeast2, test: not.less, params: [ 2 ] }

There is quite a bit going on here, so lets break it down.

  1. The context 'activity' stipulates that an object must have an array of 'participants', each having a 'name' and 'age' that are a string and a number, respectively.

  2. The 'enoughAdults' poll constraint stipulates that 'age' must be at least 21 (See rules page for more info on this syntax) for every object value of the current target.

    NOTE
    Remember that the target value of poll is always the current target object, and it aims to aggregate validation results across all property values of that object. The property to which a poll constraint is attached is merely the one to be blamed for any error that may result. To emphasize this point (or cause further confusion), the special _ property being used here actually refers to the current target object, which works as if the object had a property that referred to itself.

  3. The results rule in the poll constraint is evaluated against a separate aggregate results object (described below), and here we are checking that 'passCount', the number of values that passed the poll validation, is at least two.

So, in a nutshell, this validation scheme is ensuring that a given activity has some participants (with names and ages) and that at least two of them are "adults" 21 or older. We don't want to see any children getting hurt, of course.

The Aggregate Results Object

Here are the properties of a poll constraint's aggregate object that is made available in results.

  • valid (boolean)
    This is true if all tests passed. Also, if a results test is not provided on a poll constraint then this is the value that will be returned from the constraint.

  • passed (array)
    Names of properties that passed

  • failed (array)
    Names of properties that failed

  • tested (array)
    Names of properties that were tested

  • passCount (number)
    Number of properties that passed

  • failCount (number)
    Number of properties that failed

  • testCount (number)
    Number of properties that were tested