Skip to content

selections

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

Selections

Basic Selections

In JSMF Check, Selection are used both to declare the values that must be checked by a rule and how these values will be passed to the chek-function. There are basically three constructors that can be use to create selection: all, any and raw. Each of this function takes an argument, which can be either a function, a Reference` or an arbitrary value.

Selection resolution

Depending of the type of the argument, it will be held differently by jsmf-check:

  • Functions, will be resolved when the rule is runned, passing the value used to run the rule as an argument to the function.
  • ContextualReferences, will be resolved against previous selections. See Contextual References for details.
  • References, are resolved against a key value map, this map is either given as an attribute of the run function, or as a map of helpers in the checkers.
  • Arbitrary values are passed as-is to the selection.

Selection type

The selection type are: all (or forall), any (or exists) or raw. Their semantic is repesctively inspired by the mathematical operators , and =.

  • all (or forall): once resolved, the argument used to create this rule will be treated as an enumerable. The check function must held for each element of the selection.
  • any (or exists): once resolved, the argument used to create this rule will be treated as an enumerable. The check function must held for at least one element of the selection.
  • any (or exists): once resolved, the argument used to create this rule will passed as-is to the check function.

Examples

The easiest way to understand how selections works is probably to see examples of their usage in rules.

One selection rules

// Check that at least one element of the object passed to the rule is even
const oneEven = new check.Rule([check.any(x => x)], x => x / 2 === 0)
console.log(oneEven.run([3,5,7,8])) // ~> ∃x∈[3,5,7,8]. x / 2 = 0
// > RuleResult {errors: [], succeed: true}
/* Check that at all the elements of the field foo of the object passed
  to the rule are even numbers */
const allFooEven = new check.Rule([check.any(x => x.foo)], x => x / 2 === 0)
console.log(allFooEven.run({foo: [3,8]})) // ~> ∀x∈[3,8]. x / 2 = 0
// > RuleResult {errors: [[3]], succeed: false}
// The "bar" reference have a legnth of 4
const barLengthIs4 = new check.Rule([new check.Reference('bar')], x => x.length == 4)
// note, that run is called with 2 parameters, the second one is used to solve references
console.log(barLengthIs4.run(undefined, {bar: [1,2,3,4]})) // ~> x=[1,2,3,4]. x.length = 4
// > RuleResult {errors: [], succeed: true}

Several selection rules

// for all elements in foo, there is at least one element in bar with the same value
const barHasElementsOfFoo = new check.Rule(
    [ check.all(x => x.foo)
    , check.any(x => x.bar)
    ], (x,y) => x == y)
console.log(barHasElementsOfFoo.run({foo: {a: 1, b: 2}, bar: [1,3,4,5,2]})
// > RuleResult {errors: [], succeed: true}
// there is at least one element in bar with the same value than all the elements of foo
const oneBarToRuleThemAll = new check.Rule(
    [ check.any(x => x.bar)
    , check.all(x => x.foo)
    ], (x,y) => x == y)
console.log(oneBarToRuleThemAll.run({foo: {a: 1, b: 1}, bar: [1,3,4,5,2]})
// > RuleResult {errors: [], succeed: true}

Advanced Selections

Contextual References

In a standard selection, selection functions are applied to the value passed as an argument to the rule when the rule is triggered. In many cases, it can be useful to build selections based no elements of previous references. This is when contextual references comes into play.

ContextualReferences are build using a function (WARNING: It must be a regular function, it can't be an arrow function). At the selection time, this function will be evaluated and this will be replaced by an array of the current selections.

Let's see an example. Given an array of trees of integer, we want to check that each child of a node has a lower value than its parent's.

Here is an example of data input:

let sample =
  [ {value: 10, children: [{value:3},{value: 6, children: [{value: 4}]}]}
  , {value: 10, children: [{value:8, children: [{value: 12, children: [{value: 4}]}]}]}
  ]

and here is the checking rule:

const allChildrenAreLower = new check.Rule(
  [ check.all(x => x)
  , check.all(new check.ContextualReference(function() {return allNodes(this[0])}))
  , check.all(new check.ContextualReference(function() {return this[1].children || []}))
  ],
  (x,y,z) => y.value > z.value)

function allNodes(x) {
  const nodes = []
  let toVisit = [x]
  while (toVisit.length > 0) {
    const current = toVisit.shift()
    nodes.push(current)
    toVisit = toVisit.concat(current.children || [])
  }
  return nodes
}