Skip to content

Authoring Rules

Gabe Stocco edited this page Aug 27, 2020 · 24 revisions

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/.

Rule Basics

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)

Rule matching

A Rule matches an Object when:

  • The Object's type maps to the specified Target OR Target is null
  • AND any of
    1. The rule has no Clauses
    2. The rule has no Expression AND all its Clauses are true
    3. The evaluation of the Expression is true

Clause Basics

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 an Expression in the associated Rule). The Label may contain characters other than whitespace and parentheses.
  • Data a List<string>, as appropriate depending on the Operation.
  • DictData a Dictionary<string,string>, as appropriate depending on the Operation.
  • 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
    1. Required if using a Delegate
    2. Optional if using a Script
  • They may include a Script to execute
    • Must be null if using a Delegate

Expressions

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

Verifying Your Rules

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)

Rule Examples

Simple Rule

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"
            }
        }
    }
},

Rule With Expression

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"
            }
        },
    }
},

Expression with Implicit Labels

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"
        }
    }
};

Custom Operations

Defined with a Delegate

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);

Defined with a Script

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)
        }
    }
};