Skip to content

Operations

Gabe Stocco edited this page Sep 9, 2020 · 18 revisions

Operations are defined in the Operation enum.

All Operations by default have access to some data:

  • state1 The first object state provided.
  • state2 The second object state provided.

In addition some operations call ObjectToValues which attempts to convert the targeted field of the object into string representations. When those operations have done so they will reference the values extracted:

  • valsToCheck a List<string>
  • dictToCheck a List<KeyValuePair<string,string>>

In addition, many operations take argument data either in the form of:

  • A List<string> called Data
  • A List<KeyValuePair<string,string>> called DictData.

Finally, the Regex Operation and Custom operations may take arguments

  • A List<string> called Arguments

Regex

Performs regular expression matching using the provided Data.

Takes

  • Data - List<string> of Regular Expressions to match
  • Arguments - List<string> of regex options.

Arguments

  • i sets RegexOptions.IgnoreCase
  • m sets RegexOptions.MultiLine

Returns

true when any of the Regexes in Data match any of the valsToCheck values extracted from the Field

Captures

A TypedClauseCapture<Match> containing the Match resulting from the regex call.

Example

From https://github.com/microsoft/OAT/blob/4b35986ca31058694035a4f77694070da3091957/OAT.Tests/OperationsTests.cs#L871-L895

[TestMethod]
public void VerifyRegexOperator()
{
    var falseRegexObject = "TestPathHere";
    var trueRegexObject = "Directory/File";

    var regexRule = new Rule("Regex Rule")
    {
        Clauses = new List<Clause>()
        {
            new Clause(OPERATION.REGEX)
            {
                Data = new List<string>()
                {
                    ".+\\/.+"
                }
            }
        }
    };

    var regexAnalyzer = new Analyzer();
    var ruleList = new List<Rule>() { regexRule }; ;

    Assert.IsTrue(regexAnalyzer.Analyze(ruleList, trueRegexObject).Any());
    Assert.IsFalse(regexAnalyzer.Analyze(ruleList, falseRegexObject).Any());
}

Eq

Simple equals operation.

Takes

Data - List<string> to check equality

Returns

true when any of the strings in Data are in valsToCheck

Captures

  • TypedClauseCapture<string> when the captured target is a single string
  • TypedClauseCapture<List<string>> when the captured target is a list of strings

Example

https://github.com/microsoft/OAT/blob/4b35986ca31058694035a4f77694070da3091957/OAT.Tests/OperationsTests.cs#L455-L484

public void VerifyEqOperator()
{
    var assertTrueObject = new TestObject()
    {
        StringField = "Magic",
        BoolField = true,
        IntField = 700
    };

    var assertFalseObject = new TestObject()
    {
        StringField = "NotMagic",
        BoolField = false,
        IntField = 701
    };

    var stringEquals = new Rule("String Equals Rule")
    {
        Target = "TestObject",
        Clauses = new List<Clause>()
        {
            new Clause(OPERATION.EQ, "StringField")
            {
                Data = new List<string>()
                {
                    "Magic"
                }
            }
        }
    };

    var analyzer = new Analyzer();
    var ruleList = new List<Rule>() { stringEquals };

    var trueObjectResults = analyzer.Analyze(ruleList, assertTrueObject);
    var falseObjectResults = analyzer.Analyze(ruleList, assertFalseObject);

    Assert.IsTrue(trueObjectResults.Any(x => x.Name == "String Equals Rule"));

    Assert.IsFalse(falseObjectResults.Any(x => x.Name == "String Equals Rule"));
}

Neq

Simple Not equals operation.

Takes

Data - List<string> to check equality

Returns

true when none of the strings in Data are in valsToCheck

Captures

  • TypedClauseCapture<string> when the captured target is a single string
  • TypedClauseCapture<List<string>> when the captured target is a list of strings

Example

From https://github.com/microsoft/OAT/blob/4b35986ca31058694035a4f77694070da3091957/OAT.Tests/OperationsTests.cs#L532-L561

public void VerifyNeqOperator()
{
    var assertFalseObject = new TestObject()
    {
        StringField = "Magic",
        BoolField = true,
        IntField = 700
    };

    var assertTrueObject = new TestObject()
    {
        StringField = "NotMagic",
        BoolField = false,
        IntField = 701
    };

    var intNotEquals = new Rule("Int Not Equals Rule")
    {
        Target = "TestObject",
        Clauses = new List<Clause>()
        {
            new Clause(OPERATION.NEQ, "IntField")
            {
                Data = new List<string>()
                {
                    "700"
                }
            }
        }
    };

    var analyzer = new Analyzer();
    var ruleList = new List<Rule>() { intNotEquals};

    var trueObjectResults = analyzer.Analyze(ruleList, assertTrueObject);
    var falseObjectResults = analyzer.Analyze(ruleList, assertFalseObject);

    Assert.IsTrue(trueObjectResults.Any(x => x == intNotEquals));

    Assert.IsFalse(falseObjectResults.Any(x => x == intNotEquals));
}

Contains

Checks if the ALL of the provided values are in the extracted values.

Takes

  • DictData - A List<KVP<string,string>>
  • Data - A List<string>

Returns

These rules are processed in this order, once a rule has matched its first clause here, it won't attempt to match the numbered rules. Each rule is true if:

  1. dictToCheck is not empty, and all of the KVP in DictData have a Key and Value match in dictToCheck
  2. The Target is a List<string>, and all of the strings in Data are contained in valsToCheck
  3. The Target is a string,and all of the strings in Data are contained in the string
  4. The Target is an Enum with the Flags attribute and all of the Flags specified in Data are set in the Enum

Captures

  • TypedClauseCapture<string> when the captured target is a single string
  • TypedClauseCapture<List<string>> when the captured target is a list of strings
  • TypedClauseCapture<List<KeyValuePair<string,string>>> when the captured target is key value pairs
  • TypedClauseCapture<Enum> when the captured target is an enum

Example

public void VerifyContainsOperator()
{
    var trueStringObject = new TestObject()
    {
        StringField = "ThisStringContainsMagic"
    };

    var falseStringObject = new TestObject()
    {
        StringField = "ThisStringDoesNot"
    };

    var stringContains = new Rule("String Contains Rule")
    {
        Target = "TestObject",
        Clauses = new List<Clause>()
        {
            new Clause(OPERATION.CONTAINS, "StringField")
            {
                Data = new List<string>()
                {
                    "Magic",
                    "String"
                }
            }
        }
    };

    var stringAnalyzer = new Analyzer();
    var ruleList = new List<Rule>() { stringContains }; ;

    Assert.IsTrue(stringAnalyzer.Analyze(ruleList, trueStringObject).Any());
    Assert.IsFalse(stringAnalyzer.Analyze(ruleList, falseStringObject).Any());
}

ContainsAny

Checks if the ANY of the provided values are in the extracted values.

Takes

  • DictData - A List<KVP<string,string>>
  • Data - A List<string>

Returns

These rules are processed in this order, once a rule has matched its first clause here, it won't attempt to match the numbered rules. Each rule is true if:

  1. dictToCheck is not empty, and any of the KVP in DictData have a Key and Value match in dictToCheck
  2. The Target is a List<string>, and any of the strings in Data are contained in valsToCheck
  3. The Target is a string,and any of the strings in Data are contained in the string
  4. The Target is an Enum with the Flags attribute and any of the Flags specified in Data are set in the Enum

Captures

  • TypedClauseCapture<string> when the captured target is a single string
  • TypedClauseCapture<List<string>> when the captured target is a list of strings
  • TypedClauseCapture<List<KeyValuePair<string,string>>> when the captured target is key value pairs
  • TypedClauseCapture<Enum> when the captured target is an enum

Example

public void VerifyContainsAnyOperator()
{
    var enumFlagsContains = new Rule("Enum Flags Contains Rule")
    {
        Clauses = new List<Clause>()
        {
            new Clause(OPERATION.CONTAINS_ANY)
            {
                Data = new List<string>(){"Magic", "Normal"}
            }
        }
    };

    var enumAnalyzer = new Analyzer();
    ruleList = new List<Rule>() { enumFlagsContains };

    Assert.IsTrue(enumAnalyzer.Analyze(ruleList, Words.Magic).Any());
    Assert.IsTrue(enumAnalyzer.Analyze(ruleList, Words.Normal).Any());
    Assert.IsTrue(enumAnalyzer.Analyze(ruleList, Words.Magic | Words.Normal).Any());
    Assert.IsFalse(enumAnalyzer.Analyze(ruleList, Words.Shazam).Any());
}

Gt

Checks if any of the provided ints are Greater Than the extracted values

Takes

Data - A Lists representing ints

Returns

true when any of the Data when parsed as ints is greater than any of the values in valsToCheck parsed as ints.

Captures

TypedClauseCapture<int> the captured int

Example

public void VerifyGtOperator()
{
    var gtRule = new Rule("Gt Rule")
    {
        Clauses = new List<Clause>()
        {
            new Clause(OPERATION.GT)
            {
                Data = new List<string>()
                {
                    "9000"
                }
            }
        }
    };

    var gtAnalyzer = new Analyzer();
    var ruleList = new List<Rule>() { gtRule }; ;

    Assert.IsTrue(gtAnalyzer.Analyze(ruleList, 500).Any());
    Assert.IsFalse(gtAnalyzer.Analyze(ruleList, 9001).Any());
}

Lt

Checks if any of the provided ints are Less Than the extracted values

Takes

Data - A List representing ints

Returns

true when any of the Data when parsed as ints is less than any of the values in valsToCheck parsed as ints.

Captures

TypedClauseCapture<int> the captured int

Example

public void VerifyLtOperator()
{
    var ltRule = new Rule("Lt Rule")
    {
        Clauses = new List<Clause>()
        {
            new Clause(OPERATION.LT)
            {
                Data = new List<string>()
                {
                    "20000"
                }
            }
        }
    };

    var ltAnalyzer = new Analyzer();
    var ruleList = new List<Rule>() { ltRule }; ;

    Assert.IsTrue(ltAnalyzer.Analyze(ruleList, 10).Any());
    Assert.IsFalse(ltAnalyzer.Analyze(ruleList, 30000).Any());
}

WasModified

Checks if the two object states provided differ

Takes

No Arguments

Returns

true if the two object states appear to be different.

Captures

TypedClauseCapture<ComparisonResult> whose Result is a KellermanSoftware.CompareNetObjects.ComparisonResult, the result of the comparison between the two states.

Example

public void VerifyWasModifiedOperator()
{
    var firstObject = new TestObject()
    {
        StringDictField = new Dictionary<string, string>() { { "Magic Word", "Please" }, { "Another Key", "Another Value" } }
    };

    var secondObject = new TestObject()
    {
        StringDictField = new Dictionary<string, string>() { { "Magic Word", "Abra Kadabra" }, { "Another Key", "A Different Value" } }
    };

    var wasModifiedRule = new Rule("Was Modified Rule")
    {
        Clauses = new List<Clause>()
        {
            new Clause(OPERATION.WAS_MODIFIED)
        }
    };

    var analyzer = new Analyzer();
    var ruleList = new List<Rule>() { wasModifiedRule }; ;

    Assert.IsTrue(analyzer.Analyze(ruleList, true, false).Any());
    Assert.IsTrue(analyzer.Analyze(ruleList, "A String", "Another string").Any());
    Assert.IsTrue(analyzer.Analyze(ruleList, 3, 4).Any());
    Assert.IsTrue(analyzer.Analyze(ruleList, new List<string>() { "One", "Two" }, new List<string>() { "Three", "Four" }).Any());
    Assert.IsTrue(analyzer.Analyze(ruleList, firstObject, secondObject).Any());


    Assert.IsFalse(analyzer.Analyze(ruleList, true, true).Any());
    Assert.IsFalse(analyzer.Analyze(ruleList, "A String", "A String").Any());
    Assert.IsFalse(analyzer.Analyze(ruleList, 3, 3).Any());
    Assert.IsFalse(analyzer.Analyze(ruleList, new List<string>() { "One", "Two" }, new List<string>() { "One", "Two" }).Any());
    Assert.IsFalse(analyzer.Analyze(ruleList, firstObject, firstObject).Any());
}

EndsWith

Checks if any of the strings extracted end with the provided data.

Takes

Data - A List<string> of potential endings

Returns

true if any of the strings in valsToCheck end with any of the strings in Data

Captures

  • TypedClauseCapture<string> when the captured target is a single string
  • TypedClauseCapture<List<string>> when the captured target is a list of strings

Example

public void VerifyEndsWithOperator()
{
    var trueEndsWithObject = "ThisStringEndsWithMagic";
    var falseEndsWithObject = "ThisStringHasMagicButDoesn't";

    var endsWithRule = new Rule("Ends With Rule")
    {
        Clauses = new List<Clause>()
        {
            new Clause(OPERATION.ENDS_WITH)
            {
                Data = new List<string>()
                {
                    "Magic"
                }
            }
        }
    };

    var endsWithAnalyzer = new Analyzer();
    var ruleList = new List<Rule>() { endsWithRule }; ;

    Assert.IsTrue(endsWithAnalyzer.Analyze(ruleList, trueEndsWithObject).Any());
    Assert.IsFalse(endsWithAnalyzer.Analyze(ruleList, falseEndsWithObject).Any());
}

StartsWith

Checks if any of the strings extracted start with the provided data.

Takes

Data - A List<string> of potential starts

Returns

true if any of the strings in valsToCheck start with any of the strings in Data

Captures

  • TypedClauseCapture<string> when the captured target is a single string
  • TypedClauseCapture<List<string>> when the captured target is a list of strings

Example

public void VerifyStartsWithOperator()
{
    var trueEndsWithObject = "MagicStartsThisStringOff";
    var falseEndsWithObject = "ThisStringHasMagicButLater";

    var startsWithRule = new Rule("Starts With Rule")
    {
        Clauses = new List<Clause>()
        {
            new Clause(OPERATION.STARTS_WITH)
            {
                Data = new List<string>()
                {
                    "Magic"
                }
            }
        }
    };

    var analyzer = new Analyzer();
    var ruleList = new List<Rule>() { startsWithRule }; ;

    Assert.IsTrue(analyzer.Analyze(ruleList, trueEndsWithObject).Any());
    Assert.IsFalse(analyzer.Analyze(ruleList, falseEndsWithObject).Any());
}

IsNull

Checks if both object states are null

Takes

No Arguments

Returns

true if both object states are null

Captures

CaptureClause where the object states represent the object states passed to the Clause for operating.

Example

public void VerifyIsNullOperator()
{
    var isNullRule = new Rule("Is Null Rule")
    {
        Clauses = new List<Clause>()
        {
            new Clause(OPERATION.IS_NULL)
        }
    };

    var isNullAnalyzer = new Analyzer();
    var ruleList = new List<Rule>() { isNullRule }; ;

    Assert.IsTrue(isNullAnalyzer.Analyze(ruleList, null).Any());
    Assert.IsFalse(isNullAnalyzer.Analyze(ruleList, "Not Null").Any());
}

IsTrue

Checks if the object state is a true bool

Takes

No Arguments

Returns

true if either object state is true

Captures

TypedClauseCapture<bool> the target bool

Example

public void VerifyIsTrueOperator()
{
    var isTrueRule = new Rule("Is True Rule")
    {
        Clauses = new List<Clause>()
        {
            new Clause(OPERATION.IS_TRUE)
        }
    };

    var isTrueAnalyzer = new Analyzer();
    var ruleList = new List<Rule>() { isTrueRule }; ;

    Assert.IsTrue(isTrueAnalyzer.Analyze(ruleList, true).Any());
    Assert.IsFalse(isTrueAnalyzer.Analyze(ruleList, false).Any());
}

IsBefore

Checks if the object state is a DateTime and is before any of the times specified in Data

Takes

Data - A List of DateTime.Parse compatible DateTime strings

Returns

true if either object state is a date time and any of the provided DateTimes in Data are after it

Captures

TypedClauseCapture<DateTime> the targeted time

Example

public void VerifyIsBeforeOperator()
{
    var falseIsBeforeObject = DateTime.MaxValue;

    var falseIsAfterObject = DateTime.MinValue;

    var isBeforeRule = new Rule("Is Before Rule")
    {
        Clauses = new List<Clause>()
        {
            new Clause(OPERATION.IS_BEFORE)
            {
                Data = new List<string>()
                {
                    DateTime.Now.ToString()
                }
            }
        }
    };

    var analyzer = new Analyzer();
    var ruleList = new List<Rule>() { isBeforeRule }; ;

    Assert.IsTrue(analyzer.Analyze(ruleList, falseIsAfterObject).Any());
    Assert.IsFalse(analyzer.Analyze(ruleList, falseIsBeforeObject).Any());
}

IsAfter

Checks if the object state is a DateTime and is after any of the times specified in Data

Takes

Data - A List of DateTime.Parse compatible DateTime strings

Returns

  • true if either object state is a date time and any of the provided DateTimes in Data are before it

Captures

TypedClauseCapture<DateTime> the targeted time

Example

public void VerifyIsAfterOperator()
{
    var falseIsAfterObject = DateTime.MinValue;

    var trueIsAfterObject = DateTime.MaxValue;

    var isAfterRule = new Rule("Is After Rule")
    {
        Clauses = new List<Clause>()
        {
            new Clause(OPERATION.IS_AFTER)
            {
                Data = new List<string>()
                {
                    DateTime.Now.ToString()
                }
            }
        }
    };

    var isAfterAnalyzer = new Analyzer();
    var ruleList = new List<Rule>() { isAfterRule }; ;

    Assert.IsTrue(isAfterAnalyzer.Analyze(ruleList, trueIsAfterObject).Any());
    Assert.IsFalse(isAfterAnalyzer.Analyze(ruleList, falseIsAfterObject).Any());
}

IsExpired

Checks if the object state is a DateTime and is after DateTime.Now when executed

Takes

No Arguments

Returns

  • true if either object state is a date time and after DateTime.Now

Captures

TypedClauseCapture<DateTime> the targeted time

Example

public void VerifyIsExpiredOperation()
{
    var falseIsExpiredObject = DateTime.MaxValue;

    var trueIsExpiredObject = DateTime.MinValue;

    var isExpiredRule = new Rule("Is Expired Rule")
    {
        Clauses = new List<Clause>()
        {
            new Clause(OPERATION.IS_EXPIRED)
        }
    };

    var isAfterAnalyzer = new Analyzer();
    var ruleList = new List<Rule>() { isExpiredRule }; ;

    Assert.IsTrue(isAfterAnalyzer.Analyze(ruleList, trueIsExpiredObject).Any());
    Assert.IsFalse(isAfterAnalyzer.Analyze(ruleList, falseIsExpiredObject).Any());
}

ContainsKey

If dictionary values were extracted if any key in that dictionary matches the provided Data

Takes

  • Data - A List of dictionary keys to check

Returns

  • true if any of the Data strings are present in dictToCheck.Keys

Captures

  • TypedClauseCapture<List<string>> of the found keys

Example

public void VerifyContainsKeyOperator()
{
    var trueAlgDict = new TestObject()
    {
        StringDictField = new Dictionary<string, string>()
        {
            { "Magic", "Anything" }
        }
    };

    var falseAlgDict = new TestObject()
    {
        StringDictField = new Dictionary<string, string>()
        {
            { "No Magic", "Anything" }
        }
    };

    var algDictContains = new Rule("Alg Dict Changed PCR 1")
    {
        Target = "TestObject",
        Clauses = new List<Clause>()
        {
            new Clause(OPERATION.CONTAINS_KEY, "StringDictField")
            {
                Data = new List<string>()
                {
                    "Magic"
                }
            }
        }
    };

    var algDictAnalyzer = new Analyzer();
    var ruleList = new List<Rule>() { algDictContains }; ;

    Assert.IsTrue(algDictAnalyzer.Analyze(ruleList, trueAlgDict).Any());
    Assert.IsFalse(algDictAnalyzer.Analyze(ruleList, falseAlgDict).Any());
}

Custom

Indicates a custom operation

Takes

  • Data - A List
  • DictData - A List<KeyValuePair<string,string>>
  • CustomOperation - The Label matching a Custom Operation Delegate you've set

Returns

true if

  1. A custom operation delegate with a matching CustomOperation label is found. a. That delegate returns true.

Captures

  • May return a capture as anything that inherits from ClauseCapture.

Delegate Example

You can create a Delegate for custom operations. From the Walkthrough we have the overweight operation, which looks like this:

public OperationResult OverweightOperationDelegate(Clause clause, object? state1, object? state2, IEnumerable<ClauseCapture>? captures)
{
    if (state1 is Vehicle vehicle)
    {
        var res = vehicle.Weight > vehicle.Capacity;
        if ((res && !clause.Invert) || (clause.Invert && !res))
        {
            // The rule applies and is true and the capture is available if capture is enabled
            return new OperationResult(true, clause.Capture ? new TypedClauseCapture<int>(clause, vehicle.Weight, state1, state2) : null);
        }
    }
    return new OperationResult(false, null);
}

The Overweight rule also has a validation delegate

public IEnumerable<Violation> OverweightOperationValidationDelegate(Rule r, Clause c)
{
    var violations = new List<Violation>();
    if (r.Target != "Vehicle")
    {
        violations.Add(new Violation("Overweight operation requires a Vehicle object", r, c));
    }

    if (c.Data != null || c.DictData != null)
    {
        violations.Add(new Violation("Overweight operation takes no data.", r, c));
    }
    return violations;
}

Now we can instantiate our operation

var analyzer = new Analyzer();
OatOperation OverweightOperation = new OatOperation(Operation.Custom, analyzer)
{
    CustomOperation = "OVERWEIGHT",
    OperationDelegate = OverweightOperationDelegate,
    ValidationDelegate = OverweightOperationValidationDelegate
};
analyzer.SetOperation(OverWeightOperation);

With our operation set we can test it.

var overweightTruck = new Vehicle()
{
    Weight = 30000,
    Capacity = 20000,
};

var rules = new Rule[] {
    new Rule("Overweight")
    {
        Target = "Vehicle",
        Clauses = new List<Clause>()
        {
            new Clause(OPERATION.CUSTOM)
            {
                CustomOperation = "OVERWEIGHT"
            }
        }
    },
};

var issues = analyzer.EnumerateRuleIssues(rules).ToList();

Assert.IsFalse(issues.Any());

Assert.IsTrue(analyzer.Analyze(rules,overweightTruck).Any());

Script

Indicates an Operation with a provided script

Takes

  • Script - A ScriptData object containing a Script to execute
  • Data - A List
  • DictData - A List<KeyValuePair<string,string>>

Returns

true if

  1. The provided ScriptData compiles
  2. The Script returns true when passed the Data and States.

Captures

  • May return a capture as anything that inherits from ClauseCapture.

Script Example

Instead of using a Delegate you can provide a Script inline to run.

var overweightTruck = new Vehicle()
{
    Weight = 30000,
    Capacity = 20000,
};

new Rule("Overweight Script")
{
    Expression = "Overweight",
    Target = "Vehicle",
    Clauses = new List<Clause>()
    {
        new Clause(Operation.Script)
        {
            Label = "Overweight",
                            Script = new ScriptData(code: @"
if (State1 is Vehicle vehicle)
{
    var res = vehicle.Weight > vehicle.Capacity;
    if ((res && !Clause.Invert) || (Clause.Invert && !res))
    {
        // The rule applies and is true and the capture is available if capture is enabled
        return new OperationResult(true, Clause.Capture ? new TypedClauseCapture<int>(Clause, vehicle.Weight, State1, State2) : null);
    }
}
return new OperationResult(false, null);",
                imports: new List<string>() {"System", "Microsoft.CST.OAT.VehicleDemo"},
                references: new List<string>(){ "VehicleDemo" }),
            Capture = true
        }
    }
};


var analyzer = new Analyzer(new AnalyzerOptions(true));

var ruleIssues = analyzer.EnumerateRuleIssues(rule);
Assert.IsFalse(ruleIssues.Any());

Assert.IsTrue(analyzer.Analyze(new Rule[]{ rule },overweightTruck).Any());