-
Notifications
You must be signed in to change notification settings - Fork 1
Home
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.
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.
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.
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
.
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.
We often want to run different rules on the same data. Checker
s
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.
For a better understanding of jsmf-check, you can have a look at the Selections section, which details how selections work.