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 thelet
keyword and use expressons syntax. Fields within objects cannot be modified with anassert
. For example put cannot be called on amap
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 anassert
,raise
can be used to log one of manyerror
orwarning
. 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
.
- Accuracy
- Completeness
- Conformity
- Consistency
- Coverage
- Integrity
- Provenance
- Timeliness
- Uniqueness
- Validity
- 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")
}