-
Notifications
You must be signed in to change notification settings - Fork 18
Authoring Rules
OAT operates by checking if Rules apply to targeted objects. This page documents the format for specifying those rules.
OAT has a Rules Authoring PWA available to use in your browser: https://microsoft.github.io/OAT/.
A Rule
contains
-
Name
string -
Severity
int -
Description
string (optional) -
Clauses
List<Clause> (optional) -
Expression
string (optional) the boolean expression to calculate over the clauses -
Target
string - Name of the object to apply to (optional)
A Rule
matches an Object when:
- The Object's type maps to the specified
Target
ORTarget
is null - AND any of
- The rule has no
Clauses
- The rule has no
Expression
AND all itsClauses
are true - The evaluation of the
Expression
is true
- The rule has no
A Clause
contains:
- An
Operation
to be applied. The result of the Clause is the result of the Operation and its data. See Operations. - A
Field
that the Clause applies to. This can be any property or field, or any sub-property or sub-field. If null the object itself is used. - A
Label
(recommended if using anExpression
in the associatedRule
). The Label may contain characters other than whitespace and parentheses. -
Data
aList<string>
, as appropriate depending on theOperation
. -
DictData
aDictionary<string,string>
, as appropriate depending on theOperation
. -
Invert
, an optional boolean indicating if the result of the Clause should be inverted -
Capture
, an optional boolean indicating if the result of the Clause should be captured
Clauses for Custom Operations also contain
- A
CustomOperation
specifier- Required if using a Delegate
- Optional if using a Script
- They may include a Script to execute
- Must be null if using a Delegate
The Expression
in a rule is a user specified boolean expression across the defined Clauses
' Labels in the Rule
. Expressions are evaluated left to right, parenthesis are respected. Clause evaluation may be shortcutted when logically appropriate except that marking Capture = true
will always ensure a clause runs.
These are some examples of Valid Expressions:
FOO
FOO AND BAR
FOO OR BAR NAND NOT BAZ OR (BAT123 XOR $B@A%N)
0 AND 1
0 AND FOO NOR 2
These are some examples of Invalid Expressions
FOO BAR OR // Each clause must be separated by an operator
((FOO OR BAR ) // Parenthesis must be balanced
F(OO OR BA)R // Clause Labels may not contain parenthesis
F OO OR B AR // Clause Labels may not contain spaces
FOO O R BAR // Operations may not contain spaces
( FOO OR BAR ) // Parenthesis may not be separated from the label by a space
Expressions can be created using any of the standard boolean operators in addition to parenthesis.
OR
AND
NOR
NAND
XOR
NOT
To see if your rules are valid you can check if they have any violations. Each violation includes a message explaining the issue and where it is occurring.
var analyzer = new Analyzer();
var violations = analyzer.EnumerateRuleIssues(rules)
This is an example of a simple rule from the toll system demo in the walkthrough.
This rule checks if a Vehicle object's VehicleType field is Equals to Car
.
new VehicleRule("Normal Car"){
Cost = 3,
Severity = 1,
Target = "Vehicle",
Clauses = new List<Clause>()
{
new Clause(Operation.Equals, "VehicleType")
{
Data = new List<string>()
{
"Car"
}
}
}
},
This is an example of a more complicated rule which uses an AND
based Expression
in order to match Cars that have more than two passengers.
new VehicleRule("Carpool Car"){
Cost = 2,
Severity = 2,
Target = "Vehicle",
Expression = "IsCar AND OccupantsGT2",
Clauses = new List<Clause>()
{
new Clause(Operation.Equals, "VehicleType")
{
Label = "IsCar",
Data = new List<string>()
{
"Car"
}
},
new Clause(Operation.GreaterThan, "Occupants")
{
Label = "OccupantsGT2",
Data = new List<string>()
{
"2"
}
},
}
},
In addition to labeling a clause you can use the index of the clause you want to reference.
var implicitClauseLabels = new Rule(RuleName)
{
Target = "TestObject",
Expression = "0 OR 1",
Clauses = new List<Clause>()
{
new Clause(Operation.Equals, "StringField")
{
Data = new List<string>()
{
"MagicWord"
}
},
new Clause(Operation.IsTrue, "BoolField")
}
};
You can also mix implicit and explicit labels
var mixedClauseLabels = new Rule(RuleName)
{
Target = "TestObject",
Expression = "0 OR Label",
Clauses = new List<Clause>()
{
new Clause(Operation.Equals, "StringField")
{
Data = new List<string>()
{
"MagicWord"
}
},
new Clause(Operation.IsTrue, "BoolField")
{
Label = "Label"
}
}
};
You can implement custom behavior to be triggered when a clause is encountered.
This rule references a defined Delegate to check if a Truck is overweight.
This example is from the Walkthrough.
new VehicleRule("Overweight Truck")
{
Cost = 50,
Severity = 9,
Expression = "Overweight AND IsTruck",
Target = "Vehicle",
Clauses = new List<Clause>()
{
new Clause(Operation.Custom)
{
Label = "Overweight",
CustomOperation = "OverweightOperation"
},
new Clause(Operation.Equals, "VehicleType")
{
Label = "IsTruck",
Data = new List<string>()
{
"Truck"
}
}
}
},
var OverweightOperation = new OatOperation(Operation.Custom, analyzer)
{
CustomOperation = "OverweightOperation",
OperationDelegate = OverweightOperationDelegate,
ValidationDelegate = OverweightOperationValidationDelegate
};
var analyzer = new Analyzer();
analyzer.SetOperation(OverweightOperation);
You can define the behavior for a Clause directly in the rule with a code snippet called a Script.
This Script returns true if the object passed is a boolean and is true.
var lambda = @"return new OperationResult(State1 is true, null);";
var rule = new Rule("Lambda Rule")
{
Clauses = new List<Clause>()
{
new Clause(Operation.Custom)
{
Script = new ScriptData(lambda)
}
}
};