assert

assert is one or many optional nested definitions within UDTs defining fields that allow object level validations to be expressed.

Assert is used for object level validation. The rule is applied on a single object.

  • The validation is to be applied on ‘complete’ objects, i.e., not partially constructed. Language implementations may apply assert as a object post-construction phase to validate that the object adheres to a set of validation rules.
  • entity, key, record, trait and union support nested assert definitions for object validation.
  • An assert block can define local variable using the let keyword and use expressons syntax. Fields within objects cannot be modified with an assert. For example put cannot be called on a map data-typed field, although locally declared variables can be modified.
  • An assert has an associated name. Multiple uniquely named asserts can be associated with a user-defined type. Asserts defined in traits take part in UDTs that includes the trait.
// Outer type definition
...
   assert <Unique assertion identifier> {
       < Expressions >
   }
...
  • An assert does not return a value. Within an assert, raise can be used to log one of many error or warning. E.g. ..

    if ( Age < 18 )
       raise error("Not an adult")
    
  • ALFA assert and expression syntax has been designed from ground-up using concepts such as higher-order functions. The expressions can be generated into Java, C#, Python, Scala, TypeScript etc., therefore validation can be executed in optimised native code.

  • An ALFA service, can also be used as part of assert expressions. Therefore using a service, it is possible to call out to external code as part of object validation.

  • Errors or warning raised from assert can reference field values using $field syntax in order to report meaningful errors. For example "Student $name aged $age cannot be in $schoolType school".

raise syntax

In addition to the message, raise can specify the Data Quality Dimension that should be given to the raised message/event. E.g.

if ( Age < 18 )
   raise error(Confirmity, "Not an adult")

If a dimension is not specified, by default raise will use Unclassified as the dimension.

List of Data Quality Dimensions

The following dimensions can be specified to raise.

  1. Accuracy
  2. Completeness
  3. Conformity
  4. Consistency
  5. Coverage
  6. Integrity
  7. Provenance
  8. Timeliness
  9. Uniqueness
  10. Validity
  11. Unclassified

Simple assert Example

In the example below, object level validation is required to validate contents of 2 fields. The CorrectSchool assert block contains expressions to that will determine if the schoolType is assigned correctly.

enum SchoolType {
    Primary Junior Senior
}

record Student {
    name : string
    dob : date
    schoolType : SchoolType

    assert SchoolingAge {
        let age : int = year( dateDiff( today(), dob ) )

        if ( age > 18 )
            raise warning( Accuracy, "Student ${name} aged ${age} is an adult" )
    }

    assert CorrectSchool {
        let age : int = year( dateDiff( today(), dob ) )

        let errored = ( age < 7 && schoolType != SchoolType.Primary ) ||
                      ( age >= 7 && and age < 12 && schoolType != SchoolType.Junior ) ||
                      ( age >= 12 && schoolType != SchoolType.Senior ) ||

        if ( errored )
            raise error( Accuracy, "Student aged ${age} cannot be in ${schoolType} school" )
    }
}

Decoupling Model Definition and Asserts

Using fragment definitions, existing types can be extended with assert statements. The above example can be decoupled into 2 declarations as shown below.

Only fields are defined for the Teacher type.

record Teacher {
    name : string
    dob : date
    schoolType : SchoolType
}

Student defined as a fragment with all assert definitions.

fragment record Teacher {
    assert QualifiedAge {
        let age : int = year( dateDiff( today(), dob ) )

        if ( age < 18 )
            raise warning( "Teacher ${name} aged ${age} is not an adult" )
    }
}

Generated code for assert

The assert expressions are fully generated into underlying code, where supported in ALFA. This is not applicable for some targets such as Avro or Protocol Buffers as those do not support such a feature.

Given the small example below:

record Passenger {
  Name : string
  LuggageWeights : list< int >

  assert LuggageWeightLimit {
     let total = reduce( LuggageWeights, 0, ( acc, w ) => acc + w )
     if ( total > 100 )
         raise warning("Passenger ${Name} has excess luggage")
  }
}

The Java generates the following function corresponding to the assert definition. The function generated is called to validate the object:

private void __assertLuggageWeightLimit() {
  int _total =
      _luggageWeights
          .stream()
          .reduce(
              0,
              (_acc, _w) -> {
                return _acc + _w;
              },
              (c1, c2) -> {
                throw new UnsupportedOperationException();
              });
  if ( _total > 100 )
      builderConfig().assertListener().addAlert("Passenger " + _name + " has excess luggage")
}