A simplistic validation language for validating complex objects.
Built with Antlr and Golang.
Visit the blog https://talal.zone/static/mooncake/
Validation normally consists of a sequence of rules or statements that verify different parts of object(s). For now objects in json format are handled.
A simple validation statement has the following syntax:
item.name eq nil => ['E0123', 'item name is null']!!! # if item.name is empty then fatal error
Breaking it down we have:
- Identifier:
item.name
- Operator:
eq
- Literal:
nil
- Implication:
=>
- Error:
['E406', 'item name is null']!!
containing error code, info and severity - Comment:
# if item.name is empty then severe error
A group of statements can depend on each or could be independent:
~item.name eq nil => ['E001', 'info-1']!!!
~item.value eq nil => ['E002', 'info-2']!!
item.info eq nil => ['E003', 'info-3']!
Here, ~
implies an inorder dependent relation i.e., only if the item.name
expression doesn't hold
then the following statement item.value
is evaluated.
item.info
statement is independent and will be evaluated in any case.
A statement can also have a body block which is evaluated only if the expression doesn't hold:
item.name eq nil => ['E001', 'info-1']!!!
{
item.value eq nil => ['E002', 'info-2']!!
{
item.value.first eq nil => ['E003', 'info-3']!
{
item.value.second eq nil => ['E004', 'info-4']!
}
}
}
This is similar to using ~
to have dependent relations:
~item.name eq nil => ['E001', 'info-1']!!!
~item.value eq nil => ['E002', 'info-2']!!
~item.value.first eq nil => ['E003', 'info-3']!
~item.value.second eq nil => ['E004', 'info-4']!
Blocks are useful when using functions and inline declarations:
_x @len item.list eq 0 => ['E005', 'info-5']!!
{
~ _x eq 10 => ['E006', 'info-6']!! # some special case
~ _x eq 20 => ['E007', 'info-7']!! # another special case
}
If there is a need to reference structures then it could be done as:
item.name eq ${ctx.item1} => ['E005', 'info-5']!!
Here, ${...}
reflectively gets the passed struct
and check it against the expression.
For evaluation the validation rules are passed to the mooncake executor
along with an optional context struct
:
// read rules
content, err := ioutil.ReadFile("rules.mck")
rules := string(content)
// to validate
json := "sample.json"
// sample context struct
ctx := struct {
item1 string
item2 string
item3 int
}{
"comingFromContextA",
"comingFromContextB",
100000,
}
// pass to executor
result := executor.Execute(rules, json, ctx)
Currently, the output of validation rules is a structure comprising of all encountered errors with respect to severity. Example:
{
"Fatal":[
{
"Code":"E406",
"Info":"sample is empty"
}
],
"Severe":[
],
"Warning":[
]
}
Type | Specification |
---|---|
Boolean | true or false |
Integer | signed or unsigned integer values e.g., 12, 0, -50, |
Float | signed or unsiged floating point values e.g., 49.10, -23.21 |
Null | nil or null |
String | single quoted characters e.g., 'sample' , 'ExampleString' |
Operator | Description |
---|---|
eq or == |
left is equal to right |
ne or != |
left is not equal to right |
lt or < |
left is less than right |
lte or <= |
left is less or equal to right |
gt or > |
left is greater than right |
gte or >= |
left is greater than or equal to right |
in |
left exists in right. incomplete |
nin |
left does not exists in right. incomplete |
Functions are applied on the given object identifier and are later used in the expression.
Function | Description | Output |
---|---|---|
@len | Gets the length of an array | Integer |
@dateTimeLong | Converts a timestamp to long | Long |
@afterCurrentTime | Checks if the timestamp is after the given timestamp | Boolean |
@min | TODO | |
@max | TODO | |
@avg | TODO |
Notation | Severity |
---|---|
!!! |
Fatal |
!! |
Severe |
! |
Warning |
Inline comments are represented with #
and can come after a rule statement:
~item.name eq nil => ['E001', 'info-1']!! # this is a comment
Commented out statement looks as follows:
# ~item.name eq nil => ['E001', 'info-1']!! # this rule is commented out
- Ability to pass custom reference functions in implication
- Ability to have further expression in literal's place
- Ability to configure Error Structure
- Ability to validate Golang structures
- Ability to handle more data types
This project is licensed under the MIT License