Skip to content

Operations

Gabe Stocco edited this page Jul 22, 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.

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

REGEX

Performs regular expression matching using the provided Data.

Takes

Data - List<strings> of Regular Expressions to match

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 == "Bool Equals Rule"));
    Assert.IsTrue(trueObjectResults.Any(x => x.Name == "Int Equals Rule"));
    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());
}

CONTAINS_ANY

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

WAS_MODIFIED

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

ENDS_WITH

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

STARTS_WITH

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

IS_NULL

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

IS_TRUE

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

IS_BEFORE

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

IS_AFTER

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

IS_EXPIRED

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

CONTAINS_KEY

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 - A string identifying the custom operation

Returns

true if:

  1. A matching custom operation delegate is found
  2. That delegate returns true.

Captures

  • Anything that inherits from ClauseCapture.

Example

# First we have to set up our Operation's Delegate
public (bool Applies, bool Result) OverweightOperationDelegate(Clause clause, 
    IEnumerable<string>? valsToCheck, IEnumerable<KeyValuePair<string, string>> dictToCheck, object? state1, object? state2)
{
    if (clause.CustomOperation == "OVERWEIGHT")
    {
        if (state1 is Vehicle vehicle)
        {
            if (vehicle.Weight > vehicle.Capacity)
            {
                return (true, true);
            }
        }
        return (true, false);
    }
    return (false, false);
}

# And the Delegate to ensure we can validate rules with our new operation
public (bool Applies, IEnumerable<Violation> Violations) OverweightOperationValidationDelegate(Rule r, Clause c)
{
    if (c.CustomOperation == "OVERWEIGHT")
    {
        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 (true, violations);
    }
    else
    {
        return (false, Array.Empty<Violation>());
    }
}

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 analyzer = new Analyzer();
analyzer.CustomOperationDelegates.Add(OverweightOperationDelegate);
analyzer.CustomOperationValidationDelegates.Add(OverweightOperationValidationDelegate);

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

Assert.IsFalse(issues.Any());

Assert.IsTrue(analyzer.Analyze(rules,overweightTruck).Any());
Clone this wiki locally