Skip to content
Nicolas B edited this page Sep 9, 2016 · 1 revision

JSMF-Check

JSMF is a library that can be used to check that given data validate some rules. Its goal is to provide an error report as precise as possible when some rules fail.

It was initially developped to validate rules of JSMF models but can be used in any other context.

It was developped with the underlying idea of providing an esay way to specify forall and exists properties for data.

Rules

Rule is the core element. A rule is composed of two parts: selections and a checking rule. The selections define which elements of the incoming data will be tested, and how they will be tested, by the checking rule.

Let's start with a simple example. Let's write a rule to ensure that in an array of numbers, for each even number in the array, it's successor is also in the array.

Rule example

const evenHasSuccessor = new check.Rule(
  [ check.all(xs => _.filter(xs, x => x % 2 === 0))
  , check.any(xs => _.filter(xs, x => x % 2 !== 0))
  ]
  , (e, o) => e + 1 === o
)

We declare a rule with two selections:

  • the first one check.all(x => _.filter(xs, x => x % 2 === 0)) express that we want to check a property for all even numbers;
  • the second one check.any(x => _.filter(xs, x => x % 2 !== 0)) express that for each element of the previous selection, jsmf-check will check if it exists an odd number that validates the checking rule.

Then we declare the checking rule itself: (e, o) => e + 1 === o: the element of the first selection plus one is equal to the element of the second selection.

Run and succeed

It might be a bit obscure at the moment, things will be clearer when we run the example:

console.log(evenHasSuccessor.run([2,5,4,3])
// > RuleResult {errors: [], succeed: true}

Let's run the rule execution by hand. We start by the selection. The first selection is evaluated to [2,4] and the second one to [5,3].

As the first selection is declared with check.all the cehck function must be true for all the elements of the first selection. The second selection, declared with any will be tested for each element of the first selection until we find one that match the predicate. Thus, in this case, the check function will be evaluated consecutively with:

(e = 2, o = 5) ~> e + 1 === o is false
(e = 2, o = 3) ~> e + 1 === o is true
(e = 4, o = 5) ~> e + 1 === o is true

As the evaluation with (e = 4, o = 5) suceed, there won't be a call with (e = 4, o = 3), since we already find an element of the second selection that fullfill the checking rule for e = 4.

Run and error

The previous execution was a success, but how does the rule execution behave when the check fails? The objective of check is to provide a detailed report of the problems encountered when a rule is not validated. Let's run the evenHasSuccessor rule on invalid data:

console.log(evenHasSuccessor.run([2,3,4,1,6])
// > RuleResult {errors: [[4, [3,1]], [6, [3,1]]], succeed: false}

Here, neither 4 nor 6 have their successor in the array. Thus, the RuleResult will log the data that invalidates the rules in its errors fields. errors is an array that contains all the data combination that did not satisfies the rules. Here, we see that we weren't able to satisfy the rule for the element 4 of the first selection, as none of the values [3,1] are its successor and, for the same reason, the rule could not be validated for 6.

Checkers

We often want to run different rules on the same data. Checkers provide facilities to define a set of rules and run simultaneously on given data.

Aside the rule defined in the previous section, let's check if all the even elements are positive.

const myChecker = new check.Checker()
myChecker.rules.evenHasSuccessor = evenHasSuccessor
myChecker.rules.evenArePositive = new check.Rule(
  [ check.all(xs => _.filter(xs, x => x % 2 === 0))]
  , x => x > 0
)

As for a single rule, running the checker on valid data is boring:

console.log(myChecker.run([2,5,4,3])
// > RuleResult {errors: [], succeed: true}

But let's run it on data that invalidates several rules and look at the output:

console.log(myChecker.run([-2,3,4,6]))
/* RuleResult {
      errors: 
       [ { name: 'evenHasSuccessor', path: [-2, [3]] },
         { name: 'evenHasSuccessor', path: [4, [3]] },
         { name: 'evenHasSuccessor', path: [6, [3]] },
         { name: 'evenArePositive', path: [-2] } ],
      succeed: false }
*/

The RuleResult object is a bit different than when we run a single Rule. For each error, we have the path of the error as before, but we also provide the name of the rule that is not fullfilled.

Next steps

For a better understanding of jsmf-check, you can have a look at the Selections section, which details how selections work.

Clone this wiki locally