Features
Types and Tests - protecting bad values

Type

Although as described in actions, the form of a value is by default locked-in, there is another level of specificty, type.

Leaf remembers the form of the first value of the leaf as one of

  • FORM_MAP
  • FORM_OBJECT
  • FORM_ARRAY
  • FORM_LEAF
  • FORM_FUNCTION
  • FORM_VALUE (if none of the above is true).

This ia fairly coarse test; you can replace a number with a string, say, or null, and Leaf will allow it.

If the type option is set to true, Leaf restricts updates of scalar values to a more specific value type -- or one of the above forms of a compound value):

  • TYPE_STRING
  • TYPE_NUMBER
  • TYPE_DATE

You can also set the type explicitly using these constants, or an array of constants [TYPE_DATE, TYPE_NUMBER] to accept any of several types/forms.

 
      const num = new Forest(100, { type: true });
      const numUntyped = new Forest(100);
      const numAny = new Forest(100, { any: true });
 
      num.subscribe(value => console.log('num value: ', value));
      numUntyped.subscribe(value => console.log('numUntyped value: ', value));
      numAny.subscribe(value => console.log('numAny value: ', value));
 
      num.next(200);
      numUntyped.next(200);
      numAny.next(200);
      try {
        num.next('three hundred');
      } catch (err) {
        console.log('type error:', err);
      }
      numUntyped.next('three hundred');
      numAny.next('three hundred');
      num.next(400);
      numUntyped.next(400);
      numAny.next(200);
 
      try {
        num.next([500]);
      } catch (err) {
        console.log('num form error: ', err);
      }
 
      try {
        numUntyped.next([500]);
      } catch (err) {
        console.log('numUntyped form error: ', err);
      }
      numAny.next([500]);
 
      num.next(600);
      numUntyped.next(600);
      numAny.next(600);
 
      /**
       *
       num value:  100
       numUntyped value:  100
       numAny value:  100
       num value:  200
       numUntyped value:  200
       numAny value:  200
       type error: Error: incorrect type for leaf [ROOT]
       numUntyped value:  three hundred
       numAny value:  three hundred
       num value:  400
       numUntyped value:  400
       num form error:  Error: incorrect type for leaf [ROOT]
       numUntyped form error:  Error: incorrect form for leaf [ROOT]; wanted Symbol(form:value), got Symbol(form:array)
       numAny value:  [ 500 ]
       num value:  600
       numUntyped value:  600
       numAny value:  600
       */
 

If you want to restrict the type of a leaf's properties, define a branch for that property with type = true in the options (or one of the constants defined above for specific expectations)

TYPE_ANY

If you want to disable ALL type checking, pass any = true on configurations; this sets the type to TYPE_ANY and bypasses all form and type checking.

Validation Tests

You can achieve even more granularity with a test. A test is a function that, if it throws, will block the updating of a value.

Tests check values for any number of conditions and any positive return value or thrown error will preempt a change from passing through and initiate a cross-system rollback to the previous state. Either throwing errors or returning values (or errors) will signal a test failure. Returning falsy values (or not explicitly returning anything) will allow the value to pass.

There is no guaranteed order of execution of tests.

Test functions are passed a Change instance that has the following signature:

  target : (your leaf)
  value: (your submitted change)
  next: (a blend of your change and the current value -- or in simpler types, will be identical to value)

Do not attempt to change the value or the target leaf in any way inside a test.

 
const numLeaf = new Forest(0, {
  test({ next }) {
    if (next < 0) throw new Error('cannot be negative');
    if (next % 2) return 'must be even';
  },
});
 
numLeaf.subscribe(value => console.log('leaf value: ', value));
 
numLeaf.next(4);
 
try {
  numLeaf.next(5);
} catch (err) {
  console.log('error:', err);
}
 console.log('leaf is still', numLeaf.value);
 
numLeaf.next(8);
try {
  numLeaf.next(-4);
} catch (err2) {
  console.log('error 2:', err2);
}
console.log('leaf is still', numLeaf.value);
numLeaf.next(10);
 
/**
  leaf value:  0
  leaf value:  4
  error: Error: must be even
  leaf is still 4
  leaf value:  8
  error 2: Error: cannot be negative
  leaf is still 8
  leaf value:  10
*/
 
Last updated on April 8, 2023