-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Support contains operator with many to one multiplicity (#165)
* feat: support contains operator with many to one multiplicity * fix: fix codacy issue * fix: fix codacy issue
- Loading branch information
1 parent
e56fe84
commit 324fee7
Showing
19 changed files
with
457 additions
and
83 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
49 changes: 49 additions & 0 deletions
49
...work/Evaluation/Compiled/ConditionBuilders/ContainsManyToOneConditionExpressionBuilder.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders | ||
{ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Linq.Expressions; | ||
using System.Reflection; | ||
using Rules.Framework.Core; | ||
using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; | ||
|
||
internal sealed class ContainsManyToOneConditionExpressionBuilder : IConditionExpressionBuilder | ||
{ | ||
private static readonly Dictionary<Type, MethodInfo> containsLinqGenericMethodInfos = InitializeLinqContainsMethodInfos(); | ||
private static readonly DataTypes[] supportedDataTypes = { DataTypes.Boolean, DataTypes.Decimal, DataTypes.Integer, DataTypes.String }; | ||
|
||
public Expression BuildConditionExpression(IExpressionBlockBuilder builder, BuildConditionExpressionArgs args) | ||
{ | ||
if (!supportedDataTypes.Contains(args.DataTypeConfiguration.DataType)) | ||
{ | ||
throw new NotSupportedException( | ||
$"The operator '{nameof(Operators.Contains)}' is not supported for data type '{args.DataTypeConfiguration.DataType}' on a many to one scenario."); | ||
} | ||
|
||
var containsMethodInfo = containsLinqGenericMethodInfos[args.DataTypeConfiguration.Type]; | ||
|
||
return builder.AndAlso( | ||
builder.NotEqual(args.LeftHandOperand, builder.Constant<object>(value: null!)), | ||
builder.Call( | ||
null!, | ||
containsMethodInfo, | ||
new Expression[] { args.LeftHandOperand, args.RightHandOperand })); | ||
} | ||
|
||
private static Dictionary<Type, MethodInfo> InitializeLinqContainsMethodInfos() | ||
{ | ||
var genericMethodInfo = typeof(Enumerable) | ||
.GetMethods() | ||
.First(m => string.Equals(m.Name, nameof(Enumerable.Contains), StringComparison.Ordinal) && m.GetParameters().Length == 2); | ||
|
||
return new Dictionary<Type, MethodInfo> | ||
{ | ||
{ typeof(bool), genericMethodInfo.MakeGenericMethod(typeof(bool)) }, | ||
{ typeof(decimal), genericMethodInfo.MakeGenericMethod(typeof(decimal)) }, | ||
{ typeof(int), genericMethodInfo.MakeGenericMethod(typeof(int)) }, | ||
{ typeof(string), genericMethodInfo.MakeGenericMethod(typeof(string)) }, | ||
}; | ||
} | ||
} | ||
} |
8 changes: 5 additions & 3 deletions
8
...ework/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilder.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 13 additions & 4 deletions
17
src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/ContainsOperatorEvalStrategy.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,29 @@ | ||
namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation | ||
{ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
|
||
internal sealed class ContainsOperatorEvalStrategy : IOneToOneOperatorEvalStrategy | ||
internal sealed class ContainsOperatorEvalStrategy : IOneToOneOperatorEvalStrategy, IManyToOneOperatorEvalStrategy | ||
{ | ||
public bool Eval(object leftOperand, object rightOperand) | ||
{ | ||
if (leftOperand is string) | ||
{ | ||
string leftOperandAsString = leftOperand as string; | ||
string rightOperandAsString = rightOperand as string; | ||
var leftOperandAsString = leftOperand as string; | ||
var rightOperandAsString = rightOperand as string; | ||
|
||
return leftOperandAsString.Contains(rightOperandAsString); | ||
#if NETSTANDARD2_1_OR_GREATER | ||
return leftOperandAsString!.Contains(rightOperandAsString, StringComparison.Ordinal); | ||
#else | ||
return leftOperandAsString!.Contains(rightOperandAsString); | ||
#endif | ||
} | ||
|
||
throw new NotSupportedException($"Unsupported 'contains' comparison between operands of type '{leftOperand?.GetType().FullName}'."); | ||
} | ||
|
||
public bool Eval(IEnumerable<object> leftOperand, object rightOperand) | ||
=> leftOperand.Contains(rightOperand); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
87 changes: 87 additions & 0 deletions
87
...ory.IntegrationTests/Features/RulesEngine/RulesMatching/OperatorContainsManyToOneTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
namespace Rules.Framework.Providers.InMemory.IntegrationTests.Features.RulesEngine.RulesMatching | ||
{ | ||
using System.Collections.Generic; | ||
using System.Threading.Tasks; | ||
using FluentAssertions; | ||
using Rules.Framework.Core; | ||
using Rules.Framework.IntegrationTests.Common.Features; | ||
using Rules.Framework.Tests.Stubs; | ||
using Xunit; | ||
|
||
public class OperatorContainsManyToOneTests : RulesEngineTestsBase | ||
{ | ||
private static readonly ContentType testContentType = ContentType.ContentType1; | ||
private readonly Rule<ContentType, ConditionType> expectedMatchRule; | ||
private readonly Rule<ContentType, ConditionType> otherRule; | ||
|
||
public OperatorContainsManyToOneTests() | ||
: base(testContentType) | ||
{ | ||
this.expectedMatchRule = RuleBuilder.NewRule<ContentType, ConditionType>() | ||
.WithName("Expected rule") | ||
.WithDateBegin(UtcDate("2020-01-01Z")) | ||
.WithContent(testContentType, "Just as expected!") | ||
.WithCondition(ConditionType.ConditionType1, Operators.Contains, "Cat") | ||
.Build() | ||
.Rule; | ||
|
||
this.otherRule = RuleBuilder.NewRule<ContentType, ConditionType>() | ||
.WithName("Other rule") | ||
.WithDateBegin(UtcDate("2020-01-01Z")) | ||
.WithContent(testContentType, "Oops! Not expected to be matched.") | ||
.Build() | ||
.Rule; | ||
|
||
this.AddRules(this.CreateTestRules()); | ||
} | ||
|
||
[Theory] | ||
[InlineData(false)] | ||
[InlineData(true)] | ||
public async Task RulesEngine_GivenConditionType1WithArrayOfStringsContainingCat_MatchesExpectedRule(bool compiled) | ||
{ | ||
// Arrange | ||
var emptyConditions = new[] | ||
{ | ||
new Condition<ConditionType>(ConditionType.ConditionType1, new[]{ "Dog", "Fish", "Cat", "Spider", "Mockingbird", }) | ||
}; | ||
var matchDate = UtcDate("2020-01-02Z"); | ||
|
||
// Act | ||
var actualMatch = await this.MatchOneAsync(matchDate, emptyConditions, compiled); | ||
|
||
// Assert | ||
actualMatch.Should().BeEquivalentTo(expectedMatchRule); | ||
} | ||
|
||
[Theory] | ||
[InlineData(false)] | ||
[InlineData(true)] | ||
public async Task RulesEngine_GivenConditionType1WithArrayOfStringsNotContainingCat_MatchesOtherRule(bool compiled) | ||
{ | ||
// Arrange | ||
var emptyConditions = new[] | ||
{ | ||
new Condition<ConditionType>(ConditionType.ConditionType1, new[]{ "Dog", "Fish", "Bat", "Spider", "Mockingbird", }) | ||
}; | ||
var matchDate = UtcDate("2020-01-02Z"); | ||
|
||
// Act | ||
var actualMatch = await this.MatchOneAsync(matchDate, emptyConditions, compiled); | ||
|
||
// Assert | ||
actualMatch.Should().BeEquivalentTo(otherRule); | ||
} | ||
|
||
private IEnumerable<RuleSpecification> CreateTestRules() | ||
{ | ||
var ruleSpecs = new List<RuleSpecification> | ||
{ | ||
new RuleSpecification(expectedMatchRule, RuleAddPriorityOption.ByPriorityNumber(1)), | ||
new RuleSpecification(otherRule, RuleAddPriorityOption.ByPriorityNumber(2)) | ||
}; | ||
|
||
return ruleSpecs; | ||
} | ||
} | ||
} |
Oops, something went wrong.