Skip to content

Commit

Permalink
refactor!: replace conditions collection with dictionary (#172)
Browse files Browse the repository at this point in the history
  • Loading branch information
luispfgarces authored Oct 8, 2024
1 parent 5555311 commit cf3b3c4
Show file tree
Hide file tree
Showing 36 changed files with 230 additions and 317 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ namespace Rules.Framework.InMemory.Sample.Engine
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using global::Rules.Framework.InMemory.Sample.Enums;
using global::Rules.Framework.InMemory.Sample.Exceptions;
Expand All @@ -21,18 +20,14 @@ public async Task<T> MatchOneAsync<T>(
DateTime dateTime,
IDictionary<ConditionNames, object> conditions)
{
var rulesConditions = (conditions is null) ? new Condition<ConditionNames>[] { } :
conditions.Select(x => new Condition<ConditionNames>(x.Key, x.Value))
.ToArray();

var rulesEngine = await
rulesEngineProvider
.GetRulesEngineAsync()
.ConfigureAwait(false);

var match = await rulesEngine
.MakeGeneric<RulesetNames, ConditionNames>()
.MatchOneAsync(ruleset, dateTime, rulesConditions)
.MatchOneAsync(ruleset, dateTime, conditions)
.ConfigureAwait(false);

if (match is null)
Expand Down
34 changes: 0 additions & 34 deletions src/Rules.Framework/Condition.cs

This file was deleted.

4 changes: 2 additions & 2 deletions src/Rules.Framework/Generic/IRulesEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public interface IRulesEngine<TRuleset, TCondition>
/// <para>All rules matching supplied conditions are returned.</para>
/// </remarks>
/// <returns>the matched rule; otherwise, null.</returns>
Task<IEnumerable<Rule<TRuleset, TCondition>>> MatchManyAsync(TRuleset ruleset, DateTime matchDateTime, IEnumerable<Condition<TCondition>> conditions);
Task<IEnumerable<Rule<TRuleset, TCondition>>> MatchManyAsync(TRuleset ruleset, DateTime matchDateTime, IDictionary<TCondition, object> conditions);

/// <summary>
/// Provides a rule match (if any) to the given <paramref name="ruleset"/> at the specified
Expand All @@ -121,7 +121,7 @@ public interface IRulesEngine<TRuleset, TCondition>
/// </para>
/// </remarks>
/// <returns>the matched rule; otherwise, null.</returns>
Task<Rule<TRuleset, TCondition>> MatchOneAsync(TRuleset ruleset, DateTime matchDateTime, IEnumerable<Condition<TCondition>> conditions);
Task<Rule<TRuleset, TCondition>> MatchOneAsync(TRuleset ruleset, DateTime matchDateTime, IDictionary<TCondition, object> conditions);

/// <summary>
/// Searches for rules that match on supplied <paramref name="searchArgs"/>.
Expand Down
16 changes: 7 additions & 9 deletions src/Rules.Framework/Generic/RulesEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,14 @@ public async Task<IEnumerable<TCondition>> GetUniqueConditionsAsync(TRuleset rul
public async Task<IEnumerable<Rule<TRuleset, TCondition>>> MatchManyAsync(
TRuleset ruleset,
DateTime matchDateTime,
IEnumerable<Condition<TCondition>> conditions)
IDictionary<TCondition, object> conditions)
{
var rulesetAsString = GenericConversions.Convert(ruleset);
var conditionsConverted = conditions.ToDictionary(c => GenericConversions.Convert(c.Key), c => c.Value, StringComparer.Ordinal);
var rules = await this.wrappedRulesEngine.MatchManyAsync(
rulesetAsString,
matchDateTime,
conditions
.Select(c => new Condition<string>(GenericConversions.Convert(c.Type), c.Value))
.ToArray()).ConfigureAwait(false);
conditionsConverted).ConfigureAwait(false);

return rules.Select(r => r.ToGenericRule<TRuleset, TCondition>()).ToArray();
}
Expand All @@ -104,15 +103,14 @@ public async Task<IEnumerable<Rule<TRuleset, TCondition>>> MatchManyAsync(
public async Task<Rule<TRuleset, TCondition>> MatchOneAsync(
TRuleset ruleset,
DateTime matchDateTime,
IEnumerable<Condition<TCondition>> conditions)
IDictionary<TCondition, object> conditions)
{
var rulesetAsString = GenericConversions.Convert(ruleset);
var conditionsConverted = conditions.ToDictionary(c => GenericConversions.Convert(c.Key), c => c.Value, StringComparer.Ordinal);
var rule = await this.wrappedRulesEngine.MatchOneAsync(
rulesetAsString,
matchDateTime,
conditions
.Select(c => new Condition<string>(GenericConversions.Convert(c.Type), c.Value))
.ToArray()).ConfigureAwait(false);
conditionsConverted).ConfigureAwait(false);

return rule?.ToGenericRule<TRuleset, TCondition>()!;
}
Expand All @@ -128,7 +126,7 @@ public async Task<IEnumerable<Rule<TRuleset, TCondition>>> SearchAsync(SearchArg
var rulesetAsString = GenericConversions.Convert(searchArgs.Ruleset);
var searchArgsNew = new SearchArgs<string, string>(rulesetAsString, searchArgs.DateBegin, searchArgs.DateEnd)
{
Conditions = searchArgs.Conditions.Select(c => new Condition<string>(GenericConversions.Convert(c.Type), c.Value)).ToArray(),
Conditions = searchArgs.Conditions.ToDictionary(c => GenericConversions.Convert(c.Key), c => c.Value, StringComparer.Ordinal),
ExcludeRulesWithoutSearchConditions = searchArgs.ExcludeRulesWithoutSearchConditions,
};

Expand Down
4 changes: 2 additions & 2 deletions src/Rules.Framework/IRulesEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public interface IRulesEngine
/// <para>All rules matching supplied conditions are returned.</para>
/// </remarks>
/// <returns>the matched rule; otherwise, null.</returns>
Task<IEnumerable<Rule>> MatchManyAsync(string ruleset, DateTime matchDateTime, IEnumerable<Condition<string>> conditions);
Task<IEnumerable<Rule>> MatchManyAsync(string ruleset, DateTime matchDateTime, IDictionary<string, object> conditions);

/// <summary>
/// Provides a rule match (if any) to the given <paramref name="ruleset"/> at the specified
Expand All @@ -116,7 +116,7 @@ public interface IRulesEngine
/// </para>
/// </remarks>
/// <returns>the matched rule; otherwise, null.</returns>
Task<Rule> MatchOneAsync(string ruleset, DateTime matchDateTime, IEnumerable<Condition<string>> conditions);
Task<Rule> MatchOneAsync(string ruleset, DateTime matchDateTime, IDictionary<string, object> conditions);

/// <summary>
/// Searches for rules that match on supplied <paramref name="searchArgs"/>.
Expand Down
23 changes: 10 additions & 13 deletions src/Rules.Framework/RulesEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ public async Task<IEnumerable<string>> GetUniqueConditionsAsync(string ruleset,
public async Task<IEnumerable<Rule>> MatchManyAsync(
string ruleset,
DateTime matchDateTime,
IEnumerable<Condition<string>> conditions)
IDictionary<string, object> conditions)
{
if (string.IsNullOrWhiteSpace(ruleset))
{
Expand All @@ -152,16 +152,15 @@ public async Task<IEnumerable<Rule>> MatchManyAsync(
DateEnd = matchDateTime,
};

var conditionsAsDictionary = conditions.ToDictionary(ks => ks.Type, ks => ks.Value, StringComparer.Ordinal);
var orderedRules = await this.GetRulesOrderedAscendingAsync(getRulesArgs).ConfigureAwait(false);
return this.EvalAll(orderedRules, evaluationOptions, conditionsAsDictionary, active: true);
return this.EvalAll(orderedRules, evaluationOptions, conditions, active: true);
}

/// <inheritdoc/>
public async Task<Rule> MatchOneAsync(
string ruleset,
DateTime matchDateTime,
IEnumerable<Condition<string>> conditions)
IDictionary<string, object> conditions)
{
if (string.IsNullOrWhiteSpace(ruleset))
{
Expand All @@ -181,11 +180,10 @@ public async Task<Rule> MatchOneAsync(
DateEnd = matchDateTime,
};

var conditionsAsDictionary = conditions.ToDictionary(ks => ks.Type, ks => ks.Value);
var orderedRules = await this.GetRulesOrderedAscendingAsync(getRulesArgs).ConfigureAwait(false);
return this.Options.PriorityCriteria == PriorityCriterias.TopmostRuleWins
? EvalOneTraverse(orderedRules, evaluationOptions, conditionsAsDictionary, active: true)
: EvalOneReverse(orderedRules, evaluationOptions, conditionsAsDictionary, active: true);
? EvalOneTraverse(orderedRules, evaluationOptions, conditions, active: true)
: EvalOneReverse(orderedRules, evaluationOptions, conditions, active: true);
}

/// <inheritdoc/>
Expand Down Expand Up @@ -226,9 +224,8 @@ public async Task<IEnumerable<Rule>> SearchAsync(SearchArgs<string, string> sear
DateEnd = searchArgs.DateEnd,
};

var conditionsAsDictionary = searchArgs.Conditions.ToDictionary(ks => ks.Type, ks => ks.Value);
var orderedRules = await this.GetRulesOrderedAscendingAsync(getRulesArgs).ConfigureAwait(false);
return this.EvalAll(orderedRules, evaluationOptions, conditionsAsDictionary, searchArgs.Active);
return this.EvalAll(orderedRules, evaluationOptions, searchArgs.Conditions, searchArgs.Active);
}

/// <inheritdoc/>
Expand Down Expand Up @@ -384,7 +381,7 @@ private async Task<OperationResult> CreateRulesetInternalAsync(string ruleset)
private IEnumerable<Rule> EvalAll(
List<Rule> orderedRules,
EvaluationOptions evaluationOptions,
Dictionary<string, object> conditionsAsDictionary,
IDictionary<string, object> conditionsAsDictionary,
bool? active)
{
// Begins evaluation at the first element of the given list as parameter. Returns all
Expand All @@ -404,7 +401,7 @@ private IEnumerable<Rule> EvalAll(
private Rule EvalOneReverse(
List<Rule> rules,
EvaluationOptions evaluationOptions,
Dictionary<string, object> conditionsAsDictionary,
IDictionary<string, object> conditionsAsDictionary,
bool? active)
{
// Begins evaluation at the last element of the given list as parameter. Returns the
Expand All @@ -424,7 +421,7 @@ private Rule EvalOneReverse(
private Rule EvalOneTraverse(
List<Rule> rules,
EvaluationOptions evaluationOptions,
Dictionary<string, object> conditionsAsDictionary,
IDictionary<string, object> conditionsAsDictionary,
bool? active)
{
// Begins evaluation at the first element of the given list as parameter. Returns the
Expand All @@ -445,7 +442,7 @@ private Rule EvalOneTraverse(
private bool EvalRule(
Rule rule,
EvaluationOptions evaluationOptions,
Dictionary<string, object> conditionsAsDictionary,
IDictionary<string, object> conditionsAsDictionary,
bool? active)
=> rule.Active == active.GetValueOrDefault(defaultValue: true) && (rule.RootCondition == null || this.conditionsEvalEngine.Eval(rule.RootCondition, conditionsAsDictionary, evaluationOptions));

Expand Down
7 changes: 3 additions & 4 deletions src/Rules.Framework/SearchArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ namespace Rules.Framework
{
using System;
using System.Collections.Generic;
using System.Linq;

/// <summary>
/// The set of search arguments to find rules.
Expand All @@ -19,7 +18,7 @@ public class SearchArgs<TRuleset, TCondition>
/// <param name="dateEnd">The date end.</param>
public SearchArgs(TRuleset ruleset, DateTime dateBegin, DateTime dateEnd)
{
this.Conditions = Enumerable.Empty<Condition<TCondition>>();
this.Conditions = new Dictionary<TCondition, object>();
this.Ruleset = ruleset;
this.DateBegin = dateBegin;
this.DateEnd = dateEnd;
Expand All @@ -36,7 +35,7 @@ public SearchArgs(TRuleset ruleset, DateTime dateBegin, DateTime dateEnd)
/// <param name="active">The active status.</param>
public SearchArgs(TRuleset ruleset, DateTime dateBegin, DateTime dateEnd, bool active)
{
this.Conditions = Enumerable.Empty<Condition<TCondition>>();
this.Conditions = new Dictionary<TCondition, object>();
this.Ruleset = ruleset;
this.DateBegin = dateBegin;
this.DateEnd = dateEnd;
Expand All @@ -54,7 +53,7 @@ public SearchArgs(TRuleset ruleset, DateTime dateBegin, DateTime dateEnd, bool a
/// Gets or sets the search conditions.
/// </summary>
/// <value>The conditions.</value>
public IEnumerable<Condition<TCondition>> Conditions { get; set; }
public IDictionary<TCondition, object> Conditions { get; set; }

/// <summary>
/// Gets or sets the date begin.
Expand Down
8 changes: 4 additions & 4 deletions src/Rules.Framework/Validation/SearchArgsValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@ public SearchArgsValidator()
this.RuleForEach(sa => sa.Conditions)
.ChildRules(conditionValidator =>
{
conditionValidator.RuleFor(condition => condition.Type)
.Must(conditionType =>
conditionValidator.RuleFor(condition => condition.Key)
.Must(conditionKey =>
{
if (this.conditionTypeRuntimeType.IsClass && conditionType is null)
if (this.conditionTypeRuntimeType.IsClass && conditionKey is null)
{
return false;
}
if (this.conditionTypeRuntimeType.IsEnum && !Enum.IsDefined(this.conditionTypeRuntimeType, conditionType))
if (this.conditionTypeRuntimeType.IsEnum && !Enum.IsDefined(this.conditionTypeRuntimeType, conditionKey))
{
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ namespace Rules.Framework.BenchmarkTests.Tests
{
using System;
using System.Collections.Generic;
using Rules.Framework;
using Rules.Framework.Generic;

public interface IScenarioData<TRuleset, TCondition>
{
IEnumerable<Condition<TCondition>> Conditions { get; }
IDictionary<TCondition, object> Conditions { get; }

DateTime MatchDate { get; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark1

public class Scenario6Data : IScenarioData<Rulesets, ConditionNames>
{
public IEnumerable<Condition<ConditionNames>> Conditions => new[]
public IDictionary<ConditionNames, object> Conditions => new Dictionary<ConditionNames, object>
{
new Condition<ConditionNames>(ConditionNames.StringCondition, "Let's benchmark this!")
{ ConditionNames.StringCondition, "Let's benchmark this!" },
};

public DateTime MatchDate => DateTime.Parse("2022-10-01");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark2

public class Scenario7Data : IScenarioData<Rulesets, ConditionNames>
{
public IEnumerable<Condition<ConditionNames>> Conditions => new[]
public IDictionary<ConditionNames, object> Conditions => new Dictionary<ConditionNames, object>
{
new Condition<ConditionNames>(ConditionNames.Artist, "Queen"),
new Condition<ConditionNames>(ConditionNames.Lyrics, "Is this the real life?\nIs this just fantasy?\nCaught in a landside,\nNo escape from reality" ),
new Condition<ConditionNames>(ConditionNames.ReleaseYear, 1975 )
{ ConditionNames.Artist, "Queen" },
{ ConditionNames.Lyrics, "Is this the real life?\nIs this just fantasy?\nCaught in a landside,\nNo escape from reality" },
{ ConditionNames.ReleaseYear, 1975 },
};

public DateTime MatchDate => DateTime.Parse("2022-11-01");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,22 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3
using System;
using System.Collections.Generic;
using System.Linq;
using Rules.Framework;
using Rules.Framework.Generic;

public partial class Scenario8Data : IScenarioData<PokerRulesets, PokerConditions>
{
public IEnumerable<Condition<PokerConditions>> Conditions => new[]
public IDictionary<PokerConditions, object> Conditions => new Dictionary<PokerConditions, object>
{
new Condition<PokerConditions>(PokerConditions.NumberOfKings, 1),
new Condition<PokerConditions>(PokerConditions.NumberOfQueens, 1 ),
new Condition<PokerConditions>(PokerConditions.NumberOfJacks, 1),
new Condition<PokerConditions>(PokerConditions.NumberOfTens, 1 ),
new Condition<PokerConditions>(PokerConditions.NumberOfNines, 1 ),
new Condition<PokerConditions>(PokerConditions.KingOfClubs, true ),
new Condition<PokerConditions>(PokerConditions.QueenOfDiamonds, true ),
new Condition<PokerConditions>(PokerConditions.JackOfClubs, true ),
new Condition<PokerConditions>(PokerConditions.TenOfHearts, true ),
new Condition<PokerConditions>(PokerConditions.NineOfSpades, true ),
{ PokerConditions.NumberOfKings, 1 },
{ PokerConditions.NumberOfQueens, 1 },
{ PokerConditions.NumberOfJacks, 1 },
{ PokerConditions.NumberOfTens, 1 },
{ PokerConditions.NumberOfNines, 1 },
{ PokerConditions.KingOfClubs, true },
{ PokerConditions.QueenOfDiamonds, true },
{ PokerConditions.JackOfClubs, true },
{ PokerConditions.TenOfHearts, true },
{ PokerConditions.NineOfSpades, true },
};

public DateTime MatchDate => DateTime.Parse("2022-12-01");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace Rules.Framework.IntegrationTests.Scenarios.Scenario1
{
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -214,7 +215,7 @@ public async Task BodyMassIndex_NoConditions_ReturnsDefaultFormula(bool enableCo
var expectedFormulaValue = "weight / (height ^ 2)";
const Scenario1RulesetNames expectedContent = Scenario1RulesetNames.BodyMassIndexFormula;
var expectedMatchDate = new DateTime(2018, 06, 01);
var expectedConditions = Array.Empty<Condition<Scenario1ConditionNames>>();
var expectedConditions = new Dictionary<Scenario1ConditionNames, object>();

var serviceProvider = new ServiceCollection()
.AddInMemoryRulesDataSource(ServiceLifetime.Singleton)
Expand Down
Loading

0 comments on commit cf3b3c4

Please sign in to comment.