Minimizing Validation Noise with Firefly Semantics Validator | Task

Ole Ersoy
Nov - 13  -  2 min

Scenario

The Firefly Semantics Validator package provides decorators that are used to validate your typescript model class instances.

Features that minimize validation noise are:

  • Progressive Validation
  • Cross Property Filtered Validation

We will illustrate how these are applied with a decorated PurchaseOrder class:

import {
  IfValid,
  IsAfterInstant,
  IsDate,
  IsDefined,
  IsString,
  IsInt,
} from '@fireflysemantics/validator';

export class PurchaseOrder {
  @IsString()
  @IsDefined()
  id: string;
  @IsString()
  @IsDefined()
  sku: string;

  @IsDate()
  @IsDefined()
  purchaseDate: Date;

  @IsAfterInstant('purchaseDate')
  @IsDate()
  @IsDefined()
  @IfValid('purchaseDate')
  receiptDate: Date;

  @IsInt()
  @IsDefined()
  quantity: number;
  constructor(o: any) {
    //===================================
    // Initialize the sku and id properties
    // with Object.assign
    //===================================
    Object.assign(this, o);
  }
}

We want to learn how to use these features.

Approach

Progressive Validation

If the @IsDefined decorator validation on the sku property fails, the rest of the decorators on the sku property will not fire.

That is what progressive validation is.

It minimizes validation noise. Here’s an example:

let valid: boolean = validateProperty(po, 'sku');
assert.equal(valid, true, 'valid should be true');
po.sku = null;
valid = validateProperty(po, 'sku');
assert.equal(valid, false, 'valid should be false');
let oe: ObjectErrors = validate(po);
let errors: ValidationError[] = oe.errors;
assert.equal(errors.length, 1, 'There should only be one error');

Cross Property Filtered Validation

The @IsAfterInstant(‘purchaseDate’) decorator checks that the purchaseDate comes before the receiptDate.

However we only want to perform the check if the purchaseDate property is valid. That is what the @IfValid(‘purchaseDate’) does. If the purchaseDate property is invalid, then receiptDate validation will not proceed, as only the @IfValid annotation will fire.

po.sku = 'sku';
po.purchaseDate = null;
valid = validateProperty(po, 'purchaseDate');
assert.equal(valid, false, 'valid should be false');
oe = validate(po);
errors = oe.errors;
assert.equal(errors.length, 2, 'There should only be two errors');

So in the above case only two ValidationError instances are created. One for the @IsDefined decorator on the purchaseDate property and one for the @IfValid decorator on the receiptDate property.

Demo