Operations - microsoft/OAT GitHub Wiki
Operations are defined in the Operation enum.
All Operations by default have access to some data:
-
state1The first object state provided. -
state2The 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:
-
valsToCheckaList<string> -
dictToCheckaList<KeyValuePair<string,string>>
In addition, many operations take argument data either in the form of:
- A
List<string>calledData - A
List<KeyValuePair<string,string>>calledDictData.
Finally, the Regex Operation and Custom operations may take arguments
- A
List<string>calledArguments
Performs regular expression matching using the provided Data.
-
Data-List<string>of Regular Expressions to match -
Arguments-List<string>of regex options.
-
isetsRegexOptions.IgnoreCase -
msetsRegexOptions.MultiLine
true when any of the Regexes in Data match any of the valsToCheck values extracted from the Field
A TypedClauseCapture<Match> containing the Match resulting from the regex call.
[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());
}Simple equals operation.
Data - List<string> to check equality
true when any of the strings in Data are in valsToCheck
-
TypedClauseCapture<string>when the captured target is a single string -
TypedClauseCapture<List<string>>when the captured target is a list of strings
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"));
}Simple Not equals operation.
Data - List<string> to check equality
true when none of the strings in Data are in valsToCheck
-
TypedClauseCapture<string>when the captured target is a single string -
TypedClauseCapture<List<string>>when the captured target is a list of strings
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));
}Checks if the ALL of the provided values are in the extracted values.
-
DictData- AList<KVP<string,string>> -
Data- AList<string>
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:
-
dictToCheckis not empty, and all of the KVP inDictDatahave a Key and Value match indictToCheck - The Target is a
List<string>, and all of the strings inDataare contained invalsToCheck - The Target is a
string,and all of the strings inDataare contained in the string - The Target is an
Enumwith the Flags attribute and all of the Flags specified inDataare set in the Enum
-
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
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());
}Checks if the ANY of the provided values are in the extracted values.
-
DictData- AList<KVP<string,string>> -
Data- AList<string>
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:
-
dictToCheckis not empty, and any of the KVP inDictDatahave a Key and Value match indictToCheck - The Target is a
List<string>, and any of the strings inDataare contained invalsToCheck - The Target is a
string,and any of the strings inDataare contained in the string - The Target is an
Enumwith the Flags attribute and any of the Flags specified inDataare set in the Enum
-
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
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());
}Checks if any of the provided ints are Greater Than the extracted values
Data - A Lists representing ints
true when any of the Data when parsed as ints is greater than any of the values in valsToCheck parsed as ints.
TypedClauseCapture<int> the captured int
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());
}Checks if any of the provided ints are Less Than the extracted values
Data - A List representing ints
true when any of the Data when parsed as ints is less than any of the values in valsToCheck parsed as ints.
TypedClauseCapture<int> the captured int
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());
}Checks if the two object states provided differ
No Arguments
true if the two object states appear to be different.
TypedClauseCapture<ComparisonResult> whose Result is a KellermanSoftware.CompareNetObjects.ComparisonResult, the result of the comparison between the two states.
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());
}Checks if any of the strings extracted end with the provided data.
Data - A List<string> of potential endings
true if any of the strings in valsToCheck end with any of the strings in Data
-
TypedClauseCapture<string>when the captured target is a single string -
TypedClauseCapture<List<string>>when the captured target is a list of strings
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());
}Checks if any of the strings extracted start with the provided data.
Data - A List<string> of potential starts
true if any of the strings in valsToCheck start with any of the strings in Data
-
TypedClauseCapture<string>when the captured target is a single string -
TypedClauseCapture<List<string>>when the captured target is a list of strings
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());
}Checks if both object states are null
No Arguments
true if both object states are null
CaptureClause where the object states represent the object states passed to the Clause for operating.
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());
}Checks if the object state is a true bool
No Arguments
true if either object state is true
TypedClauseCapture<bool> the target bool
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());
}Checks if the object state is a DateTime and is before any of the times specified in Data
Data - A List of DateTime.Parse compatible DateTime strings
true if either object state is a date time and any of the provided DateTimes in Data are after it
TypedClauseCapture<DateTime> the targeted time
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());
}Checks if the object state is a DateTime and is after any of the times specified in Data
Data - A List of DateTime.Parse compatible DateTime strings
-
trueif either object state is a date time and any of the provided DateTimes inDataare before it
TypedClauseCapture<DateTime> the targeted time
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());
}Checks if the object state is a DateTime and is after DateTime.Now when executed
No Arguments
-
trueif either object state is a date time and after DateTime.Now
TypedClauseCapture<DateTime> the targeted time
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());
}If dictionary values were extracted if any key in that dictionary matches the provided Data
-
Data- A List of dictionary keys to check
-
trueif any of theDatastrings are present indictToCheck.Keys
-
TypedClauseCapture<List<string>>of the found keys
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());
}Indicates a custom operation
-
Data- A List -
DictData- A List<KeyValuePair<string,string>> -
CustomOperation- The Label matching a Custom Operation Delegate you've set
true if
- A custom operation delegate with a matching
CustomOperationlabel is found. a. That delegate returns true.
- May return a capture as anything that inherits from
ClauseCapture.
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());Indicates an Operation with a provided script
-
Script- A ScriptData object containing a Script to execute -
Data- A List -
DictData- A List<KeyValuePair<string,string>>
true if
- The provided
ScriptDatacompiles - The
Scriptreturnstruewhen passed the Data and States.
- May return a capture as anything that inherits from
ClauseCapture.
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());