diff --git a/.editorconfig b/.editorconfig index e122fba1..4b268ac5 100644 --- a/.editorconfig +++ b/.editorconfig @@ -66,9 +66,9 @@ dotnet_code_quality_unused_parameters = all:suggestion #### C# Coding Conventions #### # var preferences -csharp_style_var_elsewhere = false:silent -csharp_style_var_for_built_in_types = false:silent -csharp_style_var_when_type_is_apparent = false:silent +csharp_style_var_elsewhere = true:silent +csharp_style_var_for_built_in_types = true:silent +csharp_style_var_when_type_is_apparent = true:silent # Expression-bodied members csharp_style_expression_bodied_accessors = true:silent @@ -231,6 +231,7 @@ dotnet_style_namespace_match_folder = true:suggestion csharp_style_prefer_switch_expression = true:suggestion csharp_style_prefer_pattern_matching = true:silent csharp_style_prefer_not_pattern = true:suggestion +csharp_style_prefer_primary_constructors = true:suggestion [*.vb] visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion:suggestion diff --git a/samples/Rules.Framework.InMemory.Sample/Engine/IContentTypes.cs b/samples/Rules.Framework.InMemory.Sample/Engine/IRuleSpecificationsProvider.cs similarity index 52% rename from samples/Rules.Framework.InMemory.Sample/Engine/IContentTypes.cs rename to samples/Rules.Framework.InMemory.Sample/Engine/IRuleSpecificationsProvider.cs index 9c852cef..1306bacc 100644 --- a/samples/Rules.Framework.InMemory.Sample/Engine/IContentTypes.cs +++ b/samples/Rules.Framework.InMemory.Sample/Engine/IRuleSpecificationsProvider.cs @@ -1,9 +1,12 @@ namespace Rules.Framework.InMemory.Sample.Engine { using System.Collections.Generic; + using global::Rules.Framework.InMemory.Sample.Enums; - internal interface IContentTypes + internal interface IRuleSpecificationsProvider { + RulesetNames[] Rulesets { get; } + IEnumerable GetRulesSpecifications(); } } \ No newline at end of file diff --git a/samples/Rules.Framework.InMemory.Sample/Engine/RuleSpecification.cs b/samples/Rules.Framework.InMemory.Sample/Engine/RuleSpecification.cs index 4799dc03..c569d868 100644 --- a/samples/Rules.Framework.InMemory.Sample/Engine/RuleSpecification.cs +++ b/samples/Rules.Framework.InMemory.Sample/Engine/RuleSpecification.cs @@ -1,13 +1,13 @@ namespace Rules.Framework.InMemory.Sample.Engine { using global::Rules.Framework; - using global::Rules.Framework.Builder; + using global::Rules.Framework.Builder.Generic; using global::Rules.Framework.InMemory.Sample.Enums; internal class RuleSpecification { public RuleAddPriorityOption RuleAddPriorityOption { get; set; } - public RuleBuilderResult RuleBuilderResult { get; set; } + public RuleBuilderResult RuleBuilderResult { get; set; } } } \ No newline at end of file diff --git a/samples/Rules.Framework.InMemory.Sample/Engine/RulesBuilder.cs b/samples/Rules.Framework.InMemory.Sample/Engine/RulesBuilder.cs index 7941ff07..2a0e6725 100644 --- a/samples/Rules.Framework.InMemory.Sample/Engine/RulesBuilder.cs +++ b/samples/Rules.Framework.InMemory.Sample/Engine/RulesBuilder.cs @@ -2,20 +2,24 @@ namespace Rules.Framework.InMemory.Sample.Engine { using System.Collections.Generic; using System.Threading.Tasks; - using global::Rules.Framework.InMemory.Sample.Enums; using global::Rules.Framework.InMemory.Sample.Exceptions; internal class RulesBuilder { - private readonly IEnumerable contentTypes; + private readonly IEnumerable ruleSpecificationsRegistrars; - public RulesBuilder(IEnumerable contentTypes) => this.contentTypes = contentTypes; + public RulesBuilder(IEnumerable ruleSpecificationsProviders) => this.ruleSpecificationsRegistrars = ruleSpecificationsProviders; - public async Task BuildAsync(RulesEngine rulesEngine) + public async Task BuildAsync(IRulesEngine rulesEngine) { - foreach (var contentType in contentTypes) + foreach (var ruleSpecificationsProvider in ruleSpecificationsRegistrars) { - var rulesSpecifications = contentType.GetRulesSpecifications(); + foreach (var ruleset in ruleSpecificationsProvider.Rulesets) + { + await rulesEngine.CreateRulesetAsync(ruleset.ToString()); + } + + var rulesSpecifications = ruleSpecificationsProvider.GetRulesSpecifications(); foreach (var ruleSpecification in rulesSpecifications) { @@ -28,7 +32,7 @@ public async Task BuildAsync(RulesEngine rulesEngi .AddRuleAsync( ruleSpecification.RuleBuilderResult.Rule, ruleSpecification.RuleAddPriorityOption - ).ConfigureAwait(false); + ); if (!ruleOperationResult.IsSuccess) { diff --git a/samples/Rules.Framework.InMemory.Sample/Engine/RulesEngineProvider.cs b/samples/Rules.Framework.InMemory.Sample/Engine/RulesEngineProvider.cs index 949b4d08..f24a6f91 100644 --- a/samples/Rules.Framework.InMemory.Sample/Engine/RulesEngineProvider.cs +++ b/samples/Rules.Framework.InMemory.Sample/Engine/RulesEngineProvider.cs @@ -3,21 +3,17 @@ namespace Rules.Framework.InMemory.Sample.Engine using System; using System.Threading; using System.Threading.Tasks; - using global::Rules.Framework.InMemory.Sample.Enums; - using global::Rules.Framework.Providers.InMemory; internal class RulesEngineProvider { - private readonly Lazy>> lazyRulesEngine; + private readonly Lazy> lazyRulesEngine; public RulesEngineProvider(RulesBuilder rulesBuilder) { - lazyRulesEngine = new Lazy>>(async () => + lazyRulesEngine = new Lazy>(async () => { var rulesEngine = RulesEngineBuilder .CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetInMemoryDataSource() .Configure(opt => opt.PriorityCriteria = PriorityCriterias.BottommostRuleWins) .Build(); @@ -28,7 +24,7 @@ public RulesEngineProvider(RulesBuilder rulesBuilder) }, LazyThreadSafetyMode.ExecutionAndPublication); } - public Task> GetRulesEngineAsync() + public Task GetRulesEngineAsync() => lazyRulesEngine.Value; } } \ No newline at end of file diff --git a/samples/Rules.Framework.InMemory.Sample/Engine/RulesService.cs b/samples/Rules.Framework.InMemory.Sample/Engine/RulesService.cs index 879e228f..c37d6553 100644 --- a/samples/Rules.Framework.InMemory.Sample/Engine/RulesService.cs +++ b/samples/Rules.Framework.InMemory.Sample/Engine/RulesService.cs @@ -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; @@ -11,32 +10,29 @@ internal class RulesService { private readonly RulesEngineProvider rulesEngineProvider; - public RulesService(IEnumerable contentTypes) + public RulesService(IEnumerable ruleSpecificationsProviders) { - this.rulesEngineProvider = new RulesEngineProvider(new RulesBuilder(contentTypes)); + this.rulesEngineProvider = new RulesEngineProvider(new RulesBuilder(ruleSpecificationsProviders)); } public async Task MatchOneAsync( - ContentTypes contentType, + RulesetNames ruleset, DateTime dateTime, - IDictionary conditions) + IDictionary conditions) { - var rulesConditions = (conditions is null) ? new Condition[] { } : - conditions.Select(x => new Condition(x.Key, x.Value)) - .ToArray(); - var rulesEngine = await rulesEngineProvider .GetRulesEngineAsync() .ConfigureAwait(false); var match = await rulesEngine - .MatchOneAsync(contentType, dateTime, rulesConditions) + .MakeGeneric() + .MatchOneAsync(ruleset, dateTime, conditions) .ConfigureAwait(false); if (match is null) { - var message = $"Error trying to match one rule. No rule was found {contentType} {dateTime} {string.Join(", ", conditions)}."; + var message = $"Error trying to match one rule. No rule was found {ruleset} {dateTime} {string.Join(", ", conditions)}."; throw new RulesNotFoundException(message); } diff --git a/samples/Rules.Framework.InMemory.Sample/Enums/ConditionTypes.cs b/samples/Rules.Framework.InMemory.Sample/Enums/ConditionNames.cs similarity index 57% rename from samples/Rules.Framework.InMemory.Sample/Enums/ConditionTypes.cs rename to samples/Rules.Framework.InMemory.Sample/Enums/ConditionNames.cs index cd020a5c..13110543 100644 --- a/samples/Rules.Framework.InMemory.Sample/Enums/ConditionTypes.cs +++ b/samples/Rules.Framework.InMemory.Sample/Enums/ConditionNames.cs @@ -1,29 +1,29 @@ namespace Rules.Framework.InMemory.Sample.Enums { - internal enum ConditionTypes + internal enum ConditionNames { /// - /// no condition type defined + /// no condition defined /// None = 0, /// - /// condition type to filter by royal numbers + /// condition to filter by royal numbers /// RoyalNumber = 1, /// - /// condition type to filter if the number can be divided by 3 + /// condition to filter if the number can be divided by 3 /// CanNumberBeDividedBy3 = 2, /// - /// condition type to filter if the number is prime + /// condition to filter if the number is prime /// IsPrimeNumber = 3, /// - /// condition type to filter number sums + /// condition to filter number sums /// SumAll = 4 } diff --git a/samples/Rules.Framework.InMemory.Sample/Enums/ContentTypes.cs b/samples/Rules.Framework.InMemory.Sample/Enums/RulesetNames.cs similarity index 57% rename from samples/Rules.Framework.InMemory.Sample/Enums/ContentTypes.cs rename to samples/Rules.Framework.InMemory.Sample/Enums/RulesetNames.cs index 4b4ee73c..b34981ee 100644 --- a/samples/Rules.Framework.InMemory.Sample/Enums/ContentTypes.cs +++ b/samples/Rules.Framework.InMemory.Sample/Enums/RulesetNames.cs @@ -1,11 +1,11 @@ namespace Rules.Framework.InMemory.Sample.Enums { - internal enum ContentTypes + internal enum RulesetNames { None = 0, /// - /// this content verifies if number is cool or not + /// this ruleset contains rules that verify if a number is cool or not /// TestNumber = 1, } diff --git a/samples/Rules.Framework.InMemory.Sample/Program.cs b/samples/Rules.Framework.InMemory.Sample/Program.cs index 7dced43a..002caeb8 100644 --- a/samples/Rules.Framework.InMemory.Sample/Program.cs +++ b/samples/Rules.Framework.InMemory.Sample/Program.cs @@ -3,6 +3,7 @@ namespace Rules.Framework.InMemory.Sample using System; using System.Collections.Generic; using System.Globalization; + using System.Threading.Tasks; using global::Rules.Framework.InMemory.Sample.Engine; using global::Rules.Framework.InMemory.Sample.Enums; using global::Rules.Framework.InMemory.Sample.Helper; @@ -10,9 +11,9 @@ namespace Rules.Framework.InMemory.Sample internal class Program { - private static void Main(string[] args) + private static async Task Main(string[] args) { - var rulesService = new RulesService(new List() + var rulesService = new RulesService(new List() { new TestNumberRules() }); @@ -31,18 +32,15 @@ private static void Main(string[] args) break; } - var conditions = new Dictionary { - { ConditionTypes.IsPrimeNumber, value.IsPrime() }, - { ConditionTypes.CanNumberBeDividedBy3, value.CanNumberBeDividedBy3() }, - { ConditionTypes.SumAll, value.SumAll() }, - { ConditionTypes.RoyalNumber, value } + var conditions = new Dictionary { + { ConditionNames.IsPrimeNumber, value.IsPrime() }, + { ConditionNames.CanNumberBeDividedBy3, value.CanNumberBeDividedBy3() }, + { ConditionNames.SumAll, value.SumAll() }, + { ConditionNames.RoyalNumber, value } }; - var result = rulesService - .MatchOneAsync(ContentTypes.TestNumber, targetDate, conditions) - .ConfigureAwait(false) - .GetAwaiter() - .GetResult(); + var result = await rulesService + .MatchOneAsync(RulesetNames.TestNumber, targetDate, conditions); Console.WriteLine($"The result is: {result}"); } diff --git a/samples/Rules.Framework.InMemory.Sample/Rules/TestNumberRules.cs b/samples/Rules.Framework.InMemory.Sample/Rules/TestNumberRules.cs index b4539520..797b4377 100644 --- a/samples/Rules.Framework.InMemory.Sample/Rules/TestNumberRules.cs +++ b/samples/Rules.Framework.InMemory.Sample/Rules/TestNumberRules.cs @@ -2,12 +2,11 @@ namespace Rules.Framework.InMemory.Sample.Rules { using System; using System.Collections.Generic; - using global::Rules.Framework.Builder; - using global::Rules.Framework.Core; + using global::Rules.Framework.Builder.Generic; using global::Rules.Framework.InMemory.Sample.Engine; using global::Rules.Framework.InMemory.Sample.Enums; - internal class TestNumberRules : IContentTypes + internal class TestNumberRules : IRuleSpecificationsProvider { public readonly List rulesSpecifications; @@ -16,6 +15,8 @@ public TestNumberRules() this.rulesSpecifications = new List(); } + public RulesetNames[] Rulesets => new[] { RulesetNames.TestNumber, }; + public IEnumerable GetRulesSpecifications() { Add(CreateRuleForCoolNumbers(), RuleAddPriorityOption.ByPriorityNumber(3)); @@ -26,7 +27,7 @@ public IEnumerable GetRulesSpecifications() } private void Add( - RuleBuilderResult rule, + RuleBuilderResult rule, RuleAddPriorityOption ruleAddPriorityOption) => rulesSpecifications.Add( new RuleSpecification { @@ -34,39 +35,33 @@ private void Add( RuleAddPriorityOption = ruleAddPriorityOption, }); - private RuleBuilderResult CreateDefaultRule() => - RuleBuilder - .NewRule() - .WithName("Default rule for test number") - .WithContent(ContentTypes.TestNumber, ":| default nothing special about this number") - .WithDateBegin(new DateTime(2019, 01, 01)) + private RuleBuilderResult CreateDefaultRule() => Rule.Create("Default rule for test number") + .InRuleset(RulesetNames.TestNumber) + .SetContent(":| default nothing special about this number") + .Since(new DateTime(2019, 01, 01)) .Build(); - private RuleBuilderResult CreateRuleForCoolNumbers() => - RuleBuilder - .NewRule() - .WithName("Rule for cool numbers") - .WithContent(ContentTypes.TestNumber, ":D this number is so COOL!") - .WithDateBegin(new DateTime(2019, 01, 01)) - .WithCondition(c => c - .Or(o => o - .Value(ConditionTypes.RoyalNumber, Operators.Equal, 7) - .And(a => a - .Value(ConditionTypes.IsPrimeNumber, Operators.Equal, 7) - .Value(ConditionTypes.SumAll, Operators.StartsWith, "5")))) + private RuleBuilderResult CreateRuleForCoolNumbers() => Rule.Create("Rule for cool numbers") + .InRuleset(RulesetNames.TestNumber) + .SetContent(":D this number is so COOL!") + .Since(new DateTime(2019, 01, 01)) + .ApplyWhen(c => c + .Or(o => o + .Value(ConditionNames.RoyalNumber, Operators.Equal, 7) + .And(a => a + .Value(ConditionNames.IsPrimeNumber, Operators.Equal, 7) + .Value(ConditionNames.SumAll, Operators.StartsWith, "5")))) .Build(); - private RuleBuilderResult CreateRuleForSosoNumbers() => - RuleBuilder - .NewRule() - .WithName("Rule for so so numbers") - .WithContent(ContentTypes.TestNumber, ":) this number is so so") - .WithDateBegin(new DateTime(2019, 01, 01)) - .WithCondition(c => c + private RuleBuilderResult CreateRuleForSosoNumbers() => Rule.Create("Rule for so so numbers") + .InRuleset(RulesetNames.TestNumber) + .SetContent(":) this number is so so") + .Since(new DateTime(2019, 01, 01)) + .ApplyWhen(c => c .Or(o => o - .Value(ConditionTypes.CanNumberBeDividedBy3, Operators.Equal, true) - .Value(ConditionTypes.IsPrimeNumber, Operators.Equal, false) - .Value(ConditionTypes.SumAll, Operators.StartsWith, "9"))) + .Value(ConditionNames.CanNumberBeDividedBy3, Operators.Equal, true) + .Value(ConditionNames.IsPrimeNumber, Operators.Equal, false) + .Value(ConditionNames.SumAll, Operators.StartsWith, "9"))) .Build(); } } \ No newline at end of file diff --git a/samples/Rules.Framework.WebUI.Sample/Engine/IContentTypes.cs b/samples/Rules.Framework.WebUI.Sample/Engine/IRuleSpecificationsProvider.cs similarity index 52% rename from samples/Rules.Framework.WebUI.Sample/Engine/IContentTypes.cs rename to samples/Rules.Framework.WebUI.Sample/Engine/IRuleSpecificationsProvider.cs index 30fbfdf6..bddfce3d 100644 --- a/samples/Rules.Framework.WebUI.Sample/Engine/IContentTypes.cs +++ b/samples/Rules.Framework.WebUI.Sample/Engine/IRuleSpecificationsProvider.cs @@ -1,9 +1,12 @@ namespace Rules.Framework.WebUI.Sample.Engine { using System.Collections.Generic; + using global::Rules.Framework.WebUI.Sample.Enums; - internal interface IContentTypes + internal interface IRuleSpecificationsProvider { + RulesetNames[] Rulesets { get; } + IEnumerable GetRulesSpecifications(); } } \ No newline at end of file diff --git a/samples/Rules.Framework.WebUI.Sample/Engine/RuleSpecification.cs b/samples/Rules.Framework.WebUI.Sample/Engine/RuleSpecification.cs index d912faf9..b39d330d 100644 --- a/samples/Rules.Framework.WebUI.Sample/Engine/RuleSpecification.cs +++ b/samples/Rules.Framework.WebUI.Sample/Engine/RuleSpecification.cs @@ -1,13 +1,13 @@ namespace Rules.Framework.WebUI.Sample.Engine { using global::Rules.Framework; - using global::Rules.Framework.Builder; + using global::Rules.Framework.Builder.Generic; using global::Rules.Framework.WebUI.Sample.Enums; - internal sealed class RuleSpecification : RuleSpecificationBase + internal sealed class RuleSpecification : RuleSpecificationBase { public RuleSpecification( - RuleBuilderResult ruleBuilderResult, + RuleBuilderResult ruleBuilderResult, RuleAddPriorityOption ruleAddPriorityOption) : base(ruleBuilderResult, ruleAddPriorityOption) { diff --git a/samples/Rules.Framework.WebUI.Sample/Engine/RuleSpecificationBase.cs b/samples/Rules.Framework.WebUI.Sample/Engine/RuleSpecificationBase.cs index d8a0bbea..b0b408e5 100644 --- a/samples/Rules.Framework.WebUI.Sample/Engine/RuleSpecificationBase.cs +++ b/samples/Rules.Framework.WebUI.Sample/Engine/RuleSpecificationBase.cs @@ -1,7 +1,7 @@ namespace Rules.Framework.WebUI.Sample.Engine { using global::Rules.Framework; - using global::Rules.Framework.Builder; + using global::Rules.Framework.Builder.Generic; internal class RuleSpecificationBase { diff --git a/samples/Rules.Framework.WebUI.Sample/Engine/RulesBuilder.cs b/samples/Rules.Framework.WebUI.Sample/Engine/RulesBuilder.cs index 2ae3e7da..daba8249 100644 --- a/samples/Rules.Framework.WebUI.Sample/Engine/RulesBuilder.cs +++ b/samples/Rules.Framework.WebUI.Sample/Engine/RulesBuilder.cs @@ -2,20 +2,24 @@ namespace Rules.Framework.WebUI.Sample.Engine { using System.Collections.Generic; using System.Threading.Tasks; - using global::Rules.Framework.WebUI.Sample.Enums; using global::Rules.Framework.WebUI.Sample.Exceptions; internal class RulesBuilder { - private readonly IEnumerable contentTypes; + private readonly IEnumerable ruleSpecificationsProviders; - public RulesBuilder(IEnumerable contentTypes) => this.contentTypes = contentTypes; + public RulesBuilder(IEnumerable ruleSpecificationsProviders) => this.ruleSpecificationsProviders = ruleSpecificationsProviders; - public async Task BuildAsync(RulesEngine rulesEngine) + public async Task BuildAsync(IRulesEngine rulesEngine) { - foreach (var contentType in contentTypes) + foreach (var ruleSpecificationsProvider in ruleSpecificationsProviders) { - var rulesSpecifications = contentType.GetRulesSpecifications(); + foreach (var ruleset in ruleSpecificationsProvider.Rulesets) + { + await rulesEngine.CreateRulesetAsync(ruleset.ToString()); + } + + var rulesSpecifications = ruleSpecificationsProvider.GetRulesSpecifications(); foreach (var ruleSpecification in rulesSpecifications) { @@ -28,7 +32,7 @@ public async Task BuildAsync(RulesEngine rulesEngi .AddRuleAsync( ruleSpecification.RuleBuilderResult.Rule, ruleSpecification.RuleAddPriorityOption - ).ConfigureAwait(false); + ); if (!ruleOperationResult.IsSuccess) { diff --git a/samples/Rules.Framework.WebUI.Sample/Engine/RulesEngineProvider.cs b/samples/Rules.Framework.WebUI.Sample/Engine/RulesEngineProvider.cs index 8d8d733c..ac5c8a1c 100644 --- a/samples/Rules.Framework.WebUI.Sample/Engine/RulesEngineProvider.cs +++ b/samples/Rules.Framework.WebUI.Sample/Engine/RulesEngineProvider.cs @@ -3,23 +3,19 @@ namespace Rules.Framework.WebUI.Sample.Engine using System; using System.Threading; using System.Threading.Tasks; - using global::Rules.Framework.Providers.InMemory; - using global::Rules.Framework.WebUI.Sample.Enums; internal class RulesEngineProvider { - private readonly Lazy>> lazyRulesEngine; + private readonly Lazy> lazyRulesEngine; public RulesEngineProvider(RulesBuilder rulesBuilder) { - lazyRulesEngine = new Lazy>>(async () => + lazyRulesEngine = new Lazy>(async () => { var rulesEngine = RulesEngineBuilder .CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetInMemoryDataSource() - .Configure(c => c.PriotityCriteria = PriorityCriterias.TopmostRuleWins) + .Configure(c => c.PriorityCriteria = PriorityCriterias.TopmostRuleWins) .Build(); await rulesBuilder.BuildAsync(rulesEngine).ConfigureAwait(false); @@ -28,7 +24,7 @@ public RulesEngineProvider(RulesBuilder rulesBuilder) }, LazyThreadSafetyMode.ExecutionAndPublication); } - public Task> GetRulesEngineAsync() + public Task GetRulesEngineAsync() => lazyRulesEngine.Value; } } \ No newline at end of file diff --git a/samples/Rules.Framework.WebUI.Sample/Enums/ConditionTypes.cs b/samples/Rules.Framework.WebUI.Sample/Enums/ConditionNames.cs similarity index 57% rename from samples/Rules.Framework.WebUI.Sample/Enums/ConditionTypes.cs rename to samples/Rules.Framework.WebUI.Sample/Enums/ConditionNames.cs index 46b77fd0..0809506b 100644 --- a/samples/Rules.Framework.WebUI.Sample/Enums/ConditionTypes.cs +++ b/samples/Rules.Framework.WebUI.Sample/Enums/ConditionNames.cs @@ -1,29 +1,29 @@ namespace Rules.Framework.WebUI.Sample.Enums { - public enum ConditionTypes + public enum ConditionNames { /// - /// no condition type defined + /// no condition defined /// None = 0, /// - /// condition type to filter by royal numbers + /// condition to filter by royal numbers /// RoyalNumber = 1, /// - /// condition type to filter if the number can be divided by 3 + /// condition to filter if the number can be divided by 3 /// CanNumberBeDividedBy3 = 2, /// - /// condition type to filter if the number is prime + /// condition to filter if the number is prime /// IsPrimeNumber = 3, /// - /// condition type to filter number sums + /// condition to filter number sums /// SumAll = 4 } diff --git a/samples/Rules.Framework.WebUI.Sample/Enums/ContentTypes.cs b/samples/Rules.Framework.WebUI.Sample/Enums/RulesetNames.cs similarity index 89% rename from samples/Rules.Framework.WebUI.Sample/Enums/ContentTypes.cs rename to samples/Rules.Framework.WebUI.Sample/Enums/RulesetNames.cs index 78d10b65..f9df938b 100644 --- a/samples/Rules.Framework.WebUI.Sample/Enums/ContentTypes.cs +++ b/samples/Rules.Framework.WebUI.Sample/Enums/RulesetNames.cs @@ -1,6 +1,6 @@ namespace Rules.Framework.WebUI.Sample.Enums { - public enum ContentTypes + public enum RulesetNames { TestNumber = 0, @@ -18,4 +18,4 @@ public enum ContentTypes TestBlob = 7 } -} +} \ No newline at end of file diff --git a/samples/Rules.Framework.WebUI.Sample/Program.cs b/samples/Rules.Framework.WebUI.Sample/Program.cs index a28f73c5..94ba3684 100644 --- a/samples/Rules.Framework.WebUI.Sample/Program.cs +++ b/samples/Rules.Framework.WebUI.Sample/Program.cs @@ -1,6 +1,5 @@ namespace Rules.Framework.WebUI.Sample { - using global::Rules.Framework.Extension; using global::Rules.Framework.WebUI.Sample.Engine; using global::Rules.Framework.WebUI.Sample.ReadmeExample; using global::Rules.Framework.WebUI.Sample.Rules; @@ -12,7 +11,22 @@ public static void Main(string[] args) var builder = WebApplication.CreateBuilder(args); // Add services to the container. - builder.Services.AddControllersWithViews(); + builder.Services.AddControllersWithViews() + .AddRulesFrameworkWebUI(registrar => + { + registrar.AddInstance("Readme example", (_, _) => new BasicRulesEngineExample().RulesEngine) + .AddInstance("Random rules example", async (_, _) => + { + var rulesProvider = new RulesEngineProvider(new RulesBuilder(new List() + { + new RulesRandomFactory() + })); + + return await rulesProvider.GetRulesEngineAsync(); + }); + }); + + builder.Logging.SetMinimumLevel(LogLevel.Trace).AddConsole(); var app = builder.Build(); @@ -25,15 +39,18 @@ public static void Main(string[] args) app.UseHsts(); } - app.UseHttpsRedirection(); - app.UseStaticFiles(); app.UseRouting(); + app.UseAntiforgery(); + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); - AddRulesFrameworkUI(app, useReadmeExample: false); + app.UseRulesFrameworkWebUI(opt => + { + opt.DocumentTitle = "Sample rules"; + }); app.MapControllerRoute( name: "default", @@ -41,28 +58,5 @@ public static void Main(string[] args) app.Run(); } - - private static void AddRulesFrameworkUI(IApplicationBuilder app, bool useReadmeExample = false) - { - if (useReadmeExample) - { - app.UseRulesFrameworkWebUI(new BasicRulesEngineExample().RulesEngine.CreateGenericEngine()); - - return; - } - - var rulesProvider = new RulesEngineProvider(new RulesBuilder(new List() - { - new RulesRandomFactory() - })); - - var rulesEngine = rulesProvider - .GetRulesEngineAsync() - .ConfigureAwait(false) - .GetAwaiter() - .GetResult(); - - app.UseRulesFrameworkWebUI(rulesEngine.CreateGenericEngine()); - } } } \ No newline at end of file diff --git a/samples/Rules.Framework.WebUI.Sample/Properties/launchSettings.json b/samples/Rules.Framework.WebUI.Sample/Properties/launchSettings.json index 7f60a9ce..2d5650b7 100644 --- a/samples/Rules.Framework.WebUI.Sample/Properties/launchSettings.json +++ b/samples/Rules.Framework.WebUI.Sample/Properties/launchSettings.json @@ -12,7 +12,7 @@ "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, - "launchUrl": "rules/index.html", + "launchUrl": "rules-ui/instance-selection", "applicationUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" diff --git a/samples/Rules.Framework.WebUI.Sample/ReadmeExample/BasicConditionType.cs b/samples/Rules.Framework.WebUI.Sample/ReadmeExample/BasicConditionNames.cs similarity index 75% rename from samples/Rules.Framework.WebUI.Sample/ReadmeExample/BasicConditionType.cs rename to samples/Rules.Framework.WebUI.Sample/ReadmeExample/BasicConditionNames.cs index 34e83242..9adf2580 100644 --- a/samples/Rules.Framework.WebUI.Sample/ReadmeExample/BasicConditionType.cs +++ b/samples/Rules.Framework.WebUI.Sample/ReadmeExample/BasicConditionNames.cs @@ -1,6 +1,6 @@ namespace Rules.Framework.WebUI.Sample.ReadmeExample { - public enum BasicConditionType + public enum BasicConditionNames { ClientType = 1, Country = 2 diff --git a/samples/Rules.Framework.WebUI.Sample/ReadmeExample/BasicRulesEngineExample.cs b/samples/Rules.Framework.WebUI.Sample/ReadmeExample/BasicRulesEngineExample.cs index 3d5ae365..fdcac8cb 100644 --- a/samples/Rules.Framework.WebUI.Sample/ReadmeExample/BasicRulesEngineExample.cs +++ b/samples/Rules.Framework.WebUI.Sample/ReadmeExample/BasicRulesEngineExample.cs @@ -1,8 +1,6 @@ namespace Rules.Framework.WebUI.Sample.ReadmeExample { using System; - using global::Rules.Framework.Core; - using global::Rules.Framework.Providers.InMemory; using global::Rules.Framework.WebUI.Sample.Engine; internal class BasicRulesEngineExample @@ -11,63 +9,71 @@ public BasicRulesEngineExample() { this.RulesEngine = RulesEngineBuilder .CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetInMemoryDataSource() .Configure(c => c.PriorityCriteria = PriorityCriterias.TopmostRuleWins) .Build(); + this.CreateRulesets(); + var rules = this.CreateRules(); this.AddRules(rules); } - public RulesEngine RulesEngine { get; } + public IRulesEngine RulesEngine { get; } - protected void AddRules(IEnumerable> ruleSpecifications) + protected void AddRules(IEnumerable> ruleSpecifications) { foreach (var ruleSpecification in ruleSpecifications) { this.RulesEngine.AddRuleAsync(ruleSpecification.RuleBuilderResult.Rule, ruleSpecification.RuleAddPriorityOption) - .ConfigureAwait(false) .GetAwaiter() .GetResult(); } } - private IEnumerable> CreateRules() + protected void CreateRulesets() + { + foreach (var rulesetName in Enum.GetValues()) + { + this.RulesEngine.CreateRulesetAsync(rulesetName.ToString()) + .GetAwaiter() + .GetResult(); + } + } + + private IEnumerable> CreateRules() { - var ruleForPremiumFreeSampleJanuary = RuleBuilder - .NewRule() - .WithName("Rule for January sample for premium clients.") - .WithContent(BasicContentType.FreeSample, "SmallPerfumeSample") - .WithCondition(BasicConditionType.ClientType, Operators.Equal, "Premium") - .WithDatesInterval(new DateTime(2023, 01, 01), new DateTime(2023, 02, 01)) + var ruleForPremiumFreeSampleJanuary = Rule.Create("Rule for January sample for premium clients.") + .InRuleset(BasicRulesetNames.FreeSample) + .SetContent("SmallPerfumeSample") + .Since(new DateTime(2023, 01, 01)) + .Until(new DateTime(2023, 02, 01)) + .ApplyWhen(BasicConditionNames.ClientType, Operators.Equal, "Premium") .Build(); - var ruleForPremiumFreeSampleApril = RuleBuilder - .NewRule() - .WithName("Rule for April sample for premium clients.") - .WithContent(BasicContentType.FreeSample, "ShampooSample") - .WithCondition(BasicConditionType.ClientType, Operators.Equal, "Premium") - .WithDatesInterval(new DateTime(2023, 04, 01), new DateTime(2023, 05, 01)) + var ruleForPremiumFreeSampleApril = Rule.Create("Rule for April sample for premium clients.") + .InRuleset(BasicRulesetNames.FreeSample) + .SetContent("ShampooSample") + .Since(new DateTime(2023, 04, 01)) + .Until(new DateTime(2023, 05, 01)) + .ApplyWhen(BasicConditionNames.ClientType, Operators.Equal, "Premium") .Build(); - var ruleForPremiumFreeSampleSeptember = RuleBuilder - .NewRule() - .WithName("Rule for September sample for premium clients.") - .WithContent(BasicContentType.FreeSample, "ConditionerSample") - .WithCondition(BasicConditionType.ClientType, Operators.Equal, "Premium") - .WithDatesInterval(new DateTime(2023, 09, 01), new DateTime(2023, 10, 01)) + var ruleForPremiumFreeSampleSeptember = Rule.Create("Rule for September sample for premium clients.") + .InRuleset(BasicRulesetNames.FreeSample) + .SetContent("ConditionerSample") + .Since(new DateTime(2023, 09, 01)).Until(new DateTime(2023, 10, 01)) + .ApplyWhen(BasicConditionNames.ClientType, Operators.Equal, "Premium") .Build(); - return new List>() + return new List>() { - new RuleSpecificationBase( + new RuleSpecificationBase( ruleForPremiumFreeSampleJanuary, RuleAddPriorityOption.ByPriorityNumber(1)), - new RuleSpecificationBase( + new RuleSpecificationBase( ruleForPremiumFreeSampleApril, RuleAddPriorityOption.ByPriorityNumber(2)), - new RuleSpecificationBase( + new RuleSpecificationBase( ruleForPremiumFreeSampleSeptember, RuleAddPriorityOption.ByPriorityNumber(3)) }; } diff --git a/samples/Rules.Framework.WebUI.Sample/ReadmeExample/BasicContentType.cs b/samples/Rules.Framework.WebUI.Sample/ReadmeExample/BasicRulesetNames.cs similarity index 77% rename from samples/Rules.Framework.WebUI.Sample/ReadmeExample/BasicContentType.cs rename to samples/Rules.Framework.WebUI.Sample/ReadmeExample/BasicRulesetNames.cs index 891e6bec..495c47d7 100644 --- a/samples/Rules.Framework.WebUI.Sample/ReadmeExample/BasicContentType.cs +++ b/samples/Rules.Framework.WebUI.Sample/ReadmeExample/BasicRulesetNames.cs @@ -1,6 +1,6 @@ namespace Rules.Framework.WebUI.Sample.ReadmeExample { - public enum BasicContentType + public enum BasicRulesetNames { FreeSample = 1, ShippingCost = 2 diff --git a/samples/Rules.Framework.WebUI.Sample/Rules.Framework.WebUI.Sample.csproj b/samples/Rules.Framework.WebUI.Sample/Rules.Framework.WebUI.Sample.csproj index 2155439f..c6467180 100644 --- a/samples/Rules.Framework.WebUI.Sample/Rules.Framework.WebUI.Sample.csproj +++ b/samples/Rules.Framework.WebUI.Sample/Rules.Framework.WebUI.Sample.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 enable enable diff --git a/samples/Rules.Framework.WebUI.Sample/Rules/RulesRandomFactory.cs b/samples/Rules.Framework.WebUI.Sample/Rules/RulesRandomFactory.cs index 113cdaba..7e41098b 100644 --- a/samples/Rules.Framework.WebUI.Sample/Rules/RulesRandomFactory.cs +++ b/samples/Rules.Framework.WebUI.Sample/Rules/RulesRandomFactory.cs @@ -2,12 +2,11 @@ namespace Rules.Framework.WebUI.Sample.Rules { using System; using System.Collections.Generic; - using global::Rules.Framework.Builder; - using global::Rules.Framework.Core; + using global::Rules.Framework.Builder.Generic; using global::Rules.Framework.WebUI.Sample.Engine; using global::Rules.Framework.WebUI.Sample.Enums; - internal class RulesRandomFactory : IContentTypes + internal class RulesRandomFactory : IRuleSpecificationsProvider { private readonly int finalNumber = 50; private readonly int intialNumber = 10; @@ -18,25 +17,37 @@ public RulesRandomFactory() this.random = new Random(); } + public RulesetNames[] Rulesets => new[] + { + RulesetNames.TestDateTime, + RulesetNames.TestDecimal, + RulesetNames.TestLong, + RulesetNames.TestBoolean, + RulesetNames.TestShort, + RulesetNames.TestNumber, + RulesetNames.TestString, + RulesetNames.TestBlob, + }; + public IEnumerable GetRulesSpecifications() { var currentYear = DateTime.UtcNow.Year; var rulesSpecifications = new List(); - foreach (var contentType in Enum.GetValues(typeof(ContentTypes))) + foreach (var ruleset in Enum.GetValues(typeof(RulesetNames)).Cast()) { for (var i = 1; i < random.Next(intialNumber, finalNumber); i++) { var dateBegin = CreateRandomDateBegin(currentYear); - Add(CreateMultipleRule((ContentTypes)contentType, i, dateBegin, CreateRandomDateEnd(dateBegin)), + Add(CreateMultipleRule(ruleset, i, dateBegin, CreateRandomDateEnd(dateBegin)), RuleAddPriorityOption.ByPriorityNumber(i), rulesSpecifications); } var deactiveDateBegin = CreateRandomDateBegin(currentYear); - Add(CreateMultipleRule((ContentTypes)contentType, finalNumber, deactiveDateBegin, CreateRandomDateEnd(deactiveDateBegin), isActive: false), + Add(CreateMultipleRule(ruleset, finalNumber, deactiveDateBegin, CreateRandomDateEnd(deactiveDateBegin), isActive: false), RuleAddPriorityOption.ByPriorityNumber(finalNumber), rulesSpecifications); } @@ -44,38 +55,38 @@ public IEnumerable GetRulesSpecifications() return rulesSpecifications; } - private static RuleBuilderResult CreateMultipleRule( - ContentTypes contentTypes, + private static RuleBuilderResult CreateMultipleRule( + RulesetNames ruleset, int value, DateTime dateBegin, DateTime? dateEnd, - bool isActive = true) => RuleBuilder - .NewRule() - .WithName($"Multi rule for test {contentTypes} {value}") - .WithContent(contentTypes, new { Value = value }) - .WithDatesInterval(dateBegin, dateEnd) + bool isActive = true) => Rule.Create($"Multi rule for test {ruleset} {value}") + .InRuleset(ruleset) + .SetContent(new { Value = value }) + .Since(dateBegin) + .Until(dateEnd) .WithActive(isActive) - .WithCondition(rootCond => rootCond + .ApplyWhen(rootCond => rootCond .Or(o => o - .Value(ConditionTypes.RoyalNumber, Operators.Equal, 7) - .Value(ConditionTypes.SumAll, Operators.In, new int[] { 9, 8, 6 }) + .Value(ConditionNames.RoyalNumber, Operators.Equal, 7) + .Value(ConditionNames.SumAll, Operators.In, new int[] { 9, 8, 6 }) .And(a => a - .Value(ConditionTypes.IsPrimeNumber, Operators.Equal, false) - .Value(ConditionTypes.SumAll, Operators.StartsWith, "15") + .Value(ConditionNames.IsPrimeNumber, Operators.Equal, false) + .Value(ConditionNames.SumAll, Operators.StartsWith, "15") ) .And(a => a - .Value(ConditionTypes.CanNumberBeDividedBy3, Operators.Equal, false) - .Value(ConditionTypes.SumAll, Operators.NotEqual, string.Empty) + .Value(ConditionNames.CanNumberBeDividedBy3, Operators.Equal, false) + .Value(ConditionNames.SumAll, Operators.NotEqual, string.Empty) ) .And(a => a - .Value(ConditionTypes.IsPrimeNumber, Operators.Equal, true) - .Value(ConditionTypes.SumAll, Operators.StartsWith, "5") - .Value(ConditionTypes.CanNumberBeDividedBy3, Operators.Equal, false) + .Value(ConditionNames.IsPrimeNumber, Operators.Equal, true) + .Value(ConditionNames.SumAll, Operators.StartsWith, "5") + .Value(ConditionNames.CanNumberBeDividedBy3, Operators.Equal, false) ))) .Build(); private void Add( - RuleBuilderResult rule, + RuleBuilderResult rule, RuleAddPriorityOption ruleAddPriorityOption, List rulesSpecifications) => rulesSpecifications.Add(new RuleSpecification(rule, ruleAddPriorityOption)); diff --git a/samples/Rules.Framework.WebUI.Sample/appsettings.Development.json b/samples/Rules.Framework.WebUI.Sample/appsettings.Development.json index 0c208ae9..ce466063 100644 --- a/samples/Rules.Framework.WebUI.Sample/appsettings.Development.json +++ b/samples/Rules.Framework.WebUI.Sample/appsettings.Development.json @@ -1,8 +1,9 @@ { - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" + "DetailedErrors": true, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } } - } -} +} \ No newline at end of file diff --git a/src/Rules.Framework.Providers.MongoDb/DataModel/ConditionNodeDataModel.cs b/src/Rules.Framework.Providers.MongoDb/DataModel/ConditionNodeDataModel.cs index 858f353f..a35084d3 100644 --- a/src/Rules.Framework.Providers.MongoDb/DataModel/ConditionNodeDataModel.cs +++ b/src/Rules.Framework.Providers.MongoDb/DataModel/ConditionNodeDataModel.cs @@ -5,7 +5,7 @@ namespace Rules.Framework.Providers.MongoDb.DataModel using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; using MongoDB.Bson.Serialization.Options; - using Rules.Framework.Core; + using Rules.Framework; [BsonKnownTypes(typeof(ComposedConditionNodeDataModel), typeof(ValueConditionNodeDataModel))] internal class ConditionNodeDataModel diff --git a/src/Rules.Framework.Providers.MongoDb/DataModel/RuleDataModel.cs b/src/Rules.Framework.Providers.MongoDb/DataModel/RuleDataModel.cs index 25f4078b..6042d3c5 100644 --- a/src/Rules.Framework.Providers.MongoDb/DataModel/RuleDataModel.cs +++ b/src/Rules.Framework.Providers.MongoDb/DataModel/RuleDataModel.cs @@ -11,9 +11,6 @@ internal sealed class RuleDataModel [BsonElement(Order = 7)] public dynamic Content { get; set; } - [BsonElement(Order = 2)] - public string ContentType { get; set; } - [BsonElement(Order = 3)] public DateTime DateBegin { get; set; } @@ -28,5 +25,8 @@ internal sealed class RuleDataModel [BsonElement(Order = 6)] public ConditionNodeDataModel RootCondition { get; set; } + + [BsonElement(Order = 2)] + public string Ruleset { get; set; } } } \ No newline at end of file diff --git a/src/Rules.Framework.Providers.MongoDb/DataModel/RulesetDataModel.cs b/src/Rules.Framework.Providers.MongoDb/DataModel/RulesetDataModel.cs new file mode 100644 index 00000000..3e300b81 --- /dev/null +++ b/src/Rules.Framework.Providers.MongoDb/DataModel/RulesetDataModel.cs @@ -0,0 +1,17 @@ +namespace Rules.Framework.Providers.MongoDb.DataModel +{ + using System; + using MongoDB.Bson.Serialization.Attributes; + + internal class RulesetDataModel + { + [BsonElement("creation", Order = 3)] + public DateTime Creation { get; set; } + + [BsonElement("id", Order = 1)] + public Guid Id { get; set; } + + [BsonElement("name", Order = 2)] + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/src/Rules.Framework.Providers.MongoDb/DataModel/ValueConditionNodeDataModel.cs b/src/Rules.Framework.Providers.MongoDb/DataModel/ValueConditionNodeDataModel.cs index 6a4613a1..84b4c683 100644 --- a/src/Rules.Framework.Providers.MongoDb/DataModel/ValueConditionNodeDataModel.cs +++ b/src/Rules.Framework.Providers.MongoDb/DataModel/ValueConditionNodeDataModel.cs @@ -1,23 +1,23 @@ namespace Rules.Framework.Providers.MongoDb.DataModel { using MongoDB.Bson.Serialization.Attributes; - using Rules.Framework.Core; + using Rules.Framework; [BsonDiscriminator("value")] internal sealed class ValueConditionNodeDataModel : ConditionNodeDataModel { [BsonElement(Order = 1)] - public string ConditionType { get; set; } + public string Condition { get; set; } [BsonElement(Order = 2)] [BsonRepresentation(MongoDB.Bson.BsonType.String)] public DataTypes DataType { get; set; } - - [BsonElement(Order = 3)] - [BsonRepresentation(MongoDB.Bson.BsonType.String)] - public Operators Operator { get; set; } [BsonElement(Order = 4)] public object Operand { get; set; } + + [BsonElement(Order = 3)] + [BsonRepresentation(MongoDB.Bson.BsonType.String)] + public Operators Operator { get; set; } } } \ No newline at end of file diff --git a/src/Rules.Framework.Providers.MongoDb/IRuleFactory.cs b/src/Rules.Framework.Providers.MongoDb/IRuleFactory.cs index 30ed89dc..ed43d3ee 100644 --- a/src/Rules.Framework.Providers.MongoDb/IRuleFactory.cs +++ b/src/Rules.Framework.Providers.MongoDb/IRuleFactory.cs @@ -1,12 +1,11 @@ namespace Rules.Framework.Providers.MongoDb { - using Rules.Framework.Core; using Rules.Framework.Providers.MongoDb.DataModel; - internal interface IRuleFactory + internal interface IRuleFactory { - Rule CreateRule(RuleDataModel ruleDataModel); + Rule CreateRule(RuleDataModel ruleDataModel); - RuleDataModel CreateRule(Rule rule); + RuleDataModel CreateRule(Rule rule); } } \ No newline at end of file diff --git a/src/Rules.Framework.Providers.MongoDb/MongoDbProviderRulesDataSource.cs b/src/Rules.Framework.Providers.MongoDb/MongoDbProviderRulesDataSource.cs index dd5893b5..a69eb0c3 100644 --- a/src/Rules.Framework.Providers.MongoDb/MongoDbProviderRulesDataSource.cs +++ b/src/Rules.Framework.Providers.MongoDb/MongoDbProviderRulesDataSource.cs @@ -5,25 +5,22 @@ namespace Rules.Framework.Providers.MongoDb using System.Linq; using System.Threading.Tasks; using MongoDB.Driver; - using Rules.Framework.Core; using Rules.Framework.Providers.MongoDb.DataModel; /// /// The rules data source implementation for usage backed with a Mongo DB database. /// - /// The type of the content type. - /// The type of the condition type. - /// - public class MongoDbProviderRulesDataSource : IRulesDataSource + /// + public class MongoDbProviderRulesDataSource : IRulesDataSource { private readonly IMongoDatabase mongoDatabase; private readonly MongoDbProviderSettings mongoDbProviderSettings; - private readonly IRuleFactory ruleFactory; + private readonly IRuleFactory ruleFactory; internal MongoDbProviderRulesDataSource( IMongoClient mongoClient, MongoDbProviderSettings mongoDbProviderSettings, - IRuleFactory ruleFactory) + IRuleFactory ruleFactory) { if (mongoClient is null) { @@ -39,7 +36,7 @@ internal MongoDbProviderRulesDataSource( /// Adds a new rule to data source. /// /// The rule. - public async Task AddRuleAsync(Rule rule) + public async Task AddRuleAsync(Rule rule) { var rulesCollection = this.mongoDatabase.GetCollection(this.mongoDbProviderSettings.RulesCollectionName); @@ -49,19 +46,37 @@ public async Task AddRuleAsync(Rule rule) } /// - /// Gets the rules categorized with specified between - /// and . + /// Creates a new ruleset on the data source. /// - /// the content type categorization. + /// Type of the content. + public async Task CreateRulesetAsync(string contentType) + { + var rulesetsCollection = this.mongoDatabase.GetCollection(this.mongoDbProviderSettings.RulesetsCollectionName); + + var rulesetDataModel = new RulesetDataModel + { + Creation = DateTime.UtcNow, + Id = Guid.NewGuid(), + Name = contentType, + }; + + await rulesetsCollection.InsertOneAsync(rulesetDataModel).ConfigureAwait(false); + } + + /// + /// Gets the rules categorized with specified between and . + /// + /// the ruleset name. /// the filtering begin date. /// the filtering end date. /// - public async Task>> GetRulesAsync(TContentType contentType, DateTime dateBegin, DateTime dateEnd) + public async Task> GetRulesAsync(string ruleset, DateTime dateBegin, DateTime dateEnd) { - var getRulesByContentTypeAndDatesInterval = MongoDbProviderRulesDataSource - .BuildFilterByContentTypeAndDatesInterval(contentType, dateBegin, dateEnd); + var getRulesByRulesetAndDatesInterval = MongoDbProviderRulesDataSource + .BuildFilterByContentTypeAndDatesInterval(ruleset, dateBegin, dateEnd); - return await this.GetRulesAsync(getRulesByContentTypeAndDatesInterval).ConfigureAwait(false); + return await this.GetRulesAsync(getRulesByRulesetAndDatesInterval).ConfigureAwait(false); } /// @@ -69,24 +84,47 @@ public async Task>> GetRulesAsync /// /// The rules filter arguments. /// - public Task>> GetRulesByAsync(RulesFilterArgs rulesFilterArgs) + public Task> GetRulesByAsync(RulesFilterArgs rulesFilterArgs) { if (rulesFilterArgs is null) { throw new ArgumentNullException(nameof(rulesFilterArgs)); } - var filterDefinition = MongoDbProviderRulesDataSource + var filterDefinition = MongoDbProviderRulesDataSource .BuildFilterFromRulesFilterArgs(rulesFilterArgs); return this.GetRulesAsync(filterDefinition); } + /// + /// Gets the content types from the data source. + /// + /// + public async Task> GetRulesetsAsync() + { + var rulesetsCollection = this.mongoDatabase.GetCollection(this.mongoDbProviderSettings.RulesetsCollectionName); + + var findAllFilterDefinition = FilterDefinition.Empty; + + var resultsCursor = await rulesetsCollection.FindAsync(findAllFilterDefinition).ConfigureAwait(false); + var rulesets = new List(); + while (await resultsCursor.MoveNextAsync().ConfigureAwait(false)) + { + foreach (var rulesetDataModel in resultsCursor.Current) + { + rulesets.Add(new Ruleset(rulesetDataModel.Name, rulesetDataModel.Creation)); + } + } + + return rulesets; + } + /// /// Updates the existent rule on data source. /// /// The rule. - public async Task UpdateRuleAsync(Rule rule) + public async Task UpdateRuleAsync(Rule rule) { var rulesCollection = this.mongoDatabase.GetCollection(this.mongoDbProviderSettings.RulesCollectionName); @@ -97,7 +135,7 @@ public async Task UpdateRuleAsync(Rule rule) var updateDefinitions = new UpdateDefinition[] { Builders.Update.Set(contentField, (object)ruleDataModel.Content), - Builders.Update.Set(r => r.ContentType, ruleDataModel.ContentType), + Builders.Update.Set(r => r.Ruleset, ruleDataModel.Ruleset), Builders.Update.Set(r => r.DateBegin, ruleDataModel.DateBegin), Builders.Update.Set(r => r.DateEnd, ruleDataModel.DateEnd), Builders.Update.Set(r => r.Name, ruleDataModel.Name), @@ -111,9 +149,9 @@ public async Task UpdateRuleAsync(Rule rule) await rulesCollection.UpdateOneAsync(filterDefinition, updateDefinition).ConfigureAwait(false); } - private static FilterDefinition BuildFilterByContentTypeAndDatesInterval(TContentType contentType, DateTime dateBegin, DateTime dateEnd) + private static FilterDefinition BuildFilterByContentTypeAndDatesInterval(string ruleset, DateTime dateBegin, DateTime dateEnd) { - var contentTypeFilter = Builders.Filter.Eq(x => x.ContentType, contentType.ToString()); + var rulesetFilter = Builders.Filter.Eq(x => x.Ruleset, ruleset); var datesFilter = Builders.Filter.And( Builders.Filter.Lte(rule => rule.DateBegin, dateEnd), @@ -122,16 +160,16 @@ private static FilterDefinition BuildFilterByContentTypeAndDatesI Builders.Filter.Eq(rule => rule.DateEnd, null)) ); - return Builders.Filter.And(contentTypeFilter, datesFilter); + return Builders.Filter.And(rulesetFilter, datesFilter); } - private static FilterDefinition BuildFilterFromRulesFilterArgs(RulesFilterArgs rulesFilterArgs) + private static FilterDefinition BuildFilterFromRulesFilterArgs(RulesFilterArgs rulesFilterArgs) { var filtersToApply = new List>(3); - if (!object.Equals(rulesFilterArgs.ContentType, default(TContentType))) + if (!object.Equals(rulesFilterArgs.Ruleset, default(string))) { - filtersToApply.Add(Builders.Filter.Eq(x => x.ContentType, rulesFilterArgs.ContentType.ToString())); + filtersToApply.Add(Builders.Filter.Eq(x => x.Ruleset, rulesFilterArgs.Ruleset)); } if (!string.IsNullOrWhiteSpace(rulesFilterArgs.Name)) @@ -147,7 +185,7 @@ private static FilterDefinition BuildFilterFromRulesFilterArgs(Ru return filtersToApply.Any() ? Builders.Filter.And(filtersToApply) : Builders.Filter.Empty; } - private async Task>> GetRulesAsync(FilterDefinition getRulesByContentTypeAndDatesInterval) + private async Task> GetRulesAsync(FilterDefinition getRulesByContentTypeAndDatesInterval) { var rulesCollection = this.mongoDatabase.GetCollection(this.mongoDbProviderSettings.RulesCollectionName); @@ -158,8 +196,8 @@ private async Task>> GetRulesAsyn // We won't use LINQ from this point onwards to avoid projected queries to database at a // later point. This approach assures the definitive realization of the query results // and does not produce side effects later on. - var result = new Rule[fetchedRules.Count]; - for (int i = 0; i < result.Length; i++) + var result = new Rule[fetchedRules.Count]; + for (var i = 0; i < result.Length; i++) { result[i] = this.ruleFactory.CreateRule(fetchedRules[i]); } @@ -167,4 +205,4 @@ private async Task>> GetRulesAsyn return result; } } -} +} \ No newline at end of file diff --git a/src/Rules.Framework.Providers.MongoDb/MongoDbProviderSettings.cs b/src/Rules.Framework.Providers.MongoDb/MongoDbProviderSettings.cs index 94fd89c3..1a0be271 100644 --- a/src/Rules.Framework.Providers.MongoDb/MongoDbProviderSettings.cs +++ b/src/Rules.Framework.Providers.MongoDb/MongoDbProviderSettings.cs @@ -5,20 +5,32 @@ namespace Rules.Framework.Providers.MongoDb /// public class MongoDbProviderSettings { + private const string DefaultRulesetsCollectionName = "rulesets"; + + /// + /// Initializes a new instance of the class. + /// + public MongoDbProviderSettings() + { + this.RulesetsCollectionName = DefaultRulesetsCollectionName; + } + /// /// Gets or sets the name of the database. /// - /// - /// The name of the database. - /// + /// The name of the database. public string DatabaseName { get; set; } /// /// Gets or sets the name of the rules collection. /// - /// - /// The name of the rules collection. - /// + /// The name of the rules collection. public string RulesCollectionName { get; set; } + + /// + /// Gets or sets the name of the rulesets collection. + /// + /// The name of the content types collection. + public string RulesetsCollectionName { get; set; } } } \ No newline at end of file diff --git a/src/Rules.Framework.Providers.MongoDb/MongoDbRulesDataSourceInitializer.cs b/src/Rules.Framework.Providers.MongoDb/MongoDbRulesDataSourceInitializer.cs index 5850611f..1fb83f7a 100644 --- a/src/Rules.Framework.Providers.MongoDb/MongoDbRulesDataSourceInitializer.cs +++ b/src/Rules.Framework.Providers.MongoDb/MongoDbRulesDataSourceInitializer.cs @@ -34,27 +34,49 @@ public static async Task InitializeAsync(IMongoClient mongoClient, MongoDbProvid WriteConcern = WriteConcern.Acknowledged, }); - await CreateRulesCollectionIfNotExists(mongoDatabase, mongoDbProviderSettings).ConfigureAwait(false); - - var rulesCollection = mongoDatabase.GetCollection(mongoDbProviderSettings.RulesCollectionName); - - await CreateIndexesIfNotExists(rulesCollection, mongoDbProviderSettings).ConfigureAwait(false); + await CreateCollectionsIfNotExists(mongoDatabase, mongoDbProviderSettings).ConfigureAwait(false); + await CreateIndexesIfNotExists(mongoDatabase, mongoDbProviderSettings).ConfigureAwait(false); // Mark as initialized as this initialization code is never run anymore throughout the // app lifecycle. isInitialized = true; } - private static async Task CreateIndexesIfNotExists(IMongoCollection rulesCollection, MongoDbProviderSettings mongoDbProviderSettings) + private static async Task CreateCollectionsIfNotExists(IMongoDatabase mongoDatabase, MongoDbProviderSettings mongoDbProviderSettings) { - var getRulesIndex = $"ix_{mongoDbProviderSettings.RulesCollectionName.ToLower(CultureInfo.InvariantCulture)}_get_rules"; - var getRulesIndexKeysDefinition = Builders.IndexKeys - .Ascending("ContentType").Ascending("DateBegin").Ascending("DateEnd"); - await CreateIndexOnBackgroundAsync(rulesCollection, getRulesIndex, getRulesIndexKeysDefinition).ConfigureAwait(false); - var getRulesByIndex = $"ix_{mongoDbProviderSettings.RulesCollectionName.ToLower(CultureInfo.InvariantCulture)}_get_rules_by"; - var getRulesByIndexKeysDefinition = Builders.IndexKeys - .Ascending("ContentType").Ascending("Name").Ascending("Priority"); - await CreateIndexOnBackgroundAsync(rulesCollection, getRulesByIndex, getRulesByIndexKeysDefinition).ConfigureAwait(false); + var collectionsCursor = await mongoDatabase.ListCollectionNamesAsync().ConfigureAwait(false); + var collections = await collectionsCursor.ToListAsync().ConfigureAwait(false); + + if (!collections.Contains(mongoDbProviderSettings.RulesetsCollectionName)) + { + await mongoDatabase.CreateCollectionAsync(mongoDbProviderSettings.RulesetsCollectionName).ConfigureAwait(false); + } + + if (!collections.Contains(mongoDbProviderSettings.RulesCollectionName)) + { + await mongoDatabase.CreateCollectionAsync(mongoDbProviderSettings.RulesCollectionName).ConfigureAwait(false); + } + } + + private static async Task CreateIndexesIfNotExists(IMongoDatabase mongoDatabase, MongoDbProviderSettings mongoDbProviderSettings) + { + var rulesetsCollection = mongoDatabase.GetCollection(mongoDbProviderSettings.RulesetsCollectionName); + var rulesCollection = mongoDatabase.GetCollection(mongoDbProviderSettings.RulesCollectionName); + + var rulesetsByNameIndexName = $"ix_{mongoDbProviderSettings.RulesetsCollectionName.ToLower(CultureInfo.InvariantCulture)}_rulesets_by_name"; + var rulesetsByNameIndexKeys = Builders.IndexKeys + .Ascending(x => x.Name); + await CreateIndexOnBackgroundAsync(rulesetsCollection, rulesetsByNameIndexName, rulesetsByNameIndexKeys).ConfigureAwait(false); + + var rulesByRulesetAndDatesIndexName = $"ix_{mongoDbProviderSettings.RulesCollectionName.ToLower(CultureInfo.InvariantCulture)}_rules_by_ruleset_and_dates"; + var rulesByRulesetAndDatesIndexKeys = Builders.IndexKeys + .Ascending(x => x.Ruleset).Ascending(x => x.DateBegin).Ascending(x => x.DateEnd); + await CreateIndexOnBackgroundAsync(rulesCollection, rulesByRulesetAndDatesIndexName, rulesByRulesetAndDatesIndexKeys).ConfigureAwait(false); + + var rulesByRulesetNamePriorityIndexName = $"ix_{mongoDbProviderSettings.RulesCollectionName.ToLower(CultureInfo.InvariantCulture)}_rules_by_ruleset_name_priority"; + var rulesByRulesetNamePriorityIndexKeys = Builders.IndexKeys + .Ascending(x => x.Ruleset).Ascending(x => x.Name).Ascending(x => x.Priority); + await CreateIndexOnBackgroundAsync(rulesCollection, rulesByRulesetNamePriorityIndexName, rulesByRulesetNamePriorityIndexKeys).ConfigureAwait(false); } private static async Task CreateIndexOnBackgroundAsync(IMongoCollection mongoCollection, string indexName, IndexKeysDefinition indexKeysDefinition) @@ -67,15 +89,5 @@ private static async Task CreateIndexOnBackgroundAsync(IMongoCollection mo var createIndexModel = new CreateIndexModel(indexKeysDefinition, createIndexOptions); _ = await mongoCollection.Indexes.CreateOneAsync(createIndexModel).ConfigureAwait(false); } - - private static async Task CreateRulesCollectionIfNotExists(IMongoDatabase mongoDatabase, MongoDbProviderSettings mongoDbProviderSettings) - { - var collectionsCursor = await mongoDatabase.ListCollectionNamesAsync().ConfigureAwait(false); - var collections = await collectionsCursor.ToListAsync().ConfigureAwait(false); - if (!collections.Contains(mongoDbProviderSettings.RulesCollectionName)) - { - await mongoDatabase.CreateCollectionAsync(mongoDbProviderSettings.RulesCollectionName).ConfigureAwait(false); - } - } } } \ No newline at end of file diff --git a/src/Rules.Framework.Providers.MongoDb/MongoDbRulesDataSourceSelectorExtensions.cs b/src/Rules.Framework.Providers.MongoDb/MongoDbRulesDataSourceSelectorExtensions.cs index 6d97fa50..641b59f6 100644 --- a/src/Rules.Framework.Providers.MongoDb/MongoDbRulesDataSourceSelectorExtensions.cs +++ b/src/Rules.Framework.Providers.MongoDb/MongoDbRulesDataSourceSelectorExtensions.cs @@ -14,8 +14,6 @@ public static class MongoDbRulesDataSourceSelectorExtensions /// /// Sets the rules engine data source from a Mongo DB database. /// - /// The type of the content type. - /// The type of the condition type. /// The rules data source selector. /// The mongo client. /// The mongo database provider settings. @@ -23,8 +21,8 @@ public static class MongoDbRulesDataSourceSelectorExtensions /// /// rulesDataSourceSelector or mongoClient or mongoDbProviderSettings /// - public static IConfiguredRulesEngineBuilder SetMongoDbDataSource( - this IRulesDataSourceSelector rulesDataSourceSelector, + public static IConfiguredRulesEngineBuilder SetMongoDbDataSource( + this IRulesDataSourceSelector rulesDataSourceSelector, IMongoClient mongoClient, MongoDbProviderSettings mongoDbProviderSettings) { @@ -44,10 +42,10 @@ public static IConfiguredRulesEngineBuilder SetMon } MongoDbRulesDataSourceInitializer.InitializeAsync(mongoClient, mongoDbProviderSettings).GetAwaiter().GetResult(); - IContentSerializationProvider contentSerializationProvider = new DynamicToStrongTypeContentSerializationProvider(); - IRuleFactory ruleFactory = new RuleFactory(contentSerializationProvider); - MongoDbProviderRulesDataSource mongoDbProviderRulesDataSource - = new MongoDbProviderRulesDataSource( + IContentSerializationProvider contentSerializationProvider = new DynamicToStrongTypeContentSerializationProvider(); + IRuleFactory ruleFactory = new RuleFactory(contentSerializationProvider); + var mongoDbProviderRulesDataSource + = new MongoDbProviderRulesDataSource( mongoClient, mongoDbProviderSettings, ruleFactory); diff --git a/src/Rules.Framework.Providers.MongoDb/RuleFactory.cs b/src/Rules.Framework.Providers.MongoDb/RuleFactory.cs index c41fdfeb..6083aa5f 100644 --- a/src/Rules.Framework.Providers.MongoDb/RuleFactory.cs +++ b/src/Rules.Framework.Providers.MongoDb/RuleFactory.cs @@ -4,36 +4,33 @@ namespace Rules.Framework.Providers.MongoDb using System.Collections.Generic; using System.Globalization; using System.Linq; - using Rules.Framework.Builder; - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; + using Rules.Framework.ConditionNodes; using Rules.Framework.Providers.MongoDb.DataModel; using Rules.Framework.Serialization; - internal sealed class RuleFactory : IRuleFactory + internal sealed class RuleFactory : IRuleFactory { - private readonly IContentSerializationProvider contentSerializationProvider; + private readonly IContentSerializationProvider contentSerializationProvider; - public RuleFactory(IContentSerializationProvider contentSerializationProvider) + public RuleFactory(IContentSerializationProvider contentSerializationProvider) { this.contentSerializationProvider = contentSerializationProvider; } - public Rule CreateRule(RuleDataModel ruleDataModel) + public Rule CreateRule(RuleDataModel ruleDataModel) { if (ruleDataModel is null) { throw new ArgumentNullException(nameof(ruleDataModel)); } - var contentType = Parse(ruleDataModel.ContentType); - - var ruleBuilderResult = RuleBuilder.NewRule() - .WithName(ruleDataModel.Name) - .WithDatesInterval(ruleDataModel.DateBegin, ruleDataModel.DateEnd) + var ruleBuilderResult = Rule.Create(ruleDataModel.Name) + .InRuleset(ruleDataModel.Ruleset) + .SetContent((object)ruleDataModel.Content, this.contentSerializationProvider) + .Since(ruleDataModel.DateBegin) + .Until(ruleDataModel.DateEnd) .WithActive(ruleDataModel.Active ?? true) - .WithCondition(cnb => ruleDataModel.RootCondition is { } ? ConvertConditionNode(cnb, ruleDataModel.RootCondition) : null) - .WithSerializedContent(contentType, (object)ruleDataModel.Content, this.contentSerializationProvider) + .ApplyWhen(_ => ruleDataModel.RootCondition is { } ? ConvertConditionNode(ruleDataModel.RootCondition) : null) .Build(); if (!ruleBuilderResult.IsSuccess) @@ -53,7 +50,7 @@ public Rule CreateRule(RuleDataModel ruleDataModel return ruleBuilderResult.Rule; } - public RuleDataModel CreateRule(Rule rule) + public RuleDataModel CreateRule(Rule rule) { if (rule is null) { @@ -61,44 +58,42 @@ public RuleDataModel CreateRule(Rule rule) } var content = rule.ContentContainer.GetContentAs(); - var serializedContent = this.contentSerializationProvider.GetContentSerializer(rule.ContentContainer.ContentType).Serialize(content); + var serializedContent = this.contentSerializationProvider.GetContentSerializer(rule.Ruleset).Serialize(content); var ruleDataModel = new RuleDataModel { + Active = rule.Active, Content = serializedContent, - ContentType = Convert.ToString(rule.ContentContainer.ContentType, CultureInfo.InvariantCulture), DateBegin = rule.DateBegin, DateEnd = rule.DateEnd, Name = rule.Name, Priority = rule.Priority, - Active = rule.Active, RootCondition = rule.RootCondition is { } ? ConvertConditionNode(rule.RootCondition) : null, + Ruleset = rule.Ruleset, }; return ruleDataModel; } - private static IConditionNode ConvertConditionNode( - IConditionNodeBuilder conditionNodeBuilder, ConditionNodeDataModel conditionNodeDataModel) + private static IConditionNode ConvertConditionNode(ConditionNodeDataModel conditionNodeDataModel) { if (conditionNodeDataModel.LogicalOperator == LogicalOperators.Eval) { - return CreateValueConditionNode(conditionNodeBuilder, conditionNodeDataModel as ValueConditionNodeDataModel); + return CreateValueConditionNode(conditionNodeDataModel as ValueConditionNodeDataModel); } var composedConditionNodeDataModel = conditionNodeDataModel as ComposedConditionNodeDataModel; - - var composedConditionNodeBuilder = conditionNodeBuilder.AsComposed() - .WithLogicalOperator(composedConditionNodeDataModel.LogicalOperator); - var childConditionNodes = composedConditionNodeDataModel.ChildConditionNodes; - var count = childConditionNodes.Length; - var i = -1; - while (++i < count) + var childConditionNodeDataModels = composedConditionNodeDataModel.ChildConditionNodes; + var count = childConditionNodeDataModels.Length; + var childConditionNodes = new IConditionNode[count]; + for (var i = 0; i < count; i++) { - composedConditionNodeBuilder.AddCondition(cnb => ConvertConditionNode(cnb, childConditionNodes[i])); + childConditionNodes[i] = ConvertConditionNode(childConditionNodeDataModels[i]); } - var composedConditionNode = composedConditionNodeBuilder.Build(); + var composedConditionNode = new ComposedConditionNode( + composedConditionNodeDataModel.LogicalOperator, + childConditionNodes); foreach (var property in composedConditionNodeDataModel.Properties) { composedConditionNode.Properties[property.Key] = property.Value; @@ -107,13 +102,13 @@ private static IConditionNode ConvertConditionNode( return composedConditionNode; } - private static ValueConditionNodeDataModel ConvertValueConditionNode(ValueConditionNode valueConditionNode) + private static ValueConditionNodeDataModel ConvertValueConditionNode(ValueConditionNode valueConditionNode) { var properties = FilterProperties(valueConditionNode.Properties); return new ValueConditionNodeDataModel { - ConditionType = Convert.ToString(valueConditionNode.ConditionType, CultureInfo.InvariantCulture), + Condition = Convert.ToString(valueConditionNode.Condition, CultureInfo.InvariantCulture), LogicalOperator = LogicalOperators.Eval, DataType = valueConditionNode.DataType, Operand = valueConditionNode.Operand, @@ -122,55 +117,24 @@ private static ValueConditionNodeDataModel ConvertValueConditionNode(ValueCondit }; } - private static IConditionNode CreateValueConditionNode(IConditionNodeBuilder conditionNodeBuilder, ValueConditionNodeDataModel conditionNodeDataModel) + private static ValueConditionNode CreateValueConditionNode(ValueConditionNodeDataModel conditionNodeDataModel) { - TConditionType conditionType = Parse(conditionNodeDataModel.ConditionType); - var valueConditionNode = conditionNodeDataModel.DataType switch + var operand = conditionNodeDataModel.DataType switch { - DataTypes.Integer => conditionNodeBuilder.AsValued(conditionType) - .OfDataType() - .WithComparisonOperator(conditionNodeDataModel.Operator) - .SetOperand(Convert.ToInt32(conditionNodeDataModel.Operand, CultureInfo.InvariantCulture)) - .Build(), - DataTypes.Decimal => conditionNodeBuilder.AsValued(conditionType) - .OfDataType() - .WithComparisonOperator(conditionNodeDataModel.Operator) - .SetOperand(Convert.ToDecimal(conditionNodeDataModel.Operand, CultureInfo.InvariantCulture)) - .Build(), - DataTypes.String => conditionNodeBuilder.AsValued(conditionType) - .OfDataType() - .WithComparisonOperator(conditionNodeDataModel.Operator) - .SetOperand(Convert.ToString(conditionNodeDataModel.Operand, CultureInfo.InvariantCulture)) - .Build(), - DataTypes.Boolean => conditionNodeBuilder.AsValued(conditionType) - .OfDataType() - .WithComparisonOperator(conditionNodeDataModel.Operator) - .SetOperand(Convert.ToBoolean(conditionNodeDataModel.Operand, CultureInfo.InvariantCulture)) - .Build(), - - DataTypes.ArrayInteger => conditionNodeBuilder.AsValued(conditionType) - .OfDataType>() - .WithComparisonOperator(conditionNodeDataModel.Operator) - .SetOperand(conditionNodeDataModel.Operand as IEnumerable) - .Build(), - DataTypes.ArrayDecimal => conditionNodeBuilder.AsValued(conditionType) - .OfDataType>() - .WithComparisonOperator(conditionNodeDataModel.Operator) - .SetOperand(conditionNodeDataModel.Operand as IEnumerable) - .Build(), - DataTypes.ArrayString => conditionNodeBuilder.AsValued(conditionType) - .OfDataType>() - .WithComparisonOperator(conditionNodeDataModel.Operator) - .SetOperand(conditionNodeDataModel.Operand as IEnumerable) - .Build(), - DataTypes.ArrayBoolean => conditionNodeBuilder.AsValued(conditionType) - .OfDataType>() - .WithComparisonOperator(conditionNodeDataModel.Operator) - .SetOperand(conditionNodeDataModel.Operand as IEnumerable) - .Build(), + DataTypes.Integer => Convert.ToInt32(conditionNodeDataModel.Operand, CultureInfo.InvariantCulture), + DataTypes.Decimal => Convert.ToDecimal(conditionNodeDataModel.Operand, CultureInfo.InvariantCulture), + DataTypes.String => Convert.ToString(conditionNodeDataModel.Operand, CultureInfo.InvariantCulture), + DataTypes.Boolean => Convert.ToBoolean(conditionNodeDataModel.Operand, CultureInfo.InvariantCulture), + DataTypes.ArrayInteger or DataTypes.ArrayDecimal or DataTypes.ArrayString or DataTypes.ArrayBoolean => conditionNodeDataModel.Operand, _ => throw new NotSupportedException($"Unsupported data type: {conditionNodeDataModel.DataType}."), }; + var valueConditionNode = new ValueConditionNode( + conditionNodeDataModel.DataType, + conditionNodeDataModel.Condition, + conditionNodeDataModel.Operator, + operand); + foreach (var property in conditionNodeDataModel.Properties) { valueConditionNode.Properties[property.Key] = property.Value; @@ -195,13 +159,7 @@ private static IDictionary FilterProperties(IDictionary(string value) - => (T)Parse(value, typeof(T)); - - private static object Parse(string value, Type type) - => type.IsEnum ? Enum.Parse(type, value) : Convert.ChangeType(value, type, CultureInfo.InvariantCulture); - - private ConditionNodeDataModel ConvertComposedConditionNode(ComposedConditionNode composedConditionNode) + private ComposedConditionNodeDataModel ConvertComposedConditionNode(ComposedConditionNode composedConditionNode) { var conditionNodeDataModels = new ConditionNodeDataModel[composedConditionNode.ChildConditionNodes.Count()]; var i = 0; @@ -220,14 +178,14 @@ private ConditionNodeDataModel ConvertComposedConditionNode(ComposedConditionNod }; } - private ConditionNodeDataModel ConvertConditionNode(IConditionNode conditionNode) + private ConditionNodeDataModel ConvertConditionNode(IConditionNode conditionNode) { if (conditionNode.LogicalOperator == LogicalOperators.Eval) { - return ConvertValueConditionNode(conditionNode as ValueConditionNode); + return ConvertValueConditionNode(conditionNode as ValueConditionNode); } - return ConvertComposedConditionNode(conditionNode as ComposedConditionNode); + return ConvertComposedConditionNode(conditionNode as ComposedConditionNode); } } -} +} \ No newline at end of file diff --git a/src/Rules.Framework.Providers.MongoDb/Serialization/DynamicToStrongTypeContentSerializationProvider.cs b/src/Rules.Framework.Providers.MongoDb/Serialization/DynamicToStrongTypeContentSerializationProvider.cs index 926758ad..42a79da4 100644 --- a/src/Rules.Framework.Providers.MongoDb/Serialization/DynamicToStrongTypeContentSerializationProvider.cs +++ b/src/Rules.Framework.Providers.MongoDb/Serialization/DynamicToStrongTypeContentSerializationProvider.cs @@ -6,15 +6,14 @@ namespace Rules.Framework.Providers.MongoDb.Serialization /// /// Defines a content serialization provider for dynamic types. /// - /// The type of the content type. - /// - public class DynamicToStrongTypeContentSerializationProvider : IContentSerializationProvider + /// + public class DynamicToStrongTypeContentSerializationProvider : IContentSerializationProvider { private readonly Lazy contentSerializerLazy; /// /// Initializes a new instance of the class. + /// cref="DynamicToStrongTypeContentSerializationProvider"/> class. /// public DynamicToStrongTypeContentSerializationProvider() { @@ -28,6 +27,6 @@ public DynamicToStrongTypeContentSerializationProvider() /// /// the content type. /// the content serializer to deal with contents for specified content type. - public IContentSerializer GetContentSerializer(TContentType contentType) => this.contentSerializerLazy.Value; + public IContentSerializer GetContentSerializer(string contentType) => this.contentSerializerLazy.Value; } } \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/Assets/app.css b/src/Rules.Framework.WebUI/Assets/app.css new file mode 100644 index 00000000..7266677e --- /dev/null +++ b/src/Rules.Framework.WebUI/Assets/app.css @@ -0,0 +1,135 @@ +html, body { + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +a, .btn-link { + color: #006bb7; +} + +.btn-primary { + color: #fff; + background-color: #1b6ec2; + border-color: #1861ac; +} + +.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { + box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; +} + +.content { + padding-top: 1.1rem; +} + +h1:focus { + outline: none; +} + +.valid.modified:not([type=checkbox]) { + outline: 1px solid #26b050; +} + +.invalid { + outline: 1px solid #e50000; +} + +.validation-message { + color: #e50000; +} + +.blazor-error-boundary { + background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; + padding: 1rem 1rem 1rem 3.7rem; + color: white; +} + + .blazor-error-boundary::after { + content: "An error has occurred." + } + +.darker-border-checkbox.form-check-input { + border-color: #929292; +} + +.page { + position: relative; + display: flex; + flex-direction: column; +} + +main { + max-height: calc(100vh - 3.5rem); + height: calc(100vh - 3.5rem); +} + +.sidebar { + background-color: rgb(255, 255, 255, 100); +} + +.top-row { + height: 3.5rem; + background-color: rgb(255, 255, 255, 100); +} + +@media (max-width: 640.98px) { + .top-row { + justify-content: space-between; + } + + .top-row ::deep a, .top-row ::deep .btn-link { + margin-left: 0; + } +} + +@media (min-width: 641px) { + article { + padding-left: 2rem !important; + padding-right: 1.5rem !important; + } + + .page { + flex-direction: row; + } + + .sidebar { + width: 250px; + height: calc(100vh - 3.5rem); + position: sticky; + top: 0; + } + + .top-row { + position: sticky; + top: 0; + z-index: 1; + } + + .top-row.auth ::deep a:first-child { + flex: 1; + text-align: right; + width: 0; + } + + .top-row { + padding-left: 0 !important; + padding-right: 0 !important; + } +} + +#blazor-error-ui { + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + display: none; + left: 0; + padding: 0.6rem 1.25rem 0.7rem 1.25rem; + position: fixed; + width: 100%; + z-index: 1000; +} + + #blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; + } \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/Assets/engine.svg b/src/Rules.Framework.WebUI/Assets/engine.svg new file mode 100644 index 00000000..eaf54afe --- /dev/null +++ b/src/Rules.Framework.WebUI/Assets/engine.svg @@ -0,0 +1,15 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/Assets/favicon.svg b/src/Rules.Framework.WebUI/Assets/favicon.svg new file mode 100644 index 00000000..dae39f05 --- /dev/null +++ b/src/Rules.Framework.WebUI/Assets/favicon.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/Assets/logo.svg b/src/Rules.Framework.WebUI/Assets/logo.svg new file mode 100644 index 00000000..dae39f05 --- /dev/null +++ b/src/Rules.Framework.WebUI/Assets/logo.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/Components/Layout/MainLayout.razor b/src/Rules.Framework.WebUI/Components/Layout/MainLayout.razor new file mode 100644 index 00000000..21d280d9 --- /dev/null +++ b/src/Rules.Framework.WebUI/Components/Layout/MainLayout.razor @@ -0,0 +1,35 @@ +@attribute [ExcludeFromCodeCoverage] +@inherits LayoutComponentBase + +
+
+
+ +
+ Web UI +
+
+
+ + +
+
+ @Body +
+
+
+
+
+ +
+ An unhandled error has occurred. + Reload + 🗙 +
\ No newline at end of file diff --git a/src/Rules.Framework.WebUI/Components/Layout/NavMenu.razor b/src/Rules.Framework.WebUI/Components/Layout/NavMenu.razor new file mode 100644 index 00000000..3aeb3428 --- /dev/null +++ b/src/Rules.Framework.WebUI/Components/Layout/NavMenu.razor @@ -0,0 +1,60 @@ +@attribute [ExcludeFromCodeCoverage] +@rendermode InteractiveServer +@inject NavigationManager NavigationManager +@inject ProtectedSessionStorage Storage + + + + + +@code { + private bool shouldRenderSwitchInstanceLink = true; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + var instanceIdResult = await Storage.GetAsync(WebUIConstants.SelectedInstanceStorageKey); + if (!instanceIdResult.Success || instanceIdResult.Value == Guid.Empty) + { + this.NavigationManager.NavigateTo("/rules-ui/instance-selection"); + } + + var isUniqueInstanceResult = await this.Storage.GetAsync(WebUIConstants.IsUniqueInstanceStorageKey); + if (isUniqueInstanceResult.Success && isUniqueInstanceResult.Value) + { + shouldRenderSwitchInstanceLink = false; + this.StateHasChanged(); + } + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/Components/Layout/NavMenu.razor.css b/src/Rules.Framework.WebUI/Components/Layout/NavMenu.razor.css new file mode 100644 index 00000000..a9566a33 --- /dev/null +++ b/src/Rules.Framework.WebUI/Components/Layout/NavMenu.razor.css @@ -0,0 +1,102 @@ +.navbar-toggler { + appearance: none; + cursor: pointer; + width: 3.5rem; + height: 2.5rem; + color: white; + position: absolute; + top: 0.5rem; + right: 1rem; + border: 1px solid rgba(255, 255, 255, 0.1); + background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1); +} + + .navbar-toggler:checked { + background-color: rgba(255, 255, 255, 0.5); + } + +.top-row { + height: 3.5rem; + background-color: rgb(255, 255, 255, 100); +} + +.navbar-brand { + font-size: 1.1rem; +} + +.bi { + display: inline-block; + position: relative; + width: 1.25rem; + height: 1.25rem; + margin-right: 0.75rem; + top: -1px; + background-size: cover; +} + +.bi-house-door-fill-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E"); +} + +.bi-plus-square-fill-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E"); +} + +.bi-list-nested-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E"); +} + +.nav-item { + font-size: 0.9rem; + padding-bottom: 0.5rem; +} + + .nav-item:first-of-type { + padding-top: 1rem; + } + + .nav-item:last-of-type { + padding-bottom: 1rem; + } + + .nav-item ::deep .nav-link { + color: rgb(0, 0, 0); + background: none; + border: none; + border-radius: 4px; + height: 3rem; + display: flex; + align-items: center; + line-height: 3rem; + width: 100%; + } + + .nav-item ::deep a.active { + background-color: rgba(0,0,0,0.37); + color: white; + } + + .nav-item ::deep .nav-link:hover { + background-color: rgba(0,0,0,0.1); + color: white; + } + +.nav-scrollable { + display: none; +} + +.navbar-toggler:checked ~ .nav-scrollable { + display: block; +} + +@media (min-width: 641px) { + .navbar-toggler { + display: none; + } + + .nav-scrollable { + display: block; + height: calc(100vh - 3.5rem); + overflow-y: auto; + } +} \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/Components/Layout/SimpleLayout.razor b/src/Rules.Framework.WebUI/Components/Layout/SimpleLayout.razor new file mode 100644 index 00000000..c9e650d3 --- /dev/null +++ b/src/Rules.Framework.WebUI/Components/Layout/SimpleLayout.razor @@ -0,0 +1,34 @@ +@attribute [ExcludeFromCodeCoverage] +@inherits LayoutComponentBase + +
+
+
+ +
+ Web UI +
+
+
+ + +
+
+ @Body +
+
+
+
+
+ +
+ An unhandled error has occurred. + Reload + 🗙 +
diff --git a/src/Rules.Framework.WebUI/Components/Layout/SimpleLayout.razor.css b/src/Rules.Framework.WebUI/Components/Layout/SimpleLayout.razor.css new file mode 100644 index 00000000..e69de29b diff --git a/src/Rules.Framework.WebUI/Components/PageComponents/ExportRules.razor b/src/Rules.Framework.WebUI/Components/PageComponents/ExportRules.razor new file mode 100644 index 00000000..8fd10728 --- /dev/null +++ b/src/Rules.Framework.WebUI/Components/PageComponents/ExportRules.razor @@ -0,0 +1,137 @@ +@attribute [ExcludeFromCodeCoverage] +@using System.Text.Json +@using System.Text +@using System.IO +@using System.Text.Json.Serialization +@rendermode InteractiveServer +@inject IJSRuntime JS + +
+
+        
+            @exportText
+        
+    
+
+ +
+ +
+ + + + .json + + @if (this.CloseButtonEnable) + { + + } +
+ +@code { + private bool customizeFileNameEnabled; + private string exportText = string.Empty; + private JsonSerializerOptions jsonSerializerOptions; + + public ExportRules() + { + this.Reset(); + } + + protected override void OnInitialized() + { + if (this.CloseButtonEnable && !this.CloseButtonCallback.HasDelegate) + { + throw new InvalidOperationException("Close button needs a callback when enabled."); + } + } + + protected override void OnAfterRender(bool firstRender) + { + this.exportText = JsonSerializer.Serialize(this.Rules, this.jsonSerializerOptions); + this.StateHasChanged(); + } + + + [Parameter] + public EventCallback CloseButtonCallback { get; set; } + + [Parameter] + public bool CloseButtonEnable { get; set; } + + [Parameter] + public string ExportFileName { get; set; } + + [Parameter] + public IEnumerable Rules { get; set; } + + private async Task OnExportModalCloseButtonClickAsync(MouseEventArgs e) + { + await this.CloseButtonCallback.InvokeAsync(); + this.Reset(); + } + + private async Task OnExportModalDownloadButtonClickAsync(MouseEventArgs e) + { + if (this.Rules != null && this.Rules.Any()) + { + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(exportText)); + using var streamReference = new DotNetStreamReference(stream); + await JS.InvokeVoidAsync("downloadFileFromStream", $"{this.ExportFileName}.json", streamReference); + } + } + + private void Reset() + { + this.exportText = string.Empty; + this.customizeFileNameEnabled = false; + this.jsonSerializerOptions = new JsonSerializerOptions() + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + IncludeFields = true, + WriteIndented = true, + }; + this.jsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); + this.jsonSerializerOptions.Converters.Add(new PolymorphicWriteOnlyJsonConverter()); + } + + public sealed class Rule + { + public bool Active { get; set; } + + public object Content { get; set; } + + public DateTime DateBegin { get; set; } + + public DateTime? DateEnd { get; set; } + + public string Name { get; set; } + + public int Priority { get; set; } + + public ConditionNode RootCondition { get; set; } + + public string Ruleset { get; set; } + } + + public sealed class ComposedConditionNode : ConditionNode + { + public IEnumerable ChildConditionNodes { get; set; } + } + + public class ConditionNode + { + public string LogicalOperator { get; set; } + } + + public sealed class ValueConditionNode : ConditionNode + { + public string Condition { get; set; } + + public string DataType { get; set; } + + public dynamic Operand { get; set; } + + public string Operator { get; set; } + } +} diff --git a/src/Rules.Framework.WebUI/Components/PageComponents/RuleConditionHierarchicalAccordion.razor b/src/Rules.Framework.WebUI/Components/PageComponents/RuleConditionHierarchicalAccordion.razor new file mode 100644 index 00000000..917aa739 --- /dev/null +++ b/src/Rules.Framework.WebUI/Components/PageComponents/RuleConditionHierarchicalAccordion.razor @@ -0,0 +1,170 @@ +@attribute [ExcludeFromCodeCoverage] +@using System.Collections +@using System.Text; +@rendermode InteractiveAuto + +
+ @if (this.EnableShowAllButton) + { + + } + @if (this.EnableCollapseAllButton) + { + + } +
+
+ + @foreach (var conditionNode in this.ConditionNodes) + { + if (conditionNode is ComposedConditionNode composedConditionNode) + { + + + + + + } + else if (conditionNode is ValueConditionNode valueConditionNode) + { + var shortValueConditionText = $"{valueConditionNode.Condition} {valueConditionNode.Operator} {valueConditionNode.GetOperandPrettyPrint()}"; + + +
+
+ + + +
+
+

Condition name:

@(valueConditionNode.Condition)

+
+
+

Data type:

@(valueConditionNode.DataType)

+
+
+

Operator:

@(valueConditionNode.Operator)

+
+
+

Operand:

@(valueConditionNode.GetOperandPrettyPrint())

+
+
+
+
+
+
+
+
+
+ } + } +
+
+ +@code { + private Accordion innerAccordion; + private List innerRuleConditionHierarchicalAccordions = new List(); + private RuleConditionHierarchicalAccordion InnerReference + { + set + { + this.innerRuleConditionHierarchicalAccordions.Add(value); + } + } + + [Parameter] + public IEnumerable ConditionNodes { get; set; } = Enumerable.Empty(); + + [Parameter] + public bool EnableCollapseAllButton { get; set; } = false; + + [Parameter] + public bool EnableShowAllButton { get; set; } = false; + + public async Task CollapseAllAsync() + { + await this.innerAccordion.HideAllAccordionItemsAsync(); + if (this.innerRuleConditionHierarchicalAccordions.Count > 0) + { + foreach (var ruleConditionHierarchicalAccordion in this.innerRuleConditionHierarchicalAccordions) + { + await ruleConditionHierarchicalAccordion.CollapseAllAsync(); + } + } + } + + public async Task ShowAllAsync() + { + await this.innerAccordion.ShowAllAccordionItemsAsync(); + if (this.innerRuleConditionHierarchicalAccordions.Count > 0) + { + foreach (var ruleConditionHierarchicalAccordion in this.innerRuleConditionHierarchicalAccordions) + { + await ruleConditionHierarchicalAccordion.ShowAllAsync(); + } + } + } + + public sealed class ComposedConditionNode : ConditionNode + { + public IEnumerable ChildConditionNodes { get; internal set; } + } + + public class ConditionNode + { + public string LogicalOperator { get; internal set; } + } + + public sealed class ValueConditionNode : ConditionNode + { + public string Condition { get; internal set; } + + public string DataType { get; internal set; } + + public dynamic Operand { get; internal set; } + + public string Operator { get; internal set; } + + internal string GetOperandPrettyPrint() + { + var operandPrettyPrintBuilder = new StringBuilder(); + if (this.Operand is IEnumerable elements && elements is not string) + { + operandPrettyPrintBuilder.Append("{ "); + var elementsProcessedCount = 0; + foreach (var element in elements) + { + if (elementsProcessedCount >= 10) + { + operandPrettyPrintBuilder.Append(", ..."); + } + else if (elementsProcessedCount > 0) + { + operandPrettyPrintBuilder.Append(','); + } + + operandPrettyPrintBuilder.Append(element switch + { + null => "(null)", + "" => "(empty)", + _ => element, + }); + elementsProcessedCount++; + } + + operandPrettyPrintBuilder.Append(" }"); + } + else + { + operandPrettyPrintBuilder.Append(this.Operand switch + { + null => "(null)", + "" => "(empty)", + _ => this.Operand, + }); + } + + return operandPrettyPrintBuilder.ToString(); + } + } +} diff --git a/src/Rules.Framework.WebUI/Components/Pages/Instance.razor b/src/Rules.Framework.WebUI/Components/Pages/Instance.razor new file mode 100644 index 00000000..29ff90a4 --- /dev/null +++ b/src/Rules.Framework.WebUI/Components/Pages/Instance.razor @@ -0,0 +1,133 @@ +@attribute [ExcludeFromCodeCoverage] +@page "/rules-ui/instance" +@rendermode InteractiveServer +@inject WebUIOptions Options +@inject ProtectedSessionStorage Storage +@inject IRulesEngineInstanceProvider RulesEngineInstanceProvider + +@(this.Options.DocumentTitle) - Instance + +

Instance

+ +@if (this.instanceId == Guid.Empty) +{ +

Loading... Please wait.

+} +else +{ +

Name: @(instance.Name)

+ +

Options

+ +
    + @foreach (var option in instance.Options) + { +
  • + @(option.Name) + @if (!string.IsNullOrEmpty(option.NameDescription)) + { + + + + + } + @if (string.Equals(option.Name, "DataTypeDefaults")) + { +
      + @foreach (var dataTypeDefault in (IDictionary)option.Value) + { +
    • @(dataTypeDefault.Key) @(dataTypeDefault.Value)
    • + } +
    + } + else + { + + + + @if (!string.IsNullOrEmpty(option.ValueDescription)) + { + + @(option.Value) + + } + else + { + @(option.Value) + } + } +
  • + } +
+} + + +@code { + private Guid instanceId; + private RulesEngineInstanceViewModel instance; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + var instanceIdResult = await Storage.GetAsync(WebUIConstants.SelectedInstanceStorageKey); + if (instanceIdResult.Success) + { + this.instanceId = instanceIdResult.Value; + var instance = this.RulesEngineInstanceProvider.GetInstance(instanceId); + this.instance = new RulesEngineInstanceViewModel + { + Id = instance.Id, + Name = instance.Name, + Options = new List + { + new OptionViewModel + { + Name = nameof(instance.RulesEngine.Options.AutoCreateRulesets), + NameDescription = WebUIConstants.OptionAutoCreateRulesetsDescription, + Value = instance.RulesEngine.Options.AutoCreateRulesets.ToString().ToLower(), + }, + new OptionViewModel + { + Name = nameof(instance.RulesEngine.Options.DataTypeDefaults), + NameDescription = WebUIConstants.OptionDataTypeDefaultsDescription, + Value = instance.RulesEngine.Options.DataTypeDefaults.ToDictionary(k => k.Key.ToString(), v =>v.Key switch + { + DataTypes.Boolean => v.Value.ToString().ToLower(), + DataTypes.String when string.Equals(v.Value, string.Empty) => "string.Empty", + DataTypes.String => $"\"{v.Value}\"", + _ => v.Value, + }), + }, + new OptionViewModel + { + Name = nameof(instance.RulesEngine.Options.MissingConditionBehavior), + NameDescription = WebUIConstants.OptionMissingConditionBehaviorDescription, + Value = instance.RulesEngine.Options.MissingConditionBehavior.ToString(), + ValueDescription = instance.RulesEngine.Options.MissingConditionBehavior switch + { + MissingConditionBehaviors.UseDataTypeDefault => WebUIConstants.OptionMissingConditionBehaviorUseDataTypeDefaultDescription, + MissingConditionBehaviors.Discard => WebUIConstants.OptionMissingConditionBehaviorDiscardDescription, + _ => "", + }, + }, + new OptionViewModel + { + Name = nameof(instance.RulesEngine.Options.PriorityCriteria), + NameDescription = WebUIConstants.OptionPriorityCriteriaDescription, + Value = instance.RulesEngine.Options.PriorityCriteria.ToString(), + ValueDescription = instance.RulesEngine.Options.PriorityCriteria switch + { + PriorityCriterias.TopmostRuleWins => WebUIConstants.OptionPriorityCriteriaTopmostRuleWinsDescription, + PriorityCriterias.BottommostRuleWins => WebUIConstants.OptionPriorityCriteriaBottommostRuleWinsDescription, + _ => "", + }, + }, + }, + }; + + this.StateHasChanged(); + } + } + } +} diff --git a/src/Rules.Framework.WebUI/Components/Pages/InstanceSelection.razor b/src/Rules.Framework.WebUI/Components/Pages/InstanceSelection.razor new file mode 100644 index 00000000..7e04d972 --- /dev/null +++ b/src/Rules.Framework.WebUI/Components/Pages/InstanceSelection.razor @@ -0,0 +1,66 @@ +@attribute [ExcludeFromCodeCoverage] +@page "/rules-ui/instance-selection" +@rendermode InteractiveServer +@layout global::Rules.Framework.WebUI.Components.Layout.SimpleLayout +@inject WebUIOptions Options +@inject IRulesEngineInstanceProvider RulesEngineInstanceProvider +@inject ProtectedSessionStorage Storage +@inject NavigationManager NavigationManager + +@(this.Options.DocumentTitle) - Select instance + +

Select a rules engine instance

+ +
+ @if (this.allInstances is not null) + { + foreach (var instance in this.allInstances) + { +
+ + +

@(instance.Name)

+ +
+
+
+ } + } +
+ +@code { + private IEnumerable allInstances; + private string selectedInstanceId; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender && this.allInstances.Count() == 1) + { + var instance = this.allInstances.First(); + await this.Storage.SetAsync(WebUIConstants.SelectedInstanceStorageKey, instance.Id); + await this.Storage.SetAsync(WebUIConstants.IsUniqueInstanceStorageKey, true); + await this.Storage.DeleteAsync(WebUIConstants.SelectedRulesetsStorageKey); + this.NavigationManager.NavigateTo("rules-ui/instance"); + } + } + + protected override void OnInitialized() + { + this.allInstances = this.RulesEngineInstanceProvider.GetAllInstances(); + } + + private async Task SelectInstanceAsync(Guid instanceId) + { + if (instanceId != Guid.Empty) + { + var instance = this.RulesEngineInstanceProvider.GetInstance(instanceId); + if (instance is not null) + { + await this.Storage.SetAsync(WebUIConstants.SelectedInstanceStorageKey, instanceId); + await this.Storage.SetAsync(WebUIConstants.IsUniqueInstanceStorageKey, false); + await this.Storage.DeleteAsync(WebUIConstants.SelectedRulesetsStorageKey); + this.NavigationManager.NavigateTo("rules-ui/instance"); + } + } + } +} diff --git a/src/Rules.Framework.WebUI/Components/Pages/Rulesets.razor b/src/Rules.Framework.WebUI/Components/Pages/Rulesets.razor new file mode 100644 index 00000000..67f8f30b --- /dev/null +++ b/src/Rules.Framework.WebUI/Components/Pages/Rulesets.razor @@ -0,0 +1,117 @@ +@attribute [ExcludeFromCodeCoverage] +@page "/rules-ui/rulesets" +@rendermode InteractiveServer +@inject WebUIOptions Options +@inject ProtectedSessionStorage Storage +@inject NavigationManager NavigationManager +@inject IRulesEngineInstanceProvider RulesEngineInstanceProvider + +@(this.Options.DocumentTitle) - Rulesets + +

Rulesets

+ +
+ @if (this.instanceId == Guid.Empty) + { +
+ + +
+ } + else + { + + + + @(context.Number) + + + @(context.Name) + + + @(context.ActiveRulesCount) + + + @(context.TotalRulesCount) + + + + + + + } +
+ +@code { + private const int DefaultPageSize = 5; + private int currentPageNumber = 1; + private int totalPages = 0; + private Guid instanceId; + private List allRulesets; + private IEnumerable filteredRulesets; + private IEnumerable pagedRulesets; + private bool onlyShowRulesetsWithRules = false; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + var instanceIdResult = await this.Storage.GetAsync(WebUIConstants.SelectedInstanceStorageKey); + if (instanceIdResult.Success) + { + this.instanceId = instanceIdResult.Value; + this.StateHasChanged(); + } + } + } + + private async Task> LoadRulesetsAsync(GridDataProviderRequest request) + { + var instance = this.RulesEngineInstanceProvider.GetInstance(instanceId); + var rulesets = await instance.RulesEngine.GetRulesetsAsync(); + var number = 1; + var rulesetViewModels = rulesets.OrderBy(r => r.Name).Select(r => new RulesetViewModel + { + Id = GuidGenerator.GenerateFromString(r.Name), + Name = r.Name, + Number = number++, + }).ToList(); + + foreach (var ruleset in rulesetViewModels) + { + var rules = await instance.RulesEngine.SearchAsync(new SearchArgs( + ruleset.Name, + DateTime.MinValue, + DateTime.MaxValue)); + + ruleset.TotalRulesCount = rules.Count(); + ruleset.ActiveRulesCount = rules.Count(r => r.Active); + } + + return request.ApplyTo(rulesetViewModels); + } + + private async Task ButtonViewRulesOnClickAsync(Guid rulesetId) + { + await this.Storage.SetAsync(WebUIConstants.SelectedRulesetsStorageKey, new[] { rulesetId }); + + this.NavigationManager.NavigateTo($"rules-ui/search-rules"); + } +} diff --git a/src/Rules.Framework.WebUI/Components/Pages/SearchRules.razor b/src/Rules.Framework.WebUI/Components/Pages/SearchRules.razor new file mode 100644 index 00000000..4a8b568f --- /dev/null +++ b/src/Rules.Framework.WebUI/Components/Pages/SearchRules.razor @@ -0,0 +1,384 @@ +@attribute [ExcludeFromCodeCoverage] +@page "/rules-ui/search-rules" +@rendermode InteractiveServer +@using System.Text.Json +@using System.Text.Json.Serialization +@using System.IO +@using System.Text +@inject WebUIOptions Options +@inject IJSRuntime JS +@inject NavigationManager NavigationManager +@inject IRulesEngineInstanceProvider RulesEngineInstanceProvider +@inject ProtectedSessionStorage Storage + +@(this.Options.DocumentTitle) - Search rules + +

Search rules

+ +
+
+
+
+
+ + Select rulesets + + @if (this.instanceId == Guid.Empty) + { +

Loading...

+ } + else + { +
+ + +
+ + foreach (var ruleset in this.rulesets) + { +
+ + +
+ } + } +
+
+
+
+
+ @if (this.instanceId != Guid.Empty && this.rulesetIds.Any()) + { + @(this.rulesetIds.Count) selected: + var count = 0; + foreach (var ruleset in this.rulesets) + { + if (this.rulesetIds.Contains(ruleset.Id)) + { + if (count > 0) + { + , + } + + if (count == 5) + { + and more... + break; + } + + @(ruleset.Name) + count++; + } + } + } +
+
+
+
+
+
+
Rules
+
+
+ @priorityCriteria + + Actions + + Export JSON + + +
+
+
+ + + + @(context.Priority) + + + @(context.Ruleset) + + + @(context.Name) + + + @(context.DateBegin) + + + @(context.DateEnd) + + + @(JsonSerializer.Serialize(context.Content, this.jsonSerializerOptions)) + + + @if (context.Active) + { + Active + } + else + { + Deactivated + } + + + + + + +
+
+                                            
+                                                @(JsonSerializer.Serialize(context.RootCondition, this.jsonSerializerOptions))
+                                            
+                                        
+
+
+
+ + +
+ @if (context.RootCondition is not null) + { + var conditionNodes = new[] { ConvertToComponentModel(context.RootCondition) }; + + } +
+
+
+
+
+
+
+
+
+
+ + + +@code { + private bool allRulesetsSelected; + private const int DefaultPageSize = 5; + private DateTime? dateBegin; + private DateInput dateBeginSearch; + private DateTime? dateEnd; + private Guid instanceId; + private JsonSerializerOptions jsonSerializerOptions; + private string exportFileName; + private Modal exportModal; + private string exportText; + private string priorityCriteria; + private string priorityCriteriaTooltip; + private HashSet rulesetIds; + private List rulesets; + private Grid rulesGrid; + + public SearchRules() + { + this.jsonSerializerOptions = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + IncludeFields = true, + WriteIndented = true, + }; + this.jsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); + this.jsonSerializerOptions.Converters.Add(new PolymorphicWriteOnlyJsonConverter()); + this.rulesetIds = new HashSet(); + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + var instanceIdResult = await this.Storage.GetAsync(WebUIConstants.SelectedInstanceStorageKey); + if (instanceIdResult.Success) + { + this.instanceId = instanceIdResult.Value; + var instance = this.RulesEngineInstanceProvider.GetInstance(this.instanceId); + + switch (instance.RulesEngine.Options.PriorityCriteria) + { + case PriorityCriterias.BottommostRuleWins: + this.priorityCriteria = WebUIConstants.OptionPriorityCriteriaBottommostRuleWinsName; + this.priorityCriteriaTooltip = WebUIConstants.OptionPriorityCriteriaBottommostRuleWinsDescription; + break; + default: + this.priorityCriteria = WebUIConstants.OptionPriorityCriteriaTopmostRuleWinsName; + this.priorityCriteriaTooltip = WebUIConstants.OptionPriorityCriteriaTopmostRuleWinsDescription; + break; + } + + var rulesets = await instance.RulesEngine.GetRulesetsAsync(); + var number = 1; + this.rulesets = rulesets.OrderBy(r => r.Name).Select(r => new RulesetViewModel + { + Id = GuidGenerator.GenerateFromString(r.Name), + Name = r.Name, + Number = number++, + }).ToList(); + + foreach (var ruleset in this.rulesets) + { + var rules = await instance.RulesEngine.SearchAsync(new SearchArgs( + ruleset.Name, + DateTime.MinValue, + DateTime.MaxValue)); + + ruleset.TotalRulesCount = rules.Count(); + ruleset.ActiveRulesCount = rules.Count(r => r.Active); + } + } + + var rulesetIdResult = await this.Storage.GetAsync(WebUIConstants.SelectedRulesetsStorageKey); + if (rulesetIdResult.Success) + { + this.rulesetIds = new HashSet(rulesetIdResult.Value); + } + + this.StateHasChanged(); + await this.rulesGrid.RefreshDataAsync(); + } + } + + private RuleConditionHierarchicalAccordion.ConditionNode ConvertToComponentModel(ConditionNodeViewModel viewModel) + { + return viewModel switch + { + ComposedConditionNodeViewModel composedConditionNodeViewModel => new RuleConditionHierarchicalAccordion.ComposedConditionNode + { + ChildConditionNodes = composedConditionNodeViewModel.ChildConditionNodes.Select(x => ConvertToComponentModel(x)), + LogicalOperator = composedConditionNodeViewModel.LogicalOperator, + }, + ValueConditionNodeViewModel valueConditionNodeViewModel => new RuleConditionHierarchicalAccordion.ValueConditionNode + { + Condition = valueConditionNodeViewModel.Condition, + DataType = valueConditionNodeViewModel.DataType, + LogicalOperator = valueConditionNodeViewModel.LogicalOperator, + Operand = valueConditionNodeViewModel.Operand, + Operator = valueConditionNodeViewModel.Operator, + }, + _ => throw new NotSupportedException(), + }; + } + + private async Task> LoadRulesAsync(GridDataProviderRequest request) + { + List ruleViewModels = new List(); + if (this.instanceId != Guid.Empty && this.rulesetIds.Any()) + { + + var instance = this.RulesEngineInstanceProvider.GetInstance(instanceId); + var rulesets = await instance.RulesEngine.GetRulesetsAsync(); + var rulesetsToSearch = this.rulesetIds.Contains(Guid.Empty) + ? rulesets + : rulesets.Where(r => this.rulesetIds.Contains(GuidGenerator.GenerateFromString(r.Name))); + + var searchDateBegin = this.dateBegin.GetValueOrDefault(DateTime.MinValue); + var searchDateEnd = this.dateEnd.GetValueOrDefault(DateTime.MaxValue); + foreach (var ruleset in rulesetsToSearch) + { + var rules = await instance.RulesEngine.SearchAsync(new SearchArgs(ruleset.Name, searchDateBegin, searchDateEnd)); + + ruleViewModels.AddRange(rules.OrderBy(r => r.Priority).Select(r => r.ToViewModel())); + } + } + + return request.ApplyTo(ruleViewModels); + } + + private async Task OnRulesetCheckAsync(Guid rulesetId) + { + if (rulesetId == Guid.Empty) + { + if (this.allRulesetsSelected) + { + this.rulesetIds.Clear(); + } + else + { + this.rulesetIds.Clear(); + foreach (var ruleset in this.rulesets) + { + this.rulesetIds.Add(ruleset.Id); + } + } + + this.allRulesetsSelected = !this.allRulesetsSelected; + } + else + { + if (this.rulesetIds.Contains(rulesetId)) + { + this.rulesetIds.Remove(rulesetId); + } + else + { + this.rulesetIds.Add(rulesetId); + } + } + + await this.Storage.SetAsync(WebUIConstants.SelectedRulesetsStorageKey, this.rulesetIds); + this.StateHasChanged(); + await this.rulesGrid.RefreshDataAsync(); + } + + private async Task OnExportJsonButtonClickAsync() + { + if (this.rulesetIds.Any()) + { + var request = new GridDataProviderRequest + { + Filters = this.rulesGrid.GetFilters(), + }; + var response = await this.rulesGrid.DataProvider.Invoke(request); + var rules = response.Data.Select(r => r.ToExportRulesModel()).ToArray(); + if (this.rulesetIds.Count > 1) + { + this.exportFileName = "selection-rules"; + } + else + { + var rulesetId = this.rulesetIds.Single(); + var selectedRulesetName = this.rulesets.First(r => r.Id == rulesetId).Name; + this.exportFileName = $"{selectedRulesetName}-selection-rules"; + } + + this.StateHasChanged(); + await this.exportModal.ShowAsync( + title: "Export result", + parameters: new Dictionary(StringComparer.Ordinal) + { + { "CloseButtonCallback", EventCallback.Factory.Create(this.exportModal, async () => await this.exportModal.HideAsync()) }, + { "CloseButtonEnable", true }, + { "ExportFileName", this.exportFileName }, + { "Rules", rules }, + }); + } + } + + +} \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/Components/Routes.razor b/src/Rules.Framework.WebUI/Components/Routes.razor new file mode 100644 index 00000000..877c9eb1 --- /dev/null +++ b/src/Rules.Framework.WebUI/Components/Routes.razor @@ -0,0 +1,7 @@ +@attribute [ExcludeFromCodeCoverage] + + + + + + diff --git a/src/Rules.Framework.WebUI/Components/WebUIApp.razor b/src/Rules.Framework.WebUI/Components/WebUIApp.razor new file mode 100644 index 00000000..5d1aff5b --- /dev/null +++ b/src/Rules.Framework.WebUI/Components/WebUIApp.razor @@ -0,0 +1,40 @@ +@attribute [ExcludeFromCodeCoverage] + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Rules.Framework.WebUI/Components/_Imports.razor b/src/Rules.Framework.WebUI/Components/_Imports.razor new file mode 100644 index 00000000..e22b18bb --- /dev/null +++ b/src/Rules.Framework.WebUI/Components/_Imports.razor @@ -0,0 +1,18 @@ +@using System.Collections.Generic +@using System.Diagnostics.CodeAnalysis +@using System.Net.Http +@using System.Net.Http.Json +@using BlazorBootstrap +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage +@using Microsoft.AspNetCore.Components.Web +@using static Microsoft.AspNetCore.Components.Web.RenderMode +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.JSInterop +@using global::Rules.Framework.WebUI +@using global::Rules.Framework.WebUI.Components +@using global::Rules.Framework.WebUI.Components.PageComponents +@using global::Rules.Framework.WebUI.Services +@using global::Rules.Framework.WebUI.Utilities +@using global::Rules.Framework.WebUI.ViewModels diff --git a/src/Rules.Framework.WebUI/Dto/ComposedConditionNodeDto.cs b/src/Rules.Framework.WebUI/Dto/ComposedConditionNodeDto.cs deleted file mode 100644 index 6ba1cf97..00000000 --- a/src/Rules.Framework.WebUI/Dto/ComposedConditionNodeDto.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Rules.Framework.WebUI.Dto -{ - using System.Collections.Generic; - - internal sealed class ComposedConditionNodeDto : ConditionNodeDto - { - public IEnumerable ChildConditionNodes { get; internal set; } - } -} \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/Dto/ConditionNodeDto.cs b/src/Rules.Framework.WebUI/Dto/ConditionNodeDto.cs deleted file mode 100644 index 8c37502d..00000000 --- a/src/Rules.Framework.WebUI/Dto/ConditionNodeDto.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Rules.Framework.WebUI.Dto -{ - internal class ConditionNodeDto - { - public string LogicalOperator { get; internal set; } - } -} \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/Dto/ContentTypeDto.cs b/src/Rules.Framework.WebUI/Dto/ContentTypeDto.cs deleted file mode 100644 index 6b4af554..00000000 --- a/src/Rules.Framework.WebUI/Dto/ContentTypeDto.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Rules.Framework.WebUI.Dto -{ - internal sealed class ContentTypeDto - { - public int Index { get; internal set; } - public int ActiveRulesCount { get; internal set; } - public string Name { get; internal set; } - public int RulesCount { get; internal set; } - } -} \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/Dto/FilterDto.cs b/src/Rules.Framework.WebUI/Dto/FilterDto.cs deleted file mode 100644 index f90eb976..00000000 --- a/src/Rules.Framework.WebUI/Dto/FilterDto.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Rules.Framework.WebUI.Dto -{ - using System; - - internal sealed class RulesFilterDto - { - public string Content { get; set; } - public string ContentType { get; set; } - public DateTime? DateBegin { get; set; } - public DateTime? DateEnd { get; set; } - public string Name { get; set; } - public RuleStatusDto? Status { get; set; } - } -} diff --git a/src/Rules.Framework.WebUI/Dto/IRuleStatusDtoAnalyzer.cs b/src/Rules.Framework.WebUI/Dto/IRuleStatusDtoAnalyzer.cs deleted file mode 100644 index d41e1587..00000000 --- a/src/Rules.Framework.WebUI/Dto/IRuleStatusDtoAnalyzer.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Rules.Framework.WebUI.Dto -{ - using System; - - internal interface IRuleStatusDtoAnalyzer - { - RuleStatusDto Analyze(DateTime dateBegin, DateTime? dateEnd); - } -} \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/Dto/RuleDto.cs b/src/Rules.Framework.WebUI/Dto/RuleDto.cs deleted file mode 100644 index 24ae31c6..00000000 --- a/src/Rules.Framework.WebUI/Dto/RuleDto.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Rules.Framework.WebUI.Dto -{ - internal sealed class RuleDto - { - public ConditionNodeDto Conditions { get; internal set; } - public string ContentType { get; internal set; } - public string DateBegin { get; internal set; } - public string DateEnd { get; internal set; } - public string Name { get; internal set; } - public int? Priority { get; internal set; } - public string Status { get; internal set; } - public object Value { get; internal set; } - } -} diff --git a/src/Rules.Framework.WebUI/Dto/RuleStatusDto.cs b/src/Rules.Framework.WebUI/Dto/RuleStatusDto.cs deleted file mode 100644 index a0c2cc5f..00000000 --- a/src/Rules.Framework.WebUI/Dto/RuleStatusDto.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Rules.Framework.WebUI.Dto -{ - internal enum RuleStatusDto : short - { - Expired, - Active, - Pending, - Deactivated - } -} diff --git a/src/Rules.Framework.WebUI/Dto/RuleStatusDtoAnalyzer.cs b/src/Rules.Framework.WebUI/Dto/RuleStatusDtoAnalyzer.cs deleted file mode 100644 index 40e1d2b2..00000000 --- a/src/Rules.Framework.WebUI/Dto/RuleStatusDtoAnalyzer.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Rules.Framework.WebUI.Dto -{ - using System; - - internal sealed class RuleStatusDtoAnalyzer : IRuleStatusDtoAnalyzer - { - public RuleStatusDto Analyze(DateTime dateBegin, DateTime? dateEnd) - { - if (dateBegin > DateTime.UtcNow) - { - return RuleStatusDto.Pending; - } - - if (!dateEnd.HasValue) - { - return RuleStatusDto.Active; - } - - if (dateEnd.Value <= DateTime.UtcNow) - { - return RuleStatusDto.Expired; - } - - return RuleStatusDto.Active; - } - } -} diff --git a/src/Rules.Framework.WebUI/Extensions/RuleDtoExtensions.cs b/src/Rules.Framework.WebUI/Extensions/RuleDtoExtensions.cs deleted file mode 100644 index e50298fc..00000000 --- a/src/Rules.Framework.WebUI/Extensions/RuleDtoExtensions.cs +++ /dev/null @@ -1,59 +0,0 @@ -namespace Rules.Framework.WebUI.Extensions -{ - using System.Collections.Generic; - using System.Linq; - using Rules.Framework.Generics; - using Rules.Framework.WebUI.Dto; - - internal static class RuleDtoExtensions - { - private const string dateFormat = "dd/MM/yyyy HH:mm:ss"; - - public static ConditionNodeDto ToConditionNodeDto(this GenericConditionNode rootCondition) - { - if (rootCondition.LogicalOperator == Core.LogicalOperators.Eval || - rootCondition.LogicalOperator == 0) - { - var condition = rootCondition as GenericValueConditionNode; - - return new ValueConditionNodeDto - { - ConditionTypeName = condition.ConditionTypeName, - DataType = condition.DataType.ToString(), - Operand = condition.Operand, - Operator = condition.Operator.ToString(), - }; - } - - var composedConditionNode = rootCondition as GenericComposedConditionNode; - - var conditionNodeDataModels = new List(composedConditionNode.ChildConditionNodes.Count()); - - foreach (var child in composedConditionNode.ChildConditionNodes) - { - conditionNodeDataModels.Add(child.ToConditionNodeDto()); - } - - return new ComposedConditionNodeDto - { - ChildConditionNodes = conditionNodeDataModels, - LogicalOperator = composedConditionNode.LogicalOperator.ToString() - }; - } - - public static RuleDto ToRuleDto(this GenericRule rule, string ContentType, IRuleStatusDtoAnalyzer ruleStatusDtoAnalyzer) - { - return new RuleDto - { - Conditions = rule.RootCondition?.ToConditionNodeDto(), - ContentType = ContentType, - Priority = rule.Priority, - Name = rule.Name, - Value = rule.Content, - DateEnd = !rule.DateEnd.HasValue ? null : rule.DateEnd.Value.ToString(dateFormat), - DateBegin = rule.DateBegin.ToString(dateFormat), - Status = !rule.Active ? RuleStatusDto.Deactivated.ToString() : ruleStatusDtoAnalyzer.Analyze(rule.DateBegin, rule.DateEnd).ToString(), - }; - } - } -} diff --git a/src/Rules.Framework.WebUI/Handlers/GetConfigurationsHandler.cs b/src/Rules.Framework.WebUI/Handlers/GetConfigurationsHandler.cs deleted file mode 100644 index 60f17e85..00000000 --- a/src/Rules.Framework.WebUI/Handlers/GetConfigurationsHandler.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace Rules.Framework.WebUI.Handlers -{ - using System; - using System.Collections.Generic; - using System.Net; - using System.Threading.Tasks; - using Microsoft.AspNetCore.Http; - using Rules.Framework.Generics; - - internal sealed class GetConfigurationsHandler : WebUIRequestHandlerBase - { - private static readonly string[] resourcePath = new[] { "/{0}/api/v1/configurations" }; - - private readonly IGenericRulesEngine rulesEngine; - - public GetConfigurationsHandler(IGenericRulesEngine rulesEngine, WebUIOptions webUIOptions) : base(resourcePath, webUIOptions) - { - this.rulesEngine = rulesEngine; - } - - protected override HttpMethod HttpMethod => HttpMethod.GET; - - protected override Task HandleRequestAsync(HttpRequest httpRequest, HttpResponse httpResponse, RequestDelegate next) - { - try - { - var priorityCriteria = this.rulesEngine.GetPriorityCriteria(); - - var configurations = new Dictionary - { - { "PriorityCriteria", priorityCriteria.ToString() } - }; - - return this.WriteResponseAsync(httpResponse, configurations, (int)HttpStatusCode.OK); - } - catch (Exception ex) - { - return this.WriteExceptionResponseAsync(httpResponse, ex); - } - } - } -} \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/Handlers/GetContentTypeHandler.cs b/src/Rules.Framework.WebUI/Handlers/GetContentTypeHandler.cs deleted file mode 100644 index b4464799..00000000 --- a/src/Rules.Framework.WebUI/Handlers/GetContentTypeHandler.cs +++ /dev/null @@ -1,70 +0,0 @@ -namespace Rules.Framework.WebUI.Handlers -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Net; - using System.Threading.Tasks; - using Microsoft.AspNetCore.Http; - using Rules.Framework.Generics; - using Rules.Framework.WebUI.Dto; - - internal sealed class GetContentTypeHandler : WebUIRequestHandlerBase - { - private static readonly string[] resourcePath = new[] { "/{0}/api/v1/contentTypes" }; - - private readonly IGenericRulesEngine genericRulesEngine; - private readonly IRuleStatusDtoAnalyzer ruleStatusDtoAnalyzer; - - public GetContentTypeHandler(IGenericRulesEngine genericRulesEngine, - IRuleStatusDtoAnalyzer ruleStatusDtoAnalyzer, - WebUIOptions webUIOptions) : base(resourcePath, webUIOptions) - { - this.genericRulesEngine = genericRulesEngine; - this.ruleStatusDtoAnalyzer = ruleStatusDtoAnalyzer; - } - - protected override HttpMethod HttpMethod => HttpMethod.GET; - - protected override async Task HandleRequestAsync(HttpRequest httpRequest, HttpResponse httpResponse, RequestDelegate next) - { - try - { - var contents = this.genericRulesEngine.GetContentTypes(); - - var contentTypes = new List(); - var index = 0; - foreach (var identifier in contents.Select(c => c.Identifier)) - { - var genericContentType = new GenericContentType { Identifier = identifier }; - - var genericRules = await this.genericRulesEngine - .SearchAsync(new SearchArgs(genericContentType, - DateTime.MinValue, - DateTime.MaxValue)) - .ConfigureAwait(false); - - contentTypes.Add(new ContentTypeDto - { - Index = index, - Name = identifier, - ActiveRulesCount = genericRules.Count(IsActive), - RulesCount = genericRules.Count() - }); - index++; - } - - await this.WriteResponseAsync(httpResponse, contentTypes, (int)HttpStatusCode.OK).ConfigureAwait(false); - } - catch (Exception ex) - { - await this.WriteExceptionResponseAsync(httpResponse, ex).ConfigureAwait(false); - } - } - - private bool IsActive(GenericRule genericRule) - { - return this.ruleStatusDtoAnalyzer.Analyze(genericRule.DateBegin, genericRule.DateEnd) == RuleStatusDto.Active; - } - } -} diff --git a/src/Rules.Framework.WebUI/Handlers/GetIndexPageHandler.cs b/src/Rules.Framework.WebUI/Handlers/GetIndexPageHandler.cs deleted file mode 100644 index 04734c5a..00000000 --- a/src/Rules.Framework.WebUI/Handlers/GetIndexPageHandler.cs +++ /dev/null @@ -1,97 +0,0 @@ -namespace Rules.Framework.WebUI.Handlers -{ - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Text; - using System.Text.RegularExpressions; - using System.Threading.Tasks; - using Microsoft.AspNetCore.Http; - - internal sealed class GetIndexPageHandler : WebUIRequestHandlerBase - { - private static readonly string[] resourcePath = new[] { "/{0}", "/{0}/", "/{0}/index.html" }; - - public GetIndexPageHandler(WebUIOptions webUIOptions) : base(resourcePath, webUIOptions) - { - } - - protected override HttpMethod HttpMethod => HttpMethod.GET; - - protected override async Task HandleRequestAsync(HttpRequest httpRequest, HttpResponse httpResponse, RequestDelegate next) - { - var path = httpRequest.Path.Value; - var httpContext = httpRequest.HttpContext; - - if (Regex.IsMatch(path, $"^/?{Regex.Escape(this.WebUIOptions.RoutePrefix)}/?$", RegexOptions.IgnoreCase)) - { - // Use relative redirect to support proxy environments - var relativeIndexUrl = string.IsNullOrEmpty(path) || path.EndsWith("/") - ? "index.html" - : $"{path.Split('/').Last()}/index.html"; - - RespondWithRedirect(httpContext.Response, relativeIndexUrl); - } - - if (Regex.IsMatch(path, $"^/{Regex.Escape(this.WebUIOptions.RoutePrefix)}/?index.html$", RegexOptions.IgnoreCase)) - { - await this.RespondWithIndexHtmlAsync(httpContext.Response, next).ConfigureAwait(false); - } - } - - private static void RespondWithRedirect(HttpResponse httpResponse, string location) - { - if (!httpResponse.HasStarted) - { - httpResponse.StatusCode = 301; - httpResponse.Headers["Location"] = location; - } - } - - private IDictionary GetIndexArguments() - { - return new Dictionary - { - { "%(DocumentTitle)", this.WebUIOptions.DocumentTitle }, - { "%(HeadContent)", this.WebUIOptions.HeadContent } - }; - } - - private async Task RespondWithIndexHtmlAsync(HttpResponse httpResponse, RequestDelegate next) - { - if (!httpResponse.HasStarted) - { - httpResponse.StatusCode = 200; - httpResponse.ContentType = "text/html;charset=utf-8"; - - var originalBody = httpResponse.Body; - - using (var stream = this.WebUIOptions.IndexStream()) - { - httpResponse.Body = stream; - await next(httpResponse.HttpContext).ConfigureAwait(false); - - using (var reader = new StreamReader(stream)) - { - var body = await reader.ReadToEndAsync().ConfigureAwait(false); - - var responseTextBuilder = new StringBuilder(body); - - foreach (var entry in this.GetIndexArguments()) - { - responseTextBuilder.Replace(entry.Key, entry.Value); - } - - var byteArray = Encoding.UTF8.GetBytes(responseTextBuilder.ToString()); - using (var newStream = new MemoryStream(byteArray)) - { - httpResponse.Body = originalBody; - newStream.Seek(0, SeekOrigin.Begin); - await newStream.CopyToAsync(httpResponse.Body).ConfigureAwait(false); - } - } - } - } - } - } -} \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/Handlers/GetRulesHandler.cs b/src/Rules.Framework.WebUI/Handlers/GetRulesHandler.cs deleted file mode 100644 index cf7c00d7..00000000 --- a/src/Rules.Framework.WebUI/Handlers/GetRulesHandler.cs +++ /dev/null @@ -1,159 +0,0 @@ -namespace Rules.Framework.WebUI.Handlers -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Net; - using System.Text.Json; - using System.Threading.Tasks; - using System.Web; - using Microsoft.AspNetCore.Http; - using Rules.Framework.Generics; - using Rules.Framework.WebUI.Dto; - using Rules.Framework.WebUI.Extensions; - - internal sealed class GetRulesHandler : WebUIRequestHandlerBase - { - private static readonly string[] resourcePath = new[] { "/{0}/api/v1/rules" }; - private readonly IGenericRulesEngine genericRulesEngine; - private readonly IRuleStatusDtoAnalyzer ruleStatusDtoAnalyzer; - - public GetRulesHandler(IGenericRulesEngine genericRulesEngine, IRuleStatusDtoAnalyzer ruleStatusDtoAnalyzer, WebUIOptions webUIOptions) : base(resourcePath, webUIOptions) - { - this.genericRulesEngine = genericRulesEngine; - this.ruleStatusDtoAnalyzer = ruleStatusDtoAnalyzer; - } - - protected override HttpMethod HttpMethod => HttpMethod.GET; - - protected override async Task HandleRequestAsync(HttpRequest httpRequest, - HttpResponse httpResponse, - RequestDelegate next) - { - var rulesFilter = this.GetRulesFilterFromRequest(httpRequest); - - if (!IsValidFilterDates(rulesFilter)) - { - await this.WriteResponseAsync(httpResponse, new { Message = "Date begin cannot be greater than after" }, (int)HttpStatusCode.BadRequest) - .ConfigureAwait(false); - - return; - } - - try - { - var rules = new List(); - - if (rulesFilter.ContentType.Equals("all")) - { - var contents = this.genericRulesEngine.GetContentTypes(); - - foreach (var identifier in contents.Select(c => c.Identifier)) - { - var rulesForContentType = await this.GetRulesForContentyType(identifier, rulesFilter).ConfigureAwait(false); - rules.AddRange(rulesForContentType); - } - } - else - { - var rulesForContentType = await this.GetRulesForContentyType(rulesFilter.ContentType, rulesFilter).ConfigureAwait(false); - rules.AddRange(rulesForContentType); - } - - await this.WriteResponseAsync(httpResponse, rules, (int)HttpStatusCode.OK).ConfigureAwait(false); - } - catch (Exception ex) - { - await this.WriteExceptionResponseAsync(httpResponse, ex).ConfigureAwait(false); - } - } - - private static bool IsValidFilterDates(RulesFilterDto rulesFilter) - { - return (rulesFilter.DateBegin is null - || rulesFilter.DateEnd is null) || - (rulesFilter.DateBegin <= rulesFilter.DateEnd); - } - - private IEnumerable ApplyFilters(RulesFilterDto rulesFilter, IEnumerable genericRulesDto) - { - if (!string.IsNullOrWhiteSpace(rulesFilter.Content)) - { - genericRulesDto = genericRulesDto.Where(g => - { -#if NETSTANDARD2_0 - return JsonSerializer.Serialize(g.Value).ToUpper().Contains(rulesFilter.Content.ToUpper()); -#else - return JsonSerializer.Serialize(g.Value).Contains(rulesFilter.Content, StringComparison.OrdinalIgnoreCase); -#endif - }); - } - - if (!string.IsNullOrWhiteSpace(rulesFilter.Name)) - { - genericRulesDto = genericRulesDto.Where(g => - { -#if NETSTANDARD2_0 - return g.Name.ToUpper().Contains(rulesFilter.Name.ToUpper()); -#else - return g.Name.Contains(rulesFilter.Name, StringComparison.OrdinalIgnoreCase); -#endif - }); - } - if (rulesFilter.Status != null) - { - genericRulesDto = genericRulesDto.Where(g => - { - return g.Status.Equals(rulesFilter.Status.ToString()); - }); - } - - return genericRulesDto; - } - - private RulesFilterDto GetRulesFilterFromRequest(HttpRequest httpRequest) - { - var parseQueryString = HttpUtility.ParseQueryString(httpRequest.QueryString.Value); - - var rulesFilterAsString = JsonSerializer.Serialize(parseQueryString.Cast().ToDictionary(k => k, v => string.IsNullOrWhiteSpace(parseQueryString[v]) ? null : parseQueryString[v])); - var rulesFilter = JsonSerializer.Deserialize(rulesFilterAsString, this.SerializerOptions); - - rulesFilter.ContentType = string.IsNullOrWhiteSpace(rulesFilter.ContentType) ? "all" : rulesFilter.ContentType; - - rulesFilter.DateEnd ??= DateTime.MaxValue; - - rulesFilter.DateBegin ??= DateTime.MinValue; - - return rulesFilter; - } - - private async Task> GetRulesForContentyType(string identifier, RulesFilterDto rulesFilter) - { - var genericRules = await this.genericRulesEngine.SearchAsync( - new SearchArgs( - new GenericContentType { Identifier = identifier }, - rulesFilter.DateBegin.Value, rulesFilter.DateEnd.Value)) - .ConfigureAwait(false); - - var priorityCriteria = this.genericRulesEngine.GetPriorityCriteria(); - - if (genericRules != null && genericRules.Any()) - { - if (priorityCriteria == PriorityCriterias.BottommostRuleWins) - { - genericRules = genericRules.OrderByDescending(r => r.Priority); - } - else - { - genericRules = genericRules.OrderBy(r => r.Priority); - } - - var genericRulesDto = this.ApplyFilters(rulesFilter, genericRules.Select(g => g.ToRuleDto(identifier, this.ruleStatusDtoAnalyzer))); - - return genericRulesDto; - } - - return Enumerable.Empty(); - } - } -} \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/HttpMethod.cs b/src/Rules.Framework.WebUI/HttpMethod.cs deleted file mode 100644 index 164e6808..00000000 --- a/src/Rules.Framework.WebUI/HttpMethod.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Rules.Framework.WebUI -{ - internal enum HttpMethod - { - None = 0, - GET = 1, - POST = 2, - PUT = 3, - PATCH = 4, - DELETE = 5 - } -} \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/IHttpRequestHandler.cs b/src/Rules.Framework.WebUI/IHttpRequestHandler.cs deleted file mode 100644 index 996f5c8c..00000000 --- a/src/Rules.Framework.WebUI/IHttpRequestHandler.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Rules.Framework.WebUI -{ - using System.Threading.Tasks; - using Microsoft.AspNetCore.Http; - - internal interface IHttpRequestHandler - { - Task HandleAsync(HttpRequest httpRequest, HttpResponse httpResponse, RequestDelegate next); - } -} \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/IRulesEngineInstancesRegistrar.cs b/src/Rules.Framework.WebUI/IRulesEngineInstancesRegistrar.cs new file mode 100644 index 00000000..7483fbd7 --- /dev/null +++ b/src/Rules.Framework.WebUI/IRulesEngineInstancesRegistrar.cs @@ -0,0 +1,27 @@ +namespace Rules.Framework.WebUI +{ + using System; + using System.Threading.Tasks; + + /// + /// Represents the registration of instances on the Web UI. + /// + public interface IRulesEngineInstancesRegistrar + { + /// + /// Adds a rules engine instance to be presented and used on Web UI. + /// + /// The name. + /// The get rules engine function. + /// + IRulesEngineInstancesRegistrar AddInstance(string name, Func getRulesEngineFunc); + + /// + /// Adds a rules engine instance to be presented and used on Web UI. + /// + /// The name. + /// The get rules engine function. + /// + IRulesEngineInstancesRegistrar AddInstance(string name, Func> getRulesEngineFunc); + } +} \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/Rules.Framework.WebUI.csproj b/src/Rules.Framework.WebUI/Rules.Framework.WebUI.csproj index 50e7336a..572ea993 100644 --- a/src/Rules.Framework.WebUI/Rules.Framework.WebUI.csproj +++ b/src/Rules.Framework.WebUI/Rules.Framework.WebUI.csproj @@ -1,75 +1,47 @@ - - - - true - true - net6.0;netstandard2.0 - 10.0 - - - - - - - LICENSE.md - - - Git - rules rulesframework web ui - A rules framework web ui that allows you to see the rules configured in your application - - + + + true + true + net8.0 + 10.0 + + + + + + + LICENSE.md + + + Git + rules rulesframework web ui + A rules framework web ui that allows you to see the rules configured in your application + + True + - - - - - - - - - - - - + + + + $(AssemblyName).Assets.$(AssemblyName).styles.css + + + - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - + - + + + - - - - - - - - - + \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/Services/GuidGenerator.cs b/src/Rules.Framework.WebUI/Services/GuidGenerator.cs new file mode 100644 index 00000000..3c9dbda7 --- /dev/null +++ b/src/Rules.Framework.WebUI/Services/GuidGenerator.cs @@ -0,0 +1,20 @@ +namespace Rules.Framework.WebUI.Services +{ + using System; + using System.Security.Cryptography; + using System.Text; + + internal static class GuidGenerator + { + private static readonly HashAlgorithm hashAlgorithm = SHA256.Create(); + + public static Guid GenerateFromString(string source) + { + ArgumentNullException.ThrowIfNull(source); + ArgumentException.ThrowIfNullOrWhiteSpace(source); + + var hashedBytes = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(source)); + return new Guid(hashedBytes[..16]); + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/Services/IRulesEngineInstanceProvider.cs b/src/Rules.Framework.WebUI/Services/IRulesEngineInstanceProvider.cs new file mode 100644 index 00000000..7427f626 --- /dev/null +++ b/src/Rules.Framework.WebUI/Services/IRulesEngineInstanceProvider.cs @@ -0,0 +1,12 @@ +namespace Rules.Framework.WebUI.Services +{ + using System; + using System.Collections.Generic; + + internal interface IRulesEngineInstanceProvider + { + IEnumerable GetAllInstances(); + + RulesEngineInstance GetInstance(Guid instanceId); + } +} \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/Services/RulesEngineInstance.cs b/src/Rules.Framework.WebUI/Services/RulesEngineInstance.cs new file mode 100644 index 00000000..52db89cd --- /dev/null +++ b/src/Rules.Framework.WebUI/Services/RulesEngineInstance.cs @@ -0,0 +1,13 @@ +namespace Rules.Framework.WebUI.Services +{ + using System; + + internal class RulesEngineInstance + { + public Guid Id { get; set; } + + public string Name { get; set; } + + public IRulesEngine RulesEngine { get; set; } + } +} \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/Services/RulesEngineInstanceProvider.cs b/src/Rules.Framework.WebUI/Services/RulesEngineInstanceProvider.cs new file mode 100644 index 00000000..fba78c49 --- /dev/null +++ b/src/Rules.Framework.WebUI/Services/RulesEngineInstanceProvider.cs @@ -0,0 +1,82 @@ +namespace Rules.Framework.WebUI.Services +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + + internal sealed class RulesEngineInstanceProvider : IRulesEngineInstanceProvider, IRulesEngineInstancesRegistrar + { + private readonly List<(Guid, string, Func)> instanceMappings; + private readonly Dictionary instances; + private bool instancesEnumerated; + + public RulesEngineInstanceProvider() + { + this.instanceMappings = new List<(Guid, string, Func)>(); + this.instances = new Dictionary(); + this.instancesEnumerated = false; + } + + public IRulesEngineInstancesRegistrar AddInstance(string name, Func getRulesEngineFunc) + { + if (this.instanceMappings.Any(m => string.Equals(m.Item2, name, StringComparison.Ordinal))) + { + throw new InvalidOperationException($"A rules engine instance with name '{name}' has already been specified."); + } + + this.instanceMappings.Add((GuidGenerator.GenerateFromString(name), name, getRulesEngineFunc)); + return this; + } + + public IRulesEngineInstancesRegistrar AddInstance(string name, Func> getRulesEngineFunc) + => this.AddInstance(name, (serviceProvider, name) => getRulesEngineFunc.Invoke(serviceProvider, name).GetAwaiter().GetResult()); + + public void EnumerateInstances(IServiceProvider serviceProvider) + { + if (!instancesEnumerated) + { + foreach (var (id, name, getRulesEngineFunc) in instanceMappings) + { + var instance = new RulesEngineInstance + { + Id = id, + Name = name, + RulesEngine = getRulesEngineFunc.Invoke(serviceProvider, name), + }; + + this.instances.Add(id, instance); + } + + instancesEnumerated = true; + } + } + + public IEnumerable GetAllInstances() + { + if (!instancesEnumerated) + { + throw new InvalidOperationException($"Instances enumeration is required before invoking '{nameof(GetAllInstances)}'." + + $" Please ensure '{nameof(EnumerateInstances)}' is invoked first."); + } + + return this.instances.Values; + } + + public RulesEngineInstance GetInstance(Guid instanceId) + { + if (!instancesEnumerated) + { + throw new InvalidOperationException($"Instances enumeration is required before invoking '{nameof(GetInstance)}'." + + $" Please ensure '{nameof(EnumerateInstances)}' is invoked first."); + } + + if (this.instances.TryGetValue(instanceId, out var instance)) + { + return instance; + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/Utitlies/PolymorphicWriteOnlyJsonConverter.cs b/src/Rules.Framework.WebUI/Utilities/PolymorphicWriteOnlyJsonConverter.cs similarity index 84% rename from src/Rules.Framework.WebUI/Utitlies/PolymorphicWriteOnlyJsonConverter.cs rename to src/Rules.Framework.WebUI/Utilities/PolymorphicWriteOnlyJsonConverter.cs index 35dc6bba..186eedf5 100644 --- a/src/Rules.Framework.WebUI/Utitlies/PolymorphicWriteOnlyJsonConverter.cs +++ b/src/Rules.Framework.WebUI/Utilities/PolymorphicWriteOnlyJsonConverter.cs @@ -1,9 +1,11 @@ -namespace Rules.Framework.WebUI.Utitlies +namespace Rules.Framework.WebUI.Utilities { using System; + using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Text.Json.Serialization; + [ExcludeFromCodeCoverage] internal sealed class PolymorphicWriteOnlyJsonConverter : JsonConverter { public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) diff --git a/src/Rules.Framework.WebUI/ViewModels/ComposedConditionNodeViewModel.cs b/src/Rules.Framework.WebUI/ViewModels/ComposedConditionNodeViewModel.cs new file mode 100644 index 00000000..ef46e62e --- /dev/null +++ b/src/Rules.Framework.WebUI/ViewModels/ComposedConditionNodeViewModel.cs @@ -0,0 +1,9 @@ +namespace Rules.Framework.WebUI.ViewModels +{ + using System.Collections.Generic; + + internal sealed class ComposedConditionNodeViewModel : ConditionNodeViewModel + { + public IEnumerable ChildConditionNodes { get; internal set; } + } +} \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/ViewModels/ConditionNodeViewModel.cs b/src/Rules.Framework.WebUI/ViewModels/ConditionNodeViewModel.cs new file mode 100644 index 00000000..e1c1ec46 --- /dev/null +++ b/src/Rules.Framework.WebUI/ViewModels/ConditionNodeViewModel.cs @@ -0,0 +1,7 @@ +namespace Rules.Framework.WebUI.ViewModels +{ + internal class ConditionNodeViewModel + { + public string LogicalOperator { get; internal set; } + } +} \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/ViewModels/OptionViewModel.cs b/src/Rules.Framework.WebUI/ViewModels/OptionViewModel.cs new file mode 100644 index 00000000..16bdbe92 --- /dev/null +++ b/src/Rules.Framework.WebUI/ViewModels/OptionViewModel.cs @@ -0,0 +1,16 @@ +namespace Rules.Framework.WebUI.ViewModels +{ + using System.Diagnostics.CodeAnalysis; + + [ExcludeFromCodeCoverage] + internal sealed class OptionViewModel + { + public string Name { get; set; } + + public string NameDescription { get; set; } + + public object Value { get; set; } + + public string ValueDescription { get; set; } + } +} \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/ViewModels/RuleViewModel.cs b/src/Rules.Framework.WebUI/ViewModels/RuleViewModel.cs new file mode 100644 index 00000000..ac3b6c59 --- /dev/null +++ b/src/Rules.Framework.WebUI/ViewModels/RuleViewModel.cs @@ -0,0 +1,27 @@ +namespace Rules.Framework.WebUI.ViewModels +{ + using System; + + internal sealed class RuleViewModel + { + public bool Active { get; set; } + + public string Conditions { get; set; } + + public object Content { get; set; } + + public DateTime DateBegin { get; set; } + + public DateTime? DateEnd { get; set; } + + public Guid Id { get; set; } + + public string Name { get; set; } + + public int Priority { get; set; } + + public ConditionNodeViewModel RootCondition { get; set; } + + public string Ruleset { get; set; } + } +} \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/ViewModels/RuleViewModelExtensions.cs b/src/Rules.Framework.WebUI/ViewModels/RuleViewModelExtensions.cs new file mode 100644 index 00000000..d68ad47c --- /dev/null +++ b/src/Rules.Framework.WebUI/ViewModels/RuleViewModelExtensions.cs @@ -0,0 +1,108 @@ +namespace Rules.Framework.WebUI.ViewModels +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Rules.Framework.ConditionNodes; + using Rules.Framework.WebUI.Components.PageComponents; + using Rules.Framework.WebUI.Services; + + internal static class RuleViewModelExtensions + { + public static ExportRules.Rule ToExportRulesModel(this RuleViewModel ruleViewModel) + { + return new ExportRules.Rule + { + Active = ruleViewModel.Active, + Content = ruleViewModel.Content, + DateBegin = ruleViewModel.DateBegin, + DateEnd = ruleViewModel.DateEnd, + Name = ruleViewModel.Name, + Priority = ruleViewModel.Priority, + RootCondition = ruleViewModel.RootCondition.ToExportRulesModel(), + Ruleset = ruleViewModel.Ruleset, + }; + } + + public static RuleViewModel ToViewModel(this Rule rule) + { + return new RuleViewModel + { + Active = rule.Active, + Content = rule.ContentContainer.GetContentAs(), + DateBegin = rule.DateBegin, + DateEnd = rule.DateEnd, + Id = GuidGenerator.GenerateFromString(rule.Name), + Name = rule.Name, + Priority = rule.Priority, + RootCondition = rule.RootCondition.ToViewModel(), + Ruleset = rule.Ruleset, + }; + } + + private static ExportRules.ConditionNode ToExportRulesModel(this ConditionNodeViewModel rootCondition) + { + if (rootCondition.LogicalOperator == nameof(LogicalOperators.Eval)) + { + var condition = rootCondition as ValueConditionNodeViewModel; + + return new ExportRules.ValueConditionNode + { + Condition = condition.Condition, + DataType = condition.DataType.ToString(), + LogicalOperator = condition.LogicalOperator, + Operand = condition.Operand, + Operator = condition.Operator.ToString(), + }; + } + + var composedConditionNode = rootCondition as ComposedConditionNodeViewModel; + + var conditionNodeDataModels = new List(composedConditionNode.ChildConditionNodes.Count()); + + foreach (var child in composedConditionNode.ChildConditionNodes) + { + conditionNodeDataModels.Add(child.ToExportRulesModel()); + } + + return new ExportRules.ComposedConditionNode + { + ChildConditionNodes = conditionNodeDataModels, + LogicalOperator = composedConditionNode.LogicalOperator.ToString(), + }; + } + + private static ConditionNodeViewModel ToViewModel(this IConditionNode rootCondition) + { + if (rootCondition.LogicalOperator == LogicalOperators.Eval || + rootCondition.LogicalOperator == 0) + { + var condition = rootCondition as ValueConditionNode; + + return new ValueConditionNodeViewModel + { + Condition = condition.Condition, + DataType = condition.DataType.ToString(), + LogicalOperator = condition.LogicalOperator.ToString(), + Operand = condition.Operand, + Operator = condition.Operator.ToString(), + }; + } + + var composedConditionNode = rootCondition as ComposedConditionNode; + + var conditionNodeDataModels = new List(composedConditionNode.ChildConditionNodes.Count()); + + foreach (var child in composedConditionNode.ChildConditionNodes) + { + conditionNodeDataModels.Add(child.ToViewModel()); + } + + return new ComposedConditionNodeViewModel + { + ChildConditionNodes = conditionNodeDataModels, + LogicalOperator = composedConditionNode.LogicalOperator.ToString(), + }; + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/ViewModels/RulesEngineInstanceViewModel.cs b/src/Rules.Framework.WebUI/ViewModels/RulesEngineInstanceViewModel.cs new file mode 100644 index 00000000..5c5d43ac --- /dev/null +++ b/src/Rules.Framework.WebUI/ViewModels/RulesEngineInstanceViewModel.cs @@ -0,0 +1,16 @@ +namespace Rules.Framework.WebUI.ViewModels +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + + [ExcludeFromCodeCoverage] + internal sealed class RulesEngineInstanceViewModel + { + public Guid Id { get; set; } + + public string Name { get; set; } + + public IEnumerable Options { get; set; } + } +} \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/ViewModels/RulesetViewModel.cs b/src/Rules.Framework.WebUI/ViewModels/RulesetViewModel.cs new file mode 100644 index 00000000..68efb6fe --- /dev/null +++ b/src/Rules.Framework.WebUI/ViewModels/RulesetViewModel.cs @@ -0,0 +1,19 @@ +namespace Rules.Framework.WebUI.ViewModels +{ + using System; + using System.Diagnostics.CodeAnalysis; + + [ExcludeFromCodeCoverage] + internal sealed class RulesetViewModel + { + public int ActiveRulesCount { get; set; } + + public Guid Id { get; set; } + + public string Name { get; set; } + + public int Number { get; set; } + + public int TotalRulesCount { get; set; } + } +} \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/Dto/ValueConditionNodeDto.cs b/src/Rules.Framework.WebUI/ViewModels/ValueConditionNodeViewModel.cs similarity index 50% rename from src/Rules.Framework.WebUI/Dto/ValueConditionNodeDto.cs rename to src/Rules.Framework.WebUI/ViewModels/ValueConditionNodeViewModel.cs index 7c9e3f8d..db128258 100644 --- a/src/Rules.Framework.WebUI/Dto/ValueConditionNodeDto.cs +++ b/src/Rules.Framework.WebUI/ViewModels/ValueConditionNodeViewModel.cs @@ -1,8 +1,8 @@ -namespace Rules.Framework.WebUI.Dto +namespace Rules.Framework.WebUI.ViewModels { - internal sealed class ValueConditionNodeDto : ConditionNodeDto + internal sealed class ValueConditionNodeViewModel : ConditionNodeViewModel { - public string ConditionTypeName { get; internal set; } + public string Condition { get; internal set; } public string DataType { get; internal set; } @@ -10,4 +10,4 @@ internal sealed class ValueConditionNodeDto : ConditionNodeDto public string Operator { get; internal set; } } -} +} \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/WebUIApplicationBuilderExtensions.cs b/src/Rules.Framework.WebUI/WebUIApplicationBuilderExtensions.cs index ab9d7105..0eb15f33 100644 --- a/src/Rules.Framework.WebUI/WebUIApplicationBuilderExtensions.cs +++ b/src/Rules.Framework.WebUI/WebUIApplicationBuilderExtensions.cs @@ -1,101 +1,71 @@ namespace Rules.Framework.WebUI { using System; - using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Threading.Tasks; + using Components; using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; - using Microsoft.Extensions.Options; - using Rules.Framework.Generics; - using Rules.Framework.WebUI.Dto; - using Rules.Framework.WebUI.Handlers; + using Microsoft.Extensions.FileProviders; + using Rules.Framework.WebUI.Services; /// - /// IApplicationBuilder extension for Rules Framework Web UI + /// extension for Rules Framework Web UI /// + [ExcludeFromCodeCoverage] public static class WebUIApplicationBuilderExtensions { /// /// Uses the rules framework web UI. /// /// The application. - /// The generic rules engine factory. - /// The web UI options action. /// - public static IApplicationBuilder UseRulesFrameworkWebUI(this IApplicationBuilder app, - Func genericRulesEngineFactory, - Action webUIOptionsAction) - { - var genericRulesEngine = genericRulesEngineFactory.Invoke(app.ApplicationServices); - return app.UseRulesFrameworkWebUI(genericRulesEngine, webUIOptionsAction); - } - - /// - /// Uses the rules framework web UI. - /// - /// The application. - /// The generic rules engine factory. - /// - public static IApplicationBuilder UseRulesFrameworkWebUI(this IApplicationBuilder app, - Func genericRulesEngineFactory) - { - return app.UseRulesFrameworkWebUI(genericRulesEngineFactory, null); - } - - /// - /// Uses the rules framework web UI. - /// - /// The application. - /// The generic rules engine. - /// - public static IApplicationBuilder UseRulesFrameworkWebUI(this IApplicationBuilder app, - IGenericRulesEngine genericRulesEngine) + public static IApplicationBuilder UseRulesFrameworkWebUI( + this IApplicationBuilder app) { - return app.UseRulesFrameworkWebUI(genericRulesEngine, new WebUIOptions()); + return app.UseRulesFrameworkWebUI(options => { }); } /// /// Uses the rules framework web UI. /// /// The application. - /// The generic rules engine. - /// The web UI options action. + /// The web UI options configuration action. /// - public static IApplicationBuilder UseRulesFrameworkWebUI(this IApplicationBuilder app, - IGenericRulesEngine genericRulesEngine, + public static IApplicationBuilder UseRulesFrameworkWebUI( + this IApplicationBuilder app, Action webUIOptionsAction) { - WebUIOptions webUIOptions; + var rulesEngineInstanceProvider = app.ApplicationServices.GetRequiredService(); + rulesEngineInstanceProvider.EnumerateInstances(app.ApplicationServices); - using (var scope = app.ApplicationServices.CreateScope()) - { - webUIOptions = scope.ServiceProvider.GetRequiredService>().Value; - webUIOptionsAction?.Invoke(webUIOptions); - } + // Options + var webUIOptions = new WebUIOptions(); + webUIOptionsAction.Invoke(webUIOptions); + var webUIOptionsRegistry = app.ApplicationServices.GetRequiredService(); + webUIOptionsRegistry.Register(webUIOptions); - return app.UseRulesFrameworkWebUI(genericRulesEngine, webUIOptions); - } + // Blazor + var embeddedProvider = new EmbeddedFileProvider(typeof(WebUIApplicationBuilderExtensions).Assembly, "Rules.Framework.WebUI.Assets"); - /// - /// Uses the rules framework web UI. - /// - /// The application. - /// The generic rules engine. - /// The web UI options. - /// - private static IApplicationBuilder UseRulesFrameworkWebUI(this IApplicationBuilder app, IGenericRulesEngine genericRulesEngine, - WebUIOptions webUIOptions) - { - var ruleStatusDtoAnalyzer = new RuleStatusDtoAnalyzer(); + app.UseStaticFiles(new StaticFileOptions + { + FileProvider = embeddedProvider, + RequestPath = new PathString("/rules-ui") + }); - app.UseMiddleware( - new List + app.UseEndpoints(builder => + { + builder.MapGet("/rules-ui", ctx => { - new GetIndexPageHandler(webUIOptions), - new GetConfigurationsHandler(genericRulesEngine, webUIOptions), - new GetContentTypeHandler(genericRulesEngine, ruleStatusDtoAnalyzer, webUIOptions), - new GetRulesHandler(genericRulesEngine, ruleStatusDtoAnalyzer, webUIOptions) - }, - webUIOptions); + ctx.Response.Redirect("/rules-ui/instance"); + return Task.CompletedTask; + }); + + builder.MapRazorComponents() + .AddInteractiveServerRenderMode(); + }); return app; } diff --git a/src/Rules.Framework.WebUI/WebUIConstants.cs b/src/Rules.Framework.WebUI/WebUIConstants.cs new file mode 100644 index 00000000..cb4e8bd3 --- /dev/null +++ b/src/Rules.Framework.WebUI/WebUIConstants.cs @@ -0,0 +1,39 @@ +namespace Rules.Framework.WebUI +{ + internal static class WebUIConstants + { + public const string IsUniqueInstanceStorageKey = "Is-Unique-Instance"; + + public const string OptionAutoCreateRulesetsDescription = "Behavior of the rules engine when inserting a new rule" + + " whose ruleset does not exist. If set to true, the ruleset will be created before inserting the rule, otherwise rule" + + " insertion will fail."; + + public const string OptionDataTypeDefaultsDescription = "The default of each data type to be used when a specific" + + " condition is not given on 'conditions' parameter when matching (one or all) rules of a particular ruleset."; + + public const string OptionMissingConditionBehaviorDescription = "Behavior of the rules engine when matching (one or all)" + + " rules of a particular ruleset and a specific condition is not given on 'conditions' parameter."; + + public const string OptionMissingConditionBehaviorDiscardDescription = "When a condition is missing, instructs the rules" + + " engine to discard the rule under evaluation (rule is considered not a match)."; + + public const string OptionMissingConditionBehaviorUseDataTypeDefaultDescription = "When a condition is missing, instructs" + + " the rules engine to use the configured data type default for rule's condition."; + + public const string OptionPriorityCriteriaBottommostRuleWinsDescription = "Rules with the highest priority number have greater" + + " priority than the ones with lowest."; + + public const string OptionPriorityCriteriaBottommostRuleWinsName = "Bottommost rule wins"; + + public const string OptionPriorityCriteriaDescription = "Sets the way the rules engine interprets the 'Priority' for each rule."; + + public const string OptionPriorityCriteriaTopmostRuleWinsDescription = "Rules with the lowest priority number have greater" + + " priority than the ones with highest."; + + public const string OptionPriorityCriteriaTopmostRuleWinsName = "Topmost rule wins"; + + public const string SelectedInstanceStorageKey = "Selected-Instance-ID"; + + public const string SelectedRulesetsStorageKey = "Selected-Ruleset-IDs"; + } +} \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/WebUIMiddleware.cs b/src/Rules.Framework.WebUI/WebUIMiddleware.cs deleted file mode 100644 index 9f04643c..00000000 --- a/src/Rules.Framework.WebUI/WebUIMiddleware.cs +++ /dev/null @@ -1,89 +0,0 @@ -namespace Rules.Framework.WebUI -{ - using System.Collections.Generic; - using System.Linq; - using System.Reflection; - using System.Threading.Tasks; - using Microsoft.AspNetCore.Builder; - using Microsoft.AspNetCore.Hosting; - using Microsoft.AspNetCore.Http; - using Microsoft.AspNetCore.StaticFiles; - using Microsoft.Extensions.FileProviders; - using Microsoft.Extensions.Logging; - using Microsoft.Extensions.Options; - -#if NETSTANDARD2_0 - - using IWebHostEnvironment = Microsoft.AspNetCore.Hosting.IHostingEnvironment; - -#endif - - internal sealed class WebUIMiddleware - { - private readonly IEnumerable httpRequestHandlers; - private readonly RequestDelegate next; - private readonly StaticFileMiddleware staticFileMiddlewares; - - public WebUIMiddleware( - RequestDelegate next, - IWebHostEnvironment hostingEnv, - ILoggerFactory loggerFactory, - IEnumerable httpRequestHandlers, - WebUIOptions options) - { - this.httpRequestHandlers = httpRequestHandlers; - this.next = next; - this.staticFileMiddlewares = CreateStaticFileMiddleware(next, hostingEnv, loggerFactory, options, ".node_modules"); - } - - public async Task InvokeAsync(HttpContext httpContext) - { - var anyHandlerExecuted = await this.ExecuteHandlersAsync(httpContext).ConfigureAwait(false); - if (!anyHandlerExecuted) - { - await this.ExecuteStaticFileMiddlewareAsync(httpContext).ConfigureAwait(true); - } - } - - private static StaticFileMiddleware CreateStaticFileMiddleware(RequestDelegate next, - IWebHostEnvironment hostingEnv, - ILoggerFactory loggerFactory, - WebUIOptions options, - string path) - { - var asssembly = typeof(WebUIMiddleware).GetTypeInfo().Assembly; - - var provider = new EmbeddedFileProvider(asssembly, asssembly.GetName().Name + path); - - var staticFileOptions = new StaticFileOptions - { - RequestPath = string.IsNullOrEmpty(options.RoutePrefix) ? string.Empty : $"/{options.RoutePrefix}", - FileProvider = provider, - ServeUnknownFileTypes = true - }; - - return new StaticFileMiddleware(next, hostingEnv, Options.Create(staticFileOptions), loggerFactory); - } - - private async Task ExecuteHandlersAsync(HttpContext httpContext) - { - var results = this.httpRequestHandlers.Select(d => d - .HandleAsync(httpContext.Request, httpContext.Response, this.next)); - - var handle = await Task.WhenAll(results).ConfigureAwait(false); - - if (handle.All(d => !d)) - { - await this.next(httpContext).ConfigureAwait(false); - return false; - } - return true; - } - - private Task ExecuteStaticFileMiddlewareAsync(HttpContext httpContext) - { - return this.staticFileMiddlewares - .Invoke(httpContext); - } - } -} \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/WebUIMvcBuilderExtensions.cs b/src/Rules.Framework.WebUI/WebUIMvcBuilderExtensions.cs new file mode 100644 index 00000000..4ecbede1 --- /dev/null +++ b/src/Rules.Framework.WebUI/WebUIMvcBuilderExtensions.cs @@ -0,0 +1,59 @@ +namespace Rules.Framework.WebUI +{ + using System; + using System.Diagnostics.CodeAnalysis; + using Microsoft.Extensions.DependencyInjection; + using Rules.Framework.WebUI.Services; + + /// + /// Extensions for registering Web UI under the . + /// + [ExcludeFromCodeCoverage] + public static class WebUIMvcBuilderExtensions + { + /// + /// Adds the rules framework web UI to the application, registering the rules engine + /// instances available to the web UI. + /// + /// The MVC core builder. + /// The instances registration action. + /// + public static IMvcCoreBuilder AddRulesFrameworkWebUI(this IMvcCoreBuilder mvcCoreBuilder, Action instancesRegistrationAction) + { + mvcCoreBuilder.Services.AddRulesFrameworkWebUIServices(instancesRegistrationAction); + + return mvcCoreBuilder + .AddApplicationPart(typeof(WebUIMvcBuilderExtensions).Assembly); + } + + /// + /// Adds the rules framework web UI to the application, registering the rules engine + /// instances available to the web UI. + /// + /// The MVC builder. + /// The instances registration action. + /// + public static IMvcBuilder AddRulesFrameworkWebUI(this IMvcBuilder mvcBuilder, Action instancesRegistrationAction) + { + mvcBuilder.Services.AddRulesFrameworkWebUIServices(instancesRegistrationAction); + + return mvcBuilder + .AddApplicationPart(typeof(WebUIMvcBuilderExtensions).Assembly); + } + + private static void AddRulesFrameworkWebUIServices(this IServiceCollection services, Action instancesRegistrationAction) + { + var rulesEngineInstanceProvider = new RulesEngineInstanceProvider(); + instancesRegistrationAction.Invoke(rulesEngineInstanceProvider); + services.AddSingleton(rulesEngineInstanceProvider); + services.AddSingleton(rulesEngineInstanceProvider); + services.AddSingleton(); + services.AddTransient(sp => sp.GetRequiredService().RegisteredOptions); + + // Blazor + services.AddRazorComponents() + .AddInteractiveServerComponents(); + services.AddBlazorBootstrap(); + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/WebUIOptions.cs b/src/Rules.Framework.WebUI/WebUIOptions.cs index e5946f31..9361d0ea 100644 --- a/src/Rules.Framework.WebUI/WebUIOptions.cs +++ b/src/Rules.Framework.WebUI/WebUIOptions.cs @@ -1,33 +1,14 @@ namespace Rules.Framework.WebUI { - using System; - using System.IO; - using System.Reflection; - /// /// Options for the Rules Framework Web UI /// public sealed class WebUIOptions { /// - /// Gets title for the Rules Framework Web UI + /// Gets title to present on the Rules Framework Web UI page title. If not specified, will + /// present "Rules Framework" as default. /// - public string DocumentTitle { get; } = "Rules Framework"; - - /// - /// Gets additional content to place in the head of the Rules Framework Web UI - /// - public string HeadContent { get; } = ""; - - /// - /// Gets a Stream function for retrieving the Rules Framework Web UI - /// - public Func IndexStream { get; } = () => typeof(WebUIOptions).GetTypeInfo().Assembly - .GetManifestResourceStream("Rules.Framework.WebUI.index.html"); - - /// - /// Gets or set route prefix for accessing the Rules Framework Web UI - /// - public string RoutePrefix { get; set; } = "rules"; + public string DocumentTitle { get; set; } = "Rules Framework"; } } \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/WebUIOptionsRegistry.cs b/src/Rules.Framework.WebUI/WebUIOptionsRegistry.cs new file mode 100644 index 00000000..2b717784 --- /dev/null +++ b/src/Rules.Framework.WebUI/WebUIOptionsRegistry.cs @@ -0,0 +1,19 @@ +namespace Rules.Framework.WebUI +{ + using System; + + internal class WebUIOptionsRegistry + { + public WebUIOptions RegisteredOptions { get; private set; } + + public void Register(WebUIOptions webUIOptions) + { + if (webUIOptions == null) + { + throw new ArgumentNullException(nameof(webUIOptions)); + } + + this.RegisteredOptions = webUIOptions; + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/WebUIRequestHandlerBase.cs b/src/Rules.Framework.WebUI/WebUIRequestHandlerBase.cs deleted file mode 100644 index 9ec111c2..00000000 --- a/src/Rules.Framework.WebUI/WebUIRequestHandlerBase.cs +++ /dev/null @@ -1,97 +0,0 @@ -namespace Rules.Framework.WebUI -{ - using System; - using System.Linq; - using System.Net; - using System.Text; - using System.Text.Json; - using System.Text.Json.Serialization; - using System.Threading.Tasks; - using Microsoft.AspNetCore.Http; - using Rules.Framework.WebUI.Dto; - using Rules.Framework.WebUI.Utitlies; - - internal abstract class WebUIRequestHandlerBase : IHttpRequestHandler - { - protected readonly JsonSerializerOptions SerializerOptions; - protected readonly WebUIOptions WebUIOptions; - - protected WebUIRequestHandlerBase(string[] resourcePath, WebUIOptions webUIOptions) - { - this.ResourcePath = resourcePath; - this.WebUIOptions = webUIOptions; - this.SerializerOptions = new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - WriteIndented = true, - IncludeFields = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }; - this.SerializerOptions.Converters.Add(new JsonStringEnumConverter()); - this.SerializerOptions.Converters.Add(new PolymorphicWriteOnlyJsonConverter()); - } - - protected abstract HttpMethod HttpMethod { get; } - - protected string[] ResourcePath { get; } - - public virtual async Task HandleAsync(HttpRequest httpRequest, HttpResponse httpResponse, RequestDelegate next) - { - if (!this.CanHandle(httpRequest)) - { - return false; - } - - await this.HandleRequestAsync(httpRequest, httpResponse, next).ConfigureAwait(false); - - return true; - } - - protected bool CanHandle(HttpRequest httpRequest) - { - var resource = httpRequest.Path.ToUriComponent(); - - var resourcesPath = this.ResourcePath.Select(r => string.Format(r, this.WebUIOptions.RoutePrefix)); - - if (!resourcesPath.Contains(resource)) - { - return false; - } - - var method = httpRequest.Method; - - if (!method.Equals(this.HttpMethod.ToString())) - { - return false; - } - - return true; - } - - protected abstract Task HandleRequestAsync(HttpRequest httpRequest, HttpResponse httpResponse, RequestDelegate next); - - protected Task WriteExceptionResponseAsync(HttpResponse httpResponse, Exception exception) - { - var error = new StringBuilder(exception.Message); - if (exception.InnerException != null) - { - error.AppendLine(exception.InnerException.Message); - } - - return this.WriteResponseAsync(httpResponse, error, (int)HttpStatusCode.InternalServerError); - } - - protected virtual async Task WriteResponseAsync(HttpResponse httpResponse, T responseDto, int statusCode) - { - if (!httpResponse.HasStarted) - { - var body = JsonSerializer.Serialize(responseDto, this.SerializerOptions); - httpResponse.StatusCode = statusCode; - httpResponse.ContentType = "application/json"; - httpResponse.Headers.ContentLength = body.Length; - - await httpResponse.WriteAsync(body, Encoding.UTF8).ConfigureAwait(false); - } - } - } -} diff --git a/src/Rules.Framework.WebUI/index.html b/src/Rules.Framework.WebUI/index.html deleted file mode 100644 index 13fe9a1d..00000000 --- a/src/Rules.Framework.WebUI/index.html +++ /dev/null @@ -1,895 +0,0 @@ - - - - - %(DocumentTitle) - - - - - - %(HeadContent) - - - - - - - -
- - - -
- -
- -
-
-
-

Welcome to Rules.Framework Web UI.
Allows you to see the rules configured in your application.

-
-
- - - - - -
- -
- -
-
-
-
- - - - - - - - - - diff --git a/src/Rules.Framework.WebUI/node_modules/bootstrap/css/bootstrap.min.css b/src/Rules.Framework.WebUI/node_modules/bootstrap/css/bootstrap.min.css deleted file mode 100644 index 1359b3b7..00000000 --- a/src/Rules.Framework.WebUI/node_modules/bootstrap/css/bootstrap.min.css +++ /dev/null @@ -1,7 +0,0 @@ -@charset "UTF-8";/*! - * Bootstrap v5.2.2 (https://getbootstrap.com/) - * Copyright 2011-2022 The Bootstrap Authors - * Copyright 2011-2022 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */:root{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-black:#000;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:13,110,253;--bs-secondary-rgb:108,117,125;--bs-success-rgb:25,135,84;--bs-info-rgb:13,202,240;--bs-warning-rgb:255,193,7;--bs-danger-rgb:220,53,69;--bs-light-rgb:248,249,250;--bs-dark-rgb:33,37,41;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-body-color-rgb:33,37,41;--bs-body-bg-rgb:255,255,255;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue","Noto Sans","Liberation Sans",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-bg:#fff;--bs-border-width:1px;--bs-border-style:solid;--bs-border-color:#dee2e6;--bs-border-color-translucent:rgba(0, 0, 0, 0.175);--bs-border-radius:0.375rem;--bs-border-radius-sm:0.25rem;--bs-border-radius-lg:0.5rem;--bs-border-radius-xl:1rem;--bs-border-radius-2xl:2rem;--bs-border-radius-pill:50rem;--bs-link-color:#0d6efd;--bs-link-hover-color:#0a58ca;--bs-code-color:#d63384;--bs-highlight-bg:#fff3cd}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;border:0;border-top:1px solid;opacity:.25}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.1875em;background-color:var(--bs-highlight-bg)}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:var(--bs-link-color);text-decoration:underline}a:hover{color:var(--bs-link-hover-color)}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:var(--bs-code-color);word-wrap:break-word}a>code{color:inherit}kbd{padding:.1875rem .375rem;font-size:.875em;color:var(--bs-body-bg);background-color:var(--bs-body-color);border-radius:.25rem}kbd kbd{padding:0;font-size:1em}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator{display:none!important}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}::file-selector-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid var(--bs-border-color);border-radius:.375rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:#6c757d}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{--bs-gutter-x:1.5rem;--bs-gutter-y:0;width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media (min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:flex;flex-wrap:wrap;margin-top:calc(-1 * var(--bs-gutter-y));margin-right:calc(-.5 * var(--bs-gutter-x));margin-left:calc(-.5 * var(--bs-gutter-x))}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.6666666667%}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.33333333%}.col-2{flex:0 0 auto;width:16.66666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333333%}.col-5{flex:0 0 auto;width:41.66666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333333%}.col-8{flex:0 0 auto;width:66.66666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333333%}.col-11{flex:0 0 auto;width:91.66666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.25rem}.g-1,.gy-1{--bs-gutter-y:0.25rem}.g-2,.gx-2{--bs-gutter-x:0.5rem}.g-2,.gy-2{--bs-gutter-y:0.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (min-width:576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.6666666667%}.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.33333333%}.col-sm-2{flex:0 0 auto;width:16.66666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.33333333%}.col-sm-5{flex:0 0 auto;width:41.66666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.33333333%}.col-sm-8{flex:0 0 auto;width:66.66666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.33333333%}.col-sm-11{flex:0 0 auto;width:91.66666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:0.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:0.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (min-width:768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.6666666667%}.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.33333333%}.col-md-2{flex:0 0 auto;width:16.66666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.33333333%}.col-md-5{flex:0 0 auto;width:41.66666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.33333333%}.col-md-8{flex:0 0 auto;width:66.66666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.33333333%}.col-md-11{flex:0 0 auto;width:91.66666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:0.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:0.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (min-width:992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.6666666667%}.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.33333333%}.col-lg-2{flex:0 0 auto;width:16.66666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.33333333%}.col-lg-5{flex:0 0 auto;width:41.66666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.33333333%}.col-lg-8{flex:0 0 auto;width:66.66666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.33333333%}.col-lg-11{flex:0 0 auto;width:91.66666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:0.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:0.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (min-width:1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.33333333%}.col-xl-2{flex:0 0 auto;width:16.66666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.33333333%}.col-xl-5{flex:0 0 auto;width:41.66666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.33333333%}.col-xl-8{flex:0 0 auto;width:66.66666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.33333333%}.col-xl-11{flex:0 0 auto;width:91.66666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:0.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:0.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (min-width:1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.33333333%}.col-xxl-2{flex:0 0 auto;width:16.66666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.33333333%}.col-xxl-5{flex:0 0 auto;width:41.66666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.33333333%}.col-xxl-8{flex:0 0 auto;width:66.66666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.33333333%}.col-xxl-11{flex:0 0 auto;width:91.66666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333333%}.offset-xxl-2{margin-left:16.66666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.33333333%}.offset-xxl-5{margin-left:41.66666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.33333333%}.offset-xxl-8{margin-left:66.66666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.33333333%}.offset-xxl-11{margin-left:91.66666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:0.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:0.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-color:var(--bs-body-color);--bs-table-bg:transparent;--bs-table-border-color:var(--bs-border-color);--bs-table-accent-bg:transparent;--bs-table-striped-color:var(--bs-body-color);--bs-table-striped-bg:rgba(0, 0, 0, 0.05);--bs-table-active-color:var(--bs-body-color);--bs-table-active-bg:rgba(0, 0, 0, 0.1);--bs-table-hover-color:var(--bs-body-color);--bs-table-hover-bg:rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;color:var(--bs-table-color);vertical-align:top;border-color:var(--bs-table-border-color)}.table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table-group-divider{border-top:2px solid currentcolor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped>tbody>tr:nth-of-type(odd)>*{--bs-table-accent-bg:var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-striped-columns>:not(caption)>tr>:nth-child(2n){--bs-table-accent-bg:var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg:var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover>*{--bs-table-accent-bg:var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-color:#000;--bs-table-bg:#cfe2ff;--bs-table-border-color:#bacbe6;--bs-table-striped-bg:#c5d7f2;--bs-table-striped-color:#000;--bs-table-active-bg:#bacbe6;--bs-table-active-color:#000;--bs-table-hover-bg:#bfd1ec;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-secondary{--bs-table-color:#000;--bs-table-bg:#e2e3e5;--bs-table-border-color:#cbccce;--bs-table-striped-bg:#d7d8da;--bs-table-striped-color:#000;--bs-table-active-bg:#cbccce;--bs-table-active-color:#000;--bs-table-hover-bg:#d1d2d4;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-success{--bs-table-color:#000;--bs-table-bg:#d1e7dd;--bs-table-border-color:#bcd0c7;--bs-table-striped-bg:#c7dbd2;--bs-table-striped-color:#000;--bs-table-active-bg:#bcd0c7;--bs-table-active-color:#000;--bs-table-hover-bg:#c1d6cc;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-info{--bs-table-color:#000;--bs-table-bg:#cff4fc;--bs-table-border-color:#badce3;--bs-table-striped-bg:#c5e8ef;--bs-table-striped-color:#000;--bs-table-active-bg:#badce3;--bs-table-active-color:#000;--bs-table-hover-bg:#bfe2e9;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-warning{--bs-table-color:#000;--bs-table-bg:#fff3cd;--bs-table-border-color:#e6dbb9;--bs-table-striped-bg:#f2e7c3;--bs-table-striped-color:#000;--bs-table-active-bg:#e6dbb9;--bs-table-active-color:#000;--bs-table-hover-bg:#ece1be;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-danger{--bs-table-color:#000;--bs-table-bg:#f8d7da;--bs-table-border-color:#dfc2c4;--bs-table-striped-bg:#eccccf;--bs-table-striped-color:#000;--bs-table-active-bg:#dfc2c4;--bs-table-active-color:#000;--bs-table-hover-bg:#e5c7ca;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-light{--bs-table-color:#000;--bs-table-bg:#f8f9fa;--bs-table-border-color:#dfe0e1;--bs-table-striped-bg:#ecedee;--bs-table-striped-color:#000;--bs-table-active-bg:#dfe0e1;--bs-table-active-color:#000;--bs-table-hover-bg:#e5e6e7;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-dark{--bs-table-color:#fff;--bs-table-bg:#212529;--bs-table-border-color:#373b3e;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width:575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:#6c757d}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.375rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#212529;background-color:#fff;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled{background-color:#e9ecef;opacity:1}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}.form-control::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#dde0e3}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext:focus{outline:0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;border-radius:.25rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;border-radius:.5rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + .75rem + 2px)}textarea.form-control-sm{min-height:calc(1.5em + .5rem + 2px)}textarea.form-control-lg{min-height:calc(1.5em + 1rem + 2px)}.form-control-color{width:3rem;height:calc(1.5em + .75rem + 2px);padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{border:0!important;border-radius:.375rem}.form-control-color::-webkit-color-swatch{border-radius:.375rem}.form-control-color.form-control-sm{height:calc(1.5em + .5rem + 2px)}.form-control-color.form-control-lg{height:calc(1.5em + 1rem + 2px)}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;-moz-padding-start:calc(0.75rem - 3px);font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:.375rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #212529}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem;border-radius:.25rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem;border-radius:.5rem}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-reverse{padding-right:1.5em;padding-left:0;text-align:right}.form-check-reverse .form-check-input{float:right;margin-right:-1.5em;margin-left:0}.form-check-input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact;print-color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-check-input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#0d6efd;border-color:#0d6efd;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{cursor:default;opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-switch.form-check-reverse{padding-right:2.5em;padding-left:0}.form-switch.form-check-reverse .form-check-input{margin-right:-2.5em;margin-left:0}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{pointer-events:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#0d6efd;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#0d6efd;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-control-plaintext,.form-floating>.form-select{height:calc(3.5rem + 2px);line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;width:100%;height:100%;padding:1rem .75rem;overflow:hidden;text-align:start;text-overflow:ellipsis;white-space:nowrap;pointer-events:none;border:1px solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control,.form-floating>.form-control-plaintext{padding:1rem .75rem}.form-floating>.form-control-plaintext::-moz-placeholder,.form-floating>.form-control::-moz-placeholder{color:transparent}.form-floating>.form-control-plaintext::placeholder,.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control-plaintext:not(:-moz-placeholder-shown),.form-floating>.form-control:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control-plaintext:focus,.form-floating>.form-control-plaintext:not(:placeholder-shown),.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control-plaintext:-webkit-autofill,.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-moz-placeholder-shown)~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control-plaintext~label,.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control-plaintext~label{border-width:1px 0}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-floating,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-floating:focus-within,.input-group>.form-select:focus{z-index:5}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:5}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.375rem}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:.5rem}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:.25rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-control,.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-select,.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-control,.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-select,.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.form-floating:not(:first-child)>.form-control,.input-group>.form-floating:not(:first-child)>.form-select{border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#198754}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(25,135,84,.9);border-radius:.375rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#198754;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-valid,.was-validated .form-select:valid{border-color:#198754}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-control-color.is-valid,.was-validated .form-control-color:valid{width:calc(3rem + calc(1.5em + .75rem))}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:#198754}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:#198754}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#198754}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group>.form-control:not(:focus).is-valid,.input-group>.form-floating:not(:focus-within).is-valid,.input-group>.form-select:not(:focus).is-valid,.was-validated .input-group>.form-control:not(:focus):valid,.was-validated .input-group>.form-floating:not(:focus-within):valid,.was-validated .input-group>.form-select:not(:focus):valid{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.375rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:#dc3545}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-control-color.is-invalid,.was-validated .form-control-color:invalid{width:calc(3rem + calc(1.5em + .75rem))}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:#dc3545}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:#dc3545}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group>.form-control:not(:focus).is-invalid,.input-group>.form-floating:not(:focus-within).is-invalid,.input-group>.form-select:not(:focus).is-invalid,.was-validated .input-group>.form-control:not(:focus):invalid,.was-validated .input-group>.form-floating:not(:focus-within):invalid,.was-validated .input-group>.form-select:not(:focus):invalid{z-index:4}.btn{--bs-btn-padding-x:0.75rem;--bs-btn-padding-y:0.375rem;--bs-btn-font-family: ;--bs-btn-font-size:1rem;--bs-btn-font-weight:400;--bs-btn-line-height:1.5;--bs-btn-color:#212529;--bs-btn-bg:transparent;--bs-btn-border-width:1px;--bs-btn-border-color:transparent;--bs-btn-border-radius:0.375rem;--bs-btn-hover-border-color:transparent;--bs-btn-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.15),0 1px 1px rgba(0, 0, 0, 0.075);--bs-btn-disabled-opacity:0.65;--bs-btn-focus-box-shadow:0 0 0 0.25rem rgba(var(--bs-btn-focus-shadow-rgb), .5);display:inline-block;padding:var(--bs-btn-padding-y) var(--bs-btn-padding-x);font-family:var(--bs-btn-font-family);font-size:var(--bs-btn-font-size);font-weight:var(--bs-btn-font-weight);line-height:var(--bs-btn-line-height);color:var(--bs-btn-color);text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;border:var(--bs-btn-border-width) solid var(--bs-btn-border-color);border-radius:var(--bs-btn-border-radius);background-color:var(--bs-btn-bg);transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color)}.btn-check+.btn:hover{color:var(--bs-btn-color);background-color:var(--bs-btn-bg);border-color:var(--bs-btn-border-color)}.btn:focus-visible{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:focus-visible+.btn{border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:checked+.btn,.btn.active,.btn.show,.btn:first-child:active,:not(.btn-check)+.btn:active{color:var(--bs-btn-active-color);background-color:var(--bs-btn-active-bg);border-color:var(--bs-btn-active-border-color)}.btn-check:checked+.btn:focus-visible,.btn.active:focus-visible,.btn.show:focus-visible,.btn:first-child:active:focus-visible,:not(.btn-check)+.btn:active:focus-visible{box-shadow:var(--bs-btn-focus-box-shadow)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{color:var(--bs-btn-disabled-color);pointer-events:none;background-color:var(--bs-btn-disabled-bg);border-color:var(--bs-btn-disabled-border-color);opacity:var(--bs-btn-disabled-opacity)}.btn-primary{--bs-btn-color:#fff;--bs-btn-bg:#0d6efd;--bs-btn-border-color:#0d6efd;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#0b5ed7;--bs-btn-hover-border-color:#0a58ca;--bs-btn-focus-shadow-rgb:49,132,253;--bs-btn-active-color:#fff;--bs-btn-active-bg:#0a58ca;--bs-btn-active-border-color:#0a53be;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#0d6efd;--bs-btn-disabled-border-color:#0d6efd}.btn-secondary{--bs-btn-color:#fff;--bs-btn-bg:#6c757d;--bs-btn-border-color:#6c757d;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#5c636a;--bs-btn-hover-border-color:#565e64;--bs-btn-focus-shadow-rgb:130,138,145;--bs-btn-active-color:#fff;--bs-btn-active-bg:#565e64;--bs-btn-active-border-color:#51585e;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#6c757d;--bs-btn-disabled-border-color:#6c757d}.btn-success{--bs-btn-color:#fff;--bs-btn-bg:#198754;--bs-btn-border-color:#198754;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#157347;--bs-btn-hover-border-color:#146c43;--bs-btn-focus-shadow-rgb:60,153,110;--bs-btn-active-color:#fff;--bs-btn-active-bg:#146c43;--bs-btn-active-border-color:#13653f;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#198754;--bs-btn-disabled-border-color:#198754}.btn-info{--bs-btn-color:#000;--bs-btn-bg:#0dcaf0;--bs-btn-border-color:#0dcaf0;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#31d2f2;--bs-btn-hover-border-color:#25cff2;--bs-btn-focus-shadow-rgb:11,172,204;--bs-btn-active-color:#000;--bs-btn-active-bg:#3dd5f3;--bs-btn-active-border-color:#25cff2;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#0dcaf0;--bs-btn-disabled-border-color:#0dcaf0}.btn-warning{--bs-btn-color:#000;--bs-btn-bg:#ffc107;--bs-btn-border-color:#ffc107;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#ffca2c;--bs-btn-hover-border-color:#ffc720;--bs-btn-focus-shadow-rgb:217,164,6;--bs-btn-active-color:#000;--bs-btn-active-bg:#ffcd39;--bs-btn-active-border-color:#ffc720;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#ffc107;--bs-btn-disabled-border-color:#ffc107}.btn-danger{--bs-btn-color:#fff;--bs-btn-bg:#dc3545;--bs-btn-border-color:#dc3545;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#bb2d3b;--bs-btn-hover-border-color:#b02a37;--bs-btn-focus-shadow-rgb:225,83,97;--bs-btn-active-color:#fff;--bs-btn-active-bg:#b02a37;--bs-btn-active-border-color:#a52834;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#dc3545;--bs-btn-disabled-border-color:#dc3545}.btn-light{--bs-btn-color:#000;--bs-btn-bg:#f8f9fa;--bs-btn-border-color:#f8f9fa;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#d3d4d5;--bs-btn-hover-border-color:#c6c7c8;--bs-btn-focus-shadow-rgb:211,212,213;--bs-btn-active-color:#000;--bs-btn-active-bg:#c6c7c8;--bs-btn-active-border-color:#babbbc;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#f8f9fa;--bs-btn-disabled-border-color:#f8f9fa}.btn-dark{--bs-btn-color:#fff;--bs-btn-bg:#212529;--bs-btn-border-color:#212529;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#424649;--bs-btn-hover-border-color:#373b3e;--bs-btn-focus-shadow-rgb:66,70,73;--bs-btn-active-color:#fff;--bs-btn-active-bg:#4d5154;--bs-btn-active-border-color:#373b3e;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#212529;--bs-btn-disabled-border-color:#212529}.btn-outline-primary{--bs-btn-color:#0d6efd;--bs-btn-border-color:#0d6efd;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#0d6efd;--bs-btn-hover-border-color:#0d6efd;--bs-btn-focus-shadow-rgb:13,110,253;--bs-btn-active-color:#fff;--bs-btn-active-bg:#0d6efd;--bs-btn-active-border-color:#0d6efd;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#0d6efd;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#0d6efd;--bs-gradient:none}.btn-outline-secondary{--bs-btn-color:#6c757d;--bs-btn-border-color:#6c757d;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#6c757d;--bs-btn-hover-border-color:#6c757d;--bs-btn-focus-shadow-rgb:108,117,125;--bs-btn-active-color:#fff;--bs-btn-active-bg:#6c757d;--bs-btn-active-border-color:#6c757d;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#6c757d;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#6c757d;--bs-gradient:none}.btn-outline-success{--bs-btn-color:#198754;--bs-btn-border-color:#198754;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#198754;--bs-btn-hover-border-color:#198754;--bs-btn-focus-shadow-rgb:25,135,84;--bs-btn-active-color:#fff;--bs-btn-active-bg:#198754;--bs-btn-active-border-color:#198754;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#198754;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#198754;--bs-gradient:none}.btn-outline-info{--bs-btn-color:#0dcaf0;--bs-btn-border-color:#0dcaf0;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#0dcaf0;--bs-btn-hover-border-color:#0dcaf0;--bs-btn-focus-shadow-rgb:13,202,240;--bs-btn-active-color:#000;--bs-btn-active-bg:#0dcaf0;--bs-btn-active-border-color:#0dcaf0;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#0dcaf0;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#0dcaf0;--bs-gradient:none}.btn-outline-warning{--bs-btn-color:#ffc107;--bs-btn-border-color:#ffc107;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#ffc107;--bs-btn-hover-border-color:#ffc107;--bs-btn-focus-shadow-rgb:255,193,7;--bs-btn-active-color:#000;--bs-btn-active-bg:#ffc107;--bs-btn-active-border-color:#ffc107;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#ffc107;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#ffc107;--bs-gradient:none}.btn-outline-danger{--bs-btn-color:#dc3545;--bs-btn-border-color:#dc3545;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#dc3545;--bs-btn-hover-border-color:#dc3545;--bs-btn-focus-shadow-rgb:220,53,69;--bs-btn-active-color:#fff;--bs-btn-active-bg:#dc3545;--bs-btn-active-border-color:#dc3545;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#dc3545;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#dc3545;--bs-gradient:none}.btn-outline-light{--bs-btn-color:#f8f9fa;--bs-btn-border-color:#f8f9fa;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#f8f9fa;--bs-btn-hover-border-color:#f8f9fa;--bs-btn-focus-shadow-rgb:248,249,250;--bs-btn-active-color:#000;--bs-btn-active-bg:#f8f9fa;--bs-btn-active-border-color:#f8f9fa;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#f8f9fa;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#f8f9fa;--bs-gradient:none}.btn-outline-dark{--bs-btn-color:#212529;--bs-btn-border-color:#212529;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#212529;--bs-btn-hover-border-color:#212529;--bs-btn-focus-shadow-rgb:33,37,41;--bs-btn-active-color:#fff;--bs-btn-active-bg:#212529;--bs-btn-active-border-color:#212529;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#212529;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#212529;--bs-gradient:none}.btn-link{--bs-btn-font-weight:400;--bs-btn-color:var(--bs-link-color);--bs-btn-bg:transparent;--bs-btn-border-color:transparent;--bs-btn-hover-color:var(--bs-link-hover-color);--bs-btn-hover-border-color:transparent;--bs-btn-active-color:var(--bs-link-hover-color);--bs-btn-active-border-color:transparent;--bs-btn-disabled-color:#6c757d;--bs-btn-disabled-border-color:transparent;--bs-btn-box-shadow:none;--bs-btn-focus-shadow-rgb:49,132,253;text-decoration:underline}.btn-link:focus-visible{color:var(--bs-btn-color)}.btn-link:hover{color:var(--bs-btn-hover-color)}.btn-group-lg>.btn,.btn-lg{--bs-btn-padding-y:0.5rem;--bs-btn-padding-x:1rem;--bs-btn-font-size:1.25rem;--bs-btn-border-radius:0.5rem}.btn-group-sm>.btn,.btn-sm{--bs-btn-padding-y:0.25rem;--bs-btn-padding-x:0.5rem;--bs-btn-font-size:0.875rem;--bs-btn-border-radius:0.25rem}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media (prefers-reduced-motion:reduce){.collapsing.collapse-horizontal{transition:none}}.dropdown,.dropdown-center,.dropend,.dropstart,.dropup,.dropup-center{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{--bs-dropdown-zindex:1000;--bs-dropdown-min-width:10rem;--bs-dropdown-padding-x:0;--bs-dropdown-padding-y:0.5rem;--bs-dropdown-spacer:0.125rem;--bs-dropdown-font-size:1rem;--bs-dropdown-color:#212529;--bs-dropdown-bg:#fff;--bs-dropdown-border-color:var(--bs-border-color-translucent);--bs-dropdown-border-radius:0.375rem;--bs-dropdown-border-width:1px;--bs-dropdown-inner-border-radius:calc(0.375rem - 1px);--bs-dropdown-divider-bg:var(--bs-border-color-translucent);--bs-dropdown-divider-margin-y:0.5rem;--bs-dropdown-box-shadow:0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-dropdown-link-color:#212529;--bs-dropdown-link-hover-color:#1e2125;--bs-dropdown-link-hover-bg:#e9ecef;--bs-dropdown-link-active-color:#fff;--bs-dropdown-link-active-bg:#0d6efd;--bs-dropdown-link-disabled-color:#adb5bd;--bs-dropdown-item-padding-x:1rem;--bs-dropdown-item-padding-y:0.25rem;--bs-dropdown-header-color:#6c757d;--bs-dropdown-header-padding-x:1rem;--bs-dropdown-header-padding-y:0.5rem;position:absolute;z-index:var(--bs-dropdown-zindex);display:none;min-width:var(--bs-dropdown-min-width);padding:var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x);margin:0;font-size:var(--bs-dropdown-font-size);color:var(--bs-dropdown-color);text-align:left;list-style:none;background-color:var(--bs-dropdown-bg);background-clip:padding-box;border:var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color);border-radius:var(--bs-dropdown-border-radius)}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:var(--bs-dropdown-spacer)}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:var(--bs-dropdown-spacer)}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:var(--bs-dropdown-spacer)}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:var(--bs-dropdown-spacer)}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:var(--bs-dropdown-divider-margin-y) 0;overflow:hidden;border-top:1px solid var(--bs-dropdown-divider-bg);opacity:1}.dropdown-item{display:block;width:100%;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);clear:both;font-weight:400;color:var(--bs-dropdown-link-color);text-align:inherit;text-decoration:none;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:var(--bs-dropdown-link-hover-color);background-color:var(--bs-dropdown-link-hover-bg)}.dropdown-item.active,.dropdown-item:active{color:var(--bs-dropdown-link-active-color);text-decoration:none;background-color:var(--bs-dropdown-link-active-bg)}.dropdown-item.disabled,.dropdown-item:disabled{color:var(--bs-dropdown-link-disabled-color);pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x);margin-bottom:0;font-size:.875rem;color:var(--bs-dropdown-header-color);white-space:nowrap}.dropdown-item-text{display:block;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);color:var(--bs-dropdown-link-color)}.dropdown-menu-dark{--bs-dropdown-color:#dee2e6;--bs-dropdown-bg:#343a40;--bs-dropdown-border-color:var(--bs-border-color-translucent);--bs-dropdown-box-shadow: ;--bs-dropdown-link-color:#dee2e6;--bs-dropdown-link-hover-color:#fff;--bs-dropdown-divider-bg:var(--bs-border-color-translucent);--bs-dropdown-link-hover-bg:rgba(255, 255, 255, 0.15);--bs-dropdown-link-active-color:#fff;--bs-dropdown-link-active-bg:#0d6efd;--bs-dropdown-link-disabled-color:#adb5bd;--bs-dropdown-header-color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group{border-radius:.375rem}.btn-group>.btn-group:not(:first-child),.btn-group>:not(.btn-check:first-child)+.btn{margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn.dropdown-toggle-split:first-child,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn~.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{--bs-nav-link-padding-x:1rem;--bs-nav-link-padding-y:0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color:var(--bs-link-color);--bs-nav-link-hover-color:var(--bs-link-hover-color);--bs-nav-link-disabled-color:#6c757d;display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x);font-size:var(--bs-nav-link-font-size);font-weight:var(--bs-nav-link-font-weight);color:var(--bs-nav-link-color);text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:var(--bs-nav-link-hover-color)}.nav-link.disabled{color:var(--bs-nav-link-disabled-color);pointer-events:none;cursor:default}.nav-tabs{--bs-nav-tabs-border-width:1px;--bs-nav-tabs-border-color:#dee2e6;--bs-nav-tabs-border-radius:0.375rem;--bs-nav-tabs-link-hover-border-color:#e9ecef #e9ecef #dee2e6;--bs-nav-tabs-link-active-color:#495057;--bs-nav-tabs-link-active-bg:#fff;--bs-nav-tabs-link-active-border-color:#dee2e6 #dee2e6 #fff;border-bottom:var(--bs-nav-tabs-border-width) solid var(--bs-nav-tabs-border-color)}.nav-tabs .nav-link{margin-bottom:calc(-1 * var(--bs-nav-tabs-border-width));background:0 0;border:var(--bs-nav-tabs-border-width) solid transparent;border-top-left-radius:var(--bs-nav-tabs-border-radius);border-top-right-radius:var(--bs-nav-tabs-border-radius)}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{isolation:isolate;border-color:var(--bs-nav-tabs-link-hover-border-color)}.nav-tabs .nav-link.disabled,.nav-tabs .nav-link:disabled{color:var(--bs-nav-link-disabled-color);background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:var(--bs-nav-tabs-link-active-color);background-color:var(--bs-nav-tabs-link-active-bg);border-color:var(--bs-nav-tabs-link-active-border-color)}.nav-tabs .dropdown-menu{margin-top:calc(-1 * var(--bs-nav-tabs-border-width));border-top-left-radius:0;border-top-right-radius:0}.nav-pills{--bs-nav-pills-border-radius:0.375rem;--bs-nav-pills-link-active-color:#fff;--bs-nav-pills-link-active-bg:#0d6efd}.nav-pills .nav-link{background:0 0;border:0;border-radius:var(--bs-nav-pills-border-radius)}.nav-pills .nav-link:disabled{color:var(--bs-nav-link-disabled-color);background-color:transparent;border-color:transparent}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:var(--bs-nav-pills-link-active-color);background-color:var(--bs-nav-pills-link-active-bg)}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{--bs-navbar-padding-x:0;--bs-navbar-padding-y:0.5rem;--bs-navbar-color:rgba(0, 0, 0, 0.55);--bs-navbar-hover-color:rgba(0, 0, 0, 0.7);--bs-navbar-disabled-color:rgba(0, 0, 0, 0.3);--bs-navbar-active-color:rgba(0, 0, 0, 0.9);--bs-navbar-brand-padding-y:0.3125rem;--bs-navbar-brand-margin-end:1rem;--bs-navbar-brand-font-size:1.25rem;--bs-navbar-brand-color:rgba(0, 0, 0, 0.9);--bs-navbar-brand-hover-color:rgba(0, 0, 0, 0.9);--bs-navbar-nav-link-padding-x:0.5rem;--bs-navbar-toggler-padding-y:0.25rem;--bs-navbar-toggler-padding-x:0.75rem;--bs-navbar-toggler-font-size:1.25rem;--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");--bs-navbar-toggler-border-color:rgba(0, 0, 0, 0.1);--bs-navbar-toggler-border-radius:0.375rem;--bs-navbar-toggler-focus-width:0.25rem;--bs-navbar-toggler-transition:box-shadow 0.15s ease-in-out;position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding:var(--bs-navbar-padding-y) var(--bs-navbar-padding-x)}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:var(--bs-navbar-brand-padding-y);padding-bottom:var(--bs-navbar-brand-padding-y);margin-right:var(--bs-navbar-brand-margin-end);font-size:var(--bs-navbar-brand-font-size);color:var(--bs-navbar-brand-color);text-decoration:none;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{color:var(--bs-navbar-brand-hover-color)}.navbar-nav{--bs-nav-link-padding-x:0;--bs-nav-link-padding-y:0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color:var(--bs-navbar-color);--bs-nav-link-hover-color:var(--bs-navbar-hover-color);--bs-nav-link-disabled-color:var(--bs-navbar-disabled-color);display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link.active,.navbar-nav .show>.nav-link{color:var(--bs-navbar-active-color)}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem;color:var(--bs-navbar-color)}.navbar-text a,.navbar-text a:focus,.navbar-text a:hover{color:var(--bs-navbar-active-color)}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:var(--bs-navbar-toggler-padding-y) var(--bs-navbar-toggler-padding-x);font-size:var(--bs-navbar-toggler-font-size);line-height:1;color:var(--bs-navbar-color);background-color:transparent;border:var(--bs-border-width) solid var(--bs-navbar-toggler-border-color);border-radius:var(--bs-navbar-toggler-border-radius);transition:var(--bs-navbar-toggler-transition)}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 var(--bs-navbar-toggler-focus-width)}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-image:var(--bs-navbar-toggler-icon-bg);background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (min-width:576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-sm .offcanvas .offcanvas-header{display:none}.navbar-expand-sm .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-md .offcanvas .offcanvas-header{display:none}.navbar-expand-md .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-lg .offcanvas .offcanvas-header{display:none}.navbar-expand-lg .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-xl .offcanvas .offcanvas-header{display:none}.navbar-expand-xl .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-xxl .offcanvas .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand .offcanvas .offcanvas-header{display:none}.navbar-expand .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}.navbar-dark{--bs-navbar-color:rgba(255, 255, 255, 0.55);--bs-navbar-hover-color:rgba(255, 255, 255, 0.75);--bs-navbar-disabled-color:rgba(255, 255, 255, 0.25);--bs-navbar-active-color:#fff;--bs-navbar-brand-color:#fff;--bs-navbar-brand-hover-color:#fff;--bs-navbar-toggler-border-color:rgba(255, 255, 255, 0.1);--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.card{--bs-card-spacer-y:1rem;--bs-card-spacer-x:1rem;--bs-card-title-spacer-y:0.5rem;--bs-card-border-width:1px;--bs-card-border-color:var(--bs-border-color-translucent);--bs-card-border-radius:0.375rem;--bs-card-box-shadow: ;--bs-card-inner-border-radius:calc(0.375rem - 1px);--bs-card-cap-padding-y:0.5rem;--bs-card-cap-padding-x:1rem;--bs-card-cap-bg:rgba(0, 0, 0, 0.03);--bs-card-cap-color: ;--bs-card-height: ;--bs-card-color: ;--bs-card-bg:#fff;--bs-card-img-overlay-padding:1rem;--bs-card-group-margin:0.75rem;position:relative;display:flex;flex-direction:column;min-width:0;height:var(--bs-card-height);word-wrap:break-word;background-color:var(--bs-card-bg);background-clip:border-box;border:var(--bs-card-border-width) solid var(--bs-card-border-color);border-radius:var(--bs-card-border-radius)}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-left-radius:var(--bs-card-inner-border-radius)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;padding:var(--bs-card-spacer-y) var(--bs-card-spacer-x);color:var(--bs-card-color)}.card-title{margin-bottom:var(--bs-card-title-spacer-y)}.card-subtitle{margin-top:calc(-.5 * var(--bs-card-title-spacer-y));margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:var(--bs-card-spacer-x)}.card-header{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);margin-bottom:0;color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-bottom:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card-header:first-child{border-radius:var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius) 0 0}.card-footer{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-top:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card-footer:last-child{border-radius:0 0 var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius)}.card-header-tabs{margin-right:calc(-.5 * var(--bs-card-cap-padding-x));margin-bottom:calc(-1 * var(--bs-card-cap-padding-y));margin-left:calc(-.5 * var(--bs-card-cap-padding-x));border-bottom:0}.card-header-tabs .nav-link.active{background-color:var(--bs-card-bg);border-bottom-color:var(--bs-card-bg)}.card-header-pills{margin-right:calc(-.5 * var(--bs-card-cap-padding-x));margin-left:calc(-.5 * var(--bs-card-cap-padding-x))}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:var(--bs-card-img-overlay-padding);border-radius:var(--bs-card-inner-border-radius)}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-img,.card-img-top{border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius)}.card-img,.card-img-bottom{border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-left-radius:var(--bs-card-inner-border-radius)}.card-group>.card{margin-bottom:var(--bs-card-group-margin)}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.accordion{--bs-accordion-color:#212529;--bs-accordion-bg:#fff;--bs-accordion-transition:color 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out,border-radius 0.15s ease;--bs-accordion-border-color:var(--bs-border-color);--bs-accordion-border-width:1px;--bs-accordion-border-radius:0.375rem;--bs-accordion-inner-border-radius:calc(0.375rem - 1px);--bs-accordion-btn-padding-x:1.25rem;--bs-accordion-btn-padding-y:1rem;--bs-accordion-btn-color:#212529;--bs-accordion-btn-bg:var(--bs-accordion-bg);--bs-accordion-btn-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");--bs-accordion-btn-icon-width:1.25rem;--bs-accordion-btn-icon-transform:rotate(-180deg);--bs-accordion-btn-icon-transition:transform 0.2s ease-in-out;--bs-accordion-btn-active-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230c63e4'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");--bs-accordion-btn-focus-border-color:#86b7fe;--bs-accordion-btn-focus-box-shadow:0 0 0 0.25rem rgba(13, 110, 253, 0.25);--bs-accordion-body-padding-x:1.25rem;--bs-accordion-body-padding-y:1rem;--bs-accordion-active-color:#0c63e4;--bs-accordion-active-bg:#e7f1ff}.accordion-button{position:relative;display:flex;align-items:center;width:100%;padding:var(--bs-accordion-btn-padding-y) var(--bs-accordion-btn-padding-x);font-size:1rem;color:var(--bs-accordion-btn-color);text-align:left;background-color:var(--bs-accordion-btn-bg);border:0;border-radius:0;overflow-anchor:none;transition:var(--bs-accordion-transition)}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:var(--bs-accordion-active-color);background-color:var(--bs-accordion-active-bg);box-shadow:inset 0 calc(-1 * var(--bs-accordion-border-width)) 0 var(--bs-accordion-border-color)}.accordion-button:not(.collapsed)::after{background-image:var(--bs-accordion-btn-active-icon);transform:var(--bs-accordion-btn-icon-transform)}.accordion-button::after{flex-shrink:0;width:var(--bs-accordion-btn-icon-width);height:var(--bs-accordion-btn-icon-width);margin-left:auto;content:"";background-image:var(--bs-accordion-btn-icon);background-repeat:no-repeat;background-size:var(--bs-accordion-btn-icon-width);transition:var(--bs-accordion-btn-icon-transition)}@media (prefers-reduced-motion:reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:var(--bs-accordion-btn-focus-border-color);outline:0;box-shadow:var(--bs-accordion-btn-focus-box-shadow)}.accordion-header{margin-bottom:0}.accordion-item{color:var(--bs-accordion-color);background-color:var(--bs-accordion-bg);border:var(--bs-accordion-border-width) solid var(--bs-accordion-border-color)}.accordion-item:first-of-type{border-top-left-radius:var(--bs-accordion-border-radius);border-top-right-radius:var(--bs-accordion-border-radius)}.accordion-item:first-of-type .accordion-button{border-top-left-radius:var(--bs-accordion-inner-border-radius);border-top-right-radius:var(--bs-accordion-inner-border-radius)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:var(--bs-accordion-border-radius);border-bottom-left-radius:var(--bs-accordion-border-radius)}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:var(--bs-accordion-inner-border-radius);border-bottom-left-radius:var(--bs-accordion-inner-border-radius)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:var(--bs-accordion-border-radius);border-bottom-left-radius:var(--bs-accordion-border-radius)}.accordion-body{padding:var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x)}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button,.accordion-flush .accordion-item .accordion-button.collapsed{border-radius:0}.breadcrumb{--bs-breadcrumb-padding-x:0;--bs-breadcrumb-padding-y:0;--bs-breadcrumb-margin-bottom:1rem;--bs-breadcrumb-bg: ;--bs-breadcrumb-border-radius: ;--bs-breadcrumb-divider-color:#6c757d;--bs-breadcrumb-item-padding-x:0.5rem;--bs-breadcrumb-item-active-color:#6c757d;display:flex;flex-wrap:wrap;padding:var(--bs-breadcrumb-padding-y) var(--bs-breadcrumb-padding-x);margin-bottom:var(--bs-breadcrumb-margin-bottom);font-size:var(--bs-breadcrumb-font-size);list-style:none;background-color:var(--bs-breadcrumb-bg);border-radius:var(--bs-breadcrumb-border-radius)}.breadcrumb-item+.breadcrumb-item{padding-left:var(--bs-breadcrumb-item-padding-x)}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:var(--bs-breadcrumb-item-padding-x);color:var(--bs-breadcrumb-divider-color);content:var(--bs-breadcrumb-divider, "/")}.breadcrumb-item.active{color:var(--bs-breadcrumb-item-active-color)}.pagination{--bs-pagination-padding-x:0.75rem;--bs-pagination-padding-y:0.375rem;--bs-pagination-font-size:1rem;--bs-pagination-color:var(--bs-link-color);--bs-pagination-bg:#fff;--bs-pagination-border-width:1px;--bs-pagination-border-color:#dee2e6;--bs-pagination-border-radius:0.375rem;--bs-pagination-hover-color:var(--bs-link-hover-color);--bs-pagination-hover-bg:#e9ecef;--bs-pagination-hover-border-color:#dee2e6;--bs-pagination-focus-color:var(--bs-link-hover-color);--bs-pagination-focus-bg:#e9ecef;--bs-pagination-focus-box-shadow:0 0 0 0.25rem rgba(13, 110, 253, 0.25);--bs-pagination-active-color:#fff;--bs-pagination-active-bg:#0d6efd;--bs-pagination-active-border-color:#0d6efd;--bs-pagination-disabled-color:#6c757d;--bs-pagination-disabled-bg:#fff;--bs-pagination-disabled-border-color:#dee2e6;display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;padding:var(--bs-pagination-padding-y) var(--bs-pagination-padding-x);font-size:var(--bs-pagination-font-size);color:var(--bs-pagination-color);text-decoration:none;background-color:var(--bs-pagination-bg);border:var(--bs-pagination-border-width) solid var(--bs-pagination-border-color);transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:var(--bs-pagination-hover-color);background-color:var(--bs-pagination-hover-bg);border-color:var(--bs-pagination-hover-border-color)}.page-link:focus{z-index:3;color:var(--bs-pagination-focus-color);background-color:var(--bs-pagination-focus-bg);outline:0;box-shadow:var(--bs-pagination-focus-box-shadow)}.active>.page-link,.page-link.active{z-index:3;color:var(--bs-pagination-active-color);background-color:var(--bs-pagination-active-bg);border-color:var(--bs-pagination-active-border-color)}.disabled>.page-link,.page-link.disabled{color:var(--bs-pagination-disabled-color);pointer-events:none;background-color:var(--bs-pagination-disabled-bg);border-color:var(--bs-pagination-disabled-border-color)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item:first-child .page-link{border-top-left-radius:var(--bs-pagination-border-radius);border-bottom-left-radius:var(--bs-pagination-border-radius)}.page-item:last-child .page-link{border-top-right-radius:var(--bs-pagination-border-radius);border-bottom-right-radius:var(--bs-pagination-border-radius)}.pagination-lg{--bs-pagination-padding-x:1.5rem;--bs-pagination-padding-y:0.75rem;--bs-pagination-font-size:1.25rem;--bs-pagination-border-radius:0.5rem}.pagination-sm{--bs-pagination-padding-x:0.5rem;--bs-pagination-padding-y:0.25rem;--bs-pagination-font-size:0.875rem;--bs-pagination-border-radius:0.25rem}.badge{--bs-badge-padding-x:0.65em;--bs-badge-padding-y:0.35em;--bs-badge-font-size:0.75em;--bs-badge-font-weight:700;--bs-badge-color:#fff;--bs-badge-border-radius:0.375rem;display:inline-block;padding:var(--bs-badge-padding-y) var(--bs-badge-padding-x);font-size:var(--bs-badge-font-size);font-weight:var(--bs-badge-font-weight);line-height:1;color:var(--bs-badge-color);text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:var(--bs-badge-border-radius)}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{--bs-alert-bg:transparent;--bs-alert-padding-x:1rem;--bs-alert-padding-y:1rem;--bs-alert-margin-bottom:1rem;--bs-alert-color:inherit;--bs-alert-border-color:transparent;--bs-alert-border:1px solid var(--bs-alert-border-color);--bs-alert-border-radius:0.375rem;position:relative;padding:var(--bs-alert-padding-y) var(--bs-alert-padding-x);margin-bottom:var(--bs-alert-margin-bottom);color:var(--bs-alert-color);background-color:var(--bs-alert-bg);border:var(--bs-alert-border);border-radius:var(--bs-alert-border-radius)}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-primary{--bs-alert-color:#084298;--bs-alert-bg:#cfe2ff;--bs-alert-border-color:#b6d4fe}.alert-primary .alert-link{color:#06357a}.alert-secondary{--bs-alert-color:#41464b;--bs-alert-bg:#e2e3e5;--bs-alert-border-color:#d3d6d8}.alert-secondary .alert-link{color:#34383c}.alert-success{--bs-alert-color:#0f5132;--bs-alert-bg:#d1e7dd;--bs-alert-border-color:#badbcc}.alert-success .alert-link{color:#0c4128}.alert-info{--bs-alert-color:#055160;--bs-alert-bg:#cff4fc;--bs-alert-border-color:#b6effb}.alert-info .alert-link{color:#04414d}.alert-warning{--bs-alert-color:#664d03;--bs-alert-bg:#fff3cd;--bs-alert-border-color:#ffecb5}.alert-warning .alert-link{color:#523e02}.alert-danger{--bs-alert-color:#842029;--bs-alert-bg:#f8d7da;--bs-alert-border-color:#f5c2c7}.alert-danger .alert-link{color:#6a1a21}.alert-light{--bs-alert-color:#636464;--bs-alert-bg:#fefefe;--bs-alert-border-color:#fdfdfe}.alert-light .alert-link{color:#4f5050}.alert-dark{--bs-alert-color:#141619;--bs-alert-bg:#d3d3d4;--bs-alert-border-color:#bcbebf}.alert-dark .alert-link{color:#101214}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress{--bs-progress-height:1rem;--bs-progress-font-size:0.75rem;--bs-progress-bg:#e9ecef;--bs-progress-border-radius:0.375rem;--bs-progress-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.075);--bs-progress-bar-color:#fff;--bs-progress-bar-bg:#0d6efd;--bs-progress-bar-transition:width 0.6s ease;display:flex;height:var(--bs-progress-height);overflow:hidden;font-size:var(--bs-progress-font-size);background-color:var(--bs-progress-bg);border-radius:var(--bs-progress-border-radius)}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:var(--bs-progress-bar-color);text-align:center;white-space:nowrap;background-color:var(--bs-progress-bar-bg);transition:var(--bs-progress-bar-transition)}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:var(--bs-progress-height) var(--bs-progress-height)}.progress-bar-animated{animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{animation:none}}.list-group{--bs-list-group-color:#212529;--bs-list-group-bg:#fff;--bs-list-group-border-color:rgba(0, 0, 0, 0.125);--bs-list-group-border-width:1px;--bs-list-group-border-radius:0.375rem;--bs-list-group-item-padding-x:1rem;--bs-list-group-item-padding-y:0.5rem;--bs-list-group-action-color:#495057;--bs-list-group-action-hover-color:#495057;--bs-list-group-action-hover-bg:#f8f9fa;--bs-list-group-action-active-color:#212529;--bs-list-group-action-active-bg:#e9ecef;--bs-list-group-disabled-color:#6c757d;--bs-list-group-disabled-bg:#fff;--bs-list-group-active-color:#fff;--bs-list-group-active-bg:#0d6efd;--bs-list-group-active-border-color:#0d6efd;display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:var(--bs-list-group-border-radius)}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>.list-group-item::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:var(--bs-list-group-action-color);text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:var(--bs-list-group-action-hover-color);text-decoration:none;background-color:var(--bs-list-group-action-hover-bg)}.list-group-item-action:active{color:var(--bs-list-group-action-active-color);background-color:var(--bs-list-group-action-active-bg)}.list-group-item{position:relative;display:block;padding:var(--bs-list-group-item-padding-y) var(--bs-list-group-item-padding-x);color:var(--bs-list-group-color);text-decoration:none;background-color:var(--bs-list-group-bg);border:var(--bs-list-group-border-width) solid var(--bs-list-group-border-color)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:var(--bs-list-group-disabled-color);pointer-events:none;background-color:var(--bs-list-group-disabled-bg)}.list-group-item.active{z-index:2;color:var(--bs-list-group-active-color);background-color:var(--bs-list-group-active-bg);border-color:var(--bs-list-group-active-border-color)}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:calc(-1 * var(--bs-list-group-border-width));border-top-width:var(--bs-list-group-border-width)}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width:1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 var(--bs-list-group-border-width)}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#084298;background-color:#cfe2ff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#084298;background-color:#bacbe6}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#084298;border-color:#084298}.list-group-item-secondary{color:#41464b;background-color:#e2e3e5}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#41464b;background-color:#cbccce}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#41464b;border-color:#41464b}.list-group-item-success{color:#0f5132;background-color:#d1e7dd}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#0f5132;background-color:#bcd0c7}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#0f5132;border-color:#0f5132}.list-group-item-info{color:#055160;background-color:#cff4fc}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#055160;background-color:#badce3}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#055160;border-color:#055160}.list-group-item-warning{color:#664d03;background-color:#fff3cd}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#664d03;background-color:#e6dbb9}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#664d03;border-color:#664d03}.list-group-item-danger{color:#842029;background-color:#f8d7da}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#842029;background-color:#dfc2c4}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#842029;border-color:#842029}.list-group-item-light{color:#636464;background-color:#fefefe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#636464;background-color:#e5e5e5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#636464;border-color:#636464}.list-group-item-dark{color:#141619;background-color:#d3d3d4}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#141619;background-color:#bebebf}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#141619;border-color:#141619}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#000;background:transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;border-radius:.375rem;opacity:.5}.btn-close:hover{color:#000;text-decoration:none;opacity:.75}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25);opacity:1}.btn-close.disabled,.btn-close:disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;opacity:.25}.btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast{--bs-toast-zindex:1090;--bs-toast-padding-x:0.75rem;--bs-toast-padding-y:0.5rem;--bs-toast-spacing:1.5rem;--bs-toast-max-width:350px;--bs-toast-font-size:0.875rem;--bs-toast-color: ;--bs-toast-bg:rgba(255, 255, 255, 0.85);--bs-toast-border-width:1px;--bs-toast-border-color:var(--bs-border-color-translucent);--bs-toast-border-radius:0.375rem;--bs-toast-box-shadow:0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-toast-header-color:#6c757d;--bs-toast-header-bg:rgba(255, 255, 255, 0.85);--bs-toast-header-border-color:rgba(0, 0, 0, 0.05);width:var(--bs-toast-max-width);max-width:100%;font-size:var(--bs-toast-font-size);color:var(--bs-toast-color);pointer-events:auto;background-color:var(--bs-toast-bg);background-clip:padding-box;border:var(--bs-toast-border-width) solid var(--bs-toast-border-color);box-shadow:var(--bs-toast-box-shadow);border-radius:var(--bs-toast-border-radius)}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{--bs-toast-zindex:1090;position:absolute;z-index:var(--bs-toast-zindex);width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:var(--bs-toast-spacing)}.toast-header{display:flex;align-items:center;padding:var(--bs-toast-padding-y) var(--bs-toast-padding-x);color:var(--bs-toast-header-color);background-color:var(--bs-toast-header-bg);background-clip:padding-box;border-bottom:var(--bs-toast-border-width) solid var(--bs-toast-header-border-color);border-top-left-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width));border-top-right-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width))}.toast-header .btn-close{margin-right:calc(-.5 * var(--bs-toast-padding-x));margin-left:var(--bs-toast-padding-x)}.toast-body{padding:var(--bs-toast-padding-x);word-wrap:break-word}.modal{--bs-modal-zindex:1055;--bs-modal-width:500px;--bs-modal-padding:1rem;--bs-modal-margin:0.5rem;--bs-modal-color: ;--bs-modal-bg:#fff;--bs-modal-border-color:var(--bs-border-color-translucent);--bs-modal-border-width:1px;--bs-modal-border-radius:0.5rem;--bs-modal-box-shadow:0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);--bs-modal-inner-border-radius:calc(0.5rem - 1px);--bs-modal-header-padding-x:1rem;--bs-modal-header-padding-y:1rem;--bs-modal-header-padding:1rem 1rem;--bs-modal-header-border-color:var(--bs-border-color);--bs-modal-header-border-width:1px;--bs-modal-title-line-height:1.5;--bs-modal-footer-gap:0.5rem;--bs-modal-footer-bg: ;--bs-modal-footer-border-color:var(--bs-border-color);--bs-modal-footer-border-width:1px;position:fixed;top:0;left:0;z-index:var(--bs-modal-zindex);display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:var(--bs-modal-margin);pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - var(--bs-modal-margin) * 2)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - var(--bs-modal-margin) * 2)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;color:var(--bs-modal-color);pointer-events:auto;background-color:var(--bs-modal-bg);background-clip:padding-box;border:var(--bs-modal-border-width) solid var(--bs-modal-border-color);border-radius:var(--bs-modal-border-radius);outline:0}.modal-backdrop{--bs-backdrop-zindex:1050;--bs-backdrop-bg:#000;--bs-backdrop-opacity:0.5;position:fixed;top:0;left:0;z-index:var(--bs-backdrop-zindex);width:100vw;height:100vh;background-color:var(--bs-backdrop-bg)}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:var(--bs-backdrop-opacity)}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:var(--bs-modal-header-padding);border-bottom:var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);border-top-left-radius:var(--bs-modal-inner-border-radius);border-top-right-radius:var(--bs-modal-inner-border-radius)}.modal-header .btn-close{padding:calc(var(--bs-modal-header-padding-y) * .5) calc(var(--bs-modal-header-padding-x) * .5);margin:calc(-.5 * var(--bs-modal-header-padding-y)) calc(-.5 * var(--bs-modal-header-padding-x)) calc(-.5 * var(--bs-modal-header-padding-y)) auto}.modal-title{margin-bottom:0;line-height:var(--bs-modal-title-line-height)}.modal-body{position:relative;flex:1 1 auto;padding:var(--bs-modal-padding)}.modal-footer{display:flex;flex-shrink:0;flex-wrap:wrap;align-items:center;justify-content:flex-end;padding:calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap) * .5);background-color:var(--bs-modal-footer-bg);border-top:var(--bs-modal-footer-border-width) solid var(--bs-modal-footer-border-color);border-bottom-right-radius:var(--bs-modal-inner-border-radius);border-bottom-left-radius:var(--bs-modal-inner-border-radius)}.modal-footer>*{margin:calc(var(--bs-modal-footer-gap) * .5)}@media (min-width:576px){.modal{--bs-modal-margin:1.75rem;--bs-modal-box-shadow:0 0.5rem 1rem rgba(0, 0, 0, 0.15)}.modal-dialog{max-width:var(--bs-modal-width);margin-right:auto;margin-left:auto}.modal-sm{--bs-modal-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{--bs-modal-width:800px}}@media (min-width:1200px){.modal-xl{--bs-modal-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-footer,.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}@media (max-width:575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-footer,.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}}@media (max-width:767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-footer,.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}}@media (max-width:991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-footer,.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}}@media (max-width:1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-footer,.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}}@media (max-width:1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-footer,.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}}.tooltip{--bs-tooltip-zindex:1080;--bs-tooltip-max-width:200px;--bs-tooltip-padding-x:0.5rem;--bs-tooltip-padding-y:0.25rem;--bs-tooltip-margin: ;--bs-tooltip-font-size:0.875rem;--bs-tooltip-color:#fff;--bs-tooltip-bg:#000;--bs-tooltip-border-radius:0.375rem;--bs-tooltip-opacity:0.9;--bs-tooltip-arrow-width:0.8rem;--bs-tooltip-arrow-height:0.4rem;z-index:var(--bs-tooltip-zindex);display:block;padding:var(--bs-tooltip-arrow-height);margin:var(--bs-tooltip-margin);font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:var(--bs-tooltip-font-size);word-wrap:break-word;opacity:0}.tooltip.show{opacity:var(--bs-tooltip-opacity)}.tooltip .tooltip-arrow{display:block;width:var(--bs-tooltip-arrow-width);height:var(--bs-tooltip-arrow-height)}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,.bs-tooltip-top .tooltip-arrow::before{top:-1px;border-width:var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * .5) 0;border-top-color:var(--bs-tooltip-bg)}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:0;width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before,.bs-tooltip-end .tooltip-arrow::before{right:-1px;border-width:calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * .5) 0;border-right-color:var(--bs-tooltip-bg)}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before,.bs-tooltip-bottom .tooltip-arrow::before{bottom:-1px;border-width:0 calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height);border-bottom-color:var(--bs-tooltip-bg)}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{right:0;width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before,.bs-tooltip-start .tooltip-arrow::before{left:-1px;border-width:calc(var(--bs-tooltip-arrow-width) * .5) 0 calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height);border-left-color:var(--bs-tooltip-bg)}.tooltip-inner{max-width:var(--bs-tooltip-max-width);padding:var(--bs-tooltip-padding-y) var(--bs-tooltip-padding-x);color:var(--bs-tooltip-color);text-align:center;background-color:var(--bs-tooltip-bg);border-radius:var(--bs-tooltip-border-radius)}.popover{--bs-popover-zindex:1070;--bs-popover-max-width:276px;--bs-popover-font-size:0.875rem;--bs-popover-bg:#fff;--bs-popover-border-width:1px;--bs-popover-border-color:var(--bs-border-color-translucent);--bs-popover-border-radius:0.5rem;--bs-popover-inner-border-radius:calc(0.5rem - 1px);--bs-popover-box-shadow:0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-popover-header-padding-x:1rem;--bs-popover-header-padding-y:0.5rem;--bs-popover-header-font-size:1rem;--bs-popover-header-color: ;--bs-popover-header-bg:#f0f0f0;--bs-popover-body-padding-x:1rem;--bs-popover-body-padding-y:1rem;--bs-popover-body-color:#212529;--bs-popover-arrow-width:1rem;--bs-popover-arrow-height:0.5rem;--bs-popover-arrow-border:var(--bs-popover-border-color);z-index:var(--bs-popover-zindex);display:block;max-width:var(--bs-popover-max-width);font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:var(--bs-popover-font-size);word-wrap:break-word;background-color:var(--bs-popover-bg);background-clip:padding-box;border:var(--bs-popover-border-width) solid var(--bs-popover-border-color);border-radius:var(--bs-popover-border-radius)}.popover .popover-arrow{display:block;width:var(--bs-popover-arrow-width);height:var(--bs-popover-arrow-height)}.popover .popover-arrow::after,.popover .popover-arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid;border-width:0}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::after,.bs-popover-top>.popover-arrow::before{border-width:var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * .5) 0}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::before{bottom:0;border-top-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-top>.popover-arrow::after{bottom:var(--bs-popover-border-width);border-top-color:var(--bs-popover-bg)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{left:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::after,.bs-popover-end>.popover-arrow::before{border-width:calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * .5) 0}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::before{left:0;border-right-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-end>.popover-arrow::after{left:var(--bs-popover-border-width);border-right-color:var(--bs-popover-bg)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::before{border-width:0 calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::before{top:0;border-bottom-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::after{top:var(--bs-popover-border-width);border-bottom-color:var(--bs-popover-bg)}.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:var(--bs-popover-arrow-width);margin-left:calc(-.5 * var(--bs-popover-arrow-width));content:"";border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-header-bg)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{right:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::after,.bs-popover-start>.popover-arrow::before{border-width:calc(var(--bs-popover-arrow-width) * .5) 0 calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::before{right:0;border-left-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-start>.popover-arrow::after{right:var(--bs-popover-border-width);border-left-color:var(--bs-popover-bg)}.popover-header{padding:var(--bs-popover-header-padding-y) var(--bs-popover-header-padding-x);margin-bottom:0;font-size:var(--bs-popover-header-font-size);color:var(--bs-popover-header-color);background-color:var(--bs-popover-header-bg);border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-border-color);border-top-left-radius:var(--bs-popover-inner-border-radius);border-top-right-radius:var(--bs-popover-inner-border-radius)}.popover-header:empty{display:none}.popover-body{padding:var(--bs-popover-body-padding-y) var(--bs-popover-body-padding-x);color:var(--bs-popover-body-color)}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-end,.carousel-item-next:not(.carousel-item-start){transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-next-icon,.carousel-dark .carousel-control-prev-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}.spinner-border,.spinner-grow{display:inline-block;width:var(--bs-spinner-width);height:var(--bs-spinner-height);vertical-align:var(--bs-spinner-vertical-align);border-radius:50%;animation:var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name)}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{--bs-spinner-width:2rem;--bs-spinner-height:2rem;--bs-spinner-vertical-align:-0.125em;--bs-spinner-border-width:0.25em;--bs-spinner-animation-speed:0.75s;--bs-spinner-animation-name:spinner-border;border:var(--bs-spinner-border-width) solid currentcolor;border-right-color:transparent}.spinner-border-sm{--bs-spinner-width:1rem;--bs-spinner-height:1rem;--bs-spinner-border-width:0.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{--bs-spinner-width:2rem;--bs-spinner-height:2rem;--bs-spinner-vertical-align:-0.125em;--bs-spinner-animation-speed:0.75s;--bs-spinner-animation-name:spinner-grow;background-color:currentcolor;opacity:0}.spinner-grow-sm{--bs-spinner-width:1rem;--bs-spinner-height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{--bs-spinner-animation-speed:1.5s}}.offcanvas,.offcanvas-lg,.offcanvas-md,.offcanvas-sm,.offcanvas-xl,.offcanvas-xxl{--bs-offcanvas-zindex:1045;--bs-offcanvas-width:400px;--bs-offcanvas-height:30vh;--bs-offcanvas-padding-x:1rem;--bs-offcanvas-padding-y:1rem;--bs-offcanvas-color: ;--bs-offcanvas-bg:#fff;--bs-offcanvas-border-width:1px;--bs-offcanvas-border-color:var(--bs-border-color-translucent);--bs-offcanvas-box-shadow:0 0.125rem 0.25rem rgba(0, 0, 0, 0.075)}@media (max-width:575.98px){.offcanvas-sm{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}}@media (max-width:575.98px) and (prefers-reduced-motion:reduce){.offcanvas-sm{transition:none}}@media (max-width:575.98px){.offcanvas-sm.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}}@media (max-width:575.98px){.offcanvas-sm.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}}@media (max-width:575.98px){.offcanvas-sm.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}}@media (max-width:575.98px){.offcanvas-sm.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}}@media (max-width:575.98px){.offcanvas-sm.show:not(.hiding),.offcanvas-sm.showing{transform:none}}@media (max-width:575.98px){.offcanvas-sm.hiding,.offcanvas-sm.show,.offcanvas-sm.showing{visibility:visible}}@media (min-width:576px){.offcanvas-sm{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-sm .offcanvas-header{display:none}.offcanvas-sm .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}@media (max-width:767.98px){.offcanvas-md{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}}@media (max-width:767.98px) and (prefers-reduced-motion:reduce){.offcanvas-md{transition:none}}@media (max-width:767.98px){.offcanvas-md.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}}@media (max-width:767.98px){.offcanvas-md.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}}@media (max-width:767.98px){.offcanvas-md.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}}@media (max-width:767.98px){.offcanvas-md.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}}@media (max-width:767.98px){.offcanvas-md.show:not(.hiding),.offcanvas-md.showing{transform:none}}@media (max-width:767.98px){.offcanvas-md.hiding,.offcanvas-md.show,.offcanvas-md.showing{visibility:visible}}@media (min-width:768px){.offcanvas-md{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-md .offcanvas-header{display:none}.offcanvas-md .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}@media (max-width:991.98px){.offcanvas-lg{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}}@media (max-width:991.98px) and (prefers-reduced-motion:reduce){.offcanvas-lg{transition:none}}@media (max-width:991.98px){.offcanvas-lg.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}}@media (max-width:991.98px){.offcanvas-lg.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}}@media (max-width:991.98px){.offcanvas-lg.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}}@media (max-width:991.98px){.offcanvas-lg.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}}@media (max-width:991.98px){.offcanvas-lg.show:not(.hiding),.offcanvas-lg.showing{transform:none}}@media (max-width:991.98px){.offcanvas-lg.hiding,.offcanvas-lg.show,.offcanvas-lg.showing{visibility:visible}}@media (min-width:992px){.offcanvas-lg{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-lg .offcanvas-header{display:none}.offcanvas-lg .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}@media (max-width:1199.98px){.offcanvas-xl{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}}@media (max-width:1199.98px) and (prefers-reduced-motion:reduce){.offcanvas-xl{transition:none}}@media (max-width:1199.98px){.offcanvas-xl.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}}@media (max-width:1199.98px){.offcanvas-xl.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}}@media (max-width:1199.98px){.offcanvas-xl.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}}@media (max-width:1199.98px){.offcanvas-xl.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}}@media (max-width:1199.98px){.offcanvas-xl.show:not(.hiding),.offcanvas-xl.showing{transform:none}}@media (max-width:1199.98px){.offcanvas-xl.hiding,.offcanvas-xl.show,.offcanvas-xl.showing{visibility:visible}}@media (min-width:1200px){.offcanvas-xl{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-xl .offcanvas-header{display:none}.offcanvas-xl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}@media (max-width:1399.98px){.offcanvas-xxl{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}}@media (max-width:1399.98px) and (prefers-reduced-motion:reduce){.offcanvas-xxl{transition:none}}@media (max-width:1399.98px){.offcanvas-xxl.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}}@media (max-width:1399.98px){.offcanvas-xxl.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}}@media (max-width:1399.98px){.offcanvas-xxl.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}}@media (max-width:1399.98px){.offcanvas-xxl.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}}@media (max-width:1399.98px){.offcanvas-xxl.show:not(.hiding),.offcanvas-xxl.showing{transform:none}}@media (max-width:1399.98px){.offcanvas-xxl.hiding,.offcanvas-xxl.show,.offcanvas-xxl.showing{visibility:visible}}@media (min-width:1400px){.offcanvas-xxl{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-xxl .offcanvas-header{display:none}.offcanvas-xxl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}.offcanvas{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas.show:not(.hiding),.offcanvas.showing{transform:none}.offcanvas.hiding,.offcanvas.show,.offcanvas.showing{visibility:visible}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;align-items:center;justify-content:space-between;padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x)}.offcanvas-header .btn-close{padding:calc(var(--bs-offcanvas-padding-y) * .5) calc(var(--bs-offcanvas-padding-x) * .5);margin-top:calc(-.5 * var(--bs-offcanvas-padding-y));margin-right:calc(-.5 * var(--bs-offcanvas-padding-x));margin-bottom:calc(-.5 * var(--bs-offcanvas-padding-y))}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{flex-grow:1;padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);overflow-y:auto}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentcolor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{animation:placeholder-glow 2s ease-in-out infinite}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{-webkit-mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);-webkit-mask-size:200% 100%;mask-size:200% 100%;animation:placeholder-wave 2s linear infinite}@keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.text-bg-primary{color:#fff!important;background-color:RGBA(13,110,253,var(--bs-bg-opacity,1))!important}.text-bg-secondary{color:#fff!important;background-color:RGBA(108,117,125,var(--bs-bg-opacity,1))!important}.text-bg-success{color:#fff!important;background-color:RGBA(25,135,84,var(--bs-bg-opacity,1))!important}.text-bg-info{color:#000!important;background-color:RGBA(13,202,240,var(--bs-bg-opacity,1))!important}.text-bg-warning{color:#000!important;background-color:RGBA(255,193,7,var(--bs-bg-opacity,1))!important}.text-bg-danger{color:#fff!important;background-color:RGBA(220,53,69,var(--bs-bg-opacity,1))!important}.text-bg-light{color:#000!important;background-color:RGBA(248,249,250,var(--bs-bg-opacity,1))!important}.text-bg-dark{color:#fff!important;background-color:RGBA(33,37,41,var(--bs-bg-opacity,1))!important}.link-primary{color:#0d6efd!important}.link-primary:focus,.link-primary:hover{color:#0a58ca!important}.link-secondary{color:#6c757d!important}.link-secondary:focus,.link-secondary:hover{color:#565e64!important}.link-success{color:#198754!important}.link-success:focus,.link-success:hover{color:#146c43!important}.link-info{color:#0dcaf0!important}.link-info:focus,.link-info:hover{color:#3dd5f3!important}.link-warning{color:#ffc107!important}.link-warning:focus,.link-warning:hover{color:#ffcd39!important}.link-danger{color:#dc3545!important}.link-danger:focus,.link-danger:hover{color:#b02a37!important}.link-light{color:#f8f9fa!important}.link-light:focus,.link-light:hover{color:#f9fafb!important}.link-dark{color:#212529!important}.link-dark:focus,.link-dark:hover{color:#1a1e21!important}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:75%}.ratio-16x9{--bs-aspect-ratio:56.25%}.ratio-21x9{--bs-aspect-ratio:42.8571428571%}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}@media (min-width:576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-sm-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width:768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-md-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width:992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-lg-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width:1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-xl-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width:1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-xxl-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}.hstack{display:flex;flex-direction:row;align-items:center;align-self:stretch}.vstack{display:flex;flex:1 1 auto;flex-direction:column;align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;width:1px;min-height:1em;background-color:currentcolor;opacity:.25}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.opacity-0{opacity:0!important}.opacity-25{opacity:.25!important}.opacity-50{opacity:.5!important}.opacity-75{opacity:.75!important}.opacity-100{opacity:1!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translateX(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-0{border:0!important}.border-top{border-top:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-top-0{border-top:0!important}.border-end{border-right:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-start-0{border-left:0!important}.border-primary{--bs-border-opacity:1;border-color:rgba(var(--bs-primary-rgb),var(--bs-border-opacity))!important}.border-secondary{--bs-border-opacity:1;border-color:rgba(var(--bs-secondary-rgb),var(--bs-border-opacity))!important}.border-success{--bs-border-opacity:1;border-color:rgba(var(--bs-success-rgb),var(--bs-border-opacity))!important}.border-info{--bs-border-opacity:1;border-color:rgba(var(--bs-info-rgb),var(--bs-border-opacity))!important}.border-warning{--bs-border-opacity:1;border-color:rgba(var(--bs-warning-rgb),var(--bs-border-opacity))!important}.border-danger{--bs-border-opacity:1;border-color:rgba(var(--bs-danger-rgb),var(--bs-border-opacity))!important}.border-light{--bs-border-opacity:1;border-color:rgba(var(--bs-light-rgb),var(--bs-border-opacity))!important}.border-dark{--bs-border-opacity:1;border-color:rgba(var(--bs-dark-rgb),var(--bs-border-opacity))!important}.border-white{--bs-border-opacity:1;border-color:rgba(var(--bs-white-rgb),var(--bs-border-opacity))!important}.border-1{--bs-border-width:1px}.border-2{--bs-border-width:2px}.border-3{--bs-border-width:3px}.border-4{--bs-border-width:4px}.border-5{--bs-border-width:5px}.border-opacity-10{--bs-border-opacity:0.1}.border-opacity-25{--bs-border-opacity:0.25}.border-opacity-50{--bs-border-opacity:0.5}.border-opacity-75{--bs-border-opacity:0.75}.border-opacity-100{--bs-border-opacity:1}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:1 1 auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-3{margin-right:1rem!important;margin-left:1rem!important}.mx-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-5{margin-right:3rem!important;margin-left:3rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-3{padding-right:1rem!important;padding-left:1rem!important}.px-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-5{padding-right:3rem!important;padding-left:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-light{font-weight:300!important}.fw-lighter{font-weight:lighter!important}.fw-normal{font-weight:400!important}.fw-bold{font-weight:700!important}.fw-semibold{font-weight:600!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{--bs-text-opacity:1;color:rgba(var(--bs-primary-rgb),var(--bs-text-opacity))!important}.text-secondary{--bs-text-opacity:1;color:rgba(var(--bs-secondary-rgb),var(--bs-text-opacity))!important}.text-success{--bs-text-opacity:1;color:rgba(var(--bs-success-rgb),var(--bs-text-opacity))!important}.text-info{--bs-text-opacity:1;color:rgba(var(--bs-info-rgb),var(--bs-text-opacity))!important}.text-warning{--bs-text-opacity:1;color:rgba(var(--bs-warning-rgb),var(--bs-text-opacity))!important}.text-danger{--bs-text-opacity:1;color:rgba(var(--bs-danger-rgb),var(--bs-text-opacity))!important}.text-light{--bs-text-opacity:1;color:rgba(var(--bs-light-rgb),var(--bs-text-opacity))!important}.text-dark{--bs-text-opacity:1;color:rgba(var(--bs-dark-rgb),var(--bs-text-opacity))!important}.text-black{--bs-text-opacity:1;color:rgba(var(--bs-black-rgb),var(--bs-text-opacity))!important}.text-white{--bs-text-opacity:1;color:rgba(var(--bs-white-rgb),var(--bs-text-opacity))!important}.text-body{--bs-text-opacity:1;color:rgba(var(--bs-body-color-rgb),var(--bs-text-opacity))!important}.text-muted{--bs-text-opacity:1;color:#6c757d!important}.text-black-50{--bs-text-opacity:1;color:rgba(0,0,0,.5)!important}.text-white-50{--bs-text-opacity:1;color:rgba(255,255,255,.5)!important}.text-reset{--bs-text-opacity:1;color:inherit!important}.text-opacity-25{--bs-text-opacity:0.25}.text-opacity-50{--bs-text-opacity:0.5}.text-opacity-75{--bs-text-opacity:0.75}.text-opacity-100{--bs-text-opacity:1}.bg-primary{--bs-bg-opacity:1;background-color:rgba(var(--bs-primary-rgb),var(--bs-bg-opacity))!important}.bg-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-rgb),var(--bs-bg-opacity))!important}.bg-success{--bs-bg-opacity:1;background-color:rgba(var(--bs-success-rgb),var(--bs-bg-opacity))!important}.bg-info{--bs-bg-opacity:1;background-color:rgba(var(--bs-info-rgb),var(--bs-bg-opacity))!important}.bg-warning{--bs-bg-opacity:1;background-color:rgba(var(--bs-warning-rgb),var(--bs-bg-opacity))!important}.bg-danger{--bs-bg-opacity:1;background-color:rgba(var(--bs-danger-rgb),var(--bs-bg-opacity))!important}.bg-light{--bs-bg-opacity:1;background-color:rgba(var(--bs-light-rgb),var(--bs-bg-opacity))!important}.bg-dark{--bs-bg-opacity:1;background-color:rgba(var(--bs-dark-rgb),var(--bs-bg-opacity))!important}.bg-black{--bs-bg-opacity:1;background-color:rgba(var(--bs-black-rgb),var(--bs-bg-opacity))!important}.bg-white{--bs-bg-opacity:1;background-color:rgba(var(--bs-white-rgb),var(--bs-bg-opacity))!important}.bg-body{--bs-bg-opacity:1;background-color:rgba(var(--bs-body-bg-rgb),var(--bs-bg-opacity))!important}.bg-transparent{--bs-bg-opacity:1;background-color:transparent!important}.bg-opacity-10{--bs-bg-opacity:0.1}.bg-opacity-25{--bs-bg-opacity:0.25}.bg-opacity-50{--bs-bg-opacity:0.5}.bg-opacity-75{--bs-bg-opacity:0.75}.bg-opacity-100{--bs-bg-opacity:1}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:var(--bs-border-radius)!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:var(--bs-border-radius-sm)!important}.rounded-2{border-radius:var(--bs-border-radius)!important}.rounded-3{border-radius:var(--bs-border-radius-lg)!important}.rounded-4{border-radius:var(--bs-border-radius-xl)!important}.rounded-5{border-radius:var(--bs-border-radius-2xl)!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:var(--bs-border-radius-pill)!important}.rounded-top{border-top-left-radius:var(--bs-border-radius)!important;border-top-right-radius:var(--bs-border-radius)!important}.rounded-end{border-top-right-radius:var(--bs-border-radius)!important;border-bottom-right-radius:var(--bs-border-radius)!important}.rounded-bottom{border-bottom-right-radius:var(--bs-border-radius)!important;border-bottom-left-radius:var(--bs-border-radius)!important}.rounded-start{border-bottom-left-radius:var(--bs-border-radius)!important;border-top-left-radius:var(--bs-border-radius)!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media (min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-sm-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-3{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-sm-5{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-sm-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-3{padding-right:1rem!important;padding-left:1rem!important}.px-sm-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-sm-5{padding-right:3rem!important;padding-left:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-md-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-3{margin-right:1rem!important;margin-left:1rem!important}.mx-md-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-md-5{margin-right:3rem!important;margin-left:3rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-md-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-3{padding-right:1rem!important;padding-left:1rem!important}.px-md-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-md-5{padding-right:3rem!important;padding-left:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-lg-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-3{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-lg-5{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-lg-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-3{padding-right:1rem!important;padding-left:1rem!important}.px-lg-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-lg-5{padding-right:3rem!important;padding-left:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:1 1 auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xxl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xxl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xxl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xxl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}} -/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/node_modules/bootstrap/dist/bootstrap.bundle.min.js b/src/Rules.Framework.WebUI/node_modules/bootstrap/dist/bootstrap.bundle.min.js deleted file mode 100644 index 1d138863..00000000 --- a/src/Rules.Framework.WebUI/node_modules/bootstrap/dist/bootstrap.bundle.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * Bootstrap v5.2.2 (https://getbootstrap.com/) - * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t="transitionend",e=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return e},i=t=>{const i=e(t);return i&&document.querySelector(i)?i:null},n=t=>{const i=e(t);return i?document.querySelector(i):null},s=e=>{e.dispatchEvent(new Event(t))},o=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),r=t=>o(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(t):null,a=t=>{if(!o(t)||0===t.getClientRects().length)return!1;const e="visible"===getComputedStyle(t).getPropertyValue("visibility"),i=t.closest("details:not([open])");if(!i)return e;if(i!==t){const e=t.closest("summary");if(e&&e.parentNode!==i)return!1;if(null===e)return!1}return e},l=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),c=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?c(t.parentNode):null},h=()=>{},d=t=>{t.offsetHeight},u=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,f=[],p=()=>"rtl"===document.documentElement.dir,g=t=>{var e;e=()=>{const e=u();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(f.length||document.addEventListener("DOMContentLoaded",(()=>{for(const t of f)t()})),f.push(e)):e()},m=t=>{"function"==typeof t&&t()},_=(e,i,n=!0)=>{if(!n)return void m(e);const o=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(i)+5;let r=!1;const a=({target:n})=>{n===i&&(r=!0,i.removeEventListener(t,a),m(e))};i.addEventListener(t,a),setTimeout((()=>{r||s(i)}),o)},b=(t,e,i,n)=>{const s=t.length;let o=t.indexOf(e);return-1===o?!i&&n?t[s-1]:t[0]:(o+=i?1:-1,n&&(o=(o+s)%s),t[Math.max(0,Math.min(o,s-1))])},v=/[^.]*(?=\..*)\.|.*/,y=/\..*/,w=/::\d+$/,A={};let E=1;const T={mouseenter:"mouseover",mouseleave:"mouseout"},C=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function O(t,e){return e&&`${e}::${E++}`||t.uidEvent||E++}function x(t){const e=O(t);return t.uidEvent=e,A[e]=A[e]||{},A[e]}function k(t,e,i=null){return Object.values(t).find((t=>t.callable===e&&t.delegationSelector===i))}function L(t,e,i){const n="string"==typeof e,s=n?i:e||i;let o=N(t);return C.has(o)||(o=t),[n,s,o]}function D(t,e,i,n,s){if("string"!=typeof e||!t)return;let[o,r,a]=L(e,i,n);if(e in T){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};r=t(r)}const l=x(t),c=l[a]||(l[a]={}),h=k(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=O(r,e.replace(v,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(const a of o)if(a===r)return j(s,{delegateTarget:r}),n.oneOff&&P.off(t,s.type,e,i),i.apply(r,[s])}}(t,i,r):function(t,e){return function i(n){return j(n,{delegateTarget:t}),i.oneOff&&P.off(t,n.type,e),e.apply(t,[n])}}(t,r);u.delegationSelector=o?i:null,u.callable=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function S(t,e,i,n,s){const o=k(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function I(t,e,i,n){const s=e[i]||{};for(const o of Object.keys(s))if(o.includes(n)){const n=s[o];S(t,e,i,n.callable,n.delegationSelector)}}function N(t){return t=t.replace(y,""),T[t]||t}const P={on(t,e,i,n){D(t,e,i,n,!1)},one(t,e,i,n){D(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=L(e,i,n),a=r!==e,l=x(t),c=l[r]||{},h=e.startsWith(".");if(void 0===o){if(h)for(const i of Object.keys(l))I(t,l,i,e.slice(1));for(const i of Object.keys(c)){const n=i.replace(w,"");if(!a||e.includes(n)){const e=c[i];S(t,l,r,e.callable,e.delegationSelector)}}}else{if(!Object.keys(c).length)return;S(t,l,r,o,s?i:null)}},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=u();let s=null,o=!0,r=!0,a=!1;e!==N(e)&&n&&(s=n.Event(e,i),n(t).trigger(s),o=!s.isPropagationStopped(),r=!s.isImmediatePropagationStopped(),a=s.isDefaultPrevented());let l=new Event(e,{bubbles:o,cancelable:!0});return l=j(l,i),a&&l.preventDefault(),r&&t.dispatchEvent(l),l.defaultPrevented&&s&&s.preventDefault(),l}};function j(t,e){for(const[i,n]of Object.entries(e||{}))try{t[i]=n}catch(e){Object.defineProperty(t,i,{configurable:!0,get:()=>n})}return t}const M=new Map,H={set(t,e,i){M.has(t)||M.set(t,new Map);const n=M.get(t);n.has(e)||0===n.size?n.set(e,i):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(n.keys())[0]}.`)},get:(t,e)=>M.has(t)&&M.get(t).get(e)||null,remove(t,e){if(!M.has(t))return;const i=M.get(t);i.delete(e),0===i.size&&M.delete(t)}};function $(t){if("true"===t)return!0;if("false"===t)return!1;if(t===Number(t).toString())return Number(t);if(""===t||"null"===t)return null;if("string"!=typeof t)return t;try{return JSON.parse(decodeURIComponent(t))}catch(e){return t}}function W(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}const B={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${W(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${W(e)}`)},getDataAttributes(t){if(!t)return{};const e={},i=Object.keys(t.dataset).filter((t=>t.startsWith("bs")&&!t.startsWith("bsConfig")));for(const n of i){let i=n.replace(/^bs/,"");i=i.charAt(0).toLowerCase()+i.slice(1,i.length),e[i]=$(t.dataset[n])}return e},getDataAttribute:(t,e)=>$(t.getAttribute(`data-bs-${W(e)}`))};class F{static get Default(){return{}}static get DefaultType(){return{}}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}_getConfig(t){return t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t}_mergeConfigObj(t,e){const i=o(e)?B.getDataAttribute(e,"config"):{};return{...this.constructor.Default,..."object"==typeof i?i:{},...o(e)?B.getDataAttributes(e):{},..."object"==typeof t?t:{}}}_typeCheckConfig(t,e=this.constructor.DefaultType){for(const n of Object.keys(e)){const s=e[n],r=t[n],a=o(r)?"element":null==(i=r)?`${i}`:Object.prototype.toString.call(i).match(/\s([a-z]+)/i)[1].toLowerCase();if(!new RegExp(s).test(a))throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${n}" provided type "${a}" but expected type "${s}".`)}var i}}class z extends F{constructor(t,e){super(),(t=r(t))&&(this._element=t,this._config=this._getConfig(e),H.set(this._element,this.constructor.DATA_KEY,this))}dispose(){H.remove(this._element,this.constructor.DATA_KEY),P.off(this._element,this.constructor.EVENT_KEY);for(const t of Object.getOwnPropertyNames(this))this[t]=null}_queueCallback(t,e,i=!0){_(t,e,i)}_getConfig(t){return t=this._mergeConfigObj(t,this._element),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}static getInstance(t){return H.get(r(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.2.2"}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(t){return`${t}${this.EVENT_KEY}`}}const q=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,s=t.NAME;P.on(document,i,`[data-bs-dismiss="${s}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),l(this))return;const o=n(this)||this.closest(`.${s}`);t.getOrCreateInstance(o)[e]()}))};class R extends z{static get NAME(){return"alert"}close(){if(P.trigger(this._element,"close.bs.alert").defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),P.trigger(this._element,"closed.bs.alert"),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=R.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}q(R,"close"),g(R);const V='[data-bs-toggle="button"]';class K extends z{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=K.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}P.on(document,"click.bs.button.data-api",V,(t=>{t.preventDefault();const e=t.target.closest(V);K.getOrCreateInstance(e).toggle()})),g(K);const Q={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let n=t.parentNode.closest(e);for(;n;)i.push(n),n=n.parentNode.closest(e);return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(",");return this.find(e,t).filter((t=>!l(t)&&a(t)))}},X={endCallback:null,leftCallback:null,rightCallback:null},Y={endCallback:"(function|null)",leftCallback:"(function|null)",rightCallback:"(function|null)"};class U extends F{constructor(t,e){super(),this._element=t,t&&U.isSupported()&&(this._config=this._getConfig(e),this._deltaX=0,this._supportPointerEvents=Boolean(window.PointerEvent),this._initEvents())}static get Default(){return X}static get DefaultType(){return Y}static get NAME(){return"swipe"}dispose(){P.off(this._element,".bs.swipe")}_start(t){this._supportPointerEvents?this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX):this._deltaX=t.touches[0].clientX}_end(t){this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX-this._deltaX),this._handleSwipe(),m(this._config.endCallback)}_move(t){this._deltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this._deltaX}_handleSwipe(){const t=Math.abs(this._deltaX);if(t<=40)return;const e=t/this._deltaX;this._deltaX=0,e&&m(e>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(P.on(this._element,"pointerdown.bs.swipe",(t=>this._start(t))),P.on(this._element,"pointerup.bs.swipe",(t=>this._end(t))),this._element.classList.add("pointer-event")):(P.on(this._element,"touchstart.bs.swipe",(t=>this._start(t))),P.on(this._element,"touchmove.bs.swipe",(t=>this._move(t))),P.on(this._element,"touchend.bs.swipe",(t=>this._end(t))))}_eventIsPointerPenTouch(t){return this._supportPointerEvents&&("pen"===t.pointerType||"touch"===t.pointerType)}static isSupported(){return"ontouchstart"in document.documentElement||navigator.maxTouchPoints>0}}const G="next",J="prev",Z="left",tt="right",et="slid.bs.carousel",it="carousel",nt="active",st={ArrowLeft:tt,ArrowRight:Z},ot={interval:5e3,keyboard:!0,pause:"hover",ride:!1,touch:!0,wrap:!0},rt={interval:"(number|boolean)",keyboard:"boolean",pause:"(string|boolean)",ride:"(boolean|string)",touch:"boolean",wrap:"boolean"};class at extends z{constructor(t,e){super(t,e),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=Q.findOne(".carousel-indicators",this._element),this._addEventListeners(),this._config.ride===it&&this.cycle()}static get Default(){return ot}static get DefaultType(){return rt}static get NAME(){return"carousel"}next(){this._slide(G)}nextWhenVisible(){!document.hidden&&a(this._element)&&this.next()}prev(){this._slide(J)}pause(){this._isSliding&&s(this._element),this._clearInterval()}cycle(){this._clearInterval(),this._updateInterval(),this._interval=setInterval((()=>this.nextWhenVisible()),this._config.interval)}_maybeEnableCycle(){this._config.ride&&(this._isSliding?P.one(this._element,et,(()=>this.cycle())):this.cycle())}to(t){const e=this._getItems();if(t>e.length-1||t<0)return;if(this._isSliding)return void P.one(this._element,et,(()=>this.to(t)));const i=this._getItemIndex(this._getActive());if(i===t)return;const n=t>i?G:J;this._slide(n,e[t])}dispose(){this._swipeHelper&&this._swipeHelper.dispose(),super.dispose()}_configAfterMerge(t){return t.defaultInterval=t.interval,t}_addEventListeners(){this._config.keyboard&&P.on(this._element,"keydown.bs.carousel",(t=>this._keydown(t))),"hover"===this._config.pause&&(P.on(this._element,"mouseenter.bs.carousel",(()=>this.pause())),P.on(this._element,"mouseleave.bs.carousel",(()=>this._maybeEnableCycle()))),this._config.touch&&U.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(const t of Q.find(".carousel-item img",this._element))P.on(t,"dragstart.bs.carousel",(t=>t.preventDefault()));const t={leftCallback:()=>this._slide(this._directionToOrder(Z)),rightCallback:()=>this._slide(this._directionToOrder(tt)),endCallback:()=>{"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((()=>this._maybeEnableCycle()),500+this._config.interval))}};this._swipeHelper=new U(this._element,t)}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=st[t.key];e&&(t.preventDefault(),this._slide(this._directionToOrder(e)))}_getItemIndex(t){return this._getItems().indexOf(t)}_setActiveIndicatorElement(t){if(!this._indicatorsElement)return;const e=Q.findOne(".active",this._indicatorsElement);e.classList.remove(nt),e.removeAttribute("aria-current");const i=Q.findOne(`[data-bs-slide-to="${t}"]`,this._indicatorsElement);i&&(i.classList.add(nt),i.setAttribute("aria-current","true"))}_updateInterval(){const t=this._activeElement||this._getActive();if(!t)return;const e=Number.parseInt(t.getAttribute("data-bs-interval"),10);this._config.interval=e||this._config.defaultInterval}_slide(t,e=null){if(this._isSliding)return;const i=this._getActive(),n=t===G,s=e||b(this._getItems(),i,n,this._config.wrap);if(s===i)return;const o=this._getItemIndex(s),r=e=>P.trigger(this._element,e,{relatedTarget:s,direction:this._orderToDirection(t),from:this._getItemIndex(i),to:o});if(r("slide.bs.carousel").defaultPrevented)return;if(!i||!s)return;const a=Boolean(this._interval);this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(o),this._activeElement=s;const l=n?"carousel-item-start":"carousel-item-end",c=n?"carousel-item-next":"carousel-item-prev";s.classList.add(c),d(s),i.classList.add(l),s.classList.add(l),this._queueCallback((()=>{s.classList.remove(l,c),s.classList.add(nt),i.classList.remove(nt,c,l),this._isSliding=!1,r(et)}),i,this._isAnimated()),a&&this.cycle()}_isAnimated(){return this._element.classList.contains("slide")}_getActive(){return Q.findOne(".active.carousel-item",this._element)}_getItems(){return Q.find(".carousel-item",this._element)}_clearInterval(){this._interval&&(clearInterval(this._interval),this._interval=null)}_directionToOrder(t){return p()?t===Z?J:G:t===Z?G:J}_orderToDirection(t){return p()?t===J?Z:tt:t===J?tt:Z}static jQueryInterface(t){return this.each((function(){const e=at.getOrCreateInstance(this,t);if("number"!=typeof t){if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}else e.to(t)}))}}P.on(document,"click.bs.carousel.data-api","[data-bs-slide], [data-bs-slide-to]",(function(t){const e=n(this);if(!e||!e.classList.contains(it))return;t.preventDefault();const i=at.getOrCreateInstance(e),s=this.getAttribute("data-bs-slide-to");return s?(i.to(s),void i._maybeEnableCycle()):"next"===B.getDataAttribute(this,"slide")?(i.next(),void i._maybeEnableCycle()):(i.prev(),void i._maybeEnableCycle())})),P.on(window,"load.bs.carousel.data-api",(()=>{const t=Q.find('[data-bs-ride="carousel"]');for(const e of t)at.getOrCreateInstance(e)})),g(at);const lt="show",ct="collapse",ht="collapsing",dt='[data-bs-toggle="collapse"]',ut={parent:null,toggle:!0},ft={parent:"(null|element)",toggle:"boolean"};class pt extends z{constructor(t,e){super(t,e),this._isTransitioning=!1,this._triggerArray=[];const n=Q.find(dt);for(const t of n){const e=i(t),n=Q.find(e).filter((t=>t===this._element));null!==e&&n.length&&this._triggerArray.push(t)}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return ut}static get DefaultType(){return ft}static get NAME(){return"collapse"}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t=[];if(this._config.parent&&(t=this._getFirstLevelChildren(".collapse.show, .collapse.collapsing").filter((t=>t!==this._element)).map((t=>pt.getOrCreateInstance(t,{toggle:!1})))),t.length&&t[0]._isTransitioning)return;if(P.trigger(this._element,"show.bs.collapse").defaultPrevented)return;for(const e of t)e.hide();const e=this._getDimension();this._element.classList.remove(ct),this._element.classList.add(ht),this._element.style[e]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const i=`scroll${e[0].toUpperCase()+e.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(ht),this._element.classList.add(ct,lt),this._element.style[e]="",P.trigger(this._element,"shown.bs.collapse")}),this._element,!0),this._element.style[e]=`${this._element[i]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(P.trigger(this._element,"hide.bs.collapse").defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,d(this._element),this._element.classList.add(ht),this._element.classList.remove(ct,lt);for(const t of this._triggerArray){const e=n(t);e&&!this._isShown(e)&&this._addAriaAndCollapsedClass([t],!1)}this._isTransitioning=!0,this._element.style[t]="",this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(ht),this._element.classList.add(ct),P.trigger(this._element,"hidden.bs.collapse")}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(lt)}_configAfterMerge(t){return t.toggle=Boolean(t.toggle),t.parent=r(t.parent),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=this._getFirstLevelChildren(dt);for(const e of t){const t=n(e);t&&this._addAriaAndCollapsedClass([e],this._isShown(t))}}_getFirstLevelChildren(t){const e=Q.find(":scope .collapse .collapse",this._config.parent);return Q.find(t,this._config.parent).filter((t=>!e.includes(t)))}_addAriaAndCollapsedClass(t,e){if(t.length)for(const i of t)i.classList.toggle("collapsed",!e),i.setAttribute("aria-expanded",e)}static jQueryInterface(t){const e={};return"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1),this.each((function(){const i=pt.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}P.on(document,"click.bs.collapse.data-api",dt,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();const e=i(this),n=Q.find(e);for(const t of n)pt.getOrCreateInstance(t,{toggle:!1}).toggle()})),g(pt);var gt="top",mt="bottom",_t="right",bt="left",vt="auto",yt=[gt,mt,_t,bt],wt="start",At="end",Et="clippingParents",Tt="viewport",Ct="popper",Ot="reference",xt=yt.reduce((function(t,e){return t.concat([e+"-"+wt,e+"-"+At])}),[]),kt=[].concat(yt,[vt]).reduce((function(t,e){return t.concat([e,e+"-"+wt,e+"-"+At])}),[]),Lt="beforeRead",Dt="read",St="afterRead",It="beforeMain",Nt="main",Pt="afterMain",jt="beforeWrite",Mt="write",Ht="afterWrite",$t=[Lt,Dt,St,It,Nt,Pt,jt,Mt,Ht];function Wt(t){return t?(t.nodeName||"").toLowerCase():null}function Bt(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function Ft(t){return t instanceof Bt(t).Element||t instanceof Element}function zt(t){return t instanceof Bt(t).HTMLElement||t instanceof HTMLElement}function qt(t){return"undefined"!=typeof ShadowRoot&&(t instanceof Bt(t).ShadowRoot||t instanceof ShadowRoot)}const Rt={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];zt(s)&&Wt(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});zt(n)&&Wt(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function Vt(t){return t.split("-")[0]}var Kt=Math.max,Qt=Math.min,Xt=Math.round;function Yt(){var t=navigator.userAgentData;return null!=t&&t.brands?t.brands.map((function(t){return t.brand+"/"+t.version})).join(" "):navigator.userAgent}function Ut(){return!/^((?!chrome|android).)*safari/i.test(Yt())}function Gt(t,e,i){void 0===e&&(e=!1),void 0===i&&(i=!1);var n=t.getBoundingClientRect(),s=1,o=1;e&&zt(t)&&(s=t.offsetWidth>0&&Xt(n.width)/t.offsetWidth||1,o=t.offsetHeight>0&&Xt(n.height)/t.offsetHeight||1);var r=(Ft(t)?Bt(t):window).visualViewport,a=!Ut()&&i,l=(n.left+(a&&r?r.offsetLeft:0))/s,c=(n.top+(a&&r?r.offsetTop:0))/o,h=n.width/s,d=n.height/o;return{width:h,height:d,top:c,right:l+h,bottom:c+d,left:l,x:l,y:c}}function Jt(t){var e=Gt(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function Zt(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&qt(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function te(t){return Bt(t).getComputedStyle(t)}function ee(t){return["table","td","th"].indexOf(Wt(t))>=0}function ie(t){return((Ft(t)?t.ownerDocument:t.document)||window.document).documentElement}function ne(t){return"html"===Wt(t)?t:t.assignedSlot||t.parentNode||(qt(t)?t.host:null)||ie(t)}function se(t){return zt(t)&&"fixed"!==te(t).position?t.offsetParent:null}function oe(t){for(var e=Bt(t),i=se(t);i&&ee(i)&&"static"===te(i).position;)i=se(i);return i&&("html"===Wt(i)||"body"===Wt(i)&&"static"===te(i).position)?e:i||function(t){var e=/firefox/i.test(Yt());if(/Trident/i.test(Yt())&&zt(t)&&"fixed"===te(t).position)return null;var i=ne(t);for(qt(i)&&(i=i.host);zt(i)&&["html","body"].indexOf(Wt(i))<0;){var n=te(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function re(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}function ae(t,e,i){return Kt(t,Qt(e,i))}function le(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function ce(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const he={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,n=t.name,s=t.options,o=i.elements.arrow,r=i.modifiersData.popperOffsets,a=Vt(i.placement),l=re(a),c=[bt,_t].indexOf(a)>=0?"height":"width";if(o&&r){var h=function(t,e){return le("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:ce(t,yt))}(s.padding,i),d=Jt(o),u="y"===l?gt:bt,f="y"===l?mt:_t,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],g=r[l]-i.rects.reference[l],m=oe(o),_=m?"y"===l?m.clientHeight||0:m.clientWidth||0:0,b=p/2-g/2,v=h[u],y=_-d[c]-h[f],w=_/2-d[c]/2+b,A=ae(v,w,y),E=l;i.modifiersData[n]=((e={})[E]=A,e.centerOffset=A-w,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&Zt(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function de(t){return t.split("-")[1]}var ue={top:"auto",right:"auto",bottom:"auto",left:"auto"};function fe(t){var e,i=t.popper,n=t.popperRect,s=t.placement,o=t.variation,r=t.offsets,a=t.position,l=t.gpuAcceleration,c=t.adaptive,h=t.roundOffsets,d=t.isFixed,u=r.x,f=void 0===u?0:u,p=r.y,g=void 0===p?0:p,m="function"==typeof h?h({x:f,y:g}):{x:f,y:g};f=m.x,g=m.y;var _=r.hasOwnProperty("x"),b=r.hasOwnProperty("y"),v=bt,y=gt,w=window;if(c){var A=oe(i),E="clientHeight",T="clientWidth";A===Bt(i)&&"static"!==te(A=ie(i)).position&&"absolute"===a&&(E="scrollHeight",T="scrollWidth"),(s===gt||(s===bt||s===_t)&&o===At)&&(y=mt,g-=(d&&A===w&&w.visualViewport?w.visualViewport.height:A[E])-n.height,g*=l?1:-1),s!==bt&&(s!==gt&&s!==mt||o!==At)||(v=_t,f-=(d&&A===w&&w.visualViewport?w.visualViewport.width:A[T])-n.width,f*=l?1:-1)}var C,O=Object.assign({position:a},c&&ue),x=!0===h?function(t){var e=t.x,i=t.y,n=window.devicePixelRatio||1;return{x:Xt(e*n)/n||0,y:Xt(i*n)/n||0}}({x:f,y:g}):{x:f,y:g};return f=x.x,g=x.y,l?Object.assign({},O,((C={})[y]=b?"0":"",C[v]=_?"0":"",C.transform=(w.devicePixelRatio||1)<=1?"translate("+f+"px, "+g+"px)":"translate3d("+f+"px, "+g+"px, 0)",C)):Object.assign({},O,((e={})[y]=b?g+"px":"",e[v]=_?f+"px":"",e.transform="",e))}const pe={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:Vt(e.placement),variation:de(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s,isFixed:"fixed"===e.options.strategy};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,fe(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,fe(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var ge={passive:!0};const me={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=Bt(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,ge)})),a&&l.addEventListener("resize",i.update,ge),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,ge)})),a&&l.removeEventListener("resize",i.update,ge)}},data:{}};var _e={left:"right",right:"left",bottom:"top",top:"bottom"};function be(t){return t.replace(/left|right|bottom|top/g,(function(t){return _e[t]}))}var ve={start:"end",end:"start"};function ye(t){return t.replace(/start|end/g,(function(t){return ve[t]}))}function we(t){var e=Bt(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function Ae(t){return Gt(ie(t)).left+we(t).scrollLeft}function Ee(t){var e=te(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function Te(t){return["html","body","#document"].indexOf(Wt(t))>=0?t.ownerDocument.body:zt(t)&&Ee(t)?t:Te(ne(t))}function Ce(t,e){var i;void 0===e&&(e=[]);var n=Te(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=Bt(n),r=s?[o].concat(o.visualViewport||[],Ee(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(Ce(ne(r)))}function Oe(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function xe(t,e,i){return e===Tt?Oe(function(t,e){var i=Bt(t),n=ie(t),s=i.visualViewport,o=n.clientWidth,r=n.clientHeight,a=0,l=0;if(s){o=s.width,r=s.height;var c=Ut();(c||!c&&"fixed"===e)&&(a=s.offsetLeft,l=s.offsetTop)}return{width:o,height:r,x:a+Ae(t),y:l}}(t,i)):Ft(e)?function(t,e){var i=Gt(t,!1,"fixed"===e);return i.top=i.top+t.clientTop,i.left=i.left+t.clientLeft,i.bottom=i.top+t.clientHeight,i.right=i.left+t.clientWidth,i.width=t.clientWidth,i.height=t.clientHeight,i.x=i.left,i.y=i.top,i}(e,i):Oe(function(t){var e,i=ie(t),n=we(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=Kt(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=Kt(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+Ae(t),l=-n.scrollTop;return"rtl"===te(s||i).direction&&(a+=Kt(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(ie(t)))}function ke(t){var e,i=t.reference,n=t.element,s=t.placement,o=s?Vt(s):null,r=s?de(s):null,a=i.x+i.width/2-n.width/2,l=i.y+i.height/2-n.height/2;switch(o){case gt:e={x:a,y:i.y-n.height};break;case mt:e={x:a,y:i.y+i.height};break;case _t:e={x:i.x+i.width,y:l};break;case bt:e={x:i.x-n.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?re(o):null;if(null!=c){var h="y"===c?"height":"width";switch(r){case wt:e[c]=e[c]-(i[h]/2-n[h]/2);break;case At:e[c]=e[c]+(i[h]/2-n[h]/2)}}return e}function Le(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=void 0===n?t.placement:n,o=i.strategy,r=void 0===o?t.strategy:o,a=i.boundary,l=void 0===a?Et:a,c=i.rootBoundary,h=void 0===c?Tt:c,d=i.elementContext,u=void 0===d?Ct:d,f=i.altBoundary,p=void 0!==f&&f,g=i.padding,m=void 0===g?0:g,_=le("number"!=typeof m?m:ce(m,yt)),b=u===Ct?Ot:Ct,v=t.rects.popper,y=t.elements[p?b:u],w=function(t,e,i,n){var s="clippingParents"===e?function(t){var e=Ce(ne(t)),i=["absolute","fixed"].indexOf(te(t).position)>=0&&zt(t)?oe(t):t;return Ft(i)?e.filter((function(t){return Ft(t)&&Zt(t,i)&&"body"!==Wt(t)})):[]}(t):[].concat(e),o=[].concat(s,[i]),r=o[0],a=o.reduce((function(e,i){var s=xe(t,i,n);return e.top=Kt(s.top,e.top),e.right=Qt(s.right,e.right),e.bottom=Qt(s.bottom,e.bottom),e.left=Kt(s.left,e.left),e}),xe(t,r,n));return a.width=a.right-a.left,a.height=a.bottom-a.top,a.x=a.left,a.y=a.top,a}(Ft(y)?y:y.contextElement||ie(t.elements.popper),l,h,r),A=Gt(t.elements.reference),E=ke({reference:A,element:v,strategy:"absolute",placement:s}),T=Oe(Object.assign({},v,E)),C=u===Ct?T:A,O={top:w.top-C.top+_.top,bottom:C.bottom-w.bottom+_.bottom,left:w.left-C.left+_.left,right:C.right-w.right+_.right},x=t.modifiersData.offset;if(u===Ct&&x){var k=x[s];Object.keys(O).forEach((function(t){var e=[_t,mt].indexOf(t)>=0?1:-1,i=[gt,mt].indexOf(t)>=0?"y":"x";O[t]+=k[i]*e}))}return O}function De(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,l=i.allowedAutoPlacements,c=void 0===l?kt:l,h=de(n),d=h?a?xt:xt.filter((function(t){return de(t)===h})):yt,u=d.filter((function(t){return c.indexOf(t)>=0}));0===u.length&&(u=d);var f=u.reduce((function(e,i){return e[i]=Le(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[Vt(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}const Se={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name;if(!e.modifiersData[n]._skip){for(var s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0===r||r,l=i.fallbackPlacements,c=i.padding,h=i.boundary,d=i.rootBoundary,u=i.altBoundary,f=i.flipVariations,p=void 0===f||f,g=i.allowedAutoPlacements,m=e.options.placement,_=Vt(m),b=l||(_!==m&&p?function(t){if(Vt(t)===vt)return[];var e=be(t);return[ye(t),e,ye(e)]}(m):[be(m)]),v=[m].concat(b).reduce((function(t,i){return t.concat(Vt(i)===vt?De(e,{placement:i,boundary:h,rootBoundary:d,padding:c,flipVariations:p,allowedAutoPlacements:g}):i)}),[]),y=e.rects.reference,w=e.rects.popper,A=new Map,E=!0,T=v[0],C=0;C=0,D=L?"width":"height",S=Le(e,{placement:O,boundary:h,rootBoundary:d,altBoundary:u,padding:c}),I=L?k?_t:bt:k?mt:gt;y[D]>w[D]&&(I=be(I));var N=be(I),P=[];if(o&&P.push(S[x]<=0),a&&P.push(S[I]<=0,S[N]<=0),P.every((function(t){return t}))){T=O,E=!1;break}A.set(O,P)}if(E)for(var j=function(t){var e=v.find((function(e){var i=A.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return T=e,"break"},M=p?3:1;M>0&&"break"!==j(M);M--);e.placement!==T&&(e.modifiersData[n]._skip=!0,e.placement=T,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function Ie(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function Ne(t){return[gt,_t,mt,bt].some((function(e){return t[e]>=0}))}const Pe={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=Le(e,{elementContext:"reference"}),a=Le(e,{altBoundary:!0}),l=Ie(r,n),c=Ie(a,s,o),h=Ne(l),d=Ne(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},je={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.offset,o=void 0===s?[0,0]:s,r=kt.reduce((function(t,i){return t[i]=function(t,e,i){var n=Vt(t),s=[bt,gt].indexOf(n)>=0?-1:1,o="function"==typeof i?i(Object.assign({},e,{placement:t})):i,r=o[0],a=o[1];return r=r||0,a=(a||0)*s,[bt,_t].indexOf(n)>=0?{x:a,y:r}:{x:r,y:a}}(i,e.rects,o),t}),{}),a=r[e.placement],l=a.x,c=a.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[n]=r}},Me={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=ke({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},He={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0!==r&&r,l=i.boundary,c=i.rootBoundary,h=i.altBoundary,d=i.padding,u=i.tether,f=void 0===u||u,p=i.tetherOffset,g=void 0===p?0:p,m=Le(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:h}),_=Vt(e.placement),b=de(e.placement),v=!b,y=re(_),w="x"===y?"y":"x",A=e.modifiersData.popperOffsets,E=e.rects.reference,T=e.rects.popper,C="function"==typeof g?g(Object.assign({},e.rects,{placement:e.placement})):g,O="number"==typeof C?{mainAxis:C,altAxis:C}:Object.assign({mainAxis:0,altAxis:0},C),x=e.modifiersData.offset?e.modifiersData.offset[e.placement]:null,k={x:0,y:0};if(A){if(o){var L,D="y"===y?gt:bt,S="y"===y?mt:_t,I="y"===y?"height":"width",N=A[y],P=N+m[D],j=N-m[S],M=f?-T[I]/2:0,H=b===wt?E[I]:T[I],$=b===wt?-T[I]:-E[I],W=e.elements.arrow,B=f&&W?Jt(W):{width:0,height:0},F=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},z=F[D],q=F[S],R=ae(0,E[I],B[I]),V=v?E[I]/2-M-R-z-O.mainAxis:H-R-z-O.mainAxis,K=v?-E[I]/2+M+R+q+O.mainAxis:$+R+q+O.mainAxis,Q=e.elements.arrow&&oe(e.elements.arrow),X=Q?"y"===y?Q.clientTop||0:Q.clientLeft||0:0,Y=null!=(L=null==x?void 0:x[y])?L:0,U=N+K-Y,G=ae(f?Qt(P,N+V-Y-X):P,N,f?Kt(j,U):j);A[y]=G,k[y]=G-N}if(a){var J,Z="x"===y?gt:bt,tt="x"===y?mt:_t,et=A[w],it="y"===w?"height":"width",nt=et+m[Z],st=et-m[tt],ot=-1!==[gt,bt].indexOf(_),rt=null!=(J=null==x?void 0:x[w])?J:0,at=ot?nt:et-E[it]-T[it]-rt+O.altAxis,lt=ot?et+E[it]+T[it]-rt-O.altAxis:st,ct=f&&ot?function(t,e,i){var n=ae(t,e,i);return n>i?i:n}(at,et,lt):ae(f?at:nt,et,f?lt:st);A[w]=ct,k[w]=ct-et}e.modifiersData[n]=k}},requiresIfExists:["offset"]};function $e(t,e,i){void 0===i&&(i=!1);var n,s,o=zt(e),r=zt(e)&&function(t){var e=t.getBoundingClientRect(),i=Xt(e.width)/t.offsetWidth||1,n=Xt(e.height)/t.offsetHeight||1;return 1!==i||1!==n}(e),a=ie(e),l=Gt(t,r,i),c={scrollLeft:0,scrollTop:0},h={x:0,y:0};return(o||!o&&!i)&&(("body"!==Wt(e)||Ee(a))&&(c=(n=e)!==Bt(n)&&zt(n)?{scrollLeft:(s=n).scrollLeft,scrollTop:s.scrollTop}:we(n)),zt(e)?((h=Gt(e,!0)).x+=e.clientLeft,h.y+=e.clientTop):a&&(h.x=Ae(a))),{x:l.left+c.scrollLeft-h.x,y:l.top+c.scrollTop-h.y,width:l.width,height:l.height}}function We(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||s(t)})),n}var Be={placement:"bottom",modifiers:[],strategy:"absolute"};function Fe(){for(var t=arguments.length,e=new Array(t),i=0;iNumber.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return(this._inNavbar||"static"===this._config.display)&&(B.setDataAttribute(this._menu,"popper","static"),t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,..."function"==typeof this._config.popperConfig?this._config.popperConfig(t):this._config.popperConfig}}_selectMenuItem({key:t,target:e}){const i=Q.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter((t=>a(t)));i.length&&b(i,e,t===Ye,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=hi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(2===t.button||"keyup"===t.type&&"Tab"!==t.key)return;const e=Q.find(ti);for(const i of e){const e=hi.getInstance(i);if(!e||!1===e._config.autoClose)continue;const n=t.composedPath(),s=n.includes(e._menu);if(n.includes(e._element)||"inside"===e._config.autoClose&&!s||"outside"===e._config.autoClose&&s)continue;if(e._menu.contains(t.target)&&("keyup"===t.type&&"Tab"===t.key||/input|select|option|textarea|form/i.test(t.target.tagName)))continue;const o={relatedTarget:e._element};"click"===t.type&&(o.clickEvent=t),e._completeHide(o)}}static dataApiKeydownHandler(t){const e=/input|textarea/i.test(t.target.tagName),i="Escape"===t.key,n=[Xe,Ye].includes(t.key);if(!n&&!i)return;if(e&&!i)return;t.preventDefault();const s=this.matches(Ze)?this:Q.prev(this,Ze)[0]||Q.next(this,Ze)[0]||Q.findOne(Ze,t.delegateTarget.parentNode),o=hi.getOrCreateInstance(s);if(n)return t.stopPropagation(),o.show(),void o._selectMenuItem(t);o._isShown()&&(t.stopPropagation(),o.hide(),s.focus())}}P.on(document,Ge,Ze,hi.dataApiKeydownHandler),P.on(document,Ge,ei,hi.dataApiKeydownHandler),P.on(document,Ue,hi.clearMenus),P.on(document,"keyup.bs.dropdown.data-api",hi.clearMenus),P.on(document,Ue,Ze,(function(t){t.preventDefault(),hi.getOrCreateInstance(this).toggle()})),g(hi);const di=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",ui=".sticky-top",fi="padding-right",pi="margin-right";class gi{constructor(){this._element=document.body}getWidth(){const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)}hide(){const t=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,fi,(e=>e+t)),this._setElementAttributes(di,fi,(e=>e+t)),this._setElementAttributes(ui,pi,(e=>e-t))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,fi),this._resetElementAttributes(di,fi),this._resetElementAttributes(ui,pi)}isOverflowing(){return this.getWidth()>0}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t).getPropertyValue(e);t.style.setProperty(e,`${i(Number.parseFloat(s))}px`)}))}_saveInitialAttribute(t,e){const i=t.style.getPropertyValue(e);i&&B.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=B.getDataAttribute(t,e);null!==i?(B.removeDataAttribute(t,e),t.style.setProperty(e,i)):t.style.removeProperty(e)}))}_applyManipulationCallback(t,e){if(o(t))e(t);else for(const i of Q.find(t,this._element))e(i)}}const mi="show",_i="mousedown.bs.backdrop",bi={className:"modal-backdrop",clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:"body"},vi={className:"string",clickCallback:"(function|null)",isAnimated:"boolean",isVisible:"boolean",rootElement:"(element|string)"};class yi extends F{constructor(t){super(),this._config=this._getConfig(t),this._isAppended=!1,this._element=null}static get Default(){return bi}static get DefaultType(){return vi}static get NAME(){return"backdrop"}show(t){if(!this._config.isVisible)return void m(t);this._append();const e=this._getElement();this._config.isAnimated&&d(e),e.classList.add(mi),this._emulateAnimation((()=>{m(t)}))}hide(t){this._config.isVisible?(this._getElement().classList.remove(mi),this._emulateAnimation((()=>{this.dispose(),m(t)}))):m(t)}dispose(){this._isAppended&&(P.off(this._element,_i),this._element.remove(),this._isAppended=!1)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_configAfterMerge(t){return t.rootElement=r(t.rootElement),t}_append(){if(this._isAppended)return;const t=this._getElement();this._config.rootElement.append(t),P.on(t,_i,(()=>{m(this._config.clickCallback)})),this._isAppended=!0}_emulateAnimation(t){_(t,this._getElement(),this._config.isAnimated)}}const wi=".bs.focustrap",Ai="backward",Ei={autofocus:!0,trapElement:null},Ti={autofocus:"boolean",trapElement:"element"};class Ci extends F{constructor(t){super(),this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return Ei}static get DefaultType(){return Ti}static get NAME(){return"focustrap"}activate(){this._isActive||(this._config.autofocus&&this._config.trapElement.focus(),P.off(document,wi),P.on(document,"focusin.bs.focustrap",(t=>this._handleFocusin(t))),P.on(document,"keydown.tab.bs.focustrap",(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,P.off(document,wi))}_handleFocusin(t){const{trapElement:e}=this._config;if(t.target===document||t.target===e||e.contains(t.target))return;const i=Q.focusableChildren(e);0===i.length?e.focus():this._lastTabNavDirection===Ai?i[i.length-1].focus():i[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?Ai:"forward")}}const Oi="hidden.bs.modal",xi="show.bs.modal",ki="modal-open",Li="show",Di="modal-static",Si={backdrop:!0,focus:!0,keyboard:!0},Ii={backdrop:"(boolean|string)",focus:"boolean",keyboard:"boolean"};class Ni extends z{constructor(t,e){super(t,e),this._dialog=Q.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new gi,this._addEventListeners()}static get Default(){return Si}static get DefaultType(){return Ii}static get NAME(){return"modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||P.trigger(this._element,xi,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(ki),this._adjustDialog(),this._backdrop.show((()=>this._showElement(t))))}hide(){this._isShown&&!this._isTransitioning&&(P.trigger(this._element,"hide.bs.modal").defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(Li),this._queueCallback((()=>this._hideModal()),this._element,this._isAnimated())))}dispose(){for(const t of[window,this._dialog])P.off(t,".bs.modal");this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new yi({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new Ci({trapElement:this._element})}_showElement(t){document.body.contains(this._element)||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0;const e=Q.findOne(".modal-body",this._dialog);e&&(e.scrollTop=0),d(this._element),this._element.classList.add(Li),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,P.trigger(this._element,"shown.bs.modal",{relatedTarget:t})}),this._dialog,this._isAnimated())}_addEventListeners(){P.on(this._element,"keydown.dismiss.bs.modal",(t=>{if("Escape"===t.key)return this._config.keyboard?(t.preventDefault(),void this.hide()):void this._triggerBackdropTransition()})),P.on(window,"resize.bs.modal",(()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()})),P.on(this._element,"mousedown.dismiss.bs.modal",(t=>{P.one(this._element,"click.dismiss.bs.modal",(e=>{this._element===t.target&&this._element===e.target&&("static"!==this._config.backdrop?this._config.backdrop&&this.hide():this._triggerBackdropTransition())}))}))}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(ki),this._resetAdjustments(),this._scrollBar.reset(),P.trigger(this._element,Oi)}))}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(P.trigger(this._element,"hidePrevented.bs.modal").defaultPrevented)return;const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._element.style.overflowY;"hidden"===e||this._element.classList.contains(Di)||(t||(this._element.style.overflowY="hidden"),this._element.classList.add(Di),this._queueCallback((()=>{this._element.classList.remove(Di),this._queueCallback((()=>{this._element.style.overflowY=e}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;if(i&&!t){const t=p()?"paddingLeft":"paddingRight";this._element.style[t]=`${e}px`}if(!i&&t){const t=p()?"paddingRight":"paddingLeft";this._element.style[t]=`${e}px`}}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=Ni.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}P.on(document,"click.bs.modal.data-api",'[data-bs-toggle="modal"]',(function(t){const e=n(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),P.one(e,xi,(t=>{t.defaultPrevented||P.one(e,Oi,(()=>{a(this)&&this.focus()}))}));const i=Q.findOne(".modal.show");i&&Ni.getInstance(i).hide(),Ni.getOrCreateInstance(e).toggle(this)})),q(Ni),g(Ni);const Pi="show",ji="showing",Mi="hiding",Hi=".offcanvas.show",$i="hidePrevented.bs.offcanvas",Wi="hidden.bs.offcanvas",Bi={backdrop:!0,keyboard:!0,scroll:!1},Fi={backdrop:"(boolean|string)",keyboard:"boolean",scroll:"boolean"};class zi extends z{constructor(t,e){super(t,e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return Bi}static get DefaultType(){return Fi}static get NAME(){return"offcanvas"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||P.trigger(this._element,"show.bs.offcanvas",{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._backdrop.show(),this._config.scroll||(new gi).hide(),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add(ji),this._queueCallback((()=>{this._config.scroll&&!this._config.backdrop||this._focustrap.activate(),this._element.classList.add(Pi),this._element.classList.remove(ji),P.trigger(this._element,"shown.bs.offcanvas",{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(P.trigger(this._element,"hide.bs.offcanvas").defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add(Mi),this._backdrop.hide(),this._queueCallback((()=>{this._element.classList.remove(Pi,Mi),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._config.scroll||(new gi).reset(),P.trigger(this._element,Wi)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){const t=Boolean(this._config.backdrop);return new yi({className:"offcanvas-backdrop",isVisible:t,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:t?()=>{"static"!==this._config.backdrop?this.hide():P.trigger(this._element,$i)}:null})}_initializeFocusTrap(){return new Ci({trapElement:this._element})}_addEventListeners(){P.on(this._element,"keydown.dismiss.bs.offcanvas",(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():P.trigger(this._element,$i))}))}static jQueryInterface(t){return this.each((function(){const e=zi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}P.on(document,"click.bs.offcanvas.data-api",'[data-bs-toggle="offcanvas"]',(function(t){const e=n(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),l(this))return;P.one(e,Wi,(()=>{a(this)&&this.focus()}));const i=Q.findOne(Hi);i&&i!==e&&zi.getInstance(i).hide(),zi.getOrCreateInstance(e).toggle(this)})),P.on(window,"load.bs.offcanvas.data-api",(()=>{for(const t of Q.find(Hi))zi.getOrCreateInstance(t).show()})),P.on(window,"resize.bs.offcanvas",(()=>{for(const t of Q.find("[aria-modal][class*=show][class*=offcanvas-]"))"fixed"!==getComputedStyle(t).position&&zi.getOrCreateInstance(t).hide()})),q(zi),g(zi);const qi=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Ri=/^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i,Vi=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i,Ki=(t,e)=>{const i=t.nodeName.toLowerCase();return e.includes(i)?!qi.has(i)||Boolean(Ri.test(t.nodeValue)||Vi.test(t.nodeValue)):e.filter((t=>t instanceof RegExp)).some((t=>t.test(i)))},Qi={"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},Xi={allowList:Qi,content:{},extraClass:"",html:!1,sanitize:!0,sanitizeFn:null,template:"
"},Yi={allowList:"object",content:"object",extraClass:"(string|function)",html:"boolean",sanitize:"boolean",sanitizeFn:"(null|function)",template:"string"},Ui={entry:"(string|element|function|null)",selector:"(string|element)"};class Gi extends F{constructor(t){super(),this._config=this._getConfig(t)}static get Default(){return Xi}static get DefaultType(){return Yi}static get NAME(){return"TemplateFactory"}getContent(){return Object.values(this._config.content).map((t=>this._resolvePossibleFunction(t))).filter(Boolean)}hasContent(){return this.getContent().length>0}changeContent(t){return this._checkContent(t),this._config.content={...this._config.content,...t},this}toHtml(){const t=document.createElement("div");t.innerHTML=this._maybeSanitize(this._config.template);for(const[e,i]of Object.entries(this._config.content))this._setContent(t,i,e);const e=t.children[0],i=this._resolvePossibleFunction(this._config.extraClass);return i&&e.classList.add(...i.split(" ")),e}_typeCheckConfig(t){super._typeCheckConfig(t),this._checkContent(t.content)}_checkContent(t){for(const[e,i]of Object.entries(t))super._typeCheckConfig({selector:e,entry:i},Ui)}_setContent(t,e,i){const n=Q.findOne(i,t);n&&((e=this._resolvePossibleFunction(e))?o(e)?this._putElementInTemplate(r(e),n):this._config.html?n.innerHTML=this._maybeSanitize(e):n.textContent=e:n.remove())}_maybeSanitize(t){return this._config.sanitize?function(t,e,i){if(!t.length)return t;if(i&&"function"==typeof i)return i(t);const n=(new window.DOMParser).parseFromString(t,"text/html"),s=[].concat(...n.body.querySelectorAll("*"));for(const t of s){const i=t.nodeName.toLowerCase();if(!Object.keys(e).includes(i)){t.remove();continue}const n=[].concat(...t.attributes),s=[].concat(e["*"]||[],e[i]||[]);for(const e of n)Ki(e,s)||t.removeAttribute(e.nodeName)}return n.body.innerHTML}(t,this._config.allowList,this._config.sanitizeFn):t}_resolvePossibleFunction(t){return"function"==typeof t?t(this):t}_putElementInTemplate(t,e){if(this._config.html)return e.innerHTML="",void e.append(t);e.textContent=t.textContent}}const Ji=new Set(["sanitize","allowList","sanitizeFn"]),Zi="fade",tn="show",en=".modal",nn="hide.bs.modal",sn="hover",on="focus",rn={AUTO:"auto",TOP:"top",RIGHT:p()?"left":"right",BOTTOM:"bottom",LEFT:p()?"right":"left"},an={allowList:Qi,animation:!0,boundary:"clippingParents",container:!1,customClass:"",delay:0,fallbackPlacements:["top","right","bottom","left"],html:!1,offset:[0,0],placement:"top",popperConfig:null,sanitize:!0,sanitizeFn:null,selector:!1,template:'',title:"",trigger:"hover focus"},ln={allowList:"object",animation:"boolean",boundary:"(string|element)",container:"(string|element|boolean)",customClass:"(string|function)",delay:"(number|object)",fallbackPlacements:"array",html:"boolean",offset:"(array|string|function)",placement:"(string|function)",popperConfig:"(null|object|function)",sanitize:"boolean",sanitizeFn:"(null|function)",selector:"(string|boolean)",template:"string",title:"(string|element|function)",trigger:"string"};class cn extends z{constructor(t,e){if(void 0===Ke)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t,e),this._isEnabled=!0,this._timeout=0,this._isHovered=null,this._activeTrigger={},this._popper=null,this._templateFactory=null,this._newContent=null,this.tip=null,this._setListeners(),this._config.selector||this._fixTitle()}static get Default(){return an}static get DefaultType(){return ln}static get NAME(){return"tooltip"}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(){this._isEnabled&&(this._activeTrigger.click=!this._activeTrigger.click,this._isShown()?this._leave():this._enter())}dispose(){clearTimeout(this._timeout),P.off(this._element.closest(en),nn,this._hideModalHandler),this.tip&&this.tip.remove(),this._element.getAttribute("data-bs-original-title")&&this._element.setAttribute("title",this._element.getAttribute("data-bs-original-title")),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this._isWithContent()||!this._isEnabled)return;const t=P.trigger(this._element,this.constructor.eventName("show")),e=(c(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(t.defaultPrevented||!e)return;this.tip&&(this.tip.remove(),this.tip=null);const i=this._getTipElement();this._element.setAttribute("aria-describedby",i.getAttribute("id"));const{container:n}=this._config;if(this._element.ownerDocument.documentElement.contains(this.tip)||(n.append(i),P.trigger(this._element,this.constructor.eventName("inserted"))),this._popper?this._popper.update():this._popper=this._createPopper(i),i.classList.add(tn),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))P.on(t,"mouseover",h);this._queueCallback((()=>{P.trigger(this._element,this.constructor.eventName("shown")),!1===this._isHovered&&this._leave(),this._isHovered=!1}),this.tip,this._isAnimated())}hide(){if(!this._isShown())return;if(P.trigger(this._element,this.constructor.eventName("hide")).defaultPrevented)return;const t=this._getTipElement();if(t.classList.remove(tn),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))P.off(t,"mouseover",h);this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1,this._isHovered=null,this._queueCallback((()=>{this._isWithActiveTrigger()||(this._isHovered||t.remove(),this._element.removeAttribute("aria-describedby"),P.trigger(this._element,this.constructor.eventName("hidden")),this._disposePopper())}),this.tip,this._isAnimated())}update(){this._popper&&this._popper.update()}_isWithContent(){return Boolean(this._getTitle())}_getTipElement(){return this.tip||(this.tip=this._createTipElement(this._newContent||this._getContentForTemplate())),this.tip}_createTipElement(t){const e=this._getTemplateFactory(t).toHtml();if(!e)return null;e.classList.remove(Zi,tn),e.classList.add(`bs-${this.constructor.NAME}-auto`);const i=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME).toString();return e.setAttribute("id",i),this._isAnimated()&&e.classList.add(Zi),e}setContent(t){this._newContent=t,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(t){return this._templateFactory?this._templateFactory.changeContent(t):this._templateFactory=new Gi({...this._config,content:t,extraClass:this._resolvePossibleFunction(this._config.customClass)}),this._templateFactory}_getContentForTemplate(){return{".tooltip-inner":this._getTitle()}}_getTitle(){return this._resolvePossibleFunction(this._config.title)||this._element.getAttribute("data-bs-original-title")}_initializeOnDelegatedTarget(t){return this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_isAnimated(){return this._config.animation||this.tip&&this.tip.classList.contains(Zi)}_isShown(){return this.tip&&this.tip.classList.contains(tn)}_createPopper(t){const e="function"==typeof this._config.placement?this._config.placement.call(this,t,this._element):this._config.placement,i=rn[e.toUpperCase()];return Ve(this._element,t,this._getPopperConfig(i))}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return"function"==typeof t?t.call(this._element):t}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"preSetPlacement",enabled:!0,phase:"beforeMain",fn:t=>{this._getTipElement().setAttribute("data-popper-placement",t.state.placement)}}]};return{...e,..."function"==typeof this._config.popperConfig?this._config.popperConfig(e):this._config.popperConfig}}_setListeners(){const t=this._config.trigger.split(" ");for(const e of t)if("click"===e)P.on(this._element,this.constructor.eventName("click"),this._config.selector,(t=>{this._initializeOnDelegatedTarget(t).toggle()}));else if("manual"!==e){const t=e===sn?this.constructor.eventName("mouseenter"):this.constructor.eventName("focusin"),i=e===sn?this.constructor.eventName("mouseleave"):this.constructor.eventName("focusout");P.on(this._element,t,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusin"===t.type?on:sn]=!0,e._enter()})),P.on(this._element,i,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusout"===t.type?on:sn]=e._element.contains(t.relatedTarget),e._leave()}))}this._hideModalHandler=()=>{this._element&&this.hide()},P.on(this._element.closest(en),nn,this._hideModalHandler)}_fixTitle(){const t=this._element.getAttribute("title");t&&(this._element.getAttribute("aria-label")||this._element.textContent.trim()||this._element.setAttribute("aria-label",t),this._element.setAttribute("data-bs-original-title",t),this._element.removeAttribute("title"))}_enter(){this._isShown()||this._isHovered?this._isHovered=!0:(this._isHovered=!0,this._setTimeout((()=>{this._isHovered&&this.show()}),this._config.delay.show))}_leave(){this._isWithActiveTrigger()||(this._isHovered=!1,this._setTimeout((()=>{this._isHovered||this.hide()}),this._config.delay.hide))}_setTimeout(t,e){clearTimeout(this._timeout),this._timeout=setTimeout(t,e)}_isWithActiveTrigger(){return Object.values(this._activeTrigger).includes(!0)}_getConfig(t){const e=B.getDataAttributes(this._element);for(const t of Object.keys(e))Ji.has(t)&&delete e[t];return t={...e,..."object"==typeof t&&t?t:{}},t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t.container=!1===t.container?document.body:r(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),t}_getDelegateConfig(){const t={};for(const e in this._config)this.constructor.Default[e]!==this._config[e]&&(t[e]=this._config[e]);return t.selector=!1,t.trigger="manual",t}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null)}static jQueryInterface(t){return this.each((function(){const e=cn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}g(cn);const hn={...cn.Default,content:"",offset:[0,8],placement:"right",template:'',trigger:"click"},dn={...cn.DefaultType,content:"(null|string|element|function)"};class un extends cn{static get Default(){return hn}static get DefaultType(){return dn}static get NAME(){return"popover"}_isWithContent(){return this._getTitle()||this._getContent()}_getContentForTemplate(){return{".popover-header":this._getTitle(),".popover-body":this._getContent()}}_getContent(){return this._resolvePossibleFunction(this._config.content)}static jQueryInterface(t){return this.each((function(){const e=un.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}g(un);const fn="click.bs.scrollspy",pn="active",gn="[href]",mn={offset:null,rootMargin:"0px 0px -25%",smoothScroll:!1,target:null,threshold:[.1,.5,1]},_n={offset:"(number|null)",rootMargin:"string",smoothScroll:"boolean",target:"element",threshold:"array"};class bn extends z{constructor(t,e){super(t,e),this._targetLinks=new Map,this._observableSections=new Map,this._rootElement="visible"===getComputedStyle(this._element).overflowY?null:this._element,this._activeTarget=null,this._observer=null,this._previousScrollData={visibleEntryTop:0,parentScrollTop:0},this.refresh()}static get Default(){return mn}static get DefaultType(){return _n}static get NAME(){return"scrollspy"}refresh(){this._initializeTargetsAndObservables(),this._maybeEnableSmoothScroll(),this._observer?this._observer.disconnect():this._observer=this._getNewObserver();for(const t of this._observableSections.values())this._observer.observe(t)}dispose(){this._observer.disconnect(),super.dispose()}_configAfterMerge(t){return t.target=r(t.target)||document.body,t.rootMargin=t.offset?`${t.offset}px 0px -30%`:t.rootMargin,"string"==typeof t.threshold&&(t.threshold=t.threshold.split(",").map((t=>Number.parseFloat(t)))),t}_maybeEnableSmoothScroll(){this._config.smoothScroll&&(P.off(this._config.target,fn),P.on(this._config.target,fn,gn,(t=>{const e=this._observableSections.get(t.target.hash);if(e){t.preventDefault();const i=this._rootElement||window,n=e.offsetTop-this._element.offsetTop;if(i.scrollTo)return void i.scrollTo({top:n,behavior:"smooth"});i.scrollTop=n}})))}_getNewObserver(){const t={root:this._rootElement,threshold:this._config.threshold,rootMargin:this._config.rootMargin};return new IntersectionObserver((t=>this._observerCallback(t)),t)}_observerCallback(t){const e=t=>this._targetLinks.get(`#${t.target.id}`),i=t=>{this._previousScrollData.visibleEntryTop=t.target.offsetTop,this._process(e(t))},n=(this._rootElement||document.documentElement).scrollTop,s=n>=this._previousScrollData.parentScrollTop;this._previousScrollData.parentScrollTop=n;for(const o of t){if(!o.isIntersecting){this._activeTarget=null,this._clearActiveClass(e(o));continue}const t=o.target.offsetTop>=this._previousScrollData.visibleEntryTop;if(s&&t){if(i(o),!n)return}else s||t||i(o)}}_initializeTargetsAndObservables(){this._targetLinks=new Map,this._observableSections=new Map;const t=Q.find(gn,this._config.target);for(const e of t){if(!e.hash||l(e))continue;const t=Q.findOne(e.hash,this._element);a(t)&&(this._targetLinks.set(e.hash,e),this._observableSections.set(e.hash,t))}}_process(t){this._activeTarget!==t&&(this._clearActiveClass(this._config.target),this._activeTarget=t,t.classList.add(pn),this._activateParents(t),P.trigger(this._element,"activate.bs.scrollspy",{relatedTarget:t}))}_activateParents(t){if(t.classList.contains("dropdown-item"))Q.findOne(".dropdown-toggle",t.closest(".dropdown")).classList.add(pn);else for(const e of Q.parents(t,".nav, .list-group"))for(const t of Q.prev(e,".nav-link, .nav-item > .nav-link, .list-group-item"))t.classList.add(pn)}_clearActiveClass(t){t.classList.remove(pn);const e=Q.find("[href].active",t);for(const t of e)t.classList.remove(pn)}static jQueryInterface(t){return this.each((function(){const e=bn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}P.on(window,"load.bs.scrollspy.data-api",(()=>{for(const t of Q.find('[data-bs-spy="scroll"]'))bn.getOrCreateInstance(t)})),g(bn);const vn="ArrowLeft",yn="ArrowRight",wn="ArrowUp",An="ArrowDown",En="active",Tn="fade",Cn="show",On='[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',xn=`.nav-link:not(.dropdown-toggle), .list-group-item:not(.dropdown-toggle), [role="tab"]:not(.dropdown-toggle), ${On}`;class kn extends z{constructor(t){super(t),this._parent=this._element.closest('.list-group, .nav, [role="tablist"]'),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),P.on(this._element,"keydown.bs.tab",(t=>this._keydown(t))))}static get NAME(){return"tab"}show(){const t=this._element;if(this._elemIsActive(t))return;const e=this._getActiveElem(),i=e?P.trigger(e,"hide.bs.tab",{relatedTarget:t}):null;P.trigger(t,"show.bs.tab",{relatedTarget:e}).defaultPrevented||i&&i.defaultPrevented||(this._deactivate(e,t),this._activate(t,e))}_activate(t,e){t&&(t.classList.add(En),this._activate(n(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.removeAttribute("tabindex"),t.setAttribute("aria-selected",!0),this._toggleDropDown(t,!0),P.trigger(t,"shown.bs.tab",{relatedTarget:e})):t.classList.add(Cn)}),t,t.classList.contains(Tn)))}_deactivate(t,e){t&&(t.classList.remove(En),t.blur(),this._deactivate(n(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.setAttribute("aria-selected",!1),t.setAttribute("tabindex","-1"),this._toggleDropDown(t,!1),P.trigger(t,"hidden.bs.tab",{relatedTarget:e})):t.classList.remove(Cn)}),t,t.classList.contains(Tn)))}_keydown(t){if(![vn,yn,wn,An].includes(t.key))return;t.stopPropagation(),t.preventDefault();const e=[yn,An].includes(t.key),i=b(this._getChildren().filter((t=>!l(t))),t.target,e,!0);i&&(i.focus({preventScroll:!0}),kn.getOrCreateInstance(i).show())}_getChildren(){return Q.find(xn,this._parent)}_getActiveElem(){return this._getChildren().find((t=>this._elemIsActive(t)))||null}_setInitialAttributes(t,e){this._setAttributeIfNotExists(t,"role","tablist");for(const t of e)this._setInitialAttributesOnChild(t)}_setInitialAttributesOnChild(t){t=this._getInnerElement(t);const e=this._elemIsActive(t),i=this._getOuterElement(t);t.setAttribute("aria-selected",e),i!==t&&this._setAttributeIfNotExists(i,"role","presentation"),e||t.setAttribute("tabindex","-1"),this._setAttributeIfNotExists(t,"role","tab"),this._setInitialAttributesOnTargetPanel(t)}_setInitialAttributesOnTargetPanel(t){const e=n(t);e&&(this._setAttributeIfNotExists(e,"role","tabpanel"),t.id&&this._setAttributeIfNotExists(e,"aria-labelledby",`#${t.id}`))}_toggleDropDown(t,e){const i=this._getOuterElement(t);if(!i.classList.contains("dropdown"))return;const n=(t,n)=>{const s=Q.findOne(t,i);s&&s.classList.toggle(n,e)};n(".dropdown-toggle",En),n(".dropdown-menu",Cn),i.setAttribute("aria-expanded",e)}_setAttributeIfNotExists(t,e,i){t.hasAttribute(e)||t.setAttribute(e,i)}_elemIsActive(t){return t.classList.contains(En)}_getInnerElement(t){return t.matches(xn)?t:Q.findOne(xn,t)}_getOuterElement(t){return t.closest(".nav-item, .list-group-item")||t}static jQueryInterface(t){return this.each((function(){const e=kn.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}P.on(document,"click.bs.tab",On,(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),l(this)||kn.getOrCreateInstance(this).show()})),P.on(window,"load.bs.tab",(()=>{for(const t of Q.find('.active[data-bs-toggle="tab"], .active[data-bs-toggle="pill"], .active[data-bs-toggle="list"]'))kn.getOrCreateInstance(t)})),g(kn);const Ln="hide",Dn="show",Sn="showing",In={animation:"boolean",autohide:"boolean",delay:"number"},Nn={animation:!0,autohide:!0,delay:5e3};class Pn extends z{constructor(t,e){super(t,e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return Nn}static get DefaultType(){return In}static get NAME(){return"toast"}show(){P.trigger(this._element,"show.bs.toast").defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(Ln),d(this._element),this._element.classList.add(Dn,Sn),this._queueCallback((()=>{this._element.classList.remove(Sn),P.trigger(this._element,"shown.bs.toast"),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this.isShown()&&(P.trigger(this._element,"hide.bs.toast").defaultPrevented||(this._element.classList.add(Sn),this._queueCallback((()=>{this._element.classList.add(Ln),this._element.classList.remove(Sn,Dn),P.trigger(this._element,"hidden.bs.toast")}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove(Dn),super.dispose()}isShown(){return this._element.classList.contains(Dn)}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){P.on(this._element,"mouseover.bs.toast",(t=>this._onInteraction(t,!0))),P.on(this._element,"mouseout.bs.toast",(t=>this._onInteraction(t,!1))),P.on(this._element,"focusin.bs.toast",(t=>this._onInteraction(t,!0))),P.on(this._element,"focusout.bs.toast",(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=Pn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return q(Pn),g(Pn),{Alert:R,Button:K,Carousel:at,Collapse:pt,Dropdown:hi,Modal:Ni,Offcanvas:zi,Popover:un,ScrollSpy:bn,Tab:kn,Toast:Pn,Tooltip:cn}})); -//# sourceMappingURL=bootstrap.bundle.min.js.map \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/node_modules/glyphicons-only-bootstrap/css/bootstrap.css b/src/Rules.Framework.WebUI/node_modules/glyphicons-only-bootstrap/css/bootstrap.css deleted file mode 100644 index 133e93ed..00000000 --- a/src/Rules.Framework.WebUI/node_modules/glyphicons-only-bootstrap/css/bootstrap.css +++ /dev/null @@ -1,819 +0,0 @@ -/*! - * Bootstrap v3.3.7 (http://getbootstrap.com) - * Copyright 2011-2016 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */ - -/*! - * Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=76290a4e23bf2c3f61a4e17625be4627) - * Config saved to config.json and https://gist.github.com/76290a4e23bf2c3f61a4e17625be4627 - */ -/*! - * Bootstrap v3.3.7 (http://getbootstrap.com) - * Copyright 2011-2016 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */ -/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ -@font-face { - font-family: 'Glyphicons Halflings'; - src: url('../fonts/glyphicons-halflings-regular.eot'); - src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); -} -.glyphicon { - position: relative; - top: 1px; - display: inline-block; - font-family: 'Glyphicons Halflings'; - font-style: normal; - font-weight: normal; - line-height: 1; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} -.glyphicon-asterisk:before { - content: "\002a"; -} -.glyphicon-plus:before { - content: "\002b"; -} -.glyphicon-euro:before, -.glyphicon-eur:before { - content: "\20ac"; -} -.glyphicon-minus:before { - content: "\2212"; -} -.glyphicon-cloud:before { - content: "\2601"; -} -.glyphicon-envelope:before { - content: "\2709"; -} -.glyphicon-pencil:before { - content: "\270f"; -} -.glyphicon-glass:before { - content: "\e001"; -} -.glyphicon-music:before { - content: "\e002"; -} -.glyphicon-search:before { - content: "\e003"; -} -.glyphicon-heart:before { - content: "\e005"; -} -.glyphicon-star:before { - content: "\e006"; -} -.glyphicon-star-empty:before { - content: "\e007"; -} -.glyphicon-user:before { - content: "\e008"; -} -.glyphicon-film:before { - content: "\e009"; -} -.glyphicon-th-large:before { - content: "\e010"; -} -.glyphicon-th:before { - content: "\e011"; -} -.glyphicon-th-list:before { - content: "\e012"; -} -.glyphicon-ok:before { - content: "\e013"; -} -.glyphicon-remove:before { - content: "\e014"; -} -.glyphicon-zoom-in:before { - content: "\e015"; -} -.glyphicon-zoom-out:before { - content: "\e016"; -} -.glyphicon-off:before { - content: "\e017"; -} -.glyphicon-signal:before { - content: "\e018"; -} -.glyphicon-cog:before { - content: "\e019"; -} -.glyphicon-trash:before { - content: "\e020"; -} -.glyphicon-home:before { - content: "\e021"; -} -.glyphicon-file:before { - content: "\e022"; -} -.glyphicon-time:before { - content: "\e023"; -} -.glyphicon-road:before { - content: "\e024"; -} -.glyphicon-download-alt:before { - content: "\e025"; -} -.glyphicon-download:before { - content: "\e026"; -} -.glyphicon-upload:before { - content: "\e027"; -} -.glyphicon-inbox:before { - content: "\e028"; -} -.glyphicon-play-circle:before { - content: "\e029"; -} -.glyphicon-repeat:before { - content: "\e030"; -} -.glyphicon-refresh:before { - content: "\e031"; -} -.glyphicon-list-alt:before { - content: "\e032"; -} -.glyphicon-lock:before { - content: "\e033"; -} -.glyphicon-flag:before { - content: "\e034"; -} -.glyphicon-headphones:before { - content: "\e035"; -} -.glyphicon-volume-off:before { - content: "\e036"; -} -.glyphicon-volume-down:before { - content: "\e037"; -} -.glyphicon-volume-up:before { - content: "\e038"; -} -.glyphicon-qrcode:before { - content: "\e039"; -} -.glyphicon-barcode:before { - content: "\e040"; -} -.glyphicon-tag:before { - content: "\e041"; -} -.glyphicon-tags:before { - content: "\e042"; -} -.glyphicon-book:before { - content: "\e043"; -} -.glyphicon-bookmark:before { - content: "\e044"; -} -.glyphicon-print:before { - content: "\e045"; -} -.glyphicon-camera:before { - content: "\e046"; -} -.glyphicon-font:before { - content: "\e047"; -} -.glyphicon-bold:before { - content: "\e048"; -} -.glyphicon-italic:before { - content: "\e049"; -} -.glyphicon-text-height:before { - content: "\e050"; -} -.glyphicon-text-width:before { - content: "\e051"; -} -.glyphicon-align-left:before { - content: "\e052"; -} -.glyphicon-align-center:before { - content: "\e053"; -} -.glyphicon-align-right:before { - content: "\e054"; -} -.glyphicon-align-justify:before { - content: "\e055"; -} -.glyphicon-list:before { - content: "\e056"; -} -.glyphicon-indent-left:before { - content: "\e057"; -} -.glyphicon-indent-right:before { - content: "\e058"; -} -.glyphicon-facetime-video:before { - content: "\e059"; -} -.glyphicon-picture:before { - content: "\e060"; -} -.glyphicon-map-marker:before { - content: "\e062"; -} -.glyphicon-adjust:before { - content: "\e063"; -} -.glyphicon-tint:before { - content: "\e064"; -} -.glyphicon-edit:before { - content: "\e065"; -} -.glyphicon-share:before { - content: "\e066"; -} -.glyphicon-check:before { - content: "\e067"; -} -.glyphicon-move:before { - content: "\e068"; -} -.glyphicon-step-backward:before { - content: "\e069"; -} -.glyphicon-fast-backward:before { - content: "\e070"; -} -.glyphicon-backward:before { - content: "\e071"; -} -.glyphicon-play:before { - content: "\e072"; -} -.glyphicon-pause:before { - content: "\e073"; -} -.glyphicon-stop:before { - content: "\e074"; -} -.glyphicon-forward:before { - content: "\e075"; -} -.glyphicon-fast-forward:before { - content: "\e076"; -} -.glyphicon-step-forward:before { - content: "\e077"; -} -.glyphicon-eject:before { - content: "\e078"; -} -.glyphicon-chevron-left:before { - content: "\e079"; -} -.glyphicon-chevron-right:before { - content: "\e080"; -} -.glyphicon-plus-sign:before { - content: "\e081"; -} -.glyphicon-minus-sign:before { - content: "\e082"; -} -.glyphicon-remove-sign:before { - content: "\e083"; -} -.glyphicon-ok-sign:before { - content: "\e084"; -} -.glyphicon-question-sign:before { - content: "\e085"; -} -.glyphicon-info-sign:before { - content: "\e086"; -} -.glyphicon-screenshot:before { - content: "\e087"; -} -.glyphicon-remove-circle:before { - content: "\e088"; -} -.glyphicon-ok-circle:before { - content: "\e089"; -} -.glyphicon-ban-circle:before { - content: "\e090"; -} -.glyphicon-arrow-left:before { - content: "\e091"; -} -.glyphicon-arrow-right:before { - content: "\e092"; -} -.glyphicon-arrow-up:before { - content: "\e093"; -} -.glyphicon-arrow-down:before { - content: "\e094"; -} -.glyphicon-share-alt:before { - content: "\e095"; -} -.glyphicon-resize-full:before { - content: "\e096"; -} -.glyphicon-resize-small:before { - content: "\e097"; -} -.glyphicon-exclamation-sign:before { - content: "\e101"; -} -.glyphicon-gift:before { - content: "\e102"; -} -.glyphicon-leaf:before { - content: "\e103"; -} -.glyphicon-fire:before { - content: "\e104"; -} -.glyphicon-eye-open:before { - content: "\e105"; -} -.glyphicon-eye-close:before { - content: "\e106"; -} -.glyphicon-warning-sign:before { - content: "\e107"; -} -.glyphicon-plane:before { - content: "\e108"; -} -.glyphicon-calendar:before { - content: "\e109"; -} -.glyphicon-random:before { - content: "\e110"; -} -.glyphicon-comment:before { - content: "\e111"; -} -.glyphicon-magnet:before { - content: "\e112"; -} -.glyphicon-chevron-up:before { - content: "\e113"; -} -.glyphicon-chevron-down:before { - content: "\e114"; -} -.glyphicon-retweet:before { - content: "\e115"; -} -.glyphicon-shopping-cart:before { - content: "\e116"; -} -.glyphicon-folder-close:before { - content: "\e117"; -} -.glyphicon-folder-open:before { - content: "\e118"; -} -.glyphicon-resize-vertical:before { - content: "\e119"; -} -.glyphicon-resize-horizontal:before { - content: "\e120"; -} -.glyphicon-hdd:before { - content: "\e121"; -} -.glyphicon-bullhorn:before { - content: "\e122"; -} -.glyphicon-bell:before { - content: "\e123"; -} -.glyphicon-certificate:before { - content: "\e124"; -} -.glyphicon-thumbs-up:before { - content: "\e125"; -} -.glyphicon-thumbs-down:before { - content: "\e126"; -} -.glyphicon-hand-right:before { - content: "\e127"; -} -.glyphicon-hand-left:before { - content: "\e128"; -} -.glyphicon-hand-up:before { - content: "\e129"; -} -.glyphicon-hand-down:before { - content: "\e130"; -} -.glyphicon-circle-arrow-right:before { - content: "\e131"; -} -.glyphicon-circle-arrow-left:before { - content: "\e132"; -} -.glyphicon-circle-arrow-up:before { - content: "\e133"; -} -.glyphicon-circle-arrow-down:before { - content: "\e134"; -} -.glyphicon-globe:before { - content: "\e135"; -} -.glyphicon-wrench:before { - content: "\e136"; -} -.glyphicon-tasks:before { - content: "\e137"; -} -.glyphicon-filter:before { - content: "\e138"; -} -.glyphicon-briefcase:before { - content: "\e139"; -} -.glyphicon-fullscreen:before { - content: "\e140"; -} -.glyphicon-dashboard:before { - content: "\e141"; -} -.glyphicon-paperclip:before { - content: "\e142"; -} -.glyphicon-heart-empty:before { - content: "\e143"; -} -.glyphicon-link:before { - content: "\e144"; -} -.glyphicon-phone:before { - content: "\e145"; -} -.glyphicon-pushpin:before { - content: "\e146"; -} -.glyphicon-usd:before { - content: "\e148"; -} -.glyphicon-gbp:before { - content: "\e149"; -} -.glyphicon-sort:before { - content: "\e150"; -} -.glyphicon-sort-by-alphabet:before { - content: "\e151"; -} -.glyphicon-sort-by-alphabet-alt:before { - content: "\e152"; -} -.glyphicon-sort-by-order:before { - content: "\e153"; -} -.glyphicon-sort-by-order-alt:before { - content: "\e154"; -} -.glyphicon-sort-by-attributes:before { - content: "\e155"; -} -.glyphicon-sort-by-attributes-alt:before { - content: "\e156"; -} -.glyphicon-unchecked:before { - content: "\e157"; -} -.glyphicon-expand:before { - content: "\e158"; -} -.glyphicon-collapse-down:before { - content: "\e159"; -} -.glyphicon-collapse-up:before { - content: "\e160"; -} -.glyphicon-log-in:before { - content: "\e161"; -} -.glyphicon-flash:before { - content: "\e162"; -} -.glyphicon-log-out:before { - content: "\e163"; -} -.glyphicon-new-window:before { - content: "\e164"; -} -.glyphicon-record:before { - content: "\e165"; -} -.glyphicon-save:before { - content: "\e166"; -} -.glyphicon-open:before { - content: "\e167"; -} -.glyphicon-saved:before { - content: "\e168"; -} -.glyphicon-import:before { - content: "\e169"; -} -.glyphicon-export:before { - content: "\e170"; -} -.glyphicon-send:before { - content: "\e171"; -} -.glyphicon-floppy-disk:before { - content: "\e172"; -} -.glyphicon-floppy-saved:before { - content: "\e173"; -} -.glyphicon-floppy-remove:before { - content: "\e174"; -} -.glyphicon-floppy-save:before { - content: "\e175"; -} -.glyphicon-floppy-open:before { - content: "\e176"; -} -.glyphicon-credit-card:before { - content: "\e177"; -} -.glyphicon-transfer:before { - content: "\e178"; -} -.glyphicon-cutlery:before { - content: "\e179"; -} -.glyphicon-header:before { - content: "\e180"; -} -.glyphicon-compressed:before { - content: "\e181"; -} -.glyphicon-earphone:before { - content: "\e182"; -} -.glyphicon-phone-alt:before { - content: "\e183"; -} -.glyphicon-tower:before { - content: "\e184"; -} -.glyphicon-stats:before { - content: "\e185"; -} -.glyphicon-sd-video:before { - content: "\e186"; -} -.glyphicon-hd-video:before { - content: "\e187"; -} -.glyphicon-subtitles:before { - content: "\e188"; -} -.glyphicon-sound-stereo:before { - content: "\e189"; -} -.glyphicon-sound-dolby:before { - content: "\e190"; -} -.glyphicon-sound-5-1:before { - content: "\e191"; -} -.glyphicon-sound-6-1:before { - content: "\e192"; -} -.glyphicon-sound-7-1:before { - content: "\e193"; -} -.glyphicon-copyright-mark:before { - content: "\e194"; -} -.glyphicon-registration-mark:before { - content: "\e195"; -} -.glyphicon-cloud-download:before { - content: "\e197"; -} -.glyphicon-cloud-upload:before { - content: "\e198"; -} -.glyphicon-tree-conifer:before { - content: "\e199"; -} -.glyphicon-tree-deciduous:before { - content: "\e200"; -} -.glyphicon-cd:before { - content: "\e201"; -} -.glyphicon-save-file:before { - content: "\e202"; -} -.glyphicon-open-file:before { - content: "\e203"; -} -.glyphicon-level-up:before { - content: "\e204"; -} -.glyphicon-copy:before { - content: "\e205"; -} -.glyphicon-paste:before { - content: "\e206"; -} -.glyphicon-alert:before { - content: "\e209"; -} -.glyphicon-equalizer:before { - content: "\e210"; -} -.glyphicon-king:before { - content: "\e211"; -} -.glyphicon-queen:before { - content: "\e212"; -} -.glyphicon-pawn:before { - content: "\e213"; -} -.glyphicon-bishop:before { - content: "\e214"; -} -.glyphicon-knight:before { - content: "\e215"; -} -.glyphicon-baby-formula:before { - content: "\e216"; -} -.glyphicon-tent:before { - content: "\26fa"; -} -.glyphicon-blackboard:before { - content: "\e218"; -} -.glyphicon-bed:before { - content: "\e219"; -} -.glyphicon-apple:before { - content: "\f8ff"; -} -.glyphicon-erase:before { - content: "\e221"; -} -.glyphicon-hourglass:before { - content: "\231b"; -} -.glyphicon-lamp:before { - content: "\e223"; -} -.glyphicon-duplicate:before { - content: "\e224"; -} -.glyphicon-piggy-bank:before { - content: "\e225"; -} -.glyphicon-scissors:before { - content: "\e226"; -} -.glyphicon-bitcoin:before { - content: "\e227"; -} -.glyphicon-btc:before { - content: "\e227"; -} -.glyphicon-xbt:before { - content: "\e227"; -} -.glyphicon-yen:before { - content: "\00a5"; -} -.glyphicon-jpy:before { - content: "\00a5"; -} -.glyphicon-ruble:before { - content: "\20bd"; -} -.glyphicon-rub:before { - content: "\20bd"; -} -.glyphicon-scale:before { - content: "\e230"; -} -.glyphicon-ice-lolly:before { - content: "\e231"; -} -.glyphicon-ice-lolly-tasted:before { - content: "\e232"; -} -.glyphicon-education:before { - content: "\e233"; -} -.glyphicon-option-horizontal:before { - content: "\e234"; -} -.glyphicon-option-vertical:before { - content: "\e235"; -} -.glyphicon-menu-hamburger:before { - content: "\e236"; -} -.glyphicon-modal-window:before { - content: "\e237"; -} -.glyphicon-oil:before { - content: "\e238"; -} -.glyphicon-grain:before { - content: "\e239"; -} -.glyphicon-sunglasses:before { - content: "\e240"; -} -.glyphicon-text-size:before { - content: "\e241"; -} -.glyphicon-text-color:before { - content: "\e242"; -} -.glyphicon-text-background:before { - content: "\e243"; -} -.glyphicon-object-align-top:before { - content: "\e244"; -} -.glyphicon-object-align-bottom:before { - content: "\e245"; -} -.glyphicon-object-align-horizontal:before { - content: "\e246"; -} -.glyphicon-object-align-left:before { - content: "\e247"; -} -.glyphicon-object-align-vertical:before { - content: "\e248"; -} -.glyphicon-object-align-right:before { - content: "\e249"; -} -.glyphicon-triangle-right:before { - content: "\e250"; -} -.glyphicon-triangle-left:before { - content: "\e251"; -} -.glyphicon-triangle-bottom:before { - content: "\e252"; -} -.glyphicon-triangle-top:before { - content: "\e253"; -} -.glyphicon-console:before { - content: "\e254"; -} -.glyphicon-superscript:before { - content: "\e255"; -} -.glyphicon-subscript:before { - content: "\e256"; -} -.glyphicon-menu-left:before { - content: "\e257"; -} -.glyphicon-menu-right:before { - content: "\e258"; -} -.glyphicon-menu-down:before { - content: "\e259"; -} -.glyphicon-menu-up:before { - content: "\e260"; -} diff --git a/src/Rules.Framework.WebUI/node_modules/glyphicons-only-bootstrap/css/bootstrap.min.css b/src/Rules.Framework.WebUI/node_modules/glyphicons-only-bootstrap/css/bootstrap.min.css deleted file mode 100644 index 651b6db5..00000000 --- a/src/Rules.Framework.WebUI/node_modules/glyphicons-only-bootstrap/css/bootstrap.min.css +++ /dev/null @@ -1,14 +0,0 @@ -/*! - * Bootstrap v3.3.7 (http://getbootstrap.com) - * Copyright 2011-2016 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */ - -/*! - * Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=76290a4e23bf2c3f61a4e17625be4627) - * Config saved to config.json and https://gist.github.com/76290a4e23bf2c3f61a4e17625be4627 - *//*! - * Bootstrap v3.3.7 (http://getbootstrap.com) - * Copyright 2011-2016 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */@font-face{font-family:'Glyphicons Halflings';src:url('../fonts/glyphicons-halflings-regular.eot');src:url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'),url('../fonts/glyphicons-halflings-regular.woff') format('woff'),url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'),url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-euro:before,.glyphicon-eur:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"} diff --git a/src/Rules.Framework.WebUI/node_modules/glyphicons-only-bootstrap/fonts/glyphicons-halflings-regular.eot b/src/Rules.Framework.WebUI/node_modules/glyphicons-only-bootstrap/fonts/glyphicons-halflings-regular.eot deleted file mode 100644 index b93a4953..00000000 Binary files a/src/Rules.Framework.WebUI/node_modules/glyphicons-only-bootstrap/fonts/glyphicons-halflings-regular.eot and /dev/null differ diff --git a/src/Rules.Framework.WebUI/node_modules/glyphicons-only-bootstrap/fonts/glyphicons-halflings-regular.svg b/src/Rules.Framework.WebUI/node_modules/glyphicons-only-bootstrap/fonts/glyphicons-halflings-regular.svg deleted file mode 100644 index 94fb5490..00000000 --- a/src/Rules.Framework.WebUI/node_modules/glyphicons-only-bootstrap/fonts/glyphicons-halflings-regular.svg +++ /dev/null @@ -1,288 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/node_modules/glyphicons-only-bootstrap/fonts/glyphicons-halflings-regular.ttf b/src/Rules.Framework.WebUI/node_modules/glyphicons-only-bootstrap/fonts/glyphicons-halflings-regular.ttf deleted file mode 100644 index 1413fc60..00000000 Binary files a/src/Rules.Framework.WebUI/node_modules/glyphicons-only-bootstrap/fonts/glyphicons-halflings-regular.ttf and /dev/null differ diff --git a/src/Rules.Framework.WebUI/node_modules/glyphicons-only-bootstrap/fonts/glyphicons-halflings-regular.woff b/src/Rules.Framework.WebUI/node_modules/glyphicons-only-bootstrap/fonts/glyphicons-halflings-regular.woff deleted file mode 100644 index 9e612858..00000000 Binary files a/src/Rules.Framework.WebUI/node_modules/glyphicons-only-bootstrap/fonts/glyphicons-halflings-regular.woff and /dev/null differ diff --git a/src/Rules.Framework.WebUI/node_modules/glyphicons-only-bootstrap/fonts/glyphicons-halflings-regular.woff2 b/src/Rules.Framework.WebUI/node_modules/glyphicons-only-bootstrap/fonts/glyphicons-halflings-regular.woff2 deleted file mode 100644 index 64539b54..00000000 Binary files a/src/Rules.Framework.WebUI/node_modules/glyphicons-only-bootstrap/fonts/glyphicons-halflings-regular.woff2 and /dev/null differ diff --git a/src/Rules.Framework.WebUI/node_modules/jquery/dist/jquery.min.js b/src/Rules.Framework.WebUI/node_modules/jquery/dist/jquery.min.js deleted file mode 100644 index 2c69bc90..00000000 --- a/src/Rules.Framework.WebUI/node_modules/jquery/dist/jquery.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! jQuery v3.6.1 | (c) OpenJS Foundation and other contributors | jquery.org/license */ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,y=n.hasOwnProperty,a=y.toString,l=a.call(Object),v={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.1",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&v(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!y||!y.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ve(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ye(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ve(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],y=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&y.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||y.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||y.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||y.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||y.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||y.push(".#.+[+~]"),e.querySelectorAll("\\\f"),y.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&y.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&y.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&y.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),y.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),y=y.length&&new RegExp(y.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),v=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&v(p,e)?-1:t==C||t.ownerDocument==p&&v(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!y||!y.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),v.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",v.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",v.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ye(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),v.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(v.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return B(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=_e(v.pixelPosition,function(e,t){if(t)return t=Be(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return B(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0a{min-width:30px;height:28px;line-height:28px;display:block;background:#fff;font-size:14px;color:#333;text-decoration:none;text-align:center}.paginationjs .paginationjs-pages li>a:hover{background:#eee}.paginationjs .paginationjs-pages li.active{border:none}.paginationjs .paginationjs-pages li.active>a{height:30px;line-height:30px;background:#aaa;color:#fff}.paginationjs .paginationjs-pages li.disabled>a{opacity:.3}.paginationjs .paginationjs-pages li.disabled>a:hover{background:0 0}.paginationjs .paginationjs-pages li:first-child,.paginationjs .paginationjs-pages li:first-child>a{border-radius:3px 0 0 3px}.paginationjs .paginationjs-pages li:last-child{border-right:1px solid #aaa;border-radius:0 3px 3px 0}.paginationjs .paginationjs-pages li:last-child>a{border-radius:0 3px 3px 0}.paginationjs .paginationjs-go-input>input[type=text]{width:30px;height:28px;background:#fff;border-radius:3px;border:1px solid #aaa;padding:0;font-size:14px;text-align:center;vertical-align:baseline;outline:0;box-shadow:none;box-sizing:initial}.paginationjs .paginationjs-go-button>input[type=button]{min-width:40px;height:30px;line-height:28px;background:#fff;border-radius:3px;border:1px solid #aaa;text-align:center;padding:0 8px;font-size:14px;vertical-align:baseline;outline:0;box-shadow:none;color:#333;cursor:pointer;vertical-align:middle\9}.paginationjs.paginationjs-theme-blue .paginationjs-go-input>input[type=text],.paginationjs.paginationjs-theme-blue .paginationjs-pages li{border-color:#289de9}.paginationjs .paginationjs-go-button>input[type=button]:hover{background-color:#f8f8f8}.paginationjs .paginationjs-nav{height:30px;line-height:30px}.paginationjs .paginationjs-go-button,.paginationjs .paginationjs-go-input{margin-left:5px\9}.paginationjs.paginationjs-small{font-size:12px}.paginationjs.paginationjs-small .paginationjs-pages li>a{min-width:26px;height:24px;line-height:24px;font-size:12px}.paginationjs.paginationjs-small .paginationjs-pages li.active>a{height:26px;line-height:26px}.paginationjs.paginationjs-small .paginationjs-go-input{font-size:12px}.paginationjs.paginationjs-small .paginationjs-go-input>input[type=text]{width:26px;height:24px;font-size:12px}.paginationjs.paginationjs-small .paginationjs-go-button{font-size:12px}.paginationjs.paginationjs-small .paginationjs-go-button>input[type=button]{min-width:30px;height:26px;line-height:24px;padding:0 6px;font-size:12px}.paginationjs.paginationjs-small .paginationjs-nav{height:26px;line-height:26px;font-size:12px}.paginationjs.paginationjs-big{font-size:16px}.paginationjs.paginationjs-big .paginationjs-pages li>a{min-width:36px;height:34px;line-height:34px;font-size:16px}.paginationjs.paginationjs-big .paginationjs-pages li.active>a{height:36px;line-height:36px}.paginationjs.paginationjs-big .paginationjs-go-input{font-size:16px}.paginationjs.paginationjs-big .paginationjs-go-input>input[type=text]{width:36px;height:34px;font-size:16px}.paginationjs.paginationjs-big .paginationjs-go-button{font-size:16px}.paginationjs.paginationjs-big .paginationjs-go-button>input[type=button]{min-width:50px;height:36px;line-height:34px;padding:0 12px;font-size:16px}.paginationjs.paginationjs-big .paginationjs-nav{height:36px;line-height:36px;font-size:16px}.paginationjs.paginationjs-theme-blue .paginationjs-pages li>a{color:#289de9}.paginationjs.paginationjs-theme-blue .paginationjs-pages li>a:hover{background:#e9f4fc}.paginationjs.paginationjs-theme-blue .paginationjs-pages li.active>a{background:#289de9;color:#fff}.paginationjs.paginationjs-theme-blue .paginationjs-pages li.disabled>a:hover{background:0 0}.paginationjs.paginationjs-theme-blue .paginationjs-go-button>input[type=button]{background:#289de9;border-color:#289de9;color:#fff}.paginationjs.paginationjs-theme-green .paginationjs-go-input>input[type=text],.paginationjs.paginationjs-theme-green .paginationjs-pages li{border-color:#449d44}.paginationjs.paginationjs-theme-blue .paginationjs-go-button>input[type=button]:hover{background-color:#3ca5ea}.paginationjs.paginationjs-theme-green .paginationjs-pages li>a{color:#449d44}.paginationjs.paginationjs-theme-green .paginationjs-pages li>a:hover{background:#ebf4eb}.paginationjs.paginationjs-theme-green .paginationjs-pages li.active>a{background:#449d44;color:#fff}.paginationjs.paginationjs-theme-green .paginationjs-pages li.disabled>a:hover{background:0 0}.paginationjs.paginationjs-theme-green .paginationjs-go-button>input[type=button]{background:#449d44;border-color:#449d44;color:#fff}.paginationjs.paginationjs-theme-yellow .paginationjs-go-input>input[type=text],.paginationjs.paginationjs-theme-yellow .paginationjs-pages li{border-color:#ec971f}.paginationjs.paginationjs-theme-green .paginationjs-go-button>input[type=button]:hover{background-color:#55a555}.paginationjs.paginationjs-theme-yellow .paginationjs-pages li>a{color:#ec971f}.paginationjs.paginationjs-theme-yellow .paginationjs-pages li>a:hover{background:#fdf5e9}.paginationjs.paginationjs-theme-yellow .paginationjs-pages li.active>a{background:#ec971f;color:#fff}.paginationjs.paginationjs-theme-yellow .paginationjs-pages li.disabled>a:hover{background:0 0}.paginationjs.paginationjs-theme-yellow .paginationjs-go-button>input[type=button]{background:#ec971f;border-color:#ec971f;color:#fff}.paginationjs.paginationjs-theme-red .paginationjs-go-input>input[type=text],.paginationjs.paginationjs-theme-red .paginationjs-pages li{border-color:#c9302c}.paginationjs.paginationjs-theme-yellow .paginationjs-go-button>input[type=button]:hover{background-color:#eea135}.paginationjs.paginationjs-theme-red .paginationjs-pages li>a{color:#c9302c}.paginationjs.paginationjs-theme-red .paginationjs-pages li>a:hover{background:#faeaea}.paginationjs.paginationjs-theme-red .paginationjs-pages li.active>a{background:#c9302c;color:#fff}.paginationjs.paginationjs-theme-red .paginationjs-pages li.disabled>a:hover{background:0 0}.paginationjs.paginationjs-theme-red .paginationjs-go-button>input[type=button]{background:#c9302c;border-color:#c9302c;color:#fff}.paginationjs.paginationjs-theme-red .paginationjs-go-button>input[type=button]:hover{background-color:#ce4541}.paginationjs .paginationjs-pages li.paginationjs-next{border-right:1px solid #aaa\9}.paginationjs .paginationjs-go-input>input[type=text]{line-height:28px\9;vertical-align:middle\9}.paginationjs.paginationjs-big .paginationjs-pages li>a{line-height:36px\9}.paginationjs.paginationjs-big .paginationjs-go-input>input[type=text]{height:36px\9;line-height:36px\9} \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/node_modules/paginationjs/dist/pagination.min.js b/src/Rules.Framework.WebUI/node_modules/paginationjs/dist/pagination.min.js deleted file mode 100644 index c8ed6f1b..00000000 --- a/src/Rules.Framework.WebUI/node_modules/paginationjs/dist/pagination.min.js +++ /dev/null @@ -1,11 +0,0 @@ -/* - * pagination.js 2.1.5 - * A jQuery plugin to provide simple yet fully customisable pagination - * https://github.com/superRaytin/paginationjs - - * Homepage: http://pagination.js.org - * - * Copyright 2014-2100, superRaytin - * Released under the MIT license. -*/ -!function(a,b){function c(a){throw new Error("Pagination: "+a)}function d(a){a.dataSource||c('"dataSource" is required.'),"string"==typeof a.dataSource?void 0===a.totalNumberLocator?void 0===a.totalNumber?c('"totalNumber" is required.'):b.isNumeric(a.totalNumber)||c('"totalNumber" is incorrect. (Number)'):b.isFunction(a.totalNumberLocator)||c('"totalNumberLocator" should be a Function.'):i.isObject(a.dataSource)&&(void 0===a.locator?c('"dataSource" is an Object, please specify "locator".'):"string"==typeof a.locator||b.isFunction(a.locator)||c(a.locator+" is incorrect. (String | Function)")),void 0===a.formatResult||b.isFunction(a.formatResult)||c('"formatResult" should be a Function.')}function e(a){var c=["go","previous","next","disable","enable","refresh","show","hide","destroy"];b.each(c,function(b,c){a.off(h+c)}),a.data("pagination",{}),b(".paginationjs",a).remove()}function f(a,b){return("object"==(b=typeof a)?null==a&&"null"||Object.prototype.toString.call(a).slice(8,-1):b).toLowerCase()}void 0===b&&c("Pagination requires jQuery.");var g="pagination",h="__pagination-";b.fn.pagination&&(g="pagination2"),b.fn[g]=function(f){if(void 0===f)return this;var j=b(this),k=b.extend({},b.fn[g].defaults,f),l={initialize:function(){var a=this;if(j.data("pagination")||j.data("pagination",{}),!1!==a.callHook("beforeInit")){j.data("pagination").initialized&&b(".paginationjs",j).remove(),a.disabled=!!k.disabled;var c=a.model={pageRange:k.pageRange,pageSize:k.pageSize};a.parseDataSource(k.dataSource,function(b){a.isAsync=i.isString(b),i.isArray(b)&&(c.totalNumber=k.totalNumber=b.length),a.isDynamicTotalNumber=a.isAsync&&k.totalNumberLocator;var d=a.render(!0);k.className&&d.addClass(k.className),c.el=d,j["bottom"===k.position?"append":"prepend"](d),a.observer(),j.data("pagination").initialized=!0,a.callHook("afterInit",d)})}},render:function(a){var c=this,d=c.model,e=d.el||b('
'),f=!0!==a;c.callHook("beforeRender",f);var g=d.pageNumber||k.pageNumber,h=k.pageRange||0,i=c.getTotalPage(),j=g-h,l=g+h;return l>i&&(l=i,j=i-2*h,j=j<1?1:j),j<=1&&(j=1,l=Math.min(2*h+1,i)),e.html(c.generateHTML({currentPage:g,pageRange:h,rangeStart:j,rangeEnd:l})),k.hideWhenLessThanOnePage&&e[i<=1?"hide":"show"](),c.callHook("afterRender",f),e},generatePageNumbersHTML:function(a){var b,c=this,d=a.currentPage,e=c.getTotalPage(),f=a.rangeStart,g=a.rangeEnd,h="",i=k.pageLink,j=k.ellipsisText,l=k.classPrefix,m=k.activeClassName,n=k.disableClassName;if(null===k.pageRange){for(b=1;b<=e;b++)h+=b==d?'
  • '+b+"
  • ":'
  • '+b+"
  • ";return h}if(f<=3)for(b=1;b'+b+"":'
  • '+b+"
  • ";else k.showFirstOnEllipsisShow&&(h+='
  • 1
  • '),h+='
  • '+j+"
  • ";for(b=f;b<=g;b++)h+=b==d?'
  • '+b+"
  • ":'
  • '+b+"
  • ";if(g>=e-2)for(b=g+1;b<=e;b++)h+='
  • '+b+"
  • ";else h+='
  • '+j+"
  • ",k.showLastOnEllipsisShow&&(h+='
  • '+e+"
  • ");return h},generateHTML:function(a){var c,d=this,e=a.currentPage,f=d.getTotalPage(),g=d.getTotalNumber(),h=k.showPrevious,i=k.showNext,j=k.showPageNumbers,l=k.showNavigator,m=k.showGoInput,n=k.showGoButton,o=k.pageLink,p=k.prevText,q=k.nextText,r=k.goButtonText,s=k.classPrefix,t=k.disableClassName,u=k.ulClassName,v="",w='',x='',y=b.isFunction(k.formatNavigator)?k.formatNavigator(e,f,g):k.formatNavigator,z=b.isFunction(k.formatGoInput)?k.formatGoInput(w,e,f,g):k.formatGoInput,A=b.isFunction(k.formatGoButton)?k.formatGoButton(x,e,f,g):k.formatGoButton,B=b.isFunction(k.autoHidePrevious)?k.autoHidePrevious():k.autoHidePrevious,C=b.isFunction(k.autoHideNext)?k.autoHideNext():k.autoHideNext,D=b.isFunction(k.header)?k.header(e,f,g):k.header,E=b.isFunction(k.footer)?k.footer(e,f,g):k.footer;return D&&(c=d.replaceVariables(D,{currentPage:e,totalPage:f,totalNumber:g}),v+=c),(h||j||i)&&(v+='
    ',v+=u?'
      ':"
        ",h&&(e<=1?B||(v+='
      • '+p+"
      • "):v+='
      • '+p+"
      • "),j&&(v+=d.generatePageNumbersHTML(a)),i&&(e>=f?C||(v+='
      • '+q+"
      • "):v+='
      • '+q+"
      • "),v+="
    "),l&&y&&(c=d.replaceVariables(y,{currentPage:e,totalPage:f,totalNumber:g}),v+='
    '+c+"
    "),m&&z&&(c=d.replaceVariables(z,{currentPage:e,totalPage:f,totalNumber:g,input:w}),v+='
    '+c+"
    "),n&&A&&(c=d.replaceVariables(A,{currentPage:e,totalPage:f,totalNumber:g,button:x}),v+='
    '+c+"
    "),E&&(c=d.replaceVariables(E,{currentPage:e,totalPage:f,totalNumber:g}),v+=c),v},findTotalNumberFromRemoteResponse:function(a){this.model.totalNumber=k.totalNumberLocator(a)},go:function(a,c){function d(a){if(!1===e.callHook("beforePaging",g))return!1;if(f.direction=void 0===f.pageNumber?0:g>f.pageNumber?1:-1,f.pageNumber=g,e.render(),e.disabled&&e.isAsync&&e.enable(),j.data("pagination").model=f,k.formatResult){var d=b.extend(!0,[],a);i.isArray(a=k.formatResult(d))||(a=d)}j.data("pagination").currentPageData=a,e.doCallback(a,c),e.callHook("afterPaging",g),1==g&&e.callHook("afterIsFirstPage"),g==e.getTotalPage()&&e.callHook("afterIsLastPage")}var e=this,f=e.model;if(!e.disabled){var g=a;if((g=parseInt(g))&&!(g<1)){var h=k.pageSize,l=e.getTotalNumber(),m=e.getTotalPage();if(!(l>0&&g>m)){if(!e.isAsync)return void d(e.getDataFragment(g));var n={},o=k.alias||{};n[o.pageSize?o.pageSize:"pageSize"]=h,n[o.pageNumber?o.pageNumber:"pageNumber"]=g;var p=b.isFunction(k.ajax)?k.ajax():k.ajax,q={type:"get",cache:!1,data:{},contentType:"application/x-www-form-urlencoded; charset=UTF-8",dataType:"json",async:!0};b.extend(!0,q,p),b.extend(q.data,n),q.url=k.dataSource,q.success=function(a){e.isDynamicTotalNumber?e.findTotalNumberFromRemoteResponse(a):e.model.totalNumber=k.totalNumber,d(e.filterDataByLocator(a))},q.error=function(a,b,c){k.formatAjaxError&&k.formatAjaxError(a,b,c),e.enable()},e.disable(),b.ajax(q)}}}},doCallback:function(a,c){var d=this,e=d.model;b.isFunction(c)?c(a,e):b.isFunction(k.callback)&&k.callback(a,e)},destroy:function(){!1!==this.callHook("beforeDestroy")&&(this.model.el.remove(),j.off(),b("#paginationjs-style").remove(),this.callHook("afterDestroy"))},previous:function(a){this.go(this.model.pageNumber-1,a)},next:function(a){this.go(this.model.pageNumber+1,a)},disable:function(){var a=this,b=a.isAsync?"async":"sync";!1!==a.callHook("beforeDisable",b)&&(a.disabled=!0,a.model.disabled=!0,a.callHook("afterDisable",b))},enable:function(){var a=this,b=a.isAsync?"async":"sync";!1!==a.callHook("beforeEnable",b)&&(a.disabled=!1,a.model.disabled=!1,a.callHook("afterEnable",b))},refresh:function(a){this.go(this.model.pageNumber,a)},show:function(){var a=this;a.model.el.is(":visible")||a.model.el.show()},hide:function(){var a=this;a.model.el.is(":visible")&&a.model.el.hide()},replaceVariables:function(a,b){var c;for(var d in b){var e=b[d],f=new RegExp("<%=\\s*"+d+"\\s*%>","img");c=(c||a).replace(f,e)}return c},getDataFragment:function(a){var b=k.pageSize,c=k.dataSource,d=this.getTotalNumber(),e=b*(a-1)+1,f=Math.min(a*b,d);return c.slice(e-1,f)},getTotalNumber:function(){return this.model.totalNumber||k.totalNumber||0},getTotalPage:function(){return Math.ceil(this.getTotalNumber()/k.pageSize)},getLocator:function(a){var d;return"string"==typeof a?d=a:b.isFunction(a)?d=a():c('"locator" is incorrect. (String | Function)'),d},filterDataByLocator:function(a){var d,e=this.getLocator(k.locator);if(i.isObject(a)){try{b.each(e.split("."),function(b,c){d=(d||a)[c]})}catch(a){}d?i.isArray(d)||c("dataSource."+e+" must be an Array."):c("dataSource."+e+" is undefined.")}return d||a},parseDataSource:function(a,d){var e=this;i.isObject(a)?d(k.dataSource=e.filterDataByLocator(a)):i.isArray(a)?d(k.dataSource=a):b.isFunction(a)?k.dataSource(function(a){i.isArray(a)||c('The parameter of "done" Function should be an Array.'),e.parseDataSource.call(e,a,d)}):"string"==typeof a?(/^https?|file:/.test(a)&&(k.ajaxDataType="jsonp"),d(a)):c('Unexpected type of "dataSource".')},callHook:function(c){var d,e=j.data("pagination"),f=Array.prototype.slice.apply(arguments);return f.shift(),k[c]&&b.isFunction(k[c])&&!1===k[c].apply(a,f)&&(d=!1),e.hooks&&e.hooks[c]&&b.each(e.hooks[c],function(b,c){!1===c.apply(a,f)&&(d=!1)}),!1!==d},observer:function(){var a=this,d=a.model.el;j.on(h+"go",function(d,e,f){(e=parseInt(b.trim(e)))&&(b.isNumeric(e)||c('"pageNumber" is incorrect. (Number)'),a.go(e,f))}),d.delegate(".J-paginationjs-page","click",function(c){var d=b(c.currentTarget),e=b.trim(d.attr("data-num"));if(e&&!d.hasClass(k.disableClassName)&&!d.hasClass(k.activeClassName))return!1!==a.callHook("beforePageOnClick",c,e)&&(a.go(e),a.callHook("afterPageOnClick",c,e),!!k.pageLink&&void 0)}),d.delegate(".J-paginationjs-previous","click",function(c){var d=b(c.currentTarget),e=b.trim(d.attr("data-num"));if(e&&!d.hasClass(k.disableClassName))return!1!==a.callHook("beforePreviousOnClick",c,e)&&(a.go(e),a.callHook("afterPreviousOnClick",c,e),!!k.pageLink&&void 0)}),d.delegate(".J-paginationjs-next","click",function(c){var d=b(c.currentTarget),e=b.trim(d.attr("data-num"));if(e&&!d.hasClass(k.disableClassName))return!1!==a.callHook("beforeNextOnClick",c,e)&&(a.go(e),a.callHook("afterNextOnClick",c,e),!!k.pageLink&&void 0)}),d.delegate(".J-paginationjs-go-button","click",function(c){var e=b(".J-paginationjs-go-pagenumber",d).val();if(!1===a.callHook("beforeGoButtonOnClick",c,e))return!1;j.trigger(h+"go",e),a.callHook("afterGoButtonOnClick",c,e)}),d.delegate(".J-paginationjs-go-pagenumber","keyup",function(c){if(13===c.which){var e=b(c.currentTarget).val();if(!1===a.callHook("beforeGoInputOnEnter",c,e))return!1;j.trigger(h+"go",e),b(".J-paginationjs-go-pagenumber",d).focus(),a.callHook("afterGoInputOnEnter",c,e)}}),j.on(h+"previous",function(b,c){a.previous(c)}),j.on(h+"next",function(b,c){a.next(c)}),j.on(h+"disable",function(){a.disable()}),j.on(h+"enable",function(){a.enable()}),j.on(h+"refresh",function(b,c){a.refresh(c)}),j.on(h+"show",function(){a.show()}),j.on(h+"hide",function(){a.hide()}),j.on(h+"destroy",function(){a.destroy()});var e=Math.max(a.getTotalPage(),1),f=k.pageNumber;a.isDynamicTotalNumber&&(f=1),k.triggerPagingOnInit&&j.trigger(h+"go",Math.min(f,e))}};if(j.data("pagination")&&!0===j.data("pagination").initialized){if(b.isNumeric(f))return j.trigger.call(this,h+"go",f,arguments[1]),this;if("string"==typeof f){var m=Array.prototype.slice.apply(arguments);switch(m[0]=h+m[0],f){case"previous":case"next":case"go":case"disable":case"enable":case"refresh":case"show":case"hide":case"destroy":j.trigger.apply(this,m);break;case"getSelectedPageNum":return j.data("pagination").model?j.data("pagination").model.pageNumber:j.data("pagination").attributes.pageNumber;case"getTotalPage":return Math.ceil(j.data("pagination").model.totalNumber/j.data("pagination").model.pageSize);case"getSelectedPageData":return j.data("pagination").currentPageData;case"isDisabled":return!0===j.data("pagination").model.disabled;default:c("Unknown action: "+f)}return this}e(j)}else i.isObject(f)||c("Illegal options");return d(k),l.initialize(),this},b.fn[g].defaults={totalNumber:0,pageNumber:1,pageSize:10,pageRange:2,showPrevious:!0,showNext:!0,showPageNumbers:!0,showNavigator:!1,showGoInput:!1,showGoButton:!1,pageLink:"",prevText:"«",nextText:"»",ellipsisText:"...",goButtonText:"Go",classPrefix:"paginationjs",activeClassName:"active",disableClassName:"disabled",inlineStyle:!0,formatNavigator:"<%= currentPage %> / <%= totalPage %>",formatGoInput:"<%= input %>",formatGoButton:"<%= button %>",position:"bottom",autoHidePrevious:!1,autoHideNext:!1,triggerPagingOnInit:!0,hideWhenLessThanOnePage:!1,showFirstOnEllipsisShow:!0,showLastOnEllipsisShow:!0,callback:function(){}},b.fn.addHook=function(a,d){arguments.length<2&&c("Missing argument."),b.isFunction(d)||c("callback must be a function.");var e=b(this),f=e.data("pagination");f||(e.data("pagination",{}),f=e.data("pagination")),!f.hooks&&(f.hooks={}),f.hooks[a]=f.hooks[a]||[],f.hooks[a].push(d)},b[g]=function(a,d){arguments.length<2&&c("Requires two parameters.");var e;if(e="string"!=typeof a&&a instanceof jQuery?a:b(a),e.length)return e.pagination(d),e};var i={};b.each(["Object","Array","String"],function(a,b){i["is"+b]=function(a){return f(a)===b.toLowerCase()}}),"function"==typeof define&&define.amd&&define(function(){return b})}(this,window.jQuery); \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/node_modules/rules_list.ico b/src/Rules.Framework.WebUI/node_modules/rules_list.ico deleted file mode 100644 index 4b4d5da7..00000000 Binary files a/src/Rules.Framework.WebUI/node_modules/rules_list.ico and /dev/null differ diff --git a/src/Rules.Framework.WebUI/package.json b/src/Rules.Framework.WebUI/package.json deleted file mode 100644 index 666090c6..00000000 --- a/src/Rules.Framework.WebUI/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "rules.framework.ui", - "version": "1.0.0", - "private": true, - "dependencies": { - "glyphicons-only-bootstrap": "1.0.1", - "jquery": "3.6.1", - "paginationjs": "2.1.5" - }, - "scripts": { - } -} \ No newline at end of file diff --git a/src/Rules.Framework/AssemblyMetadata.cs b/src/Rules.Framework/AssemblyMetadata.cs index a88ba7ef..c5c22406 100644 --- a/src/Rules.Framework/AssemblyMetadata.cs +++ b/src/Rules.Framework/AssemblyMetadata.cs @@ -2,6 +2,6 @@ [assembly: InternalsVisibleTo("Rules.Framework.Tests")] [assembly: InternalsVisibleTo("Rules.Framework.IntegrationTests")] -[assembly: InternalsVisibleTo("Rules.Framework.Providers.InMemory.Tests")] [assembly: InternalsVisibleTo("Rules.Framework.Providers.InMemory.IntegrationTests")] +[assembly: InternalsVisibleTo("Rules.Framework.Providers.MongoDb")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] \ No newline at end of file diff --git a/src/Rules.Framework/Builder/ConditionNodeFactory.cs b/src/Rules.Framework/Builder/ConditionNodeFactory.cs index 2fd7da0d..0f42ead4 100644 --- a/src/Rules.Framework/Builder/ConditionNodeFactory.cs +++ b/src/Rules.Framework/Builder/ConditionNodeFactory.cs @@ -1,7 +1,11 @@ namespace Rules.Framework.Builder { using System; - using Rules.Framework.Core; + using System.Collections.Generic; + using Rules.Framework; + using Rules.Framework.Builder.Generic.RulesBuilder; + using Rules.Framework.Builder.RulesBuilder; + using Rules.Framework.ConditionNodes; /// /// Factory for creating condition nodes. @@ -16,11 +20,33 @@ public static class ConditionNodeFactory /// The function containing the logic for the composed condition node. /// /// - public static IConditionNode CreateComposedNode( + public static IConditionNode CreateComposedNode( LogicalOperators logicalOperator, - Func, IFluentComposedConditionNodeBuilder> conditionFunc) + Func conditionFunc) { - var composedConditionNodeBuilder = new FluentComposedConditionNodeBuilder(logicalOperator); + var composedConditionNodeBuilder = new FluentConditionNodeBuilder(logicalOperator); + + var composedConditionNode = conditionFunc + .Invoke(composedConditionNodeBuilder) + .Build(); + + return composedConditionNode; + } + + /// + /// Creates a composed condition node. + /// + /// The condition type that strongly types conditions. + /// The logical operator. + /// + /// The function containing the logic for the composed condition node. + /// + /// + public static IConditionNode CreateComposedNode( + LogicalOperators logicalOperator, + Func, IFluentConditionNodeBuilder> conditionFunc) + { + var composedConditionNodeBuilder = new FluentConditionNodeBuilder(logicalOperator); var composedConditionNode = conditionFunc .Invoke(composedConditionNodeBuilder) @@ -32,15 +58,46 @@ public static IConditionNode CreateComposedNode( /// /// Creates a value condition node. /// - /// The condition type. + /// the operand type. + /// The condition name. /// The condition operator. /// The condition operand. /// - public static IConditionNode CreateValueNode( - TConditionType conditionType, Operators condOperator, TDataType operand) + /// + /// The data type is not supported: {typeof(T).FullName}. + /// + public static IValueConditionNode CreateValueNode( + string condition, Operators condOperator, T operand) { - return new ValueConditionNodeBuilder(conditionType, condOperator, operand) - .Build(); + switch (operand) + { + case decimal _: + return new ValueConditionNode(DataTypes.Decimal, condition, condOperator, operand); + + case IEnumerable _: + return new ValueConditionNode(DataTypes.ArrayDecimal, condition, condOperator, operand); + + case int _: + return new ValueConditionNode(DataTypes.Integer, condition, condOperator, operand); + + case IEnumerable _: + return new ValueConditionNode(DataTypes.ArrayInteger, condition, condOperator, operand); + + case bool _: + return new ValueConditionNode(DataTypes.Boolean, condition, condOperator, operand); + + case IEnumerable _: + return new ValueConditionNode(DataTypes.ArrayBoolean, condition, condOperator, operand); + + case string _: + return new ValueConditionNode(DataTypes.String, condition, condOperator, operand); + + case IEnumerable _: + return new ValueConditionNode(DataTypes.ArrayString, condition, condOperator, operand); + + default: + throw new NotSupportedException($"The data type is not supported: {typeof(T).FullName}."); + } } } } \ No newline at end of file diff --git a/src/Rules.Framework/Builder/ConfiguredRulesEngineBuilder.cs b/src/Rules.Framework/Builder/ConfiguredRulesEngineBuilder.cs index f2e1da5d..6aa20d5f 100644 --- a/src/Rules.Framework/Builder/ConfiguredRulesEngineBuilder.cs +++ b/src/Rules.Framework/Builder/ConfiguredRulesEngineBuilder.cs @@ -12,38 +12,38 @@ namespace Rules.Framework.Builder using Rules.Framework.Source; using Rules.Framework.Validation; - internal sealed class ConfiguredRulesEngineBuilder : IConfiguredRulesEngineBuilder + internal sealed class ConfiguredRulesEngineBuilder : IConfiguredRulesEngineBuilder { - private readonly IRulesDataSource rulesDataSource; + private readonly IRulesDataSource rulesDataSource; private readonly RulesEngineOptions rulesEngineOptions; - public ConfiguredRulesEngineBuilder(IRulesDataSource rulesDataSource) + public ConfiguredRulesEngineBuilder(IRulesDataSource rulesDataSource) { this.rulesDataSource = rulesDataSource; this.rulesEngineOptions = RulesEngineOptions.NewWithDefaults(); } - public RulesEngine Build() + public IRulesEngine Build() { - var rulesSourceMiddlewares = new List>(); + var rulesSourceMiddlewares = new List(); var dataTypesConfigurationProvider = new DataTypesConfigurationProvider(this.rulesEngineOptions); var multiplicityEvaluator = new MultiplicityEvaluator(); - var conditionsTreeAnalyzer = new ConditionsTreeAnalyzer(); + var conditionsTreeAnalyzer = new ConditionsTreeAnalyzer(); - IConditionsEvalEngine conditionsEvalEngine; + IConditionsEvalEngine conditionsEvalEngine; if (this.rulesEngineOptions.EnableCompilation) { // Use specific conditions eval engine to use compiled parts of conditions tree. var conditionExpressionBuilderProvider = new ConditionExpressionBuilderProvider(); var valueConditionNodeCompilerProvider = new ValueConditionNodeExpressionBuilderProvider(conditionExpressionBuilderProvider); - var ruleConditionsExpressionBuilder = new RuleConditionsExpressionBuilder(valueConditionNodeCompilerProvider, dataTypesConfigurationProvider); - conditionsEvalEngine = new CompiledConditionsEvalEngine(conditionsTreeAnalyzer, this.rulesEngineOptions); + var ruleConditionsExpressionBuilder = new RuleConditionsExpressionBuilder(valueConditionNodeCompilerProvider, dataTypesConfigurationProvider); + conditionsEvalEngine = new CompiledConditionsEvalEngine(conditionsTreeAnalyzer, this.rulesEngineOptions); // Add conditions compiler middleware to ensure compilation occurs before rules // engine uses the rules, while also ensuring that the compilation result is kept on // data source (avoiding future re-compilation). - var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware(ruleConditionsExpressionBuilder, this.rulesDataSource); + var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware(ruleConditionsExpressionBuilder, this.rulesDataSource); rulesSourceMiddlewares.Add(compilationRulesSourceMiddleware); } else @@ -53,22 +53,22 @@ public RulesEngine Build() var operatorEvalStrategyFactory = new OperatorEvalStrategyFactory(); var conditionEvalDispatchProvider = new ConditionEvalDispatchProvider(operatorEvalStrategyFactory, multiplicityEvaluator, dataTypesConfigurationProvider); var deferredEval = new DeferredEval(conditionEvalDispatchProvider, this.rulesEngineOptions); - conditionsEvalEngine = new InterpretedConditionsEvalEngine(deferredEval, conditionsTreeAnalyzer); + conditionsEvalEngine = new InterpretedConditionsEvalEngine(deferredEval, conditionsTreeAnalyzer); } - var conditionTypeExtractor = new ConditionTypeExtractor(); + var ruleConditionsExtractor = new RuleConditionsExtractor(); var validationProvider = ValidationProvider.New() - .MapValidatorFor(new SearchArgsValidator()); + .MapValidatorFor(new SearchArgsValidator()); var orderedMiddlewares = rulesSourceMiddlewares - .Reverse>(); - var rulesSource = new RulesSource(this.rulesDataSource, orderedMiddlewares); + .Reverse(); + var rulesSource = new RulesSource(this.rulesDataSource, orderedMiddlewares); - return new RulesEngine(conditionsEvalEngine, rulesSource, validationProvider, this.rulesEngineOptions, conditionTypeExtractor); + return new RulesEngine(conditionsEvalEngine, rulesSource, validationProvider, this.rulesEngineOptions, ruleConditionsExtractor); } - public IConfiguredRulesEngineBuilder Configure(Action configurationAction) + public IConfiguredRulesEngineBuilder Configure(Action configurationAction) { configurationAction.Invoke(this.rulesEngineOptions); RulesEngineOptionsValidator.Validate(this.rulesEngineOptions); diff --git a/src/Rules.Framework/Builder/Deprecated/ComposedConditionNodeBuilder.cs b/src/Rules.Framework/Builder/Deprecated/ComposedConditionNodeBuilder.cs deleted file mode 100644 index e8baa10e..00000000 --- a/src/Rules.Framework/Builder/Deprecated/ComposedConditionNodeBuilder.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace Rules.Framework.Builder -{ - using System; - using System.Collections.Generic; - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; - - internal sealed class ComposedConditionNodeBuilder : IComposedConditionNodeBuilder - { - private readonly IConditionNodeBuilder conditionNodeBuilder; - private readonly List> conditions; - private LogicalOperators logicalOperator; - - public ComposedConditionNodeBuilder(IConditionNodeBuilder conditionNodeBuilder) - { - this.conditionNodeBuilder = conditionNodeBuilder; - this.conditions = new List>(2); // Most probable number of conditions, so that collection is initialized with right size most times. - } - - public IComposedConditionNodeBuilder AddCondition(Func, IConditionNode> conditionFunc) - { - var conditionNode = conditionFunc.Invoke(this.conditionNodeBuilder); - - this.conditions.Add(conditionNode); - - return this; - } - - public IConditionNode Build() - { - return new ComposedConditionNode(this.logicalOperator, this.conditions); - } - - public IComposedConditionNodeBuilder WithLogicalOperator(LogicalOperators logicalOperator) - { - this.logicalOperator = logicalOperator; - - return this; - } - } -} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/Deprecated/ConditionNodeBuilder.cs b/src/Rules.Framework/Builder/Deprecated/ConditionNodeBuilder.cs deleted file mode 100644 index 502d17bb..00000000 --- a/src/Rules.Framework/Builder/Deprecated/ConditionNodeBuilder.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Rules.Framework.Builder -{ - using System; - - [Obsolete("This way of building conditions has been deprecated. Please use the IRootConditionNodeBuilder and IFluentComposedConditionNodeBuilder interfaces.")] - internal sealed class ConditionNodeBuilder : IConditionNodeBuilder - { - public IComposedConditionNodeBuilder AsComposed() - => new ComposedConditionNodeBuilder(this); - - public IValueConditionNodeBuilder AsValued(TConditionType conditionType) - => new ValueConditionNodeBuilder(conditionType); - } -} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/Deprecated/IComposedConditionNodeBuilder.cs b/src/Rules.Framework/Builder/Deprecated/IComposedConditionNodeBuilder.cs deleted file mode 100644 index 5165c621..00000000 --- a/src/Rules.Framework/Builder/Deprecated/IComposedConditionNodeBuilder.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace Rules.Framework.Builder -{ - using System; - using Rules.Framework.Core; - - /// - /// Builder for composed condition nodes. - /// - /// The type of the condition type. - [Obsolete("This way of building and adding composed conditions has been deprecated. Please use the IRootConditionNodeBuilder and IFluentComposedConditionNodeBuilder interfaces.")] - public interface IComposedConditionNodeBuilder - { - /// - /// Adds a condition to the composed condition node builder. - /// - /// The function containing the logic for the new condition. - /// - IComposedConditionNodeBuilder AddCondition(Func, IConditionNode> conditionFunc); - - /// - /// Builds the composed condition node. - /// - /// - IConditionNode Build(); - - /// - /// Sets the composed condition node with the specified logical operator. - /// - /// The logical operator. - /// - IComposedConditionNodeBuilder WithLogicalOperator(LogicalOperators logicalOperator); - } -} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/Deprecated/IConditionNodeBuilder.cs b/src/Rules.Framework/Builder/Deprecated/IConditionNodeBuilder.cs deleted file mode 100644 index e6c279a9..00000000 --- a/src/Rules.Framework/Builder/Deprecated/IConditionNodeBuilder.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace Rules.Framework.Builder -{ - using System; - - /// - /// Builder to specify a new condition node. Allows for choosing between composed and valued - /// condition nodes. - /// - /// The type of the condition type. - [Obsolete("This way of building conditions has been deprecated. Please use the IRootConditionNodeBuilder and IFluentComposedConditionNodeBuilder interfaces.")] - public interface IConditionNodeBuilder - { - /// - /// Begins building a condition node as composed. - /// - /// - IComposedConditionNodeBuilder AsComposed(); - - /// - /// Begins building a condition node as valued. - /// - /// Type of the condition. - /// - IValueConditionNodeBuilder AsValued(TConditionType conditionType); - } -} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/Deprecated/IValueConditionNodeBuilder.cs b/src/Rules.Framework/Builder/Deprecated/IValueConditionNodeBuilder.cs deleted file mode 100644 index cf6ec9e1..00000000 --- a/src/Rules.Framework/Builder/Deprecated/IValueConditionNodeBuilder.cs +++ /dev/null @@ -1,60 +0,0 @@ -namespace Rules.Framework.Builder -{ - using System; - using System.Collections.Generic; - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; - - /// - /// Builder to specify the data type for a valued condition node. - /// - /// The type of the condition type. - [Obsolete("This way of building and adding value conditions has been deprecated. Please use the IRootConditionNodeBuilder and IFluentComposedConditionNodeBuilder interfaces.")] - public interface IValueConditionNodeBuilder - { - /// - /// Sets the new value condition node to have the type . - /// - /// the data type of the new value condition node. - /// - IValueConditionNodeBuilder OfDataType(); - } - - /// - /// Builder to specify create a new value condition node. - /// - /// The type of the condition type. - /// The type of the data type. - [Obsolete("This way of building and adding value conditions has been deprecated. Please use the IRootConditionNodeBuilder and IFluentComposedConditionNodeBuilder interfaces.")] - public interface IValueConditionNodeBuilder - { - /// - /// Builds the new value condition node. - /// - /// - IValueConditionNode Build(); - - /// - /// Sets the condition node right hand operand (as a single value). Remember that the rules - /// engine input conditions will be evaluated as left hand operands. - /// - /// The value. - /// - IValueConditionNodeBuilder SetOperand(TDataType value); - - /// - /// Sets the condition node right hand operand (as collection of values). Remember that the - /// rules engine input conditions will be evaluated as left hand operands. - /// - /// The value. - /// - IValueConditionNodeBuilder SetOperand(IEnumerable value); - - /// - /// Sets the new value condition node with the specified comparison operator. - /// - /// The comparison operator. - /// - IValueConditionNodeBuilder WithComparisonOperator(Operators comparisonOperator); - } -} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/Deprecated/ValueConditionNodeBuilder.cs b/src/Rules.Framework/Builder/Deprecated/ValueConditionNodeBuilder.cs deleted file mode 100644 index afb803a4..00000000 --- a/src/Rules.Framework/Builder/Deprecated/ValueConditionNodeBuilder.cs +++ /dev/null @@ -1,93 +0,0 @@ -namespace Rules.Framework.Builder -{ - using System; - using System.Collections.Generic; - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; - - internal sealed class ValueConditionNodeBuilder : IValueConditionNodeBuilder - { - private readonly TConditionType conditionType; - - public ValueConditionNodeBuilder(TConditionType conditionType) - { - this.conditionType = conditionType; - } - - public IValueConditionNodeBuilder OfDataType() - => new ValueConditionNodeBuilder(this.conditionType); - } - - internal sealed class ValueConditionNodeBuilder : IValueConditionNodeBuilder - { - private readonly TConditionType conditionType; - private Operators comparisonOperator; - private object operand; - - public ValueConditionNodeBuilder(TConditionType conditionType) - { - this.conditionType = conditionType; - } - - public ValueConditionNodeBuilder(TConditionType conditionType, Operators comparisonOperator, object operand) - { - this.conditionType = conditionType; - this.comparisonOperator = comparisonOperator; - this.operand = operand; - } - - public IValueConditionNode Build() - { - switch (this.operand) - { - case decimal _: - return new ValueConditionNode(DataTypes.Decimal, this.conditionType, this.comparisonOperator, this.operand); - - case IEnumerable _: - return new ValueConditionNode(DataTypes.ArrayDecimal, this.conditionType, this.comparisonOperator, this.operand); - - case int _: - return new ValueConditionNode(DataTypes.Integer, this.conditionType, this.comparisonOperator, this.operand); - - case IEnumerable _: - return new ValueConditionNode(DataTypes.ArrayInteger, this.conditionType, this.comparisonOperator, this.operand); - - case bool _: - return new ValueConditionNode(DataTypes.Boolean, this.conditionType, this.comparisonOperator, this.operand); - - case IEnumerable _: - return new ValueConditionNode(DataTypes.ArrayBoolean, this.conditionType, this.comparisonOperator, this.operand); - - case string _: - return new ValueConditionNode(DataTypes.String, this.conditionType, this.comparisonOperator, this.operand); - - case IEnumerable _: - return new ValueConditionNode(DataTypes.ArrayString, this.conditionType, this.comparisonOperator, this.operand); - - default: - throw new NotSupportedException($"The data type is not supported: {typeof(TDataType).FullName}."); - } - } - - public IValueConditionNodeBuilder SetOperand(TDataType value) - { - this.operand = value; - - return this; - } - - public IValueConditionNodeBuilder SetOperand(IEnumerable value) - { - this.operand = value; - - return this; - } - - public IValueConditionNodeBuilder WithComparisonOperator(Operators comparisonOperator) - { - this.comparisonOperator = comparisonOperator; - - return this; - } - } -} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/FluentComposedConditionNodeBuilder.cs b/src/Rules.Framework/Builder/FluentComposedConditionNodeBuilder.cs deleted file mode 100644 index 5698d404..00000000 --- a/src/Rules.Framework/Builder/FluentComposedConditionNodeBuilder.cs +++ /dev/null @@ -1,60 +0,0 @@ -namespace Rules.Framework.Builder -{ - using System; - using System.Collections.Generic; - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; - - internal sealed class FluentComposedConditionNodeBuilder : IFluentComposedConditionNodeBuilder - { - private readonly List> conditions; - private readonly LogicalOperators logicalOperator; - - public FluentComposedConditionNodeBuilder(LogicalOperators logicalOperator) - { - this.logicalOperator = logicalOperator; - this.conditions = new List>(2); // Most probable number of conditions, so that collection is initialized with right size most times. - } - - public IFluentComposedConditionNodeBuilder And( - Func, IFluentComposedConditionNodeBuilder> conditionFunc) - { - var composedConditionNode = ConditionNodeFactory.CreateComposedNode(LogicalOperators.And, conditionFunc); - - this.conditions.Add(composedConditionNode); - - return this; - } - - public IConditionNode Build() - { - return new ComposedConditionNode(this.logicalOperator, this.conditions); - } - - public IFluentComposedConditionNodeBuilder Condition(IConditionNode conditionNode) - { - this.conditions.Add(conditionNode); - - return this; - } - - public IFluentComposedConditionNodeBuilder Or( - Func, IFluentComposedConditionNodeBuilder> conditionFunc) - { - var composedConditionNode = ConditionNodeFactory.CreateComposedNode(LogicalOperators.Or, conditionFunc); - - this.conditions.Add(composedConditionNode); - - return this; - } - - public IFluentComposedConditionNodeBuilder Value(TConditionType conditionType, Operators condOperator, TDataType operand) - { - var valueConditionNode = ConditionNodeFactory.CreateValueNode(conditionType, condOperator, operand); - - this.conditions.Add(valueConditionNode); - - return this; - } - } -} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/Generic/RuleBuilderResult.cs b/src/Rules.Framework/Builder/Generic/RuleBuilderResult.cs new file mode 100644 index 00000000..08b375c9 --- /dev/null +++ b/src/Rules.Framework/Builder/Generic/RuleBuilderResult.cs @@ -0,0 +1,62 @@ +namespace Rules.Framework.Builder.Generic +{ + using System.Collections.Generic; + using System.Linq; + using Rules.Framework.Generic; + + /// + /// Contains the results information from a generic rule build operation. + /// + public class RuleBuilderResult : RuleBuilderResultBase + { + /// + /// Initializes a new instance of the class. + /// + /// if set to true [is success]. + /// The rule. + /// The errors. + internal RuleBuilderResult(bool isSuccess, Rule rule, IEnumerable errors) + : base(isSuccess, errors) + { + this.Rule = rule; + } + + /// + /// Gets the rule. + /// + /// The rule. + public Rule Rule { get; } + + /// + /// Creates a result marked with failure. + /// + /// The errors. + /// + /// errors + public static RuleBuilderResult Failure(IEnumerable errors) + { + if (errors is null) + { + throw new System.ArgumentNullException(nameof(errors)); + } + + return new RuleBuilderResult(isSuccess: false, null!, errors); + } + + /// + /// Creates a result marked with success. + /// + /// The rule. + /// + /// rule + public static RuleBuilderResult Success(Rule rule) + { + if (rule is null) + { + throw new System.ArgumentNullException(nameof(rule)); + } + + return new RuleBuilderResult(isSuccess: true, rule, Enumerable.Empty()); + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/Generic/RulesBuilder/FluentConditionNodeBuilder.cs b/src/Rules.Framework/Builder/Generic/RulesBuilder/FluentConditionNodeBuilder.cs new file mode 100644 index 00000000..94dca74c --- /dev/null +++ b/src/Rules.Framework/Builder/Generic/RulesBuilder/FluentConditionNodeBuilder.cs @@ -0,0 +1,55 @@ +namespace Rules.Framework.Builder.Generic.RulesBuilder +{ + using System; + using System.Collections.Generic; + using Rules.Framework; + using Rules.Framework.ConditionNodes; + using Rules.Framework.Generic; + + internal sealed class FluentConditionNodeBuilder : IFluentConditionNodeBuilder + { + private readonly List conditions; + private readonly LogicalOperators logicalOperator; + + public FluentConditionNodeBuilder(LogicalOperators logicalOperator) + { + this.logicalOperator = logicalOperator; + this.conditions = new List(2); // Most probable number of conditions, so that collection is initialized with right size most times. + } + + public IFluentConditionNodeBuilder And( + Func, IFluentConditionNodeBuilder> conditionFunc) + { + var composedConditionNode = ConditionNodeFactory.CreateComposedNode(LogicalOperators.And, conditionFunc); + this.conditions.Add(composedConditionNode); + return this; + } + + public IConditionNode Build() + { + return new ComposedConditionNode(this.logicalOperator, this.conditions); + } + + public IFluentConditionNodeBuilder Condition(IConditionNode conditionNode) + { + this.conditions.Add(conditionNode); + return this; + } + + public IFluentConditionNodeBuilder Or( + Func, IFluentConditionNodeBuilder> conditionFunc) + { + var composedConditionNode = ConditionNodeFactory.CreateComposedNode(LogicalOperators.Or, conditionFunc); + this.conditions.Add(composedConditionNode); + return this; + } + + public IFluentConditionNodeBuilder Value(TCondition condition, Operators condOperator, T operand) + { + var conditionAsString = GenericConversions.Convert(condition); + var valueConditionNode = ConditionNodeFactory.CreateValueNode(conditionAsString, condOperator, operand); + this.conditions.Add(valueConditionNode); + return this; + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/Generic/RulesBuilder/IFluentConditionNodeBuilder.cs b/src/Rules.Framework/Builder/Generic/RulesBuilder/IFluentConditionNodeBuilder.cs new file mode 100644 index 00000000..70996b21 --- /dev/null +++ b/src/Rules.Framework/Builder/Generic/RulesBuilder/IFluentConditionNodeBuilder.cs @@ -0,0 +1,49 @@ +namespace Rules.Framework.Builder.Generic.RulesBuilder +{ + using System; + using Rules.Framework; + + /// + /// Builder for condition nodes. + /// + /// The condition type that strongly types conditions. + public interface IFluentConditionNodeBuilder + { + /// + /// Adds a 'and' composed condition to the new rule's conditions tree. + /// + /// The function containing the logic for the new condition. + /// + IFluentConditionNodeBuilder And( + Func, IFluentConditionNodeBuilder> conditionFunc); + + /// + /// Builds the composed condition node. + /// + /// + IConditionNode Build(); + + /// + /// Adds a pre-built to the new rule's conditions tree. + /// + /// The condition node. + IFluentConditionNodeBuilder Condition(IConditionNode conditionNode); + + /// + /// Adds a 'or' composed condition to the new rule's conditions tree. + /// + /// The function containing the logic for the new condition. + /// + IFluentConditionNodeBuilder Or( + Func, IFluentConditionNodeBuilder> conditionFunc); + + /// + /// Adds a 'value' condition to the new rule's conditions tree. + /// + /// The condition name. + /// The condition operator. + /// The condition operand. + /// + IFluentConditionNodeBuilder Value(TCondition condition, Operators condOperator, T operand); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/IRootConditionNodeBuilder.cs b/src/Rules.Framework/Builder/Generic/RulesBuilder/IRootConditionNodeBuilder.cs similarity index 55% rename from src/Rules.Framework/Builder/IRootConditionNodeBuilder.cs rename to src/Rules.Framework/Builder/Generic/RulesBuilder/IRootConditionNodeBuilder.cs index 7c04d4b6..942f9302 100644 --- a/src/Rules.Framework/Builder/IRootConditionNodeBuilder.cs +++ b/src/Rules.Framework/Builder/Generic/RulesBuilder/IRootConditionNodeBuilder.cs @@ -1,43 +1,43 @@ -namespace Rules.Framework.Builder +namespace Rules.Framework.Builder.Generic.RulesBuilder { using System; - using Rules.Framework.Core; + using Rules.Framework; /// /// Builder for the root condition node. /// - /// The type of the condition type. - public interface IRootConditionNodeBuilder + /// The condition type that strongly types conditions. + public interface IRootConditionNodeBuilder { /// /// Sets a And composed condition to the root condition node builder. /// /// The function containing the logic for the root condition. /// - IConditionNode And( - Func, IFluentComposedConditionNodeBuilder> conditionFunc); + IConditionNode And( + Func, IFluentConditionNodeBuilder> conditionFunc); /// /// Sets a Condition to the root condition node builder. /// /// The condition node. - IConditionNode Condition(IConditionNode conditionNode); + IConditionNode Condition(IConditionNode conditionNode); /// /// Sets a Or composed condition to the root condition node builder. /// /// The function containing the logic for the root condition. /// - IConditionNode Or( - Func, IFluentComposedConditionNodeBuilder> conditionFunc); + IConditionNode Or( + Func, IFluentConditionNodeBuilder> conditionFunc); /// /// Sets a Value condition to the root condition node builder. /// - /// The condition type. + /// The condition name. /// The condition operator. /// The condition operand. /// - IConditionNode Value(TConditionType conditionType, Operators condOperator, TDataType operand); + IConditionNode Value(TCondition condition, Operators condOperator, T operand); } } \ No newline at end of file diff --git a/src/Rules.Framework/Builder/Generic/RulesBuilder/IRuleBuilder.cs b/src/Rules.Framework/Builder/Generic/RulesBuilder/IRuleBuilder.cs new file mode 100644 index 00000000..3ad00a06 --- /dev/null +++ b/src/Rules.Framework/Builder/Generic/RulesBuilder/IRuleBuilder.cs @@ -0,0 +1,51 @@ +namespace Rules.Framework.Builder.Generic.RulesBuilder +{ + using System; + using Rules.Framework; + using Rules.Framework.Builder.Generic; + + /// + /// Builder to create a new rule. + /// + /// The ruleset type that strongly types rulesets. + /// The condition type that strongly types conditions. + public interface IRuleBuilder + { + /// + /// Sets the new rule to apply when the specified condition matches. + /// + /// The condition. + /// + IRuleBuilder ApplyWhen(IConditionNode condition); + + /// + /// Sets the new rule to apply when the condition specified by the given parameters matches. + /// + /// The type of the operands. + /// The condition name. + /// The operator. + /// The operand. + /// + IRuleBuilder ApplyWhen(TCondition condition, Operators condOperator, T operand); + + /// + /// Sets the new rule to apply when the condition given by the specified builder matches. + /// + /// The condition func. + /// + IRuleBuilder ApplyWhen(Func, IConditionNode> conditionFunc); + + /// + /// Builds the new rule. + /// + /// + RuleBuilderResult Build(); + + /// + /// Sets the new rule with the specified active status. + /// + /// The active status. + /// + IRuleBuilder WithActive(bool active); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/Generic/RulesBuilder/IRuleConfigureContent.cs b/src/Rules.Framework/Builder/Generic/RulesBuilder/IRuleConfigureContent.cs new file mode 100644 index 00000000..2ae6d7e5 --- /dev/null +++ b/src/Rules.Framework/Builder/Generic/RulesBuilder/IRuleConfigureContent.cs @@ -0,0 +1,27 @@ +namespace Rules.Framework.Builder.Generic.RulesBuilder +{ + using Rules.Framework.Serialization; + + /// + /// Configurer for a rule's content. + /// + /// The ruleset type that strongly types rulesets. + /// The condition type that strongly types conditions. + public interface IRuleConfigureContent + { + /// + /// Sets the new rule with the specified content. + /// + /// The content. + /// + IRuleConfigureDateBegin SetContent(object content); + + /// + /// Sets the new rule with the specified serialized content. + /// + /// The serialized content. + /// The content serialization provider. + /// + IRuleConfigureDateBegin SetContent(object content, IContentSerializationProvider contentSerializationProvider); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/Generic/RulesBuilder/IRuleConfigureDateBegin.cs b/src/Rules.Framework/Builder/Generic/RulesBuilder/IRuleConfigureDateBegin.cs new file mode 100644 index 00000000..8f100a8c --- /dev/null +++ b/src/Rules.Framework/Builder/Generic/RulesBuilder/IRuleConfigureDateBegin.cs @@ -0,0 +1,19 @@ +namespace Rules.Framework.Builder.Generic.RulesBuilder +{ + using System; + + /// + /// Configurer for a rule's begin date. + /// + /// The ruleset type that strongly types rulesets. + /// The condition type that strongly types conditions. + public interface IRuleConfigureDateBegin + { + /// + /// Sets the new rule with the specified date begin. + /// + /// The date begin. + /// + IRuleConfigureDateEndOptional Since(DateTime dateBegin); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/Generic/RulesBuilder/IRuleConfigureDateEnd.cs b/src/Rules.Framework/Builder/Generic/RulesBuilder/IRuleConfigureDateEnd.cs new file mode 100644 index 00000000..ce64b399 --- /dev/null +++ b/src/Rules.Framework/Builder/Generic/RulesBuilder/IRuleConfigureDateEnd.cs @@ -0,0 +1,19 @@ +namespace Rules.Framework.Builder.Generic.RulesBuilder +{ + using System; + + /// + /// Configurer for a rule's end date. + /// + /// The ruleset type that strongly types rulesets. + /// The condition type that strongly types conditions. + public interface IRuleConfigureDateEnd + { + /// + /// Sets the new rule with the specified date end. + /// + /// The date end. + /// + IRuleBuilder Until(DateTime? dateEnd); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/Generic/RulesBuilder/IRuleConfigureDateEndOptional.cs b/src/Rules.Framework/Builder/Generic/RulesBuilder/IRuleConfigureDateEndOptional.cs new file mode 100644 index 00000000..26761526 --- /dev/null +++ b/src/Rules.Framework/Builder/Generic/RulesBuilder/IRuleConfigureDateEndOptional.cs @@ -0,0 +1,13 @@ +namespace Rules.Framework.Builder.Generic.RulesBuilder +{ + /// + /// Configurer for a rule's optional end date or proceed to remaining rule build options. + /// + /// The ruleset type that strongly types rulesets. + /// The condition type that strongly types conditions. + public interface IRuleConfigureDateEndOptional : + IRuleConfigureDateEnd, + IRuleBuilder + { + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/Generic/RulesBuilder/IRuleConfigureRuleset.cs b/src/Rules.Framework/Builder/Generic/RulesBuilder/IRuleConfigureRuleset.cs new file mode 100644 index 00000000..704ea7e9 --- /dev/null +++ b/src/Rules.Framework/Builder/Generic/RulesBuilder/IRuleConfigureRuleset.cs @@ -0,0 +1,17 @@ +namespace Rules.Framework.Builder.Generic.RulesBuilder +{ + /// + /// Configurer for a rule's owner ruleset. + /// + /// The ruleset type that strongly types rulesets. + /// The condition type that strongly types conditions. + public interface IRuleConfigureRuleset + { + /// + /// Sets the new rule to belong to the specified ruleset. + /// + /// The ruleset. + /// + IRuleConfigureContent InRuleset(TRuleset ruleset); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/Generic/RulesBuilder/RootConditionNodeBuilder.cs b/src/Rules.Framework/Builder/Generic/RulesBuilder/RootConditionNodeBuilder.cs new file mode 100644 index 00000000..34be9246 --- /dev/null +++ b/src/Rules.Framework/Builder/Generic/RulesBuilder/RootConditionNodeBuilder.cs @@ -0,0 +1,32 @@ +namespace Rules.Framework.Builder.Generic.RulesBuilder +{ + using System; + using Rules.Framework; + using Rules.Framework.Generic; + + internal sealed class RootConditionNodeBuilder : IRootConditionNodeBuilder + { + public IConditionNode And( + Func, IFluentConditionNodeBuilder> conditionFunc) + { + return ConditionNodeFactory.CreateComposedNode(LogicalOperators.And, conditionFunc); + } + + public IConditionNode Condition(IConditionNode conditionNode) + { + return conditionNode; + } + + public IConditionNode Or( + Func, IFluentConditionNodeBuilder> conditionFunc) + { + return ConditionNodeFactory.CreateComposedNode(LogicalOperators.Or, conditionFunc); + } + + public IConditionNode Value(TCondition condition, Operators condOperator, T operand) + { + var conditionAsString = GenericConversions.Convert(condition); + return ConditionNodeFactory.CreateValueNode(conditionAsString, condOperator, operand); + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/Generic/RulesBuilder/RuleBuilder.cs b/src/Rules.Framework/Builder/Generic/RulesBuilder/RuleBuilder.cs new file mode 100644 index 00000000..de5700b3 --- /dev/null +++ b/src/Rules.Framework/Builder/Generic/RulesBuilder/RuleBuilder.cs @@ -0,0 +1,102 @@ +namespace Rules.Framework.Builder.Generic.RulesBuilder +{ + using System; + using System.Linq; + using Rules.Framework; + using Rules.Framework.Builder.RulesBuilder; + using Rules.Framework.Builder.Validation; + using Rules.Framework.Generic; + using Rules.Framework.Serialization; + + internal sealed class RuleBuilder : + IRuleBuilder, + IRuleConfigureContent, + IRuleConfigureDateBegin, + IRuleConfigureDateEndOptional, + IRuleConfigureRuleset + { + private readonly GenericRuleValidator genericRuleValidator = GenericRuleValidator.Instance; + private readonly RuleBuilder ruleBuilder; + + public RuleBuilder(string name) + { + this.ruleBuilder = new RuleBuilder(name); + } + + public IRuleBuilder ApplyWhen(IConditionNode condition) + { + this.ruleBuilder.ApplyWhen(condition); + return this; + } + + public IRuleBuilder ApplyWhen(Func, IConditionNode> conditionFunc) + { + var rootConditionNodeBuilder = new RootConditionNodeBuilder(); + var condition = conditionFunc.Invoke(rootConditionNodeBuilder); + return this.ApplyWhen(condition); + } + + public IRuleBuilder ApplyWhen( + TCondition conditionType, Operators condOperator, TDataType operand) + { + var rootConditionNodeBuilder = new RootConditionNodeBuilder(); + var valueCondition = rootConditionNodeBuilder.Value(conditionType, condOperator, operand); + return this.ApplyWhen(valueCondition); + } + + public RuleBuilderResult Build() + { + var ruleBuilderResult = this.ruleBuilder.Build(); + + if (ruleBuilderResult.IsSuccess) + { + var genericRule = new Rule(ruleBuilderResult.Rule); + var validationResult = this.genericRuleValidator.Validate(genericRule); + if (validationResult.IsValid) + { + return RuleBuilderResult.Success(genericRule); + } + + return RuleBuilderResult.Failure(validationResult.Errors.Select(ve => ve.ErrorMessage).ToList()); + } + + return RuleBuilderResult.Failure(ruleBuilderResult.Errors); + } + + public IRuleConfigureContent InRuleset(TRuleset ruleset) + { + this.ruleBuilder.InRuleset(GenericConversions.Convert(ruleset)); + return this; + } + + public IRuleConfigureDateBegin SetContent(object content) + { + this.ruleBuilder.SetContent(content); + return this; + } + + public IRuleConfigureDateBegin SetContent(object content, IContentSerializationProvider contentSerializationProvider) + { + this.ruleBuilder.SetContent(content, contentSerializationProvider); + return this; + } + + public IRuleConfigureDateEndOptional Since(DateTime dateBegin) + { + this.ruleBuilder.Since(dateBegin); + return this; + } + + public IRuleBuilder Until(DateTime? dateEnd) + { + this.ruleBuilder.Until(dateEnd); + return this; + } + + public IRuleBuilder WithActive(bool active) + { + this.ruleBuilder.WithActive(active); + return this; + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/IConditionTypeSelector.cs b/src/Rules.Framework/Builder/IConditionTypeSelector.cs deleted file mode 100644 index ed961aa9..00000000 --- a/src/Rules.Framework/Builder/IConditionTypeSelector.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Rules.Framework.Builder -{ - /// - /// Exposes the interface contract for selecting a rules engine condition type. - /// - /// The content type that allows to categorize rules. - public interface IConditionTypeSelector - { - /// - /// Sets the condition type to use on the set of conditions to supply to rules engine, according to . - /// - /// The condition type that allows to filter rules based on a set of conditions. - /// a rules data source selector. - IRulesDataSourceSelector WithConditionType(); - } -} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/IConfiguredRulesEngineBuilder.cs b/src/Rules.Framework/Builder/IConfiguredRulesEngineBuilder.cs index b0ad7e1e..9c6cbd5b 100644 --- a/src/Rules.Framework/Builder/IConfiguredRulesEngineBuilder.cs +++ b/src/Rules.Framework/Builder/IConfiguredRulesEngineBuilder.cs @@ -3,23 +3,24 @@ namespace Rules.Framework.Builder using System; /// - /// Exposes the interface contract for a configured rules engine builder. Allows to perform additional optional configurations and finish rules engine build. + /// Exposes the interface contract for a configured rules engine builder. Allows to perform + /// additional optional configurations and finish rules engine build. /// - /// The content type that allows to categorize rules. - /// The condition type that allows to filter rules based on a set of conditions. - public interface IConfiguredRulesEngineBuilder + public interface IConfiguredRulesEngineBuilder { /// /// Builds a rules engine instance using all supplied configuration options. /// /// the rules engine instance. - RulesEngine Build(); + IRulesEngine Build(); /// /// Allows configuration of rules engine options. /// - /// the action with configuration logic for rules engine options. + /// + /// the action with configuration logic for rules engine options. + /// /// the configured rules engine builder. - IConfiguredRulesEngineBuilder Configure(Action configurationAction); + IConfiguredRulesEngineBuilder Configure(Action configurationAction); } } \ No newline at end of file diff --git a/src/Rules.Framework/Builder/IContentTypeSelector.cs b/src/Rules.Framework/Builder/IContentTypeSelector.cs deleted file mode 100644 index 58b506a6..00000000 --- a/src/Rules.Framework/Builder/IContentTypeSelector.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Rules.Framework.Builder -{ - /// - /// Exposes the interface contract for selecting a rules engine content type. - /// - public interface IContentTypeSelector - { - /// - /// Sets the rules engine content type to use, according to supplied . - /// - /// The content type that allows to categorize rules. - /// a condition type selector. - IConditionTypeSelector WithContentType(); - } -} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/IFluentComposedConditionNodeBuilder.cs b/src/Rules.Framework/Builder/IFluentComposedConditionNodeBuilder.cs deleted file mode 100644 index 3d6ecaff..00000000 --- a/src/Rules.Framework/Builder/IFluentComposedConditionNodeBuilder.cs +++ /dev/null @@ -1,49 +0,0 @@ -namespace Rules.Framework.Builder -{ - using System; - using Rules.Framework.Core; - - /// - /// Fluent builder for composed condition nodes. - /// - /// The type of the condition type. - public interface IFluentComposedConditionNodeBuilder - { - /// - /// Adds a And composed condition to the fluent condition node builder. - /// - /// The function containing the logic for the new condition. - /// - IFluentComposedConditionNodeBuilder And( - Func, IFluentComposedConditionNodeBuilder> conditionFunc); - - /// - /// Builds the composed condition node. - /// - /// - IConditionNode Build(); - - /// - /// Adds a Condition to the fluent condition node builder. - /// - /// The condition node. - IFluentComposedConditionNodeBuilder Condition(IConditionNode conditionNode); - - /// - /// Adds a Or composed condition to the fluent condition node builder. - /// - /// The function containing the logic for the new condition. - /// - IFluentComposedConditionNodeBuilder Or( - Func, IFluentComposedConditionNodeBuilder> conditionFunc); - - /// - /// Adds a Value condition to the fluent condition node builder. - /// - /// The condition type. - /// The condition operator. - /// The condition operand. - /// - IFluentComposedConditionNodeBuilder Value(TConditionType conditionType, Operators condOperator, TDataType operand); - } -} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/IRuleBuilder.cs b/src/Rules.Framework/Builder/IRuleBuilder.cs deleted file mode 100644 index aaac41aa..00000000 --- a/src/Rules.Framework/Builder/IRuleBuilder.cs +++ /dev/null @@ -1,113 +0,0 @@ -namespace Rules.Framework.Builder -{ - using System; - using Rules.Framework.Core; - using Rules.Framework.Serialization; - - /// - /// Builder to create a new rule. - /// - /// The type of the content type. - /// The type of the condition type. - public interface IRuleBuilder - { - /// - /// Builds the new rule. - /// - /// - RuleBuilderResult Build(); - - /// - /// Sets the new rule with the specified active status. - /// - /// The active status. - /// - IRuleBuilder WithActive(bool active); - - /// - /// Sets the new rule with the specified condition. - /// - /// - /// The function with specific logic to create the condition for the rule. - /// - /// - [Obsolete("This way of adding conditions is being deprecated. Please use a non-deprecated overload instead.")] - IRuleBuilder WithCondition(Func, IConditionNode> conditionFunc); - - /// - /// Sets the new rule with the specified root condition. - /// - /// The condition. - /// - IRuleBuilder WithCondition(IConditionNode condition); - - /// - /// Sets the new rule with a value condition with the specified parameters. - /// - /// The type of the data type. - /// The content type. - /// The operator. - /// The operand. - /// - IRuleBuilder WithCondition(TConditionType conditionType, Operators condOperator, TDataType operand); - - /// - /// Sets the new rule with the specified root condition. - /// - /// The condition func. - /// - IRuleBuilder WithCondition( - Func, IConditionNode> conditionFunc); - - /// - /// Sets the new rule with the specified content. - /// - /// The content type. - /// The content. - /// - IRuleBuilder WithContent(TContentType contentType, object content); - - /// - /// Sets the new rule with the specified content container. - /// - /// The content container. - /// - [Obsolete("This way of building the content is being deprecated. Please use WithContent().")] - IRuleBuilder WithContentContainer(ContentContainer contentContainer); - - /// - /// Sets the new rule with the specified date begin. - /// - /// The date begin. - /// - IRuleBuilder WithDateBegin(DateTime dateBegin); - - /// - /// Sets the new rule with the specified dates interval. - /// - /// The date begin. - /// The date end. - /// - IRuleBuilder WithDatesInterval(DateTime dateBegin, DateTime? dateEnd); - - /// - ///Sets the new rule with the specified name. - /// - /// The name. - /// - IRuleBuilder WithName(string name); - - /// - /// Sets the new rule with the specified serialized content. - /// - /// The type of the content. - /// The serialized content. - /// The content serialization provider. - /// - /// ruleBuilder or contentSerializationProvider - IRuleBuilder WithSerializedContent( - TContentType contentType, - object serializedContent, - IContentSerializationProvider contentSerializationProvider); - } -} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/IRulesDataSourceSelector.cs b/src/Rules.Framework/Builder/IRulesDataSourceSelector.cs index 89dbc563..4fd6b502 100644 --- a/src/Rules.Framework/Builder/IRulesDataSourceSelector.cs +++ b/src/Rules.Framework/Builder/IRulesDataSourceSelector.cs @@ -1,17 +1,15 @@ namespace Rules.Framework.Builder { /// - /// The interface contract for a rules data source for rules with previously specified and . + /// The interface contract for a rules data source for rules. /// - /// The content type that allows to categorize rules. - /// The condition type that allows to filter rules based on a set of conditions. - public interface IRulesDataSourceSelector + public interface IRulesDataSourceSelector { /// - /// Sets the rules data source for rules with previously specified and . + /// Sets the rules data source for rules. /// /// the rules data source. /// a configured rules engine builder. - IConfiguredRulesEngineBuilder SetDataSource(IRulesDataSource rulesDataSource); + IConfiguredRulesEngineBuilder SetDataSource(IRulesDataSource rulesDataSource); } } \ No newline at end of file diff --git a/src/Rules.Framework/Builder/RootConditionNodeBuilder.cs b/src/Rules.Framework/Builder/RootConditionNodeBuilder.cs deleted file mode 100644 index 9ff83fad..00000000 --- a/src/Rules.Framework/Builder/RootConditionNodeBuilder.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace Rules.Framework.Builder -{ - using System; - using Rules.Framework.Core; - - internal sealed class RootConditionNodeBuilder : IRootConditionNodeBuilder - { - public IConditionNode And( - Func, IFluentComposedConditionNodeBuilder> conditionFunc) - { - return ConditionNodeFactory.CreateComposedNode(LogicalOperators.And, conditionFunc); - } - - public IConditionNode Condition(IConditionNode conditionNode) - { - return conditionNode; - } - - public IConditionNode Or( - Func, IFluentComposedConditionNodeBuilder> conditionFunc) - { - return ConditionNodeFactory.CreateComposedNode(LogicalOperators.Or, conditionFunc); - } - - public IConditionNode Value( - TConditionType conditionType, Operators condOperator, TDataType operand) - { - return ConditionNodeFactory.CreateValueNode(conditionType, condOperator, operand); - } - } -} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/RuleBuilder.cs b/src/Rules.Framework/Builder/RuleBuilder.cs deleted file mode 100644 index a5510e4c..00000000 --- a/src/Rules.Framework/Builder/RuleBuilder.cs +++ /dev/null @@ -1,138 +0,0 @@ -namespace Rules.Framework.Builder -{ - using System; - using System.Linq; - using Rules.Framework.Builder.Validation; - using Rules.Framework.Core; - using Rules.Framework.Serialization; - - internal sealed class RuleBuilder : IRuleBuilder - { - private readonly RuleValidator ruleValidator = RuleValidator.Instance; - private bool? active; - private ContentContainer contentContainer; - private DateTime dateBegin; - private DateTime? dateEnd; - private string name; - private IConditionNode rootCondition; - - public RuleBuilderResult Build() - { - var rule = new Rule - { - ContentContainer = this.contentContainer, - DateBegin = this.dateBegin, - DateEnd = this.dateEnd, - Name = this.name, - RootCondition = this.rootCondition, - Active = this.active ?? true, - }; - - var validationResult = this.ruleValidator.Validate(rule); - - if (validationResult.IsValid) - { - return RuleBuilderResult.Success(rule); - } - - return RuleBuilderResult.Failure(validationResult.Errors.Select(ve => ve.ErrorMessage).ToList()); - } - - public IRuleBuilder WithActive(bool active) - { - this.active = active; - - return this; - } - - public IRuleBuilder WithCondition(IConditionNode condition) - { - this.rootCondition = condition; - - return this; - } - - [Obsolete("This way of adding conditions is being deprecated. Please use a non-deprecated overload instead.")] - public IRuleBuilder WithCondition( - Func, IConditionNode> conditionFunc) - { - var conditionNodeBuilder = new ConditionNodeBuilder(); - - var condition = conditionFunc.Invoke(conditionNodeBuilder); - - return this.WithCondition(condition); - } - - public IRuleBuilder WithCondition( - Func, IConditionNode> conditionFunc) - { - var rootConditionNodeBuilder = new RootConditionNodeBuilder(); - - var condition = conditionFunc.Invoke(rootConditionNodeBuilder); - - return this.WithCondition(condition); - } - - public IRuleBuilder WithCondition( - TConditionType conditionType, Operators condOperator, TDataType operand) - { - var rootConditionNodeBuilder = new RootConditionNodeBuilder(); - - var valueCondition = rootConditionNodeBuilder.Value(conditionType, condOperator, operand); - - return this.WithCondition(valueCondition); - } - - public IRuleBuilder WithContent(TContentType contentType, object content) - { - this.contentContainer = new ContentContainer(contentType, _ => content); - - return this; - } - - [Obsolete("This way of building the content is being deprecated. Please use WithContent().")] - public IRuleBuilder WithContentContainer(ContentContainer contentContainer) - { - this.contentContainer = contentContainer; - - return this; - } - - public IRuleBuilder WithDateBegin(DateTime dateBegin) - { - this.dateBegin = dateBegin; - - return this; - } - - public IRuleBuilder WithDatesInterval(DateTime dateBegin, DateTime? dateEnd) - { - this.dateBegin = dateBegin; - this.dateEnd = dateEnd; - - return this; - } - - public IRuleBuilder WithName(string name) - { - this.name = name; - - return this; - } - - public IRuleBuilder WithSerializedContent( - TContentType contentType, - object serializedContent, - IContentSerializationProvider contentSerializationProvider) - { - if (contentSerializationProvider is null) - { - throw new ArgumentNullException(nameof(contentSerializationProvider)); - } - - this.contentContainer = new SerializedContentContainer(contentType, serializedContent, contentSerializationProvider); - - return this; - } - } -} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/RuleBuilderResult.cs b/src/Rules.Framework/Builder/RuleBuilderResult.cs index a9d69fd9..2c005089 100644 --- a/src/Rules.Framework/Builder/RuleBuilderResult.cs +++ b/src/Rules.Framework/Builder/RuleBuilderResult.cs @@ -2,27 +2,44 @@ namespace Rules.Framework.Builder { using System.Collections.Generic; using System.Linq; - using Rules.Framework.Core; /// - /// Allows creation of a new . + /// Contains the results information from a non-generic rule build operation. /// - public static class RuleBuilderResult + public class RuleBuilderResult : RuleBuilderResultBase { + /// + /// Initializes a new instance of the class. + /// + /// if set to true [is success]. + /// The rule. + /// The errors. + internal RuleBuilderResult(bool isSuccess, Rule rule, IEnumerable errors) + : base(isSuccess, errors) + { + this.Rule = rule; + } + + /// + /// Gets the rule. + /// + /// The rule. + public Rule Rule { get; } + /// /// Creates a result marked with failure. /// /// The errors. /// /// errors - public static RuleBuilderResult Failure(IEnumerable errors) + public static RuleBuilderResult Failure(IEnumerable errors) { if (errors is null) { throw new System.ArgumentNullException(nameof(errors)); } - return new RuleBuilderResult(false, null, errors); + return new RuleBuilderResult(isSuccess: false, null!, errors); } /// @@ -31,56 +48,14 @@ public static RuleBuilderResult FailureThe rule. /// /// rule - public static RuleBuilderResult Success(Rule rule) + public static RuleBuilderResult Success(Rule rule) { if (rule is null) { throw new System.ArgumentNullException(nameof(rule)); } - return new RuleBuilderResult(true, rule, Enumerable.Empty()); - } - } - - /// - /// Contains the results information from a rule build operation. - /// - /// The type of the content type. - /// The type of the condition type. - public class RuleBuilderResult - { - /// - /// Initializes a new instance of the class. - /// - internal RuleBuilderResult(bool isSuccess, Rule rule, IEnumerable errors) - { - this.IsSuccess = isSuccess; - this.Rule = rule; - this.Errors = errors; + return new RuleBuilderResult(isSuccess: true, rule, Enumerable.Empty()); } - - /// - /// Gets the errors. - /// - /// - /// The errors. - /// - public IEnumerable Errors { get; } - - /// - /// Gets a value indicating whether rule was built successfuly without validation errors. - /// - /// - /// true if rule was built; otherwise, false. - /// - public bool IsSuccess { get; } - - /// - /// Gets the rule. - /// - /// - /// The rule. - /// - public Rule Rule { get; } } } \ No newline at end of file diff --git a/src/Rules.Framework/Builder/RuleBuilderResultBase.cs b/src/Rules.Framework/Builder/RuleBuilderResultBase.cs new file mode 100644 index 00000000..010a2f95 --- /dev/null +++ b/src/Rules.Framework/Builder/RuleBuilderResultBase.cs @@ -0,0 +1,33 @@ +namespace Rules.Framework.Builder +{ + using System.Collections.Generic; + + /// + /// Contains the common results information from a rule build operation. + /// + public class RuleBuilderResultBase + { + /// + /// Initializes a new instance of the class. + /// + /// if set to true [is success]. + /// The errors. + protected RuleBuilderResultBase(bool isSuccess, IEnumerable errors) + { + this.IsSuccess = isSuccess; + this.Errors = errors; + } + + /// + /// Gets the errors. + /// + /// The errors. + public IEnumerable Errors { get; } + + /// + /// Gets a value indicating whether rule was built successfuly without validation errors. + /// + /// true if rule was built; otherwise, false. + public bool IsSuccess { get; } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/RulesBuilder/FluentConditionNodeBuilder.cs b/src/Rules.Framework/Builder/RulesBuilder/FluentConditionNodeBuilder.cs new file mode 100644 index 00000000..db9c3fab --- /dev/null +++ b/src/Rules.Framework/Builder/RulesBuilder/FluentConditionNodeBuilder.cs @@ -0,0 +1,55 @@ +namespace Rules.Framework.Builder.RulesBuilder +{ + using System; + using System.Collections.Generic; + using Rules.Framework; + using Rules.Framework.ConditionNodes; + using Rules.Framework.Generic; + + internal sealed class FluentConditionNodeBuilder : IFluentConditionNodeBuilder + { + private readonly List conditions; + private readonly LogicalOperators logicalOperator; + + public FluentConditionNodeBuilder(LogicalOperators logicalOperator) + { + this.logicalOperator = logicalOperator; + this.conditions = new List(2); // Most probable number of conditions, so that collection is initialized with right size most times. + } + + public IFluentConditionNodeBuilder And( + Func conditionFunc) + { + var composedConditionNode = ConditionNodeFactory.CreateComposedNode(LogicalOperators.And, conditionFunc); + this.conditions.Add(composedConditionNode); + return this; + } + + public IConditionNode Build() + { + return new ComposedConditionNode(this.logicalOperator, this.conditions); + } + + public IFluentConditionNodeBuilder Condition(IConditionNode conditionNode) + { + this.conditions.Add(conditionNode); + return this; + } + + public IFluentConditionNodeBuilder Or( + Func conditionFunc) + { + var composedConditionNode = ConditionNodeFactory.CreateComposedNode(LogicalOperators.Or, conditionFunc); + this.conditions.Add(composedConditionNode); + return this; + } + + public IFluentConditionNodeBuilder Value(string condition, Operators condOperator, T operand) + { + var conditionTypeAsString = GenericConversions.Convert(condition); + var valueConditionNode = ConditionNodeFactory.CreateValueNode(conditionTypeAsString, condOperator, operand); + this.conditions.Add(valueConditionNode); + return this; + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/RulesBuilder/IFluentConditionNodeBuilder.cs b/src/Rules.Framework/Builder/RulesBuilder/IFluentConditionNodeBuilder.cs new file mode 100644 index 00000000..0ff85d51 --- /dev/null +++ b/src/Rules.Framework/Builder/RulesBuilder/IFluentConditionNodeBuilder.cs @@ -0,0 +1,46 @@ +namespace Rules.Framework.Builder.RulesBuilder +{ + using System; + using Rules.Framework; + + /// + /// Builder for condition nodes. + /// + public interface IFluentConditionNodeBuilder + { + /// + /// Adds a 'and' composed condition to the new rule's conditions tree. + /// + /// The function containing the logic for the new condition. + /// + IFluentConditionNodeBuilder And(Func conditionFunc); + + /// + /// Builds the composed condition node. + /// + /// + IConditionNode Build(); + + /// + /// Adds a pre-built to the new rule's conditions tree. + /// + /// The condition node. + IFluentConditionNodeBuilder Condition(IConditionNode conditionNode); + + /// + /// Adds a 'or' composed condition to the new rule's conditions tree. + /// + /// The function containing the logic for the new condition. + /// + IFluentConditionNodeBuilder Or(Func conditionFunc); + + /// + /// Adds a 'value' condition to the new rule's conditions tree. + /// + /// The condition name. + /// The condition operator. + /// The condition operand. + /// + IFluentConditionNodeBuilder Value(string condition, Operators condOperator, T operand); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/RulesBuilder/IRootConditionNodeBuilder.cs b/src/Rules.Framework/Builder/RulesBuilder/IRootConditionNodeBuilder.cs new file mode 100644 index 00000000..c8e7b41c --- /dev/null +++ b/src/Rules.Framework/Builder/RulesBuilder/IRootConditionNodeBuilder.cs @@ -0,0 +1,40 @@ +namespace Rules.Framework.Builder.RulesBuilder +{ + using System; + using Rules.Framework; + + /// + /// Builder for the root condition node. + /// + public interface IRootConditionNodeBuilder + { + /// + /// Adds a 'and' composed condition to the new rule's conditions tree. + /// + /// The function containing the logic for the root condition. + /// + IConditionNode And(Func conditionFunc); + + /// + /// Adds a pre-built to the new rule's conditions tree. + /// + /// The condition node. + IConditionNode Condition(IConditionNode conditionNode); + + /// + /// Adds a 'or' composed condition to the new rule's conditions tree. + /// + /// The function containing the logic for the root condition. + /// + IConditionNode Or(Func conditionFunc); + + /// + /// Adds a 'value' condition to the new rule's conditions tree. + /// + /// The condition name. + /// The condition operator. + /// The condition operand. + /// + IConditionNode Value(string condition, Operators condOperator, T operand); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/RulesBuilder/IRuleBuilder.cs b/src/Rules.Framework/Builder/RulesBuilder/IRuleBuilder.cs new file mode 100644 index 00000000..aaf88697 --- /dev/null +++ b/src/Rules.Framework/Builder/RulesBuilder/IRuleBuilder.cs @@ -0,0 +1,49 @@ +namespace Rules.Framework.Builder.RulesBuilder +{ + using System; + using Rules.Framework; + using Rules.Framework.Builder; + + /// + /// Builder to create a new rule. + /// + public interface IRuleBuilder + { + /// + /// Sets the new rule to apply when the specified condition matches. + /// + /// The condition. + /// + IRuleBuilder ApplyWhen(IConditionNode condition); + + /// + /// Sets the new rule to apply when the condition specified by the given parameters matches. + /// + /// The type of the operands. + /// The condition name. + /// The operator. + /// The operand. + /// + IRuleBuilder ApplyWhen(string condition, Operators condOperator, T operand); + + /// + /// Sets the new rule to apply when the condition given by the specified builder matches. + /// + /// The condition func. + /// + IRuleBuilder ApplyWhen(Func conditionFunc); + + /// + /// Builds the new rule. + /// + /// + RuleBuilderResult Build(); + + /// + /// Sets the new rule with the specified active status. + /// + /// The active status. + /// + IRuleBuilder WithActive(bool active); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/RulesBuilder/IRuleConfigureContent.cs b/src/Rules.Framework/Builder/RulesBuilder/IRuleConfigureContent.cs new file mode 100644 index 00000000..40c92ccc --- /dev/null +++ b/src/Rules.Framework/Builder/RulesBuilder/IRuleConfigureContent.cs @@ -0,0 +1,25 @@ +namespace Rules.Framework.Builder.RulesBuilder +{ + using Rules.Framework.Serialization; + + /// + /// Configurer for a rule's content. + /// + public interface IRuleConfigureContent + { + /// + /// Sets the new rule with the specified content. + /// + /// The content. + /// + IRuleConfigureDateBegin SetContent(object content); + + /// + /// Sets the new rule with the specified serialized content. + /// + /// The serialized content. + /// The content serialization provider. + /// + IRuleConfigureDateBegin SetContent(object content, IContentSerializationProvider contentSerializationProvider); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/RulesBuilder/IRuleConfigureDateBegin.cs b/src/Rules.Framework/Builder/RulesBuilder/IRuleConfigureDateBegin.cs new file mode 100644 index 00000000..4c7ba6e1 --- /dev/null +++ b/src/Rules.Framework/Builder/RulesBuilder/IRuleConfigureDateBegin.cs @@ -0,0 +1,17 @@ +namespace Rules.Framework.Builder.RulesBuilder +{ + using System; + + /// + /// Configurer for a rule's begin date. + /// + public interface IRuleConfigureDateBegin + { + /// + /// Sets the new rule with the specified date begin. + /// + /// The date begin. + /// + IRuleConfigureDateEndOptional Since(DateTime dateBegin); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/RulesBuilder/IRuleConfigureDateEnd.cs b/src/Rules.Framework/Builder/RulesBuilder/IRuleConfigureDateEnd.cs new file mode 100644 index 00000000..ecbc830e --- /dev/null +++ b/src/Rules.Framework/Builder/RulesBuilder/IRuleConfigureDateEnd.cs @@ -0,0 +1,17 @@ +namespace Rules.Framework.Builder.RulesBuilder +{ + using System; + + /// + /// Configurer for a rule's end date. + /// + public interface IRuleConfigureDateEnd + { + /// + /// Sets the new rule with the specified date end. + /// + /// The date end. + /// + IRuleBuilder Until(DateTime? dateEnd); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/RulesBuilder/IRuleConfigureDateEndOptional.cs b/src/Rules.Framework/Builder/RulesBuilder/IRuleConfigureDateEndOptional.cs new file mode 100644 index 00000000..ff32613e --- /dev/null +++ b/src/Rules.Framework/Builder/RulesBuilder/IRuleConfigureDateEndOptional.cs @@ -0,0 +1,9 @@ +namespace Rules.Framework.Builder.RulesBuilder +{ + /// + /// Configurer for a rule's optional end date or proceed to remaining rule build options. + /// + public interface IRuleConfigureDateEndOptional : IRuleConfigureDateEnd, IRuleBuilder + { + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/RulesBuilder/IRuleConfigureRuleset.cs b/src/Rules.Framework/Builder/RulesBuilder/IRuleConfigureRuleset.cs new file mode 100644 index 00000000..6edc95f0 --- /dev/null +++ b/src/Rules.Framework/Builder/RulesBuilder/IRuleConfigureRuleset.cs @@ -0,0 +1,15 @@ +namespace Rules.Framework.Builder.RulesBuilder +{ + /// + /// Configurer for a rule's owner ruleset. + /// + public interface IRuleConfigureRuleset + { + /// + /// Sets the new rule to belong to the specified ruleset. + /// + /// The ruleset. + /// + IRuleConfigureContent InRuleset(string ruleset); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/RulesBuilder/RootConditionNodeBuilder.cs b/src/Rules.Framework/Builder/RulesBuilder/RootConditionNodeBuilder.cs new file mode 100644 index 00000000..a4e14e1c --- /dev/null +++ b/src/Rules.Framework/Builder/RulesBuilder/RootConditionNodeBuilder.cs @@ -0,0 +1,30 @@ +namespace Rules.Framework.Builder.RulesBuilder +{ + using System; + using Rules.Framework; + using Rules.Framework.Generic; + + internal sealed class RootConditionNodeBuilder : IRootConditionNodeBuilder + { + public IConditionNode And(Func conditionFunc) + { + return ConditionNodeFactory.CreateComposedNode(LogicalOperators.And, conditionFunc); + } + + public IConditionNode Condition(IConditionNode conditionNode) + { + return conditionNode; + } + + public IConditionNode Or(Func conditionFunc) + { + return ConditionNodeFactory.CreateComposedNode(LogicalOperators.Or, conditionFunc); + } + + public IConditionNode Value(string condition, Operators condOperator, T operand) + { + var conditionAsString = GenericConversions.Convert(condition); + return ConditionNodeFactory.CreateValueNode(conditionAsString, condOperator, operand); + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/RulesBuilder/RuleBuilder.cs b/src/Rules.Framework/Builder/RulesBuilder/RuleBuilder.cs new file mode 100644 index 00000000..1721aae8 --- /dev/null +++ b/src/Rules.Framework/Builder/RulesBuilder/RuleBuilder.cs @@ -0,0 +1,114 @@ +namespace Rules.Framework.Builder.RulesBuilder +{ + using System; + using System.Linq; + using Rules.Framework.Builder.Validation; + using Rules.Framework.Serialization; + + internal sealed class RuleBuilder : + IRuleBuilder, + IRuleConfigureContent, + IRuleConfigureDateBegin, + IRuleConfigureDateEndOptional, + IRuleConfigureRuleset + { + private readonly string name; + private readonly RuleValidator ruleValidator = RuleValidator.Instance; + + private bool? active; + private ContentContainer? contentContainer; + private DateTime dateBegin; + private DateTime? dateEnd; + private IConditionNode? rootCondition; + private string? ruleset; + + public RuleBuilder(string name) + { + this.name = name; + } + + public IRuleBuilder ApplyWhen(IConditionNode condition) + { + this.rootCondition = condition; + return this; + } + + public IRuleBuilder ApplyWhen(Func conditionFunc) + { + var rootConditionNodeBuilder = new RootConditionNodeBuilder(); + var condition = conditionFunc.Invoke(rootConditionNodeBuilder); + return this.ApplyWhen(condition); + } + + public IRuleBuilder ApplyWhen(string condition, Operators condOperator, T operand) + { + var rootConditionNodeBuilder = new RootConditionNodeBuilder(); + var valueCondition = rootConditionNodeBuilder.Value(condition, condOperator, operand); + return this.ApplyWhen(valueCondition); + } + + public RuleBuilderResult Build() + { + var rule = new Rule + { + Active = this.active ?? true, + ContentContainer = this.contentContainer!, + DateBegin = this.dateBegin, + DateEnd = this.dateEnd, + Name = this.name, + RootCondition = this.rootCondition!, + Ruleset = this.ruleset!, + }; + + var validationResult = this.ruleValidator.Validate(rule); + + if (validationResult.IsValid) + { + return RuleBuilderResult.Success(rule); + } + + return RuleBuilderResult.Failure(validationResult.Errors.Select(ve => ve.ErrorMessage).ToList()); + } + + public IRuleConfigureContent InRuleset(string ruleset) + { + this.ruleset = ruleset; + return this; + } + + public IRuleConfigureDateBegin SetContent(object content) + { + this.contentContainer = new ContentContainer(_ => content); + return this; + } + + public IRuleConfigureDateBegin SetContent(object content, IContentSerializationProvider contentSerializationProvider) + { + if (contentSerializationProvider is null) + { + throw new ArgumentNullException(nameof(contentSerializationProvider)); + } + + this.contentContainer = new SerializedContentContainer(this.ruleset!, content, contentSerializationProvider); + return this; + } + + public IRuleConfigureDateEndOptional Since(DateTime dateBegin) + { + this.dateBegin = dateBegin; + return this; + } + + public IRuleBuilder Until(DateTime? dateEnd) + { + this.dateEnd = dateEnd; + return this; + } + + public IRuleBuilder WithActive(bool active) + { + this.active = active; + return this; + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/RulesEngineOptionsValidator.cs b/src/Rules.Framework/Builder/RulesEngineOptionsValidator.cs index 19a639b4..9c159307 100644 --- a/src/Rules.Framework/Builder/RulesEngineOptionsValidator.cs +++ b/src/Rules.Framework/Builder/RulesEngineOptionsValidator.cs @@ -3,7 +3,7 @@ namespace Rules.Framework.Builder using System; using System.Collections.Generic; using System.Globalization; - using Rules.Framework.Core; + using Rules.Framework; internal static class RulesEngineOptionsValidator { diff --git a/src/Rules.Framework/Builder/RulesEngineSelectors.cs b/src/Rules.Framework/Builder/RulesEngineSelectors.cs index b105af8f..87da7b4e 100644 --- a/src/Rules.Framework/Builder/RulesEngineSelectors.cs +++ b/src/Rules.Framework/Builder/RulesEngineSelectors.cs @@ -4,27 +4,16 @@ namespace Rules.Framework.Builder internal static class RulesEngineSelectors { - internal sealed class ConditionTypeSelector : IConditionTypeSelector + internal sealed class RulesDataSourceSelector : IRulesDataSourceSelector { - public IRulesDataSourceSelector WithConditionType() - => new RulesDataSourceSelector(); - } - - internal sealed class ContentTypeSelector : IContentTypeSelector - { - public IConditionTypeSelector WithContentType() => new ConditionTypeSelector(); - } - - internal sealed class RulesDataSourceSelector : IRulesDataSourceSelector - { - public IConfiguredRulesEngineBuilder SetDataSource(IRulesDataSource rulesDataSource) + public IConfiguredRulesEngineBuilder SetDataSource(IRulesDataSource rulesDataSource) { if (rulesDataSource == null) { throw new ArgumentNullException(nameof(rulesDataSource)); } - return new ConfiguredRulesEngineBuilder(rulesDataSource); + return new ConfiguredRulesEngineBuilder(rulesDataSource); } } } diff --git a/src/Rules.Framework/Builder/Validation/ComposedConditionNodeValidator.cs b/src/Rules.Framework/Builder/Validation/ComposedConditionNodeValidator.cs index 3273b037..af1db45b 100644 --- a/src/Rules.Framework/Builder/Validation/ComposedConditionNodeValidator.cs +++ b/src/Rules.Framework/Builder/Validation/ComposedConditionNodeValidator.cs @@ -1,21 +1,21 @@ namespace Rules.Framework.Builder.Validation { using FluentValidation; - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; + using Rules.Framework; + using Rules.Framework.ConditionNodes; - internal sealed class ComposedConditionNodeValidator : AbstractValidator> + internal sealed class ComposedConditionNodeValidator : AbstractValidator { - private readonly ValueConditionNodeValidator valueConditionNodeValidator; + private readonly ValueConditionNodeValidator valueConditionNodeValidator; public ComposedConditionNodeValidator() { - this.valueConditionNodeValidator = new ValueConditionNodeValidator(); + this.valueConditionNodeValidator = new ValueConditionNodeValidator(); - this.RuleFor(c => c.LogicalOperator).IsContainedOn(LogicalOperators.And, LogicalOperators.Or); + this.RuleFor(c => c.LogicalOperator).IsContainedOn(LogicalOperators.And, LogicalOperators.Or); this.RuleForEach(c => c.ChildConditionNodes) .NotNull() - .Custom((cn, cc) => cn.PerformValidation(new ConditionNodeValidationArgs> + .Custom((cn, cc) => cn.PerformValidation(new ConditionNodeValidationArgs { ComposedConditionNodeValidator = this, ValidationContext = cc, diff --git a/src/Rules.Framework/Builder/Validation/ConditionNodeValidationArgs.cs b/src/Rules.Framework/Builder/Validation/ConditionNodeValidationArgs.cs index 12626dbe..7f51687e 100644 --- a/src/Rules.Framework/Builder/Validation/ConditionNodeValidationArgs.cs +++ b/src/Rules.Framework/Builder/Validation/ConditionNodeValidationArgs.cs @@ -2,10 +2,10 @@ namespace Rules.Framework.Builder.Validation { using FluentValidation; - internal sealed class ConditionNodeValidationArgs + internal sealed class ConditionNodeValidationArgs { - public ComposedConditionNodeValidator ComposedConditionNodeValidator { get; set; } + public ComposedConditionNodeValidator ComposedConditionNodeValidator { get; set; } public ValidationContext ValidationContext { get; set; } - public ValueConditionNodeValidator ValueConditionNodeValidator { get; set; } + public ValueConditionNodeValidator ValueConditionNodeValidator { get; set; } } } \ No newline at end of file diff --git a/src/Rules.Framework/Builder/Validation/ConditionNodeValidationExtensions.cs b/src/Rules.Framework/Builder/Validation/ConditionNodeValidationExtensions.cs index c4418755..cba64c07 100644 --- a/src/Rules.Framework/Builder/Validation/ConditionNodeValidationExtensions.cs +++ b/src/Rules.Framework/Builder/Validation/ConditionNodeValidationExtensions.cs @@ -1,17 +1,18 @@ namespace Rules.Framework.Builder.Validation { using FluentValidation.Results; - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; + using Rules.Framework.ConditionNodes; + using Rules.Framework.Generic; + using Rules.Framework.Generic.ConditionNodes; internal static class ConditionNodeValidationExtensions { - public static void PerformValidation(this IConditionNode conditionNode, ConditionNodeValidationArgs conditionNodeValidationArgs) + public static void PerformValidation(this IConditionNode conditionNode, GenericConditionNodeValidationArgs conditionNodeValidationArgs) { ValidationResult validationResult; switch (conditionNode) { - case ComposedConditionNode composedConditionNode: + case ComposedConditionNode composedConditionNode: validationResult = conditionNodeValidationArgs.ComposedConditionNodeValidator.Validate(composedConditionNode); break; @@ -19,13 +20,39 @@ public static void PerformValidation(this IC return; default: - validationResult = conditionNodeValidationArgs.ValueConditionNodeValidator.Validate(conditionNode as ValueConditionNode); + validationResult = conditionNodeValidationArgs.ValueConditionNodeValidator.Validate((ValueConditionNode)conditionNode); break; } if (!validationResult.IsValid) { - foreach (ValidationFailure validationFailure in validationResult.Errors) + foreach (var validationFailure in validationResult.Errors) + { + conditionNodeValidationArgs.ValidationContext.AddFailure(validationFailure); + } + } + } + + public static void PerformValidation(this IConditionNode conditionNode, ConditionNodeValidationArgs conditionNodeValidationArgs) + { + ValidationResult validationResult; + switch (conditionNode) + { + case ComposedConditionNode composedConditionNode: + validationResult = conditionNodeValidationArgs.ComposedConditionNodeValidator.Validate(composedConditionNode); + break; + + case null: + return; + + default: + validationResult = conditionNodeValidationArgs.ValueConditionNodeValidator.Validate((ValueConditionNode)conditionNode); + break; + } + + if (!validationResult.IsValid) + { + foreach (var validationFailure in validationResult.Errors) { conditionNodeValidationArgs.ValidationContext.AddFailure(validationFailure); } diff --git a/src/Rules.Framework/Builder/Validation/GenericComposedConditionNodeValidator.cs b/src/Rules.Framework/Builder/Validation/GenericComposedConditionNodeValidator.cs new file mode 100644 index 00000000..4655d981 --- /dev/null +++ b/src/Rules.Framework/Builder/Validation/GenericComposedConditionNodeValidator.cs @@ -0,0 +1,24 @@ +namespace Rules.Framework.Builder.Validation +{ + using FluentValidation; + using Rules.Framework.Generic.ConditionNodes; + + internal sealed class GenericComposedConditionNodeValidator : AbstractValidator> + { + private readonly GenericValueConditionNodeValidator valueConditionNodeValidator; + + public GenericComposedConditionNodeValidator() + { + this.valueConditionNodeValidator = new GenericValueConditionNodeValidator(); + + this.RuleForEach(c => c.ChildConditionNodes) + .NotNull() + .Custom((cn, cc) => cn.PerformValidation(new GenericConditionNodeValidationArgs> + { + ComposedConditionNodeValidator = this, + ValidationContext = cc, + ValueConditionNodeValidator = this.valueConditionNodeValidator, + })); + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/Validation/GenericConditionNodeValidationArgs.cs b/src/Rules.Framework/Builder/Validation/GenericConditionNodeValidationArgs.cs new file mode 100644 index 00000000..c3785a30 --- /dev/null +++ b/src/Rules.Framework/Builder/Validation/GenericConditionNodeValidationArgs.cs @@ -0,0 +1,11 @@ +namespace Rules.Framework.Builder.Validation +{ + using FluentValidation; + + internal sealed class GenericConditionNodeValidationArgs + { + public GenericComposedConditionNodeValidator ComposedConditionNodeValidator { get; set; } + public ValidationContext ValidationContext { get; set; } + public GenericValueConditionNodeValidator ValueConditionNodeValidator { get; set; } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/Validation/GenericRuleValidator.cs b/src/Rules.Framework/Builder/Validation/GenericRuleValidator.cs new file mode 100644 index 00000000..5ee00a96 --- /dev/null +++ b/src/Rules.Framework/Builder/Validation/GenericRuleValidator.cs @@ -0,0 +1,36 @@ +namespace Rules.Framework.Builder.Validation +{ + using FluentValidation; + using Rules.Framework.Generic; + + internal sealed class GenericRuleValidator : AbstractValidator> + { + private static GenericRuleValidator ruleValidator; + + private readonly GenericComposedConditionNodeValidator composedConditionNodeValidator; + + private readonly GenericValueConditionNodeValidator valueConditionNodeValidator; + + private GenericRuleValidator() + { + this.composedConditionNodeValidator = new GenericComposedConditionNodeValidator(); + this.valueConditionNodeValidator = new GenericValueConditionNodeValidator(); + this.RuleFor(r => r.RootCondition).Custom((cn, cc) => cn.PerformValidation(new GenericConditionNodeValidationArgs> + { + ComposedConditionNodeValidator = this.composedConditionNodeValidator, + ValidationContext = cc, + ValueConditionNodeValidator = this.valueConditionNodeValidator, + })); + } + + public static GenericRuleValidator Instance + { + get + { + ruleValidator ??= new GenericRuleValidator(); + + return ruleValidator; + } + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/Validation/GenericValueConditionNodeValidator.cs b/src/Rules.Framework/Builder/Validation/GenericValueConditionNodeValidator.cs new file mode 100644 index 00000000..aeea4436 --- /dev/null +++ b/src/Rules.Framework/Builder/Validation/GenericValueConditionNodeValidator.cs @@ -0,0 +1,16 @@ +namespace Rules.Framework.Builder.Validation +{ + using FluentValidation; + using Rules.Framework.Generic.ConditionNodes; + + internal sealed class GenericValueConditionNodeValidator : AbstractValidator> + { + public GenericValueConditionNodeValidator() + { + this.RuleFor(c => c.Condition) + .NotEmpty() + .IsInEnum() + .When(c => c.Condition!.GetType().IsEnum); + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Builder/Validation/RuleValidator.cs b/src/Rules.Framework/Builder/Validation/RuleValidator.cs index 5cb99bfc..05a13829 100644 --- a/src/Rules.Framework/Builder/Validation/RuleValidator.cs +++ b/src/Rules.Framework/Builder/Validation/RuleValidator.cs @@ -1,41 +1,31 @@ namespace Rules.Framework.Builder.Validation { using FluentValidation; - using Rules.Framework.Core; - internal sealed class RuleValidator : AbstractValidator> + internal sealed class RuleValidator : AbstractValidator { - private static RuleValidator ruleValidator; + private readonly ComposedConditionNodeValidator composedConditionNodeValidator; - private readonly ComposedConditionNodeValidator composedConditionNodeValidator; - - private readonly ValueConditionNodeValidator valueConditionNodeValidator; + private readonly ValueConditionNodeValidator valueConditionNodeValidator; private RuleValidator() { - this.composedConditionNodeValidator = new ComposedConditionNodeValidator(); - this.valueConditionNodeValidator = new ValueConditionNodeValidator(); + this.composedConditionNodeValidator = new ComposedConditionNodeValidator(); + this.valueConditionNodeValidator = new ValueConditionNodeValidator(); this.RuleFor(r => r.ContentContainer).NotNull(); this.RuleFor(r => r.DateBegin).NotEmpty(); this.RuleFor(r => r.DateEnd).GreaterThanOrEqualTo(r => r.DateBegin).When(r => r.DateEnd != null); this.RuleFor(r => r.Name).NotNull().NotEmpty(); - this.RuleFor(r => r.RootCondition).Custom((cn, cc) => cn.PerformValidation(new ConditionNodeValidationArgs> + this.RuleFor(r => r.RootCondition).Custom((cn, cc) => cn.PerformValidation(new ConditionNodeValidationArgs { ComposedConditionNodeValidator = this.composedConditionNodeValidator, ValidationContext = cc, - ValueConditionNodeValidator = this.valueConditionNodeValidator + ValueConditionNodeValidator = this.valueConditionNodeValidator, })); + this.RuleFor(r => r.Ruleset).NotEmpty(); } - public static RuleValidator Instance - { - get - { - ruleValidator ??= new RuleValidator(); - - return ruleValidator; - } - } + public static RuleValidator Instance { get; } = new RuleValidator(); } } \ No newline at end of file diff --git a/src/Rules.Framework/Builder/Validation/ValueConditionNodeValidator.cs b/src/Rules.Framework/Builder/Validation/ValueConditionNodeValidator.cs index d8281cfe..2c2d4bb1 100644 --- a/src/Rules.Framework/Builder/Validation/ValueConditionNodeValidator.cs +++ b/src/Rules.Framework/Builder/Validation/ValueConditionNodeValidator.cs @@ -2,17 +2,15 @@ namespace Rules.Framework.Builder.Validation { using System.Collections.Generic; using FluentValidation; - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; + using Rules.Framework; + using Rules.Framework.ConditionNodes; - internal sealed class ValueConditionNodeValidator : AbstractValidator> + internal sealed class ValueConditionNodeValidator : AbstractValidator { public ValueConditionNodeValidator() { - this.RuleFor(c => c.ConditionType) - .NotEmpty() - .IsInEnum() - .When(c => c.ConditionType.GetType().IsEnum); + this.RuleFor(c => c.Condition) + .NotEmpty(); this.RuleFor(c => c.DataType) .IsInEnum() diff --git a/src/Rules.Framework/Condition.cs b/src/Rules.Framework/Condition.cs deleted file mode 100644 index 878170dd..00000000 --- a/src/Rules.Framework/Condition.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace Rules.Framework -{ - using System; - - /// - /// Defines a condition to filter rules. - /// - /// - /// The condition type that allows to filter rules based on a set of conditions. - /// - public class Condition - { - /// - /// Creates a Condition. - /// - /// The type of the condition. - /// The value of the condition. - public Condition(TConditionType type, object value) - { - this.Type = type; - this.Value = value; - } - - /// - /// Creates a Condition. - /// - [Obsolete("Please use the constructor with parameters instead.")] - public Condition() - { - } - - /// - /// Gets or sets the condition type. - /// - public TConditionType Type { get; set; } - - /// - /// Gets or sets the condition value. - /// - public object Value { get; set; } - } -} \ No newline at end of file diff --git a/src/Rules.Framework/Core/ConditionNodes/ComposedConditionNode.cs b/src/Rules.Framework/ConditionNodes/ComposedConditionNode.cs similarity index 63% rename from src/Rules.Framework/Core/ConditionNodes/ComposedConditionNode.cs rename to src/Rules.Framework/ConditionNodes/ComposedConditionNode.cs index d159c94e..54730981 100644 --- a/src/Rules.Framework/Core/ConditionNodes/ComposedConditionNode.cs +++ b/src/Rules.Framework/ConditionNodes/ComposedConditionNode.cs @@ -1,41 +1,40 @@ -namespace Rules.Framework.Core.ConditionNodes +namespace Rules.Framework.ConditionNodes { using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; + using Rules.Framework; + using Rules.Framework.Core; /// /// A composed condition node which aggregates a set of child condition nodes and defines a /// logical operator to apply to them. /// - /// - /// The condition type that allows to filter rules based on a set of conditions. - /// [DebuggerDisplay("Composed condition: apply {LogicalOperator.ToString(),nq} operator for {System.Linq.Enumerable.Count(ChildConditionNodes),nq} nodes")] - public class ComposedConditionNode : IConditionNode + public class ComposedConditionNode : IConditionNode { /// - /// Creates a new . + /// Creates a new . /// /// the logical operator. /// the set of child condition nodes. public ComposedConditionNode( LogicalOperators logicalOperator, - IEnumerable> childConditionNodes) + IEnumerable childConditionNodes) : this(logicalOperator, childConditionNodes, new PropertiesDictionary(Constants.DefaultPropertiesDictionarySize)) { } /// - /// Creates a new . + /// Creates a new . /// /// the logical operator. /// the set of child condition nodes. /// the properties. public ComposedConditionNode( LogicalOperators logicalOperator, - IEnumerable> childConditionNodes, + IEnumerable childConditionNodes, IDictionary properties) { this.LogicalOperator = logicalOperator; @@ -43,27 +42,18 @@ public ComposedConditionNode( this.Properties = properties; } - /// - /// Gets the child condition nodes. - /// - public IEnumerable> ChildConditionNodes { get; } + /// + public IEnumerable ChildConditionNodes { get; } - /// - /// Gets the logical operator to apply between child condition nodes. - /// + /// public LogicalOperators LogicalOperator { get; } - /// - /// Gets the condition node properties. - /// + /// public IDictionary Properties { get; } - /// - /// Clones the condition node into a different instance. - /// - /// - public IConditionNode Clone() - => new ComposedConditionNode( + /// + public IConditionNode Clone() + => new ComposedConditionNode( this.LogicalOperator, this.ChildConditionNodes.Select(cn => cn.Clone()).ToList().AsReadOnly(), new PropertiesDictionary(this.Properties)); @@ -76,7 +66,7 @@ public IConditionNode Clone() /// true if the specified is equal to this instance; /// otherwise, false. /// - public override bool Equals(object obj) => obj is ComposedConditionNode node && EqualityComparer>>.Default.Equals(this.ChildConditionNodes, node.ChildConditionNodes) && this.LogicalOperator == node.LogicalOperator && EqualityComparer>.Default.Equals(this.Properties, node.Properties); + public override bool Equals(object obj) => obj is ComposedConditionNode node && EqualityComparer>.Default.Equals(this.ChildConditionNodes, node.ChildConditionNodes) && this.LogicalOperator == node.LogicalOperator && EqualityComparer>.Default.Equals(this.Properties, node.Properties); /// /// Returns a hash code for this instance. diff --git a/src/Rules.Framework/Core/ConditionNodes/IValueConditionNode.cs b/src/Rules.Framework/ConditionNodes/IValueConditionNode.cs similarity index 56% rename from src/Rules.Framework/Core/ConditionNodes/IValueConditionNode.cs rename to src/Rules.Framework/ConditionNodes/IValueConditionNode.cs index 7f75aa4a..7d623395 100644 --- a/src/Rules.Framework/Core/ConditionNodes/IValueConditionNode.cs +++ b/src/Rules.Framework/ConditionNodes/IValueConditionNode.cs @@ -1,17 +1,16 @@ -namespace Rules.Framework.Core.ConditionNodes +namespace Rules.Framework.ConditionNodes { + using Rules.Framework; + /// /// Defines the interface contract for a condition node based on a value comparison. /// - /// - /// The condition type that allows to filter rules based on a set of conditions. - /// - public interface IValueConditionNode : IConditionNode + public interface IValueConditionNode : IConditionNode { /// - /// Gets the condition node type. + /// Gets the condition name. /// - TConditionType ConditionType { get; } + string Condition { get; } /// /// Gets the condition node data type. @@ -21,7 +20,7 @@ public interface IValueConditionNode : IConditionNode /// Gets the condition's operand. /// - public object Operand { get; } + object Operand { get; } /// /// Gets the condition node operator. diff --git a/src/Rules.Framework/ConditionNodes/ValueConditionNode.cs b/src/Rules.Framework/ConditionNodes/ValueConditionNode.cs new file mode 100644 index 00000000..d13ace9f --- /dev/null +++ b/src/Rules.Framework/ConditionNodes/ValueConditionNode.cs @@ -0,0 +1,92 @@ +namespace Rules.Framework.ConditionNodes +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using Rules.Framework; + using Rules.Framework.Core; + + /// + /// A generic implementation for a valued condition node. + /// + /// + [DebuggerDisplay("{DataType.ToString(),nq} condition: <{Condition,nq}> {Operator} {Operand}")] + public class ValueConditionNode : IValueConditionNode + { + /// + /// Initializes a new instance of the class. + /// + /// Type of the data. + /// The condition name. + /// The operator. + /// The operand. + public ValueConditionNode(DataTypes dataType, string condition, Operators @operator, object operand) + : this(dataType, condition, @operator, operand, new PropertiesDictionary(Constants.DefaultPropertiesDictionarySize)) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Type of the data. + /// The condition name. + /// The operator. + /// The operand. + /// The properties. + public ValueConditionNode(DataTypes dataType, string condition, Operators @operator, object operand, IDictionary properties) + { + this.Condition = condition; + this.DataType = dataType; + this.Operand = operand; + this.Operator = @operator; + this.Properties = properties; + } + + /// + public string Condition { get; } + + /// + public DataTypes DataType { get; } + + /// + public LogicalOperators LogicalOperator => LogicalOperators.Eval; + + /// + public object Operand { get; } + + /// + public Operators Operator { get; } + + /// + public IDictionary Properties { get; } + + /// + public IConditionNode Clone() + => new ValueConditionNode( + this.DataType, + this.Condition, + this.Operator, + this.Operand, + new PropertiesDictionary(this.Properties)); + + /// + /// Determines whether the specified , is equal to this instance. + /// + /// The to compare with this instance. + /// + /// true if the specified is equal to this instance; + /// otherwise, false. + /// + public override bool Equals(object obj) => obj is ValueConditionNode node && StringComparer.Ordinal.Equals(this.Condition, node.Condition) && this.DataType == node.DataType && this.LogicalOperator == node.LogicalOperator && EqualityComparer.Default.Equals(this.Operand, node.Operand) && this.Operator == node.Operator && EqualityComparer>.Default.Equals(this.Properties, node.Properties); + + /// + /// Returns a hash code for this instance. + /// + /// + /// A hash code for this instance, suitable for use in hashing algorithms and data + /// structures like a hash table. + /// + public override int GetHashCode() + => HashCode.Combine(this.Condition, this.DataType, this.LogicalOperator, this.Operand, this.Operator, this.Properties); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/ConditionTypeExtractor.cs b/src/Rules.Framework/ConditionTypeExtractor.cs deleted file mode 100644 index eb52e57a..00000000 --- a/src/Rules.Framework/ConditionTypeExtractor.cs +++ /dev/null @@ -1,75 +0,0 @@ -namespace Rules.Framework -{ - using System; - using System.Collections.Generic; - using System.Linq; - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; - - /// - /// Extracts Conditions Types from a Group of Rules. - /// - /// The content type that allows to categorize rules. - /// - /// The condition type that allows to filter rules based on a set of conditions. - /// - public class ConditionTypeExtractor : IConditionTypeExtractor - { - /// - /// Get the unique condition types associated with rules of a specific content type. - /// - /// - /// - /// - /// A set of rules is requested to rules data source and all conditions are evaluated - /// against them to provide a set of matches. - /// - /// All rules matching supplied conditions are returned. - /// - /// the matched rule; otherwise, empty. - public IEnumerable GetConditionTypes(IEnumerable> matchedRules) - { - var conditionTypes = new HashSet(); - - if (!matchedRules.Any()) - { - return conditionTypes; - } - - foreach (var rootCondition in matchedRules.Select(r => r.RootCondition)) - { - if (rootCondition is null) - { - continue; - } - - VisitConditionNode(rootCondition, conditionTypes); - } - - return conditionTypes; - } - - private static void VisitConditionNode(IConditionNode conditionNode, HashSet conditionTypes) - { - switch (conditionNode) - { - case IValueConditionNode valueConditionNode: - - conditionTypes.Add(valueConditionNode.ConditionType); - break; - - case ComposedConditionNode composedConditionNode: - - foreach (IConditionNode childConditionNode in composedConditionNode.ChildConditionNodes) - { - VisitConditionNode(childConditionNode, conditionTypes); - } - - break; - - default: - throw new NotSupportedException($"Unsupported condition node: '{conditionNode.GetType().Name}'."); - } - } - } -} \ No newline at end of file diff --git a/src/Rules.Framework/Core/ContentContainer.cs b/src/Rules.Framework/ContentContainer.cs similarity index 61% rename from src/Rules.Framework/Core/ContentContainer.cs rename to src/Rules.Framework/ContentContainer.cs index b45cbee5..da7377cf 100644 --- a/src/Rules.Framework/Core/ContentContainer.cs +++ b/src/Rules.Framework/ContentContainer.cs @@ -1,31 +1,25 @@ -namespace Rules.Framework.Core +namespace Rules.Framework { using System; /// /// Defines a content container with lazily loaded content. /// - /// - public class ContentContainer + public class ContentContainer { private readonly Func getContentFunc; /// - /// Creates a new . + /// Creates a new . /// - /// the content type. - /// the function used to fetch content casted to provided type. - public ContentContainer(TContentType contentType, Func getContentFunc) + /// + /// the function used to fetch content casted to provided type. + /// + public ContentContainer(Func getContentFunc) { - this.ContentType = contentType; this.getContentFunc = getContentFunc; } - /// - /// Gets the content type. - /// - public TContentType ContentType { get; } - /// /// Gets the content from container casted/converted to specified . /// diff --git a/src/Rules.Framework/Core/ContentTypeException.cs b/src/Rules.Framework/ContentTypeException.cs similarity index 97% rename from src/Rules.Framework/Core/ContentTypeException.cs rename to src/Rules.Framework/ContentTypeException.cs index b33ac389..711907eb 100644 --- a/src/Rules.Framework/Core/ContentTypeException.cs +++ b/src/Rules.Framework/ContentTypeException.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Core +namespace Rules.Framework { using System; using System.Diagnostics.CodeAnalysis; diff --git a/src/Rules.Framework/Core/ConditionNodes/ValueConditionNode.cs b/src/Rules.Framework/Core/ConditionNodes/ValueConditionNode.cs deleted file mode 100644 index ffd387a6..00000000 --- a/src/Rules.Framework/Core/ConditionNodes/ValueConditionNode.cs +++ /dev/null @@ -1,109 +0,0 @@ -namespace Rules.Framework.Core.ConditionNodes -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - - /// - /// A generic implementation for a valued condition node. - /// - /// - /// The condition type that allows to filter rules based on a set of conditions. - /// - /// - [DebuggerDisplay("{DataType.ToString(),nq} condition: <{ConditionType.ToString(),nq}> {Operator} {Operand}")] - public class ValueConditionNode : IValueConditionNode - { - /// - /// Initializes a new instance of the class. - /// - /// Type of the data. - /// Type of the condition. - /// The operator. - /// The operand. - public ValueConditionNode(DataTypes dataType, TConditionType conditionType, Operators @operator, object operand) - : this(dataType, conditionType, @operator, operand, new PropertiesDictionary(Constants.DefaultPropertiesDictionarySize)) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Type of the data. - /// Type of the condition. - /// The operator. - /// The operand. - /// The properties. - public ValueConditionNode(DataTypes dataType, TConditionType conditionType, Operators @operator, object operand, IDictionary properties) - { - this.ConditionType = conditionType; - this.DataType = dataType; - this.Operand = operand; - this.Operator = @operator; - this.Properties = properties; - } - - /// - /// Gets the condition node type. - /// - public TConditionType ConditionType { get; } - - /// - /// Gets the condition node data type. - /// - public DataTypes DataType { get; } - - /// - /// Gets the logical operator to apply to condition node. - /// - public LogicalOperators LogicalOperator => LogicalOperators.Eval; - - /// - /// Gets the condition's operand. - /// - /// The operand. - public object Operand { get; } - - /// - /// Gets the condition node operator. - /// - public Operators Operator { get; } - - /// - /// Gets the condition node properties. - /// - public IDictionary Properties { get; } - - /// - /// Clones the condition node into a different instance. - /// - /// - public IConditionNode Clone() - => new ValueConditionNode( - this.DataType, - this.ConditionType, - this.Operator, - this.Operand, - new PropertiesDictionary(this.Properties)); - - /// - /// Determines whether the specified , is equal to this instance. - /// - /// The to compare with this instance. - /// - /// true if the specified is equal to this instance; - /// otherwise, false. - /// - public override bool Equals(object obj) => obj is ValueConditionNode node && EqualityComparer.Default.Equals(this.ConditionType, node.ConditionType) && this.DataType == node.DataType && this.LogicalOperator == node.LogicalOperator && EqualityComparer.Default.Equals(this.Operand, node.Operand) && this.Operator == node.Operator && EqualityComparer>.Default.Equals(this.Properties, node.Properties); - - /// - /// Returns a hash code for this instance. - /// - /// - /// A hash code for this instance, suitable for use in hashing algorithms and data - /// structures like a hash table. - /// - public override int GetHashCode() - => HashCode.Combine(this.ConditionType, this.DataType, this.LogicalOperator, this.Operand, this.Operator, this.Properties); - } -} \ No newline at end of file diff --git a/src/Rules.Framework/Core/PropertiesDictionary.cs b/src/Rules.Framework/Core/PropertiesDictionary.cs index 37914120..75904f27 100644 --- a/src/Rules.Framework/Core/PropertiesDictionary.cs +++ b/src/Rules.Framework/Core/PropertiesDictionary.cs @@ -4,6 +4,7 @@ namespace Rules.Framework.Core using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; + using Rules.Framework.Generic; /// /// A dictionary to hold the properties of a . diff --git a/src/Rules.Framework/Core/TypesCache.cs b/src/Rules.Framework/Core/TypesCache.cs new file mode 100644 index 00000000..60d3bda6 --- /dev/null +++ b/src/Rules.Framework/Core/TypesCache.cs @@ -0,0 +1,9 @@ +namespace Rules.Framework.Core +{ + using System; + + internal static class TypesCache + { + public static Type String { get; } = typeof(string); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Core/DataTypes.cs b/src/Rules.Framework/DataTypes.cs similarity index 97% rename from src/Rules.Framework/Core/DataTypes.cs rename to src/Rules.Framework/DataTypes.cs index 43ece110..30a30439 100644 --- a/src/Rules.Framework/Core/DataTypes.cs +++ b/src/Rules.Framework/DataTypes.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Core +namespace Rules.Framework { /// /// Defines the supported data types a condition node can assume. diff --git a/src/Rules.Framework/Evaluation/Compiled/BuildValueConditionNodeExpressionArgs.cs b/src/Rules.Framework/Evaluation/Compiled/BuildValueConditionNodeExpressionArgs.cs index d507fed6..f5be24eb 100644 --- a/src/Rules.Framework/Evaluation/Compiled/BuildValueConditionNodeExpressionArgs.cs +++ b/src/Rules.Framework/Evaluation/Compiled/BuildValueConditionNodeExpressionArgs.cs @@ -3,7 +3,7 @@ namespace Rules.Framework.Evaluation.Compiled using System; using System.Collections.Generic; using System.Linq.Expressions; - using Rules.Framework.Core; + using Rules.Framework; internal struct BuildValueConditionNodeExpressionArgs : IEquatable { diff --git a/src/Rules.Framework/Evaluation/Compiled/CompilationRulesSourceMiddleware.cs b/src/Rules.Framework/Evaluation/Compiled/CompilationRulesSourceMiddleware.cs index ff12eecd..8543cd4f 100644 --- a/src/Rules.Framework/Evaluation/Compiled/CompilationRulesSourceMiddleware.cs +++ b/src/Rules.Framework/Evaluation/Compiled/CompilationRulesSourceMiddleware.cs @@ -5,37 +5,39 @@ namespace Rules.Framework.Evaluation.Compiled using Rules.Framework.Core; using Rules.Framework.Source; - internal sealed class CompilationRulesSourceMiddleware : IRulesSourceMiddleware + internal sealed class CompilationRulesSourceMiddleware : IRulesSourceMiddleware { - private readonly IRuleConditionsExpressionBuilder ruleConditionsExpressionBuilder; - private readonly IRulesDataSource rulesDataSource; + private readonly IRuleConditionsExpressionBuilder ruleConditionsExpressionBuilder; + private readonly IRulesDataSource rulesDataSource; public CompilationRulesSourceMiddleware( - IRuleConditionsExpressionBuilder ruleConditionsExpressionBuilder, - IRulesDataSource rulesDataSource) + IRuleConditionsExpressionBuilder ruleConditionsExpressionBuilder, + IRulesDataSource rulesDataSource) { this.ruleConditionsExpressionBuilder = ruleConditionsExpressionBuilder; this.rulesDataSource = rulesDataSource; } public async Task HandleAddRuleAsync( - AddRuleArgs args, - AddRuleDelegate next) + AddRuleArgs args, + AddRuleDelegate next) { this.TryCompile(args.Rule); await next.Invoke(args).ConfigureAwait(false); } - public async Task>> HandleGetRulesAsync( - GetRulesArgs args, - GetRulesDelegate next) + public Task HandleCreateRulesetAsync(CreateRulesetArgs args, CreateRulesetDelegate next) => next.Invoke(args); + + public async Task> HandleGetRulesAsync( + GetRulesArgs args, + GetRulesDelegate next) { var rules = await next.Invoke(args).ConfigureAwait(false); foreach (var rule in rules) { - bool compiled = this.TryCompile(rule); + var compiled = this.TryCompile(rule); if (compiled) { // Commit compilation result to data source, so that next time rule is loaded, @@ -47,15 +49,17 @@ public async Task>> HandleGetRule return rules; } - public async Task>> HandleGetRulesFilteredAsync( - GetRulesFilteredArgs args, - GetRulesFilteredDelegate next) + public Task> HandleGetRulesetsAsync(GetRulesetsArgs args, GetRulesetsDelegate next) => next.Invoke(args); + + public async Task> HandleGetRulesFilteredAsync( + GetRulesFilteredArgs args, + GetRulesFilteredDelegate next) { var rules = await next.Invoke(args).ConfigureAwait(false); foreach (var rule in rules) { - bool compiled = this.TryCompile(rule); + var compiled = this.TryCompile(rule); if (compiled) { // Commit compilation result to data source, so that next time rule is loaded, @@ -68,15 +72,15 @@ public async Task>> HandleGetRule } public async Task HandleUpdateRuleAsync( - UpdateRuleArgs args, - UpdateRuleDelegate next) + UpdateRuleArgs args, + UpdateRuleDelegate next) { this.TryCompile(args.Rule); await next.Invoke(args).ConfigureAwait(false); } - private bool TryCompile(Rule rule) + private bool TryCompile(Rule rule) { var conditionNode = rule.RootCondition; diff --git a/src/Rules.Framework/Evaluation/Compiled/CompiledConditionsEvalEngine.cs b/src/Rules.Framework/Evaluation/Compiled/CompiledConditionsEvalEngine.cs index ee7c4f5d..9b54a69f 100644 --- a/src/Rules.Framework/Evaluation/Compiled/CompiledConditionsEvalEngine.cs +++ b/src/Rules.Framework/Evaluation/Compiled/CompiledConditionsEvalEngine.cs @@ -3,21 +3,22 @@ namespace Rules.Framework.Evaluation.Compiled using System; using System.Collections.Generic; using Rules.Framework.Core; + using Rules.Framework.Generic; - internal sealed class CompiledConditionsEvalEngine : IConditionsEvalEngine + internal sealed class CompiledConditionsEvalEngine : IConditionsEvalEngine { - private readonly IConditionsTreeAnalyzer conditionsTreeAnalyzer; + private readonly IConditionsTreeAnalyzer conditionsTreeAnalyzer; private readonly RulesEngineOptions rulesEngineOptions; public CompiledConditionsEvalEngine( - IConditionsTreeAnalyzer conditionsTreeAnalyzer, + IConditionsTreeAnalyzer conditionsTreeAnalyzer, RulesEngineOptions rulesEngineOptions) { this.conditionsTreeAnalyzer = conditionsTreeAnalyzer; this.rulesEngineOptions = rulesEngineOptions; } - public bool Eval(IConditionNode conditionNode, IDictionary conditions, EvaluationOptions evaluationOptions) + public bool Eval(IConditionNode conditionNode, IDictionary conditions, EvaluationOptions evaluationOptions) { if (evaluationOptions.ExcludeRulesWithoutSearchConditions && !this.conditionsTreeAnalyzer.AreAllSearchConditionsPresent(conditionNode, conditions)) { @@ -29,8 +30,8 @@ public bool Eval(IConditionNode conditionNode, IDictionary, bool>)conditionFuncAux; - var compiledConditionsEvaluationContext = new EvaluationContext( + var conditionFunc = (Func)conditionFuncAux; + var compiledConditionsEvaluationContext = new EvaluationContext( conditions, evaluationOptions.MatchMode, this.rulesEngineOptions.MissingConditionBehavior); diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder.cs index 6d673c20..ffbf3f9c 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder.cs @@ -3,7 +3,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System; using System.Linq.Expressions; using System.Reflection; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal sealed class CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder.cs index 87beb23a..85b6b670 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder.cs @@ -3,7 +3,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System; using System.Linq.Expressions; using System.Reflection; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal sealed class CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ConditionExpressionBuilderProvider.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ConditionExpressionBuilderProvider.cs index 13ac327d..a3e25031 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ConditionExpressionBuilderProvider.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ConditionExpressionBuilderProvider.cs @@ -2,7 +2,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { using System; using System.Collections.Generic; - using Rules.Framework.Core; + using Rules.Framework; internal sealed class ConditionExpressionBuilderProvider : IConditionExpressionBuilderProvider { diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ContainsManyToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ContainsManyToOneConditionExpressionBuilder.cs index 91f5873f..f17226b2 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ContainsManyToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ContainsManyToOneConditionExpressionBuilder.cs @@ -5,7 +5,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System.Linq; using System.Linq.Expressions; using System.Reflection; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal sealed class ContainsManyToOneConditionExpressionBuilder : IConditionExpressionBuilder diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilder.cs index 00cc3b56..2374bdb5 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilder.cs @@ -4,7 +4,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System.Linq; using System.Linq.Expressions; using System.Reflection; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal sealed class ContainsOneToOneConditionExpressionBuilder : IConditionExpressionBuilder diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilder.cs index 70e0ee57..3b8f6061 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilder.cs @@ -3,7 +3,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System; using System.Linq.Expressions; using System.Reflection; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal sealed class EndsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilder.cs index 40a578c5..1071aa5c 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilder.cs @@ -2,7 +2,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { using System; using System.Linq.Expressions; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal sealed class GreaterThanOneToOneConditionExpressionBuilder : IConditionExpressionBuilder diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilder.cs index b658b96a..620a815f 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilder.cs @@ -2,7 +2,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { using System; using System.Linq.Expressions; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal sealed class GreaterThanOrEqualOneToOneConditionExpressionBuilder : IConditionExpressionBuilder diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/IConditionExpressionBuilderProvider.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/IConditionExpressionBuilderProvider.cs index 159d62d0..66e6f665 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/IConditionExpressionBuilderProvider.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/IConditionExpressionBuilderProvider.cs @@ -1,6 +1,6 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { - using Rules.Framework.Core; + using Rules.Framework; internal interface IConditionExpressionBuilderProvider { diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilder.cs index d89e9931..6830f399 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilder.cs @@ -2,7 +2,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { using System; using System.Linq.Expressions; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal sealed class LesserThanOneToOneConditionExpressionBuilder : IConditionExpressionBuilder diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilder.cs index e1597901..e2f65ab4 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilder.cs @@ -2,7 +2,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { using System; using System.Linq.Expressions; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal sealed class LesserThanOrEqualOneToOneConditionExpressionBuilder : IConditionExpressionBuilder diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilder.cs index cd6f3dc4..70bbac33 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilder.cs @@ -3,7 +3,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System; using System.Linq.Expressions; using System.Reflection; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal sealed class NotContainsOneToOneConditionExpressionBuilder : IConditionExpressionBuilder diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilder.cs index 55b422e2..9cf575ad 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilder.cs @@ -3,7 +3,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System; using System.Linq.Expressions; using System.Reflection; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal class NotEndsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilder.cs index 49696d7e..f102a2d9 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilder.cs @@ -3,7 +3,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System; using System.Linq.Expressions; using System.Reflection; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal class NotStartsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilder.cs index 7e5668a0..c8d919de 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilder.cs @@ -3,7 +3,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System; using System.Linq.Expressions; using System.Reflection; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal sealed class StartsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionsValueLookupExtension.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionsValueLookupExtension.cs index a7969401..5e403d04 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionsValueLookupExtension.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionsValueLookupExtension.cs @@ -6,7 +6,7 @@ namespace Rules.Framework.Evaluation.Compiled internal static class ConditionsValueLookupExtension { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static object GetValueOrDefault(IDictionary conditions, TConditionType conditionType) + public static object GetValueOrDefault(IDictionary conditions, string conditionType) { if (conditions.TryGetValue(conditionType, out var conditionValue)) { diff --git a/src/Rules.Framework/Evaluation/Compiled/EvaluationContext.cs b/src/Rules.Framework/Evaluation/Compiled/EvaluationContext.cs index 8ab74ce6..5edb16af 100644 --- a/src/Rules.Framework/Evaluation/Compiled/EvaluationContext.cs +++ b/src/Rules.Framework/Evaluation/Compiled/EvaluationContext.cs @@ -2,10 +2,10 @@ namespace Rules.Framework.Evaluation.Compiled { using System.Collections.Generic; - internal sealed class EvaluationContext + internal sealed class EvaluationContext { public EvaluationContext( - IDictionary conditions, + IDictionary conditions, MatchModes matchMode, MissingConditionBehaviors missingConditionBehavior) { @@ -14,7 +14,7 @@ public EvaluationContext( this.MissingConditionBehavior = missingConditionBehavior; } - public IDictionary Conditions { get; } + public IDictionary Conditions { get; } public MatchModes MatchMode { get; } diff --git a/src/Rules.Framework/Evaluation/Compiled/IRuleConditionsExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/IRuleConditionsExpressionBuilder.cs index 55cc7fc3..ce0d1818 100644 --- a/src/Rules.Framework/Evaluation/Compiled/IRuleConditionsExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/IRuleConditionsExpressionBuilder.cs @@ -2,10 +2,9 @@ namespace Rules.Framework.Evaluation.Compiled { using System; using System.Linq.Expressions; - using Rules.Framework.Core; - internal interface IRuleConditionsExpressionBuilder + internal interface IRuleConditionsExpressionBuilder { - Expression, bool>> BuildExpression(IConditionNode rootConditionNode); + Expression> BuildExpression(IConditionNode rootConditionNode); } } \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/RuleConditionsExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/RuleConditionsExpressionBuilder.cs index 8544e02f..1de8b56b 100644 --- a/src/Rules.Framework/Evaluation/Compiled/RuleConditionsExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/RuleConditionsExpressionBuilder.cs @@ -7,22 +7,21 @@ namespace Rules.Framework.Evaluation.Compiled using System.Reflection; using System.Text; using System.Text.RegularExpressions; - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; + using Rules.Framework; + using Rules.Framework.ConditionNodes; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; - internal sealed class RuleConditionsExpressionBuilder : RuleConditionsExpressionBuilderBase, IRuleConditionsExpressionBuilder + internal sealed class RuleConditionsExpressionBuilder : RuleConditionsExpressionBuilderBase, IRuleConditionsExpressionBuilder { - private static readonly MethodInfo conditionsGetterMethod = typeof(EvaluationContext) + private static readonly MethodInfo conditionsGetterMethod = typeof(EvaluationContext) .GetProperty("Conditions") .GetGetMethod(); - private static readonly MethodInfo evaluationContextMatchModeGetterMethod = typeof(EvaluationContext).GetProperty("MatchMode").GetGetMethod(); - private static readonly MethodInfo evaluationContextMissingConditionsBehaviorGetterMethod = typeof(EvaluationContext).GetProperty("MissingConditionBehavior").GetGetMethod(); + private static readonly MethodInfo evaluationContextMatchModeGetterMethod = typeof(EvaluationContext).GetProperty("MatchMode").GetGetMethod(); + private static readonly MethodInfo evaluationContextMissingConditionsBehaviorGetterMethod = typeof(EvaluationContext).GetProperty("MissingConditionBehavior").GetGetMethod(); private static readonly MethodInfo getValueOrDefaultMethod = typeof(ConditionsValueLookupExtension) - .GetMethod(nameof(ConditionsValueLookupExtension.GetValueOrDefault)) - .MakeGenericMethod(typeof(TConditionType)); + .GetMethod(nameof(ConditionsValueLookupExtension.GetValueOrDefault)); private readonly IDataTypesConfigurationProvider dataTypesConfigurationProvider; private readonly IValueConditionNodeExpressionBuilderProvider valueConditionNodeExpressionBuilderProvider; @@ -35,12 +34,12 @@ public RuleConditionsExpressionBuilder( this.dataTypesConfigurationProvider = dataTypesConfigurationProvider; } - public Expression, bool>> BuildExpression(IConditionNode rootConditionNode) + public Expression> BuildExpression(IConditionNode rootConditionNode) { var expressionResult = ExpressionBuilder.NewExpression("EvaluateConditions") .WithParameters(p => { - p.CreateParameter>("evaluationContext"); + p.CreateParameter("evaluationContext"); }) .HavingReturn(defaultValue: false) .SetImplementation(x => @@ -53,7 +52,7 @@ public Expression, bool>> BuildExpression }) .Build(); - return Expression.Lambda, bool>>( + return Expression.Lambda>( body: expressionResult.Implementation, parameters: expressionResult.Parameters); } @@ -86,11 +85,11 @@ private static void BuildExpressionForBehaviorOnNullLeftOperand(IExpressionBlock })); } - private void BuildExpression(IConditionNode conditionNode, IExpressionBlockBuilder builder) + private void BuildExpression(IConditionNode conditionNode, IExpressionBlockBuilder builder) { switch (conditionNode) { - case ComposedConditionNode composedConditionNode: + case ComposedConditionNode composedConditionNode: var conditionExpressions = new List(composedConditionNode.ChildConditionNodes.Count()); var counter = 0; foreach (var childConditionNode in composedConditionNode.ChildConditionNodes) @@ -118,7 +117,7 @@ private void BuildExpression(IConditionNode conditionNode, IExpr builder.Assign(composedResultVariableExpression, conditionExpression); break; - case ValueConditionNode valueConditionNode: + case ValueConditionNode valueConditionNode: // Variables, constants, and labels. var leftOperandVariableExpression = builder.CreateVariable("LeftOperand"); var rightOperandVariableExpression = builder.CreateVariable("RightOperand"); @@ -128,7 +127,7 @@ private void BuildExpression(IConditionNode conditionNode, IExpr // Line 1. var getConditionsCallExpression = builder.Call(parameterExpression, conditionsGetterMethod); var getConditionValueCallExpression = builder - .Call(instance: null!, getValueOrDefaultMethod, new Expression[] { getConditionsCallExpression, builder.Constant(valueConditionNode.ConditionType) }); + .Call(instance: null!, getValueOrDefaultMethod, new Expression[] { getConditionsCallExpression, builder.Constant(valueConditionNode.Condition) }); builder.Assign(leftOperandVariableExpression, getConditionValueCallExpression); // Line 2. builder.Assign(rightOperandVariableExpression, builder.Constant(valueConditionNode.Operand)); @@ -147,7 +146,7 @@ private void BuildExpression(IConditionNode conditionNode, IExpr private void BuildFetchAndSwitchOverMultiplicity( IExpressionBlockBuilder builder, - ValueConditionNode valueConditionNode) + ValueConditionNode valueConditionNode) { var operatorConstantExpression = builder.Constant(valueConditionNode.Operator); var multiplicityVariableExpression = builder.CreateVariable("Multiplicity", typeof(string)); @@ -173,7 +172,7 @@ private void BuildFetchAndSwitchOverMultiplicity( string multiplicityTransformed = Regex.Replace(multiplicity, "\\b\\p{Ll}", match => match.Value.ToUpperInvariant(), RegexOptions.None, TimeSpan.FromSeconds(1)).Replace("-", string.Empty, StringComparison.Ordinal); #endif var scopeName = new StringBuilder(builder.ScopeName) - .Append(valueConditionNode.ConditionType) + .Append(valueConditionNode.Condition) .Append(multiplicityTransformed) .ToString(); @switch.Case( diff --git a/src/Rules.Framework/Evaluation/ConditionsTreeAnalyzer.cs b/src/Rules.Framework/Evaluation/ConditionsTreeAnalyzer.cs index 8ff589a7..6fc14d8b 100644 --- a/src/Rules.Framework/Evaluation/ConditionsTreeAnalyzer.cs +++ b/src/Rules.Framework/Evaluation/ConditionsTreeAnalyzer.cs @@ -3,33 +3,32 @@ namespace Rules.Framework.Evaluation using System; using System.Collections.Generic; using System.Linq; - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; + using Rules.Framework.ConditionNodes; - internal sealed class ConditionsTreeAnalyzer : IConditionsTreeAnalyzer + internal sealed class ConditionsTreeAnalyzer : IConditionsTreeAnalyzer { - public bool AreAllSearchConditionsPresent(IConditionNode conditionNode, IDictionary conditions) + public bool AreAllSearchConditionsPresent(IConditionNode conditionNode, IDictionary conditions) { // Conditions checklist is a mere control construct to avoid a full sweep of the // condition nodes tree when we already found all conditions. - var conditionsChecklist = new Dictionary(conditions.ToDictionary(ks => ks.Key, vs => false)); + var conditionsChecklist = conditions.ToDictionary(ks => ks.Key, vs => false, StringComparer.Ordinal); return VisitConditionNode(conditionNode, conditionsChecklist); } - private static bool VisitConditionNode(IConditionNode conditionNode, IDictionary conditionsChecklist) + private static bool VisitConditionNode(IConditionNode conditionNode, IDictionary conditionsChecklist) { switch (conditionNode) { - case IValueConditionNode valueConditionNode: - if (conditionsChecklist.ContainsKey(valueConditionNode.ConditionType)) + case IValueConditionNode valueConditionNode: + if (conditionsChecklist.ContainsKey(valueConditionNode.Condition)) { - conditionsChecklist[valueConditionNode.ConditionType] = true; + conditionsChecklist[valueConditionNode.Condition] = true; } return conditionsChecklist.All(kvp => kvp.Value); - case ComposedConditionNode composedConditionNode: + case ComposedConditionNode composedConditionNode: foreach (var childConditionNode in composedConditionNode.ChildConditionNodes) { var allPresentAlready = VisitConditionNode(childConditionNode, conditionsChecklist); diff --git a/src/Rules.Framework/Evaluation/DataTypeConfiguration.cs b/src/Rules.Framework/Evaluation/DataTypeConfiguration.cs index 2b3e7a81..6ff652ed 100644 --- a/src/Rules.Framework/Evaluation/DataTypeConfiguration.cs +++ b/src/Rules.Framework/Evaluation/DataTypeConfiguration.cs @@ -1,7 +1,7 @@ namespace Rules.Framework.Evaluation { using System; - using Rules.Framework.Core; + using Rules.Framework; internal sealed class DataTypeConfiguration { diff --git a/src/Rules.Framework/Evaluation/DataTypesConfigurationProvider.cs b/src/Rules.Framework/Evaluation/DataTypesConfigurationProvider.cs index 71d7503f..991eb101 100644 --- a/src/Rules.Framework/Evaluation/DataTypesConfigurationProvider.cs +++ b/src/Rules.Framework/Evaluation/DataTypesConfigurationProvider.cs @@ -2,7 +2,7 @@ namespace Rules.Framework.Evaluation { using System; using System.Collections.Generic; - using Rules.Framework.Core; + using Rules.Framework; internal sealed class DataTypesConfigurationProvider : IDataTypesConfigurationProvider { diff --git a/src/Rules.Framework/Evaluation/IConditionsEvalEngine.cs b/src/Rules.Framework/Evaluation/IConditionsEvalEngine.cs index 10d5cb98..afc27d68 100644 --- a/src/Rules.Framework/Evaluation/IConditionsEvalEngine.cs +++ b/src/Rules.Framework/Evaluation/IConditionsEvalEngine.cs @@ -3,8 +3,8 @@ namespace Rules.Framework.Evaluation using System.Collections.Generic; using Rules.Framework.Core; - internal interface IConditionsEvalEngine + internal interface IConditionsEvalEngine { - bool Eval(IConditionNode conditionNode, IDictionary conditions, EvaluationOptions evaluationOptions); + bool Eval(IConditionNode conditionNode, IDictionary conditions, EvaluationOptions evaluationOptions); } } \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/IConditionsTreeAnalyzer.cs b/src/Rules.Framework/Evaluation/IConditionsTreeAnalyzer.cs index 5471de8d..ce29edfe 100644 --- a/src/Rules.Framework/Evaluation/IConditionsTreeAnalyzer.cs +++ b/src/Rules.Framework/Evaluation/IConditionsTreeAnalyzer.cs @@ -1,10 +1,9 @@ namespace Rules.Framework.Evaluation { using System.Collections.Generic; - using Rules.Framework.Core; - internal interface IConditionsTreeAnalyzer + internal interface IConditionsTreeAnalyzer { - bool AreAllSearchConditionsPresent(IConditionNode conditionNode, IDictionary conditions); + bool AreAllSearchConditionsPresent(IConditionNode conditionNode, IDictionary conditions); } } \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/IDataTypesConfigurationProvider.cs b/src/Rules.Framework/Evaluation/IDataTypesConfigurationProvider.cs index fa842750..6c845dc0 100644 --- a/src/Rules.Framework/Evaluation/IDataTypesConfigurationProvider.cs +++ b/src/Rules.Framework/Evaluation/IDataTypesConfigurationProvider.cs @@ -1,6 +1,6 @@ namespace Rules.Framework.Evaluation { - using Rules.Framework.Core; + using Rules.Framework; internal interface IDataTypesConfigurationProvider { diff --git a/src/Rules.Framework/Evaluation/IMultiplicityEvaluator.cs b/src/Rules.Framework/Evaluation/IMultiplicityEvaluator.cs index 7c540d43..cd07a5de 100644 --- a/src/Rules.Framework/Evaluation/IMultiplicityEvaluator.cs +++ b/src/Rules.Framework/Evaluation/IMultiplicityEvaluator.cs @@ -1,6 +1,6 @@ namespace Rules.Framework.Evaluation { - using Rules.Framework.Core; + using Rules.Framework; internal interface IMultiplicityEvaluator { diff --git a/src/Rules.Framework/Evaluation/Interpreted/DeferredEval.cs b/src/Rules.Framework/Evaluation/Interpreted/DeferredEval.cs index ce0b757c..5e5a6001 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/DeferredEval.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/DeferredEval.cs @@ -2,7 +2,7 @@ namespace Rules.Framework.Evaluation.Interpreted { using System; using System.Collections.Generic; - using Rules.Framework.Core.ConditionNodes; + using Rules.Framework.ConditionNodes; using Rules.Framework.Evaluation.Interpreted.ValueEvaluation.Dispatchers; internal sealed class DeferredEval : IDeferredEval @@ -18,14 +18,14 @@ public DeferredEval( this.rulesEngineOptions = rulesEngineOptions; } - public Func, bool> GetDeferredEvalFor(IValueConditionNode valueConditionNode, MatchModes matchMode) + public Func, bool> GetDeferredEvalFor(IValueConditionNode valueConditionNode, MatchModes matchMode) => (conditions) => Eval(conditions, valueConditionNode, matchMode); - private bool Eval(IDictionary conditions, IValueConditionNode valueConditionNode, MatchModes matchMode) + private bool Eval(IDictionary conditions, IValueConditionNode valueConditionNode, MatchModes matchMode) { var rightOperand = valueConditionNode.Operand; - conditions.TryGetValue(valueConditionNode.ConditionType, out var leftOperand); + conditions.TryGetValue(valueConditionNode.Condition, out var leftOperand); if (leftOperand is null) { diff --git a/src/Rules.Framework/Evaluation/Interpreted/IDeferredEval.cs b/src/Rules.Framework/Evaluation/Interpreted/IDeferredEval.cs index 7e6333dc..f13276ad 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/IDeferredEval.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/IDeferredEval.cs @@ -2,10 +2,10 @@ namespace Rules.Framework.Evaluation.Interpreted { using System; using System.Collections.Generic; - using Rules.Framework.Core.ConditionNodes; + using Rules.Framework.ConditionNodes; internal interface IDeferredEval { - Func, bool> GetDeferredEvalFor(IValueConditionNode valueConditionNode, MatchModes matchMode); + Func, bool> GetDeferredEvalFor(IValueConditionNode valueConditionNode, MatchModes matchMode); } } \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Interpreted/InterpretedConditionsEvalEngine.cs b/src/Rules.Framework/Evaluation/Interpreted/InterpretedConditionsEvalEngine.cs index b0d04a2b..a9e092ed 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/InterpretedConditionsEvalEngine.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/InterpretedConditionsEvalEngine.cs @@ -3,47 +3,47 @@ namespace Rules.Framework.Evaluation.Interpreted using System; using System.Collections.Generic; using System.Linq; - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; + using Rules.Framework; + using Rules.Framework.ConditionNodes; - internal sealed class InterpretedConditionsEvalEngine : IConditionsEvalEngine + internal sealed class InterpretedConditionsEvalEngine : IConditionsEvalEngine { - private readonly IConditionsTreeAnalyzer conditionsTreeAnalyzer; + private readonly IConditionsTreeAnalyzer conditionsTreeAnalyzer; private readonly IDeferredEval deferredEval; public InterpretedConditionsEvalEngine( IDeferredEval deferredEval, - IConditionsTreeAnalyzer conditionsTreeAnalyzer) + IConditionsTreeAnalyzer conditionsTreeAnalyzer) { this.deferredEval = deferredEval; this.conditionsTreeAnalyzer = conditionsTreeAnalyzer; } - public bool Eval(IConditionNode conditionNode, IDictionary conditions, EvaluationOptions evaluationOptions) + public bool Eval(IConditionNode conditionNode, IDictionary conditions, EvaluationOptions evaluationOptions) { if (evaluationOptions.ExcludeRulesWithoutSearchConditions && !this.conditionsTreeAnalyzer.AreAllSearchConditionsPresent(conditionNode, conditions)) { return false; } - ISpecification> specification = this.BuildSpecification(conditionNode, evaluationOptions.MatchMode); + ISpecification> specification = this.BuildSpecification(conditionNode, evaluationOptions.MatchMode); return specification.IsSatisfiedBy(conditions); } - private ISpecification> BuildSpecification(IConditionNode conditionNode, MatchModes matchMode) + private ISpecification> BuildSpecification(IConditionNode conditionNode, MatchModes matchMode) { return conditionNode switch { - IValueConditionNode valueConditionNode => this.BuildSpecificationForValueNode(valueConditionNode, matchMode), - ComposedConditionNode composedConditionNode => this.BuildSpecificationForComposedNode(composedConditionNode, matchMode), + IValueConditionNode valueConditionNode => this.BuildSpecificationForValueNode(valueConditionNode, matchMode), + ComposedConditionNode composedConditionNode => this.BuildSpecificationForComposedNode(composedConditionNode, matchMode), _ => throw new NotSupportedException($"Unsupported condition node: '{conditionNode.GetType().Name}'."), }; } - private ISpecification> BuildSpecificationForComposedNode(ComposedConditionNode composedConditionNode, MatchModes matchMode) + private ISpecification> BuildSpecificationForComposedNode(ComposedConditionNode composedConditionNode, MatchModes matchMode) { - IEnumerable>> childConditionNodesSpecifications = composedConditionNode + IEnumerable>> childConditionNodesSpecifications = composedConditionNode .ChildConditionNodes .Select(cn => this.BuildSpecification(cn, matchMode)); @@ -55,9 +55,9 @@ private ISpecification> BuildSpecificationFo }; } - private ISpecification> BuildSpecificationForValueNode(IValueConditionNode valueConditionNode, MatchModes matchMode) + private ISpecification> BuildSpecificationForValueNode(IValueConditionNode valueConditionNode, MatchModes matchMode) { - return new FuncSpecification>(this.deferredEval.GetDeferredEvalFor(valueConditionNode, matchMode)); + return new FuncSpecification>(this.deferredEval.GetDeferredEvalFor(valueConditionNode, matchMode)); } } } \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ConditionEvalDispatchProvider.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ConditionEvalDispatchProvider.cs index 7cc820e3..e62df71b 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ConditionEvalDispatchProvider.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ConditionEvalDispatchProvider.cs @@ -2,7 +2,7 @@ namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation.Dispatchers { using System; using System.Collections.Generic; - using Rules.Framework.Core; + using Rules.Framework; internal sealed class ConditionEvalDispatchProvider : IConditionEvalDispatchProvider { diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ConditionEvalDispatcherBase.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ConditionEvalDispatcherBase.cs index 592c2577..e32ed58f 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ConditionEvalDispatcherBase.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ConditionEvalDispatcherBase.cs @@ -5,7 +5,7 @@ namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation.Dispatchers using System.Collections.Generic; using System.Globalization; using System.Linq; - using Rules.Framework.Core; + using Rules.Framework; internal abstract class ConditionEvalDispatcherBase { diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/IConditionEvalDispatchProvider.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/IConditionEvalDispatchProvider.cs index ac697806..3fa3a04a 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/IConditionEvalDispatchProvider.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/IConditionEvalDispatchProvider.cs @@ -1,6 +1,6 @@ namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation.Dispatchers { - using Rules.Framework.Core; + using Rules.Framework; internal interface IConditionEvalDispatchProvider { diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/IConditionEvalDispatcher.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/IConditionEvalDispatcher.cs index cde1ce2d..83612a85 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/IConditionEvalDispatcher.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/IConditionEvalDispatcher.cs @@ -1,6 +1,6 @@ namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation.Dispatchers { - using Rules.Framework.Core; + using Rules.Framework; internal interface IConditionEvalDispatcher { diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcher.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcher.cs index 9b3462ec..8b931a1c 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcher.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcher.cs @@ -2,7 +2,7 @@ namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation.Dispatchers { using System.Collections.Generic; using System.Linq; - using Rules.Framework.Core; + using Rules.Framework; internal sealed class ManyToManyConditionEvalDispatcher : ConditionEvalDispatcherBase, IConditionEvalDispatcher { diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcher.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcher.cs index 44ca5841..c1d3242b 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcher.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcher.cs @@ -2,7 +2,7 @@ namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation.Dispatchers { using System.Collections.Generic; using System.Linq; - using Rules.Framework.Core; + using Rules.Framework; internal sealed class ManyToOneConditionEvalDispatcher : ConditionEvalDispatcherBase, IConditionEvalDispatcher { diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcher.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcher.cs index af3998a8..03474b57 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcher.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcher.cs @@ -2,7 +2,7 @@ namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation.Dispatchers { using System.Collections.Generic; using System.Linq; - using Rules.Framework.Core; + using Rules.Framework; internal sealed class OneToManyConditionEvalDispatcher : ConditionEvalDispatcherBase, IConditionEvalDispatcher { diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcher.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcher.cs index cd422b7f..ec74fae5 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcher.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcher.cs @@ -1,6 +1,6 @@ namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation.Dispatchers { - using Rules.Framework.Core; + using Rules.Framework; internal sealed class OneToOneConditionEvalDispatcher : ConditionEvalDispatcherBase, IConditionEvalDispatcher { diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/IOperatorEvalStrategyFactory.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/IOperatorEvalStrategyFactory.cs index 5610bd09..9e2e1b55 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/IOperatorEvalStrategyFactory.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/IOperatorEvalStrategyFactory.cs @@ -1,6 +1,6 @@ namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation { - using Rules.Framework.Core; + using Rules.Framework; internal interface IOperatorEvalStrategyFactory { diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/OperatorEvalStrategyFactory.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/OperatorEvalStrategyFactory.cs index 6119f5cc..3b71dca8 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/OperatorEvalStrategyFactory.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/OperatorEvalStrategyFactory.cs @@ -2,7 +2,7 @@ namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation { using System; using System.Collections.Generic; - using Rules.Framework.Core; + using Rules.Framework; internal sealed class OperatorEvalStrategyFactory : IOperatorEvalStrategyFactory { diff --git a/src/Rules.Framework/Evaluation/MultiplicityEvaluator.cs b/src/Rules.Framework/Evaluation/MultiplicityEvaluator.cs index c3c7c945..a48edd56 100644 --- a/src/Rules.Framework/Evaluation/MultiplicityEvaluator.cs +++ b/src/Rules.Framework/Evaluation/MultiplicityEvaluator.cs @@ -2,7 +2,7 @@ namespace Rules.Framework.Evaluation { using System; using System.Collections; - using Rules.Framework.Core; + using Rules.Framework; internal sealed class MultiplicityEvaluator : IMultiplicityEvaluator { diff --git a/src/Rules.Framework/Evaluation/OperatorMetadata.cs b/src/Rules.Framework/Evaluation/OperatorMetadata.cs index b9614e70..26eec03b 100644 --- a/src/Rules.Framework/Evaluation/OperatorMetadata.cs +++ b/src/Rules.Framework/Evaluation/OperatorMetadata.cs @@ -3,7 +3,7 @@ namespace Rules.Framework.Evaluation using System; using System.Collections.Generic; using System.Linq; - using Rules.Framework.Core; + using Rules.Framework; internal sealed class OperatorMetadata { diff --git a/src/Rules.Framework/Evaluation/OperatorsMetadata.cs b/src/Rules.Framework/Evaluation/OperatorsMetadata.cs index c11a2216..dc36e32a 100644 --- a/src/Rules.Framework/Evaluation/OperatorsMetadata.cs +++ b/src/Rules.Framework/Evaluation/OperatorsMetadata.cs @@ -4,7 +4,7 @@ namespace Rules.Framework.Evaluation using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; - using Rules.Framework.Core; + using Rules.Framework; internal static class OperatorsMetadata { diff --git a/src/Rules.Framework/Extensions/GenericRuleExtensions.cs b/src/Rules.Framework/Extensions/GenericRuleExtensions.cs deleted file mode 100644 index 7ed33f25..00000000 --- a/src/Rules.Framework/Extensions/GenericRuleExtensions.cs +++ /dev/null @@ -1,57 +0,0 @@ -namespace Rules.Framework.Extensions -{ - using System.Collections.Generic; - using System.Linq; - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; - using Rules.Framework.Generics; - - internal static class GenericRuleExtensions - { - public static GenericConditionNode ToGenericConditionNode(this IConditionNode rootCondition) - { - if (rootCondition.LogicalOperator == LogicalOperators.Eval) - { - var condition = rootCondition as ValueConditionNode; - - return new GenericValueConditionNode - { - ConditionTypeName = condition.ConditionType.ToString(), - DataType = condition.DataType, - LogicalOperator = condition.LogicalOperator, - Operand = condition.Operand, - Operator = condition.Operator, - }; - } - - var composedConditionNode = rootCondition as ComposedConditionNode; - - var conditionNodeDataModels = new List(composedConditionNode.ChildConditionNodes.Count()); - - foreach (var child in composedConditionNode.ChildConditionNodes) - { - conditionNodeDataModels.Add(child.ToGenericConditionNode()); - } - - return new GenericComposedConditionNode - { - ChildConditionNodes = conditionNodeDataModels, - LogicalOperator = composedConditionNode.LogicalOperator, - }; - } - - public static GenericRule ToGenericRule(this Rule rule) - { - return new GenericRule - { - RootCondition = rule.RootCondition?.ToGenericConditionNode(), - Content = rule.ContentContainer.GetContentAs(), - DateBegin = rule.DateBegin, - DateEnd = rule.DateEnd, - Name = rule.Name, - Priority = rule.Priority, - Active = rule.Active, - }; - } - } -} diff --git a/src/Rules.Framework/Extensions/GenericSearchArgsExtensions.cs b/src/Rules.Framework/Extensions/GenericSearchArgsExtensions.cs deleted file mode 100644 index dcb0c839..00000000 --- a/src/Rules.Framework/Extensions/GenericSearchArgsExtensions.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace Rules.Framework.Extensions -{ - using System; - using System.Linq; - using Rules.Framework.Generics; - - internal static class GenericSearchArgsExtensions - { - public static SearchArgs ToSearchArgs( - this SearchArgs genericSearchArgs) - { - if (!typeof(TContentType).IsEnum) - { - throw new ArgumentException("Only TContentType of type enum are currently supported."); - } - - var contentType = (TContentType)Enum.Parse(typeof(TContentType), genericSearchArgs.ContentType.Identifier); - - if (genericSearchArgs.Active.HasValue) - { - return new SearchArgs(contentType, genericSearchArgs.DateBegin, genericSearchArgs.DateEnd, genericSearchArgs.Active.Value) - { - Conditions = genericSearchArgs.Conditions.Select(condition => new Condition - ( - (TConditionType)Enum.Parse(typeof(TConditionType), condition.Type.Identifier), - condition.Value - )).ToList(), - ExcludeRulesWithoutSearchConditions = genericSearchArgs.ExcludeRulesWithoutSearchConditions - }; - } - - var searchArgs = new SearchArgs(contentType, genericSearchArgs.DateBegin, genericSearchArgs.DateEnd) - { - Conditions = genericSearchArgs.Conditions.Select(condition => new Condition - ( - (TConditionType)Enum.Parse(typeof(TConditionType), condition.Type.Identifier), - condition.Value - )).ToList(), - ExcludeRulesWithoutSearchConditions = genericSearchArgs.ExcludeRulesWithoutSearchConditions - }; - - return searchArgs; - } - } -} \ No newline at end of file diff --git a/src/Rules.Framework/Extensions/RuleBuilderExtensions.cs b/src/Rules.Framework/Extensions/RuleBuilderExtensions.cs new file mode 100644 index 00000000..4d150cb0 --- /dev/null +++ b/src/Rules.Framework/Extensions/RuleBuilderExtensions.cs @@ -0,0 +1,215 @@ +namespace Rules.Framework +{ + using System; + using System.Globalization; + using Rules.Framework.Builder.Generic.RulesBuilder; + using Rules.Framework.Builder.RulesBuilder; + + /// + /// Defines extension method helpers for the rule builder API. + /// + public static class RuleBuilderExtensions + { + /// + /// Sets the new rule with the specified date begin string. + /// + /// The builder. + /// The date begin. + /// + /// the is interpreted using current culture. + public static IRuleConfigureDateEndOptional Since(this IRuleConfigureDateBegin builder, string dateBegin) + => builder.Since(DateTime.Parse(dateBegin, CultureInfo.CurrentCulture, DateTimeStyles.None)); + + /// + /// Sets the new rule with the specified date begin string. + /// + /// The ruleset type that strongly types rulesets. + /// The condition type that strongly types conditions. + /// The builder. + /// The date begin. + /// + /// the is interpreted using current culture. + public static IRuleConfigureDateEndOptional Since(this IRuleConfigureDateBegin builder, string dateBegin) + => builder.Since(DateTime.Parse(dateBegin, CultureInfo.CurrentCulture, DateTimeStyles.None)); + + /// + /// Sets the new rule with a date begin using the specified year, month, and day, assuming + /// UTC timezone. + /// + /// The builder. + /// The year. + /// The month. + /// The day. + /// + public static IRuleConfigureDateEndOptional SinceUtc( + this IRuleConfigureDateBegin builder, + int year, + int month, + int day) + => builder.SinceUtc(year, month, day, 0, 0, 0); + + /// + /// Sets the new rule with a date begin using the specified year, month, day, hour, minute, + /// and second, assuming UTC timezone. + /// + /// The builder. + /// The year. + /// The month. + /// The day. + /// The hour. + /// The minute. + /// The second. + /// + public static IRuleConfigureDateEndOptional SinceUtc( + this IRuleConfigureDateBegin builder, + int year, + int month, + int day, + int hour, + int minute, + int second) + => builder.Since(new DateTime(year, month, day, hour, minute, second, DateTimeKind.Utc)); + + /// + /// Sets the new rule with a date begin using the specified year, month, and day, assuming + /// UTC timezone. + /// + /// The ruleset type that strongly types rulesets. + /// The condition type that strongly types conditions. + /// The builder. + /// The year. + /// The month. + /// The day. + /// + public static IRuleConfigureDateEndOptional SinceUtc( + this IRuleConfigureDateBegin builder, + int year, + int month, + int day) + => builder.SinceUtc(year, month, day, 0, 0, 0); + + /// + /// Sets the new rule with a date begin using the specified year, month, day, hour, minute, + /// and second, assuming UTC timezone. + /// + /// The ruleset type that strongly types rulesets. + /// The condition type that strongly types conditions. + /// The builder. + /// The year. + /// The month. + /// The day. + /// The hour. + /// The minute. + /// The second. + /// + public static IRuleConfigureDateEndOptional SinceUtc( + this IRuleConfigureDateBegin builder, + int year, + int month, + int day, + int hour, + int minute, + int second) + => builder.Since(new DateTime(year, month, day, hour, minute, second, DateTimeKind.Utc)); + + /// + /// Sets the new rule with the specified date end string. + /// + /// The builder. + /// The date end. + /// + /// the is interpreted using current culture. + public static IRuleBuilder Until(this IRuleConfigureDateEnd builder, string? dateEnd) + => builder.Until(dateEnd != null ? DateTime.Parse(dateEnd, CultureInfo.CurrentCulture, DateTimeStyles.None) : null); + + /// + /// Sets the new rule with the specified date end string. + /// + /// The ruleset type that strongly types rulesets. + /// The condition type that strongly types conditions. + /// The builder. + /// The date end. + /// + /// the is interpreted using current culture. + public static IRuleBuilder Until(this IRuleConfigureDateEnd builder, string? dateEnd) + => builder.Until(dateEnd != null ? DateTime.Parse(dateEnd, CultureInfo.CurrentCulture, DateTimeStyles.None) : null); + + /// + /// Sets the new rule with a date end using the specified year, month, and day, assuming UTC timezone. + /// + /// The builder. + /// The year. + /// The month. + /// The day. + /// + public static IRuleBuilder UntilUtc( + this IRuleConfigureDateEnd builder, + int year, + int month, + int day) + => builder.Until(new DateTime(year, month, day, 0, 0, 0, DateTimeKind.Utc)); + + /// + /// Sets the new rule with a date begin using the specified year, month, day, hour, minute, + /// and second, assuming UTC timezone. + /// + /// The builder. + /// The year. + /// The month. + /// The day. + /// The hour. + /// The minute. + /// The second. + /// + public static IRuleBuilder UntilUtc( + this IRuleConfigureDateEnd builder, + int year, + int month, + int day, + int hour, + int minute, + int second) + => builder.Until(new DateTime(year, month, day, hour, minute, second, DateTimeKind.Utc)); + + /// + /// Sets the new rule with a date end using the specified year, month, and day, assuming UTC timezone. + /// + /// The ruleset type that strongly types rulesets. + /// The condition type that strongly types conditions. + /// The builder. + /// The year. + /// The month. + /// The day. + /// + public static IRuleBuilder UntilUtc( + this IRuleConfigureDateEnd builder, + int year, + int month, + int day) + => builder.Until(new DateTime(year, month, day, 0, 0, 0, DateTimeKind.Utc)); + + /// + /// Sets the new rule with a date begin using the specified year, month, day, hour, minute, + /// and second, assuming UTC timezone. + /// + /// The ruleset type that strongly types rulesets. + /// The condition type that strongly types conditions. + /// The builder. + /// The year. + /// The month. + /// The day. + /// The hour. + /// The minute. + /// The second. + /// + public static IRuleBuilder UntilUtc( + this IRuleConfigureDateEnd builder, + int year, + int month, + int day, + int hour, + int minute, + int second) + => builder.Until(new DateTime(year, month, day, hour, minute, second, DateTimeKind.Utc)); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Extensions/RulesEngineExtensions.cs b/src/Rules.Framework/Extensions/RulesEngineExtensions.cs index 60ce6941..b3f9be6c 100644 --- a/src/Rules.Framework/Extensions/RulesEngineExtensions.cs +++ b/src/Rules.Framework/Extensions/RulesEngineExtensions.cs @@ -1,6 +1,6 @@ -namespace Rules.Framework.Extension +namespace Rules.Framework { - using Rules.Framework.Generics; + using Rules.Framework.Generic; /// /// Extensions for rules engine @@ -10,13 +10,13 @@ public static class RulesEngineExtensions /// /// Creates a generic rules engine. /// - /// The type of the content type. - /// The type of the condition type. + /// The ruleset type that strongly types rulesets. + /// The condition type that strongly types conditions. /// The rules engine. /// A new instance of generic engine - public static IGenericRulesEngine CreateGenericEngine(this RulesEngine rulesEngine) + public static IRulesEngine MakeGeneric(this IRulesEngine rulesEngine) { - return new GenericRulesEngine(rulesEngine); + return new RulesEngine(rulesEngine); } } } \ No newline at end of file diff --git a/src/Rules.Framework/Generic/ConditionNodes/ComposedConditionNode.cs b/src/Rules.Framework/Generic/ConditionNodes/ComposedConditionNode.cs new file mode 100644 index 00000000..7e25964a --- /dev/null +++ b/src/Rules.Framework/Generic/ConditionNodes/ComposedConditionNode.cs @@ -0,0 +1,81 @@ +namespace Rules.Framework.Generic.ConditionNodes +{ + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using Rules.Framework; + using Rules.Framework.ConditionNodes; + using Rules.Framework.Generic; + + /// + /// A composed condition node which aggregates a set of child condition nodes and defines a + /// logical operator to apply to them. + /// + /// The condition type that strongly types conditions. + [DebuggerDisplay("Composed condition: apply {LogicalOperator.ToString(),nq} operator for {System.Linq.Enumerable.Count(ChildConditionNodes),nq} nodes")] + public class ComposedConditionNode : IConditionNode + { + private readonly ComposedConditionNode composedConditionNode; + private List>? children; + + /// + /// Creates a new . + /// + /// The composed condition node. + public ComposedConditionNode(ComposedConditionNode composedConditionNode) + { + this.composedConditionNode = composedConditionNode; + } + + /// + /// Gets the child condition nodes. + /// + public IEnumerable> ChildConditionNodes + { + get + { + this.children ??= this.composedConditionNode.ChildConditionNodes + .Select(cn => cn.ToGenericConditionNode()) + .ToList(); + + return this.children; + } + } + + /// + /// Gets the logical operator to apply between child condition nodes. + /// + public LogicalOperators LogicalOperator => this.composedConditionNode.LogicalOperator; + + /// + /// Gets the condition node properties. + /// + public IDictionary Properties => this.composedConditionNode.Properties; + + /// + /// Clones the condition node into a different instance. + /// + /// + public IConditionNode Clone() + => new ComposedConditionNode((ComposedConditionNode)this.composedConditionNode.Clone()); + + /// + /// Determines whether the specified , is equal to this instance. + /// + /// The to compare with this instance. + /// + /// true if the specified is equal to this instance; otherwise, false. + /// + public override bool Equals(object obj) => obj is ComposedConditionNode node && EqualityComparer.Default.Equals(this.composedConditionNode, node.composedConditionNode); + + /// + /// Returns a hash code for this instance. + /// + /// + /// A hash code for this instance, suitable for use in hashing algorithms and data + /// structures like a hash table. + /// + public override int GetHashCode() + => this.composedConditionNode.GetHashCode(); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Generic/ConditionNodes/IValueConditionNode.cs b/src/Rules.Framework/Generic/ConditionNodes/IValueConditionNode.cs new file mode 100644 index 00000000..6ab80450 --- /dev/null +++ b/src/Rules.Framework/Generic/ConditionNodes/IValueConditionNode.cs @@ -0,0 +1,32 @@ +namespace Rules.Framework.Generic.ConditionNodes +{ + using Rules.Framework; + using Rules.Framework.Generic; + + /// + /// Defines the interface contract for a condition node based on a value comparison. + /// + /// The condition type that strongly types conditions. + public interface IValueConditionNode : IConditionNode + { + /// + /// Gets the condition name. + /// + TCondition Condition { get; } + + /// + /// Gets the condition node data type. + /// + DataTypes DataType { get; } + + /// + /// Gets the condition's operand. + /// + object Operand { get; } + + /// + /// Gets the condition node operator. + /// + Operators Operator { get; } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Generic/ConditionNodes/ValueConditionNode.cs b/src/Rules.Framework/Generic/ConditionNodes/ValueConditionNode.cs new file mode 100644 index 00000000..866ea61b --- /dev/null +++ b/src/Rules.Framework/Generic/ConditionNodes/ValueConditionNode.cs @@ -0,0 +1,69 @@ +namespace Rules.Framework.Generic.ConditionNodes +{ + using System.Collections.Generic; + using System.Diagnostics; + using Rules.Framework; + using Rules.Framework.ConditionNodes; + using Rules.Framework.Generic; + + /// + /// A generic implementation for a valued condition node. + /// + /// The condition type that strongly types conditions. + /// + [DebuggerDisplay("{DataType.ToString(),nq} condition: <{Condition.ToString(),nq}> {Operator} {Operand}")] + public class ValueConditionNode : IValueConditionNode + { + private readonly ValueConditionNode valueConditionNode; + + /// + /// Initializes a new instance of the class. + /// + /// The value condition node. + public ValueConditionNode(ValueConditionNode valueConditionNode) + { + this.valueConditionNode = valueConditionNode; + } + + /// + public TCondition Condition => GenericConversions.Convert(valueConditionNode.Condition); + + /// + public DataTypes DataType => this.valueConditionNode.DataType; + + /// + public LogicalOperators LogicalOperator => LogicalOperators.Eval; + + /// + public object Operand => this.valueConditionNode.Operand; + + /// + public Operators Operator => this.valueConditionNode.Operator; + + /// + public IDictionary Properties => this.valueConditionNode.Properties; + + /// + public IConditionNode Clone() + => new ValueConditionNode((ValueConditionNode)this.valueConditionNode.Clone()); + + /// + /// Determines whether the specified , is equal to this instance. + /// + /// The to compare with this instance. + /// + /// true if the specified is equal to this instance; otherwise, false. + /// + public override bool Equals(object obj) => obj is ValueConditionNode node && EqualityComparer.Default.Equals(this.valueConditionNode, node.valueConditionNode); + + /// + /// Returns a hash code for this instance. + /// + /// + /// A hash code for this instance, suitable for use in hashing algorithms and data + /// structures like a hash table. + /// + public override int GetHashCode() + => this.valueConditionNode.GetHashCode(); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Generic/GenericConversions.cs b/src/Rules.Framework/Generic/GenericConversions.cs new file mode 100644 index 00000000..4d5e547c --- /dev/null +++ b/src/Rules.Framework/Generic/GenericConversions.cs @@ -0,0 +1,29 @@ +namespace Rules.Framework.Generic +{ + using System; + using System.Globalization; + + internal static class GenericConversions + { + public static T Convert(string value) + { + var valueType = typeof(T); + if (valueType.IsEnum) + { + return (T)Enum.Parse(valueType, value, ignoreCase: false); + } + + return (T)System.Convert.ChangeType(value, valueType, CultureInfo.InvariantCulture); + } + + public static string Convert(T value) + { + if (value is string stringValue) + { + return stringValue; + } + + return value!.ToString(); + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Core/IConditionNode.cs b/src/Rules.Framework/Generic/IConditionNode.cs similarity index 70% rename from src/Rules.Framework/Core/IConditionNode.cs rename to src/Rules.Framework/Generic/IConditionNode.cs index d0c12762..d57f5f01 100644 --- a/src/Rules.Framework/Core/IConditionNode.cs +++ b/src/Rules.Framework/Generic/IConditionNode.cs @@ -1,12 +1,13 @@ -namespace Rules.Framework.Core +namespace Rules.Framework.Generic { using System.Collections.Generic; + using Rules.Framework; /// /// Defines the interface contract for a rule's condition node. /// - /// The condition type that allows to filter rules based on a set of conditions. - public interface IConditionNode + /// The condition type that strongly types conditions. + public interface IConditionNode { /// /// Gets the logical operator to apply to condition node. @@ -22,6 +23,6 @@ public interface IConditionNode /// Clones the condition node into a different instance. /// /// - IConditionNode Clone(); + IConditionNode Clone(); } } \ No newline at end of file diff --git a/src/Rules.Framework/Generic/IRulesEngine.cs b/src/Rules.Framework/Generic/IRulesEngine.cs new file mode 100644 index 00000000..34f0bfbb --- /dev/null +++ b/src/Rules.Framework/Generic/IRulesEngine.cs @@ -0,0 +1,150 @@ +namespace Rules.Framework.Generic +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + + /// + /// The engine that holds the logic to match and manage rules. + /// + /// The ruleset type that strongly types rulesets. + /// The condition type that strongly types conditions. + public interface IRulesEngine + { + /// + /// Gets the options. + /// + /// The options. + IRulesEngineOptions Options { get; } + + /// + /// Activates the specified existing rule. + /// + /// The rule. + /// + /// the operation result, containing success/failure indication and messages associated to + /// errors occurred during the operation. + /// + /// rule + Task ActivateRuleAsync(Rule rule); + + /// + /// Adds a new rule. + /// + /// The rule. + /// The rule add priority option. + /// + /// the operation result, containing success/failure indication and messages associated to + /// errors occurred during the operation. + /// + /// rule or rule + /// The priority option is not supported. + Task AddRuleAsync(Rule rule, RuleAddPriorityOption ruleAddPriorityOption); + + /// + /// Creates a ruleset. + /// + /// the ruleset name. + /// + /// the operation result, containing success/failure indication and messages associated to + /// errors occurred during the operation. + /// + /// ruleset + Task CreateRulesetAsync(TRuleset ruleset); + + /// + /// Deactivates the specified existing rule. + /// + /// The rule. + /// + /// the operation result, containing success/failure indication and messages associated to + /// errors occurred during the operation. + /// + /// rule + Task DeactivateRuleAsync(Rule rule); + + /// + /// Gets the rulesets. + /// + /// a collection of all rulesets, including respective metadata. + Task>> GetRulesetsAsync(); + + /// + /// Get the unique conditions associated with rules of a specific ruleset. + /// + /// + /// + /// + /// + /// + /// A set of rules is requested to rules data source and all conditions are fetched and a + /// distinct collection of all conditions is returned. + /// + /// + /// + /// the distinct collection of all conditions present on the rules of the provided ruleset. + /// + Task> GetUniqueConditionsAsync(TRuleset ruleset, DateTime dateBegin, DateTime dateEnd); + + /// + /// Provides all rule matches (if any) to the given at the + /// specified and satisfying the supplied . + /// + /// + /// + /// + /// + /// + /// A set of rules is requested to rules data source and all conditions are evaluated + /// against them to provide a set of matches. + /// + /// All rules matching supplied conditions are returned. + /// + /// the matched rule; otherwise, null. + Task>> MatchManyAsync(TRuleset ruleset, DateTime matchDateTime, IDictionary conditions); + + /// + /// Provides a rule match (if any) to the given at the specified + /// and satisfying the supplied . + /// + /// + /// + /// + /// + /// + /// A set of rules is requested to rules data source and all conditions are evaluated + /// against them to provide a set of matches. + /// + /// + /// If there's more than one match, a rule is selected based on the priority criteria and + /// value: topmost selects the lowest priority number and bottommost selects highest priority. + /// + /// + /// the matched rule; otherwise, null. + Task> MatchOneAsync(TRuleset ruleset, DateTime matchDateTime, IDictionary conditions); + + /// + /// Searches for rules that match on supplied . + /// + /// + /// + /// + /// Only the conditions supplied on input conditions are evaluated, the remaining conditions + /// are ignored. + /// + /// + /// the set of rules matching the conditions. + Task>> SearchAsync(SearchArgs searchArgs); + + /// + /// Updates the specified existing rule. + /// + /// The rule. + /// + /// the operation result, containing success/failure indication and messages associated to + /// errors occurred during the operation. + /// + /// rule + Task UpdateRuleAsync(Rule rule); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Generic/Rule.cs b/src/Rules.Framework/Generic/Rule.cs new file mode 100644 index 00000000..46136664 --- /dev/null +++ b/src/Rules.Framework/Generic/Rule.cs @@ -0,0 +1,102 @@ +namespace Rules.Framework.Generic +{ + using System; + using Rules.Framework.Core; + + /// + /// Defines a rule. + /// + /// The ruleset type that strongly types rulesets. + /// The condition type that strongly types conditions. + public class Rule + { + private readonly Rule wrappedRule; + private IConditionNode? rootCondition; + + internal Rule(Rule wrappedRule) + { + var typeForRuleset = typeof(TRuleset); + if (!typeForRuleset.IsEnum && typeForRuleset != TypesCache.String) + { + throw new NotSupportedException($"Only enum types or string are supported as {nameof(TRuleset)}."); + } + + var typeForCondition = typeof(TCondition); + if (!typeForCondition.IsEnum && typeForCondition != TypesCache.String) + { + throw new NotSupportedException($"Only enum types or string are supported as {nameof(TCondition)}."); + } + + this.wrappedRule = wrappedRule ?? throw new ArgumentNullException(nameof(wrappedRule)); + } + + /// + /// Gets if the rule is active. + /// + public bool Active => this.wrappedRule.Active; + + /// + /// Gets the content container which contains the rule content. + /// + public ContentContainer ContentContainer => this.wrappedRule.ContentContainer; + + /// + /// Gets the date from which the rule begins being applicable. + /// + public DateTime DateBegin => this.wrappedRule.DateBegin; + + /// + /// Gets and sets the date from which the rule ceases to be applicable. + /// + public DateTime? DateEnd + { + get => this.wrappedRule.DateEnd; + set => this.wrappedRule.DateEnd = value; + } + + /// + /// Gets the rule name. + /// + public string Name => this.wrappedRule.Name; + + /// + /// Gets and sets the rule priority compared to other rules (preferably it is unique). + /// + public int Priority + { + get => this.wrappedRule.Priority; + set => this.wrappedRule.Priority = value; + } + + /// + /// Gets the rule root condition. This property is null when rule has no conditions. + /// + public IConditionNode RootCondition + { + get + { + this.rootCondition ??= this.wrappedRule.RootCondition?.ToGenericConditionNode(); + return this.rootCondition!; + } + } + + /// + /// Gets the ruleset to which the rule belongs to. + /// + public TRuleset Ruleset => GenericConversions.Convert(this.wrappedRule.Ruleset); + + /// + /// Obtains the non-generic contained on the given instance of . + /// + /// The rule. + /// The non-generic rule. + public static implicit operator Rule(Rule rule) => rule.wrappedRule; + + /// + /// Clones the rule into a different instance. + /// + /// + public virtual Rule Clone() => new(this.wrappedRule.Clone()); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Generic/RuleExtensions.cs b/src/Rules.Framework/Generic/RuleExtensions.cs new file mode 100644 index 00000000..1a85db9d --- /dev/null +++ b/src/Rules.Framework/Generic/RuleExtensions.cs @@ -0,0 +1,24 @@ +namespace Rules.Framework.Generic +{ + using Rules.Framework; + using Rules.Framework.ConditionNodes; + using Rules.Framework.Generic.ConditionNodes; + + internal static class RuleExtensions + { + public static IConditionNode ToGenericConditionNode(this IConditionNode rootCondition) + { + if (rootCondition.LogicalOperator == LogicalOperators.Eval) + { + var condition = (ValueConditionNode)rootCondition; + + return new ValueConditionNode(condition); + } + + var composedConditionNode = (ComposedConditionNode)rootCondition; + return new ComposedConditionNode(composedConditionNode); + } + + public static Rule ToGenericRule(this Rule rule) => new(rule); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Generic/RulesEngine.cs b/src/Rules.Framework/Generic/RulesEngine.cs new file mode 100644 index 00000000..5e130a65 --- /dev/null +++ b/src/Rules.Framework/Generic/RulesEngine.cs @@ -0,0 +1,150 @@ +namespace Rules.Framework.Generic +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Rules.Framework.Builder.Validation; + + /// + /// Exposes rules engine logic to provide rule matches to requests. + /// + /// The ruleset type that strongly types rulesets. + /// The condition type that strongly types conditions. + public class RulesEngine : IRulesEngine + { + private readonly GenericRuleValidator ruleValidator = GenericRuleValidator.Instance; + private readonly IRulesEngine wrappedRulesEngine; + + internal RulesEngine(IRulesEngine wrappedRulesEngine) + { + this.wrappedRulesEngine = wrappedRulesEngine; + } + + /// + public IRulesEngineOptions Options => this.wrappedRulesEngine.Options; + + /// + public Task ActivateRuleAsync(Rule rule) + { + if (rule is null) + { + throw new ArgumentNullException(nameof(rule)); + } + + // Implicit conversion from Rule to Rule. + return this.wrappedRulesEngine.ActivateRuleAsync(rule); + } + + /// + public Task AddRuleAsync(Rule rule, RuleAddPriorityOption ruleAddPriorityOption) + { + if (rule is null) + { + throw new ArgumentNullException(nameof(rule)); + } + + // Implicit conversion from Rule to Rule. + return this.wrappedRulesEngine.AddRuleAsync(rule, ruleAddPriorityOption); + } + + /// + public async Task CreateRulesetAsync(TRuleset ruleset) + { + var rulesetAsString = GenericConversions.Convert(ruleset); + await this.wrappedRulesEngine.CreateRulesetAsync(rulesetAsString).ConfigureAwait(false); + } + + /// + public Task DeactivateRuleAsync(Rule rule) + { + if (rule is null) + { + throw new ArgumentNullException(nameof(rule)); + } + + // Implicit conversion from Rule to Rule. + return this.wrappedRulesEngine.DeactivateRuleAsync(rule); + } + + /// + public async Task>> GetRulesetsAsync() + { + var rulesets = await this.wrappedRulesEngine.GetRulesetsAsync().ConfigureAwait(false); + + return rulesets.Select(x => new Ruleset(x)).ToArray(); + } + + /// + public async Task> GetUniqueConditionsAsync(TRuleset ruleset, DateTime dateBegin, DateTime dateEnd) + { + var rulesetAsString = GenericConversions.Convert(ruleset); + var conditions = await this.wrappedRulesEngine.GetUniqueConditionsAsync(rulesetAsString, dateBegin, dateEnd).ConfigureAwait(false); + return conditions.Select(t => GenericConversions.Convert(t)).ToArray(); + } + + /// + public async Task>> MatchManyAsync( + TRuleset ruleset, + DateTime matchDateTime, + IDictionary 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, + conditionsConverted).ConfigureAwait(false); + + return rules.Select(r => r.ToGenericRule()).ToArray(); + } + + /// + public async Task> MatchOneAsync( + TRuleset ruleset, + DateTime matchDateTime, + IDictionary 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, + conditionsConverted).ConfigureAwait(false); + + return rule?.ToGenericRule()!; + } + + /// + public async Task>> SearchAsync(SearchArgs searchArgs) + { + if (searchArgs is null) + { + throw new ArgumentNullException(nameof(searchArgs)); + } + + var rulesetAsString = GenericConversions.Convert(searchArgs.Ruleset); + var searchArgsNew = new SearchArgs(rulesetAsString, searchArgs.DateBegin, searchArgs.DateEnd) + { + Conditions = searchArgs.Conditions.ToDictionary(c => GenericConversions.Convert(c.Key), c => c.Value, StringComparer.Ordinal), + ExcludeRulesWithoutSearchConditions = searchArgs.ExcludeRulesWithoutSearchConditions, + }; + + var rules = await this.wrappedRulesEngine.SearchAsync(searchArgsNew).ConfigureAwait(false); + + return rules.Select(r => r.ToGenericRule()).ToArray(); + } + + /// + public Task UpdateRuleAsync(Rule rule) + { + if (rule is null) + { + throw new ArgumentNullException(nameof(rule)); + } + + // Implicit conversion from Rule to Rule. + return this.wrappedRulesEngine.UpdateRuleAsync(rule); + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Generic/Ruleset.cs b/src/Rules.Framework/Generic/Ruleset.cs new file mode 100644 index 00000000..719f41b1 --- /dev/null +++ b/src/Rules.Framework/Generic/Ruleset.cs @@ -0,0 +1,30 @@ +namespace Rules.Framework.Generic +{ + using System; + + /// + /// Defines a ruleset that groups inter-related rules. + /// + /// The type of the ruleset. + public class Ruleset + { + private readonly Ruleset wrappedRuleset; + + internal Ruleset(Ruleset wrappedRuleset) + { + this.wrappedRuleset = wrappedRuleset; + } + + /// + /// Gets the creation date and time of the ruleset. + /// + /// The creation. + public DateTime Creation => this.wrappedRuleset.Creation; + + /// + /// Gets the name of the ruleset. + /// + /// The name. + public TRuleset Name => GenericConversions.Convert(this.wrappedRuleset.Name); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Generics/GenericComposedConditionNode.cs b/src/Rules.Framework/Generics/GenericComposedConditionNode.cs deleted file mode 100644 index 38901d2c..00000000 --- a/src/Rules.Framework/Generics/GenericComposedConditionNode.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Rules.Framework.Generics -{ - using System.Collections.Generic; - - /// - /// Defines generic condition node - /// - /// - public sealed class GenericComposedConditionNode : GenericConditionNode - { - /// - /// Gets the child condition nodes. - /// - public IEnumerable ChildConditionNodes { get; internal set; } - } -} \ No newline at end of file diff --git a/src/Rules.Framework/Generics/GenericConditionNode.cs b/src/Rules.Framework/Generics/GenericConditionNode.cs deleted file mode 100644 index 2fab53c8..00000000 --- a/src/Rules.Framework/Generics/GenericConditionNode.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Rules.Framework.Generics -{ - using Rules.Framework.Core; - - /// - /// Defines generic condition node - /// - public class GenericConditionNode - { - /// - /// Gets the logical operator to apply to condition node. - /// - public LogicalOperators LogicalOperator { get; internal set; } - } -} \ No newline at end of file diff --git a/src/Rules.Framework/Generics/GenericConditionType.cs b/src/Rules.Framework/Generics/GenericConditionType.cs deleted file mode 100644 index 1a0f9d4a..00000000 --- a/src/Rules.Framework/Generics/GenericConditionType.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace Rules.Framework.Generics -{ - using System; - - /// - /// Defines generic condition type - /// - public struct GenericConditionType : IEquatable - { - /// - /// Gets or sets the identifier. - /// - /// - /// The identifier. - /// - public string Identifier { get; set; } - - /// - /// Indicates whether the current object is equal to another object of the same type. - /// - /// An object to compare with this object. - /// - /// true if the current object is equal to the other - /// parameter; otherwise, false. - /// - public bool Equals(GenericConditionType other) - { - return other.Identifier == this.Identifier; - } - } -} \ No newline at end of file diff --git a/src/Rules.Framework/Generics/GenericContentType.cs b/src/Rules.Framework/Generics/GenericContentType.cs deleted file mode 100644 index f6c6ae46..00000000 --- a/src/Rules.Framework/Generics/GenericContentType.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace Rules.Framework.Generics -{ - using System; - - /// - /// Defines generic content type - /// - public struct GenericContentType : IEquatable - { - /// - /// Gets or sets the identifier. - /// - /// - /// The identifier. - /// - public string Identifier { get; set; } - - /// - /// Indicates whether the current object is equal to another object of the same type. - /// - /// An object to compare with this object. - /// - /// true if the current object is equal to the other - /// parameter; otherwise, false. - /// - public bool Equals(GenericContentType other) - { - return other.Identifier == this.Identifier; - } - } -} \ No newline at end of file diff --git a/src/Rules.Framework/Generics/GenericRule.cs b/src/Rules.Framework/Generics/GenericRule.cs deleted file mode 100644 index a6037ed7..00000000 --- a/src/Rules.Framework/Generics/GenericRule.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace Rules.Framework.Generics -{ - using System; - - /// - /// Defines a generic rule - /// - public sealed class GenericRule - { - /// - /// Gets and sets the if the rules ia active. - /// - public bool Active { get; internal set; } - - /// - /// Gets the content which contains the rule content. - /// - public object Content { get; internal set; } - - /// - /// Gets the date from which the rule begins being applicable. - /// - public DateTime DateBegin { get; internal set; } - - /// - /// Gets and sets the date from which the rule ceases to be applicable. - /// - public DateTime? DateEnd { get; internal set; } - - /// - /// Gets the rule name. - /// - public string Name { get; internal set; } - - /// - /// Gets the rule priority compared to other rules (preferrably it is unique). - /// - public int Priority { get; internal set; } - - /// - /// Gets the rule root condition. This property is null when rule has no conditions. - /// - public GenericConditionNode RootCondition { get; internal set; } - } -} \ No newline at end of file diff --git a/src/Rules.Framework/Generics/GenericRulesEngine.cs b/src/Rules.Framework/Generics/GenericRulesEngine.cs deleted file mode 100644 index 956eaa41..00000000 --- a/src/Rules.Framework/Generics/GenericRulesEngine.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace Rules.Framework.Generics -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading.Tasks; - using Rules.Framework.Extensions; - - internal sealed class GenericRulesEngine : IGenericRulesEngine - { - private readonly IRulesEngine rulesEngine; - - public GenericRulesEngine(IRulesEngine rulesEngine) - { - this.rulesEngine = rulesEngine; - } - - public IEnumerable GetContentTypes() - { - if (!typeof(TContentType).IsEnum) - { - throw new ArgumentException("Method only works if TContentType is a enum"); - } - - return Enum.GetValues(typeof(TContentType)) - .Cast() - .Select(t => new GenericContentType - { - Identifier = Enum.Parse(typeof(TContentType), t.ToString()).ToString() - }); - } - - - public PriorityCriterias GetPriorityCriteria() - { - return this.rulesEngine.GetPriorityCriteria(); - } - - public async Task> SearchAsync(SearchArgs genericSearchArgs) - { - var searchArgs = genericSearchArgs.ToSearchArgs(); - - var result = await this.rulesEngine.SearchAsync(searchArgs).ConfigureAwait(false); - - return result.Select(rule => rule.ToGenericRule()).ToList(); - } - } -} \ No newline at end of file diff --git a/src/Rules.Framework/Generics/GenericValueConditionNode.cs b/src/Rules.Framework/Generics/GenericValueConditionNode.cs deleted file mode 100644 index 2d4b7bc3..00000000 --- a/src/Rules.Framework/Generics/GenericValueConditionNode.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace Rules.Framework.Generics -{ - using Rules.Framework.Core; - - /// - /// Defines generic value condition node - /// - public sealed class GenericValueConditionNode : GenericConditionNode - { - /// - /// Gets the condition node type name. - /// - public string ConditionTypeName { get; internal set; } - - /// - /// Gets the condition node data type. - /// - public DataTypes DataType { get; internal set; } - - /// - /// Gets the condition's operand. - /// - public object Operand { get; internal set; } - - /// - /// Gets the condition node operator. - /// - public Operators Operator { get; internal set; } - } -} \ No newline at end of file diff --git a/src/Rules.Framework/Generics/IGenericRulesEngine.cs b/src/Rules.Framework/Generics/IGenericRulesEngine.cs deleted file mode 100644 index 9c0e322d..00000000 --- a/src/Rules.Framework/Generics/IGenericRulesEngine.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace Rules.Framework.Generics -{ - using System.Collections.Generic; - using System.Threading.Tasks; - - /// - /// Exposes generic rules engine logic to provide rule matches to requests. - /// - public interface IGenericRulesEngine - { - /// - /// Gets the content types. - /// - /// List of content types - /// - /// Method only works if TContentType is a enum - /// - IEnumerable GetContentTypes(); - - /// - /// Gets the priority criterias. - /// - /// Rules engine priority criterias - PriorityCriterias GetPriorityCriteria(); - - /// - /// Searches the asynchronous. - /// - /// The search arguments. - /// List of generic rules - Task> SearchAsync(SearchArgs genericSearchArgs); - } -} \ No newline at end of file diff --git a/src/Rules.Framework/IConditionNode.cs b/src/Rules.Framework/IConditionNode.cs new file mode 100644 index 00000000..503de12c --- /dev/null +++ b/src/Rules.Framework/IConditionNode.cs @@ -0,0 +1,26 @@ +namespace Rules.Framework +{ + using System.Collections.Generic; + + /// + /// Defines the interface contract for a rule's condition node. + /// + public interface IConditionNode + { + /// + /// Gets the logical operator to apply to condition node. + /// + LogicalOperators LogicalOperator { get; } + + /// + /// Gets the condition node properties. + /// + IDictionary Properties { get; } + + /// + /// Clones the condition node into a different instance. + /// + /// + IConditionNode Clone(); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/IConditionTypeExtractor.cs b/src/Rules.Framework/IConditionTypeExtractor.cs deleted file mode 100644 index fb80c7eb..00000000 --- a/src/Rules.Framework/IConditionTypeExtractor.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace Rules.Framework -{ - using System.Collections.Generic; - using Rules.Framework.Core; - - /// - /// Extracts Conditions Types from a Group of Rules. - /// - /// The content type that allows to categorize rules. - /// - /// The condition type that allows to filter rules based on a set of conditions. - /// - public interface IConditionTypeExtractor - { - /// - /// Get the unique condition types associated with rules of a specific content type. - /// - /// - /// - /// - /// A set of rules is requested to rules data source and all conditions are evaluated - /// against them to provide a set of matches. - /// - /// All rules matching supplied conditions are returned. - /// - /// the matched rule; otherwise, null. - IEnumerable GetConditionTypes(IEnumerable> matchedRules); - } -} \ No newline at end of file diff --git a/src/Rules.Framework/IRuleConditionsExtractor.cs b/src/Rules.Framework/IRuleConditionsExtractor.cs new file mode 100644 index 00000000..94d9af9e --- /dev/null +++ b/src/Rules.Framework/IRuleConditionsExtractor.cs @@ -0,0 +1,17 @@ +namespace Rules.Framework +{ + using System.Collections.Generic; + + /// + /// Defines the contract for extracting the conditions from rules. + /// + internal interface IRuleConditionsExtractor + { + /// + /// Get the unique conditions associated with supplied . + /// + /// + /// the distinct collection of conditions contained on the set of rules. + IEnumerable GetConditions(IEnumerable rules); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/IRulesDataSource.cs b/src/Rules.Framework/IRulesDataSource.cs index 00ea664c..94fc9c9a 100644 --- a/src/Rules.Framework/IRulesDataSource.cs +++ b/src/Rules.Framework/IRulesDataSource.cs @@ -3,43 +3,54 @@ namespace Rules.Framework using System; using System.Collections.Generic; using System.Threading.Tasks; - using Rules.Framework.Core; /// - /// Exposes the interface contract for a rules data source for specified . + /// Exposes the interface contract for a rules data source for specified content type. /// - /// The content type that allows to categorize rules. - /// The condition type that allows to filter rules based on a set of conditions. - public interface IRulesDataSource + public interface IRulesDataSource { /// /// Adds a new rule to data source. /// /// The rule. /// - Task AddRuleAsync(Rule rule); + Task AddRuleAsync(Rule rule); /// - /// Gets the rules categorized with specified between and . + /// Creates a new ruleset on the data source. + /// + /// the ruleset name. + /// + Task CreateRulesetAsync(string ruleset); + + /// + /// Gets the rules categorized with specified between + /// and . /// /// the content type categorization. /// the filtering begin date. /// the filtering end date. /// - Task>> GetRulesAsync(TContentType contentType, DateTime dateBegin, DateTime dateEnd); + Task> GetRulesAsync(string contentType, DateTime dateBegin, DateTime dateEnd); /// /// Gets the rules filtered by specified arguments. /// /// The rules filter arguments. /// - Task>> GetRulesByAsync(RulesFilterArgs rulesFilterArgs); + Task> GetRulesByAsync(RulesFilterArgs rulesFilterArgs); + + /// + /// Gets the rulesets from the data source. + /// + /// + Task> GetRulesetsAsync(); /// /// Updates the existent rule on data source. /// /// The rule. /// - Task UpdateRuleAsync(Rule rule); + Task UpdateRuleAsync(Rule rule); } } \ No newline at end of file diff --git a/src/Rules.Framework/IRulesEngine.cs b/src/Rules.Framework/IRulesEngine.cs index 5b29c9ea..516faf93 100644 --- a/src/Rules.Framework/IRulesEngine.cs +++ b/src/Rules.Framework/IRulesEngine.cs @@ -3,22 +3,143 @@ namespace Rules.Framework using System; using System.Collections.Generic; using System.Threading.Tasks; - using Rules.Framework.Core; - internal interface IRulesEngine + /// + /// The engine that holds the logic to match and manage rules. + /// + public interface IRulesEngine { - Task AddRuleAsync(Rule rule, RuleAddPriorityOption ruleAddPriorityOption); + /// + /// Gets the options. + /// + /// The options. + IRulesEngineOptions Options { get; } - PriorityCriterias GetPriorityCriteria(); + /// + /// Sets an existent inactive rule as active. + /// + /// The rule. + /// + /// the operation result, containing success/failure indication and messages associated to + /// errors occurred during the operation. + /// + /// rule + Task ActivateRuleAsync(Rule rule); - Task> GetUniqueConditionTypesAsync(TContentType contentType, DateTime dateBegin, DateTime dateEnd); + /// + /// Adds a rule. + /// + /// The rule. + /// The rule add priority option. + /// + /// the operation result, containing success/failure indication and messages associated to + /// errors occurred during the operation. + /// + Task AddRuleAsync(Rule rule, RuleAddPriorityOption ruleAddPriorityOption); - Task>> MatchManyAsync(TContentType contentType, DateTime matchDateTime, IEnumerable> conditions); + /// + /// Creates a ruleset. + /// + /// the ruleset name. + /// + /// the operation result, containing success/failure indication and messages associated to + /// errors occurred during the operation. + /// + /// ruleset + Task CreateRulesetAsync(string ruleset); - Task> MatchOneAsync(TContentType contentType, DateTime matchDateTime, IEnumerable> conditions); + /// + /// Sets an existent active rule as inactive. + /// + /// The rule. + /// + /// the operation result, containing success/failure indication and messages associated to + /// errors occurred during the operation. + /// + Task DeactivateRuleAsync(Rule rule); - Task>> SearchAsync(SearchArgs searchArgs); + /// + /// Gets the rulesets. + /// + /// a collection of all rulesets, including respective metadata. + Task> GetRulesetsAsync(); - Task UpdateRuleAsync(Rule rule); + /// + /// Get the unique conditions associated with rules of a specific ruleset. + /// + /// + /// + /// + /// + /// + /// A set of rules is requested to rules data source and all conditions are fetched and a + /// distinct collection of all conditions is returned. + /// + /// + /// + /// the distinct collection of all conditions present on the rules of the provided ruleset. + /// + Task> GetUniqueConditionsAsync(string ruleset, DateTime dateBegin, DateTime dateEnd); + + /// + /// Provides all rule matches (if any) to the given at the + /// specified and satisfying the supplied . + /// + /// + /// + /// + /// + /// + /// A set of rules is requested to rules data source and all conditions are evaluated + /// against them to provide a set of matches. + /// + /// All rules matching supplied conditions are returned. + /// + /// the matched rule; otherwise, null. + Task> MatchManyAsync(string ruleset, DateTime matchDateTime, IDictionary conditions); + + /// + /// Provides a rule match (if any) to the given at the specified + /// and satisfying the supplied . + /// + /// + /// + /// + /// + /// + /// A set of rules is requested to rules data source and all conditions are evaluated + /// against them to provide a set of matches. + /// + /// + /// If there's more than one match, a rule is selected based on the priority criteria and + /// value: topmost selects the lowest priority number and bottommost selects highest priority. + /// + /// + /// the matched rule; otherwise, null. + Task MatchOneAsync(string ruleset, DateTime matchDateTime, IDictionary conditions); + + /// + /// Searches for rules that match on supplied . + /// + /// + /// + /// + /// Only the conditions supplied on input conditions are evaluated, the remaining conditions + /// are ignored. + /// + /// + /// the set of rules matching the conditions. + Task> SearchAsync(SearchArgs searchArgs); + + /// + /// Updates an existing rule. + /// + /// The rule. + /// + /// the operation result, containing success/failure indication and messages associated to + /// errors occurred during the operation. + /// + /// rule + Task UpdateRuleAsync(Rule rule); } } \ No newline at end of file diff --git a/src/Rules.Framework/IRulesEngineOptions.cs b/src/Rules.Framework/IRulesEngineOptions.cs index 4386deaa..92d87807 100644 --- a/src/Rules.Framework/IRulesEngineOptions.cs +++ b/src/Rules.Framework/IRulesEngineOptions.cs @@ -1,30 +1,36 @@ namespace Rules.Framework { - using System; using System.Collections.Generic; - using Rules.Framework.Core; /// - /// The set of rules engine options that influence rules engine rules matching. + /// The set of rules engine options that influence rules engine behavior. /// public interface IRulesEngineOptions { /// - /// Gets the default values for each of the supported data types. + /// Gets or sets a value indicating whether automatic creation of rulesets is enabled, + /// allowing them to be added when a rule is added, when enabled. /// - public IDictionary DataTypeDefaults { get; } + /// + /// true if rulesets should be automatically created on rule add; otherwise, false. + /// + bool AutoCreateRulesets { get; } /// - /// Gets the rules engine behavior when no condition with a specific type is provided to - /// rules engine to match with a rule's condition with the same type. + /// Gets the default values for each of the supported data types. /// - public MissingConditionBehaviors MissingConditionBehavior { get; } + public IDictionary DataTypeDefaults { get; } /// - /// Gets the priority criteria to untie when multiples rules are matched. + /// + /// Gets or sets the rules engine behavior when no condition with a specific name is + /// provided to rules engine to match with a rule's condition with the same name. + /// + /// + /// e.g. a rule with a condition "Age" is under evaluation but no condition "Age" was supplied. + /// /// - [Obsolete("This property has a typo and has been replaced by PriorityCriteria.")] - public PriorityCriterias PriotityCriteria { get; } + public MissingConditionBehaviors MissingConditionBehavior { get; } /// /// Gets the priority criteria to untie when multiples rules are matched. diff --git a/src/Rules.Framework/Core/LogicalOperators.cs b/src/Rules.Framework/LogicalOperators.cs similarity index 94% rename from src/Rules.Framework/Core/LogicalOperators.cs rename to src/Rules.Framework/LogicalOperators.cs index f2bd7b9a..40928908 100644 --- a/src/Rules.Framework/Core/LogicalOperators.cs +++ b/src/Rules.Framework/LogicalOperators.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Core +namespace Rules.Framework { /// /// Defines the logical operators to use between multiple rule's condition nodes. diff --git a/src/Rules.Framework/Management/IManagementOperation.cs b/src/Rules.Framework/Management/IManagementOperation.cs index 86b9578c..c44acef0 100644 --- a/src/Rules.Framework/Management/IManagementOperation.cs +++ b/src/Rules.Framework/Management/IManagementOperation.cs @@ -4,8 +4,8 @@ namespace Rules.Framework.Management using System.Threading.Tasks; using Rules.Framework.Core; - internal interface IManagementOperation + internal interface IManagementOperation { - Task>> ApplyAsync(IEnumerable> rules); + Task> ApplyAsync(IEnumerable rules); } } \ No newline at end of file diff --git a/src/Rules.Framework/Management/ManagementOperations.cs b/src/Rules.Framework/Management/ManagementOperations.cs index 4d09908f..9033201c 100644 --- a/src/Rules.Framework/Management/ManagementOperations.cs +++ b/src/Rules.Framework/Management/ManagementOperations.cs @@ -6,21 +6,19 @@ namespace Rules.Framework.Management internal static class ManagementOperations { - public static ManagementOperationsSelector Manage( - IEnumerable> rules) - => new ManagementOperationsSelector(rules); + public static ManagementOperationsSelector Manage(IEnumerable rules) => new(rules); - internal sealed class ManagementOperationsSelector + internal sealed class ManagementOperationsSelector { - private readonly IEnumerable> rules; + private readonly IEnumerable rules; - public ManagementOperationsSelector(IEnumerable> rules) + public ManagementOperationsSelector(IEnumerable rules) { this.rules = rules; } - public ManagementOperationsController UsingSource(IRulesSource rulesDataSource) - => new ManagementOperationsController(rulesDataSource, this.rules); + public ManagementOperationsController UsingSource(IRulesSource rulesDataSource) + => new ManagementOperationsController(rulesDataSource, this.rules); } } } \ No newline at end of file diff --git a/src/Rules.Framework/Management/ManagementOperationsController.cs b/src/Rules.Framework/Management/ManagementOperationsController.cs index 67a4b6a4..fd054261 100644 --- a/src/Rules.Framework/Management/ManagementOperationsController.cs +++ b/src/Rules.Framework/Management/ManagementOperationsController.cs @@ -2,55 +2,54 @@ namespace Rules.Framework.Management { using System.Collections.Generic; using System.Threading.Tasks; - using Rules.Framework.Core; using Rules.Framework.Management.Operations; using Rules.Framework.Source; - internal sealed class ManagementOperationsController + internal sealed class ManagementOperationsController { - private readonly List> managementOperations; - private readonly IEnumerable> rules; - private readonly IRulesSource rulesSource; + private readonly List managementOperations; + private readonly IEnumerable rules; + private readonly IRulesSource rulesSource; - public ManagementOperationsController(IRulesSource rulesSource, IEnumerable> rules) + public ManagementOperationsController(IRulesSource rulesSource, IEnumerable rules) { - this.managementOperations = new List>(); + this.managementOperations = new List(); this.rulesSource = rulesSource; this.rules = rules; } - public ManagementOperationsController AddRule(Rule rule) - => this.AddOperation(new AddRuleManagementOperation(this.rulesSource, rule)); + public ManagementOperationsController AddRule(Rule rule) + => this.AddOperation(new AddRuleManagementOperation(this.rulesSource, rule)); + + public ManagementOperationsController DecreasePriority() + => this.AddOperation(new MovePriorityManagementOperation(-1)); public async Task ExecuteOperationsAsync() { - IEnumerable> rulesIntermediateResult = rules; + IEnumerable rulesIntermediateResult = rules; - foreach (IManagementOperation managementOperation in this.managementOperations) + foreach (IManagementOperation managementOperation in this.managementOperations) { rulesIntermediateResult = await managementOperation.ApplyAsync(rulesIntermediateResult).ConfigureAwait(false); } } - public ManagementOperationsController FilterFromThresholdPriorityToBottom(int thresholdPriority) - => this.AddOperation(new FilterPrioritiesRangeManagementOperation(thresholdPriority, null)); - - public ManagementOperationsController FilterPrioritiesRange(int topPriorityThreshold, int bottomPriorityThreshold) - => this.AddOperation(new FilterPrioritiesRangeManagementOperation(topPriorityThreshold, bottomPriorityThreshold)); + public ManagementOperationsController FilterFromThresholdPriorityToBottom(int thresholdPriority) + => this.AddOperation(new FilterPrioritiesRangeManagementOperation(thresholdPriority, null)); - public ManagementOperationsController IncreasePriority() - => this.AddOperation(new MovePriorityManagementOperation(1)); + public ManagementOperationsController FilterPrioritiesRange(int topPriorityThreshold, int bottomPriorityThreshold) + => this.AddOperation(new FilterPrioritiesRangeManagementOperation(topPriorityThreshold, bottomPriorityThreshold)); - public ManagementOperationsController DecreasePriority() - => this.AddOperation(new MovePriorityManagementOperation(-1)); + public ManagementOperationsController IncreasePriority() + => this.AddOperation(new MovePriorityManagementOperation(1)); - public ManagementOperationsController SetRuleForUpdate(Rule updatedRule) - => this.AddOperation(new SetRuleForUpdateManagementOperation(updatedRule)); + public ManagementOperationsController SetRuleForUpdate(Rule updatedRule) + => this.AddOperation(new SetRuleForUpdateManagementOperation(updatedRule)); - public ManagementOperationsController UpdateRules() - => this.AddOperation(new UpdateRulesManagementOperation(this.rulesSource)); + public ManagementOperationsController UpdateRules() + => this.AddOperation(new UpdateRulesManagementOperation(this.rulesSource)); - private ManagementOperationsController AddOperation(IManagementOperation managementOperation) + private ManagementOperationsController AddOperation(IManagementOperation managementOperation) { this.managementOperations.Add(managementOperation); diff --git a/src/Rules.Framework/Management/Operations/AddRuleManagementOperation.cs b/src/Rules.Framework/Management/Operations/AddRuleManagementOperation.cs index 8bd09402..95f127c0 100644 --- a/src/Rules.Framework/Management/Operations/AddRuleManagementOperation.cs +++ b/src/Rules.Framework/Management/Operations/AddRuleManagementOperation.cs @@ -2,33 +2,29 @@ namespace Rules.Framework.Management.Operations { using System.Collections.Generic; using System.Threading.Tasks; - using Rules.Framework.Core; using Rules.Framework.Source; - internal sealed class AddRuleManagementOperation : IManagementOperation + internal sealed class AddRuleManagementOperation : IManagementOperation { - private readonly Rule rule; - private readonly IRulesSource rulesDataSource; + private readonly Rule rule; + private readonly IRulesSource rulesDataSource; - public AddRuleManagementOperation(IRulesSource rulesDataSource, Rule rule) + public AddRuleManagementOperation(IRulesSource rulesDataSource, Rule rule) { this.rulesDataSource = rulesDataSource; this.rule = rule; } - public async Task>> ApplyAsync(IEnumerable> rules) + public async Task> ApplyAsync(IEnumerable rules) { - AddRuleArgs addRuleArgs = new() + var addRuleArgs = new AddRuleArgs { Rule = this.rule, }; await this.rulesDataSource.AddRuleAsync(addRuleArgs).ConfigureAwait(false); - List> rulesResult = new List>(rules) - { - this.rule - }; + var rulesResult = new List(rules) { this.rule }; return rulesResult; } diff --git a/src/Rules.Framework/Management/Operations/FilterPrioritiesRangeManagementOperation.cs b/src/Rules.Framework/Management/Operations/FilterPrioritiesRangeManagementOperation.cs index e138693f..039a8e78 100644 --- a/src/Rules.Framework/Management/Operations/FilterPrioritiesRangeManagementOperation.cs +++ b/src/Rules.Framework/Management/Operations/FilterPrioritiesRangeManagementOperation.cs @@ -5,10 +5,10 @@ namespace Rules.Framework.Management.Operations using System.Threading.Tasks; using Rules.Framework.Core; - internal sealed class FilterPrioritiesRangeManagementOperation : IManagementOperation + internal sealed class FilterPrioritiesRangeManagementOperation : IManagementOperation { - private readonly int? topPriorityThreshold; private readonly int? bottomPriorityThreshold; + private readonly int? topPriorityThreshold; public FilterPrioritiesRangeManagementOperation(int? topPriorityThreshold, int? bottomPriorityThreshold) { @@ -16,9 +16,9 @@ public FilterPrioritiesRangeManagementOperation(int? topPriorityThreshold, int? this.bottomPriorityThreshold = bottomPriorityThreshold; } - public Task>> ApplyAsync(IEnumerable> rules) + public Task> ApplyAsync(IEnumerable rules) { - IEnumerable> filteredRules = rules; + var filteredRules = rules; if (this.topPriorityThreshold.HasValue) { diff --git a/src/Rules.Framework/Management/Operations/MovePriorityManagementOperation.cs b/src/Rules.Framework/Management/Operations/MovePriorityManagementOperation.cs index 4edf5bdb..7fdd1bd3 100644 --- a/src/Rules.Framework/Management/Operations/MovePriorityManagementOperation.cs +++ b/src/Rules.Framework/Management/Operations/MovePriorityManagementOperation.cs @@ -5,7 +5,7 @@ namespace Rules.Framework.Management.Operations using System.Threading.Tasks; using Rules.Framework.Core; - internal sealed class MovePriorityManagementOperation : IManagementOperation + internal sealed class MovePriorityManagementOperation : IManagementOperation { private readonly int priorityMoveFactor; @@ -14,9 +14,9 @@ public MovePriorityManagementOperation(int priorityMoveFactor) this.priorityMoveFactor = priorityMoveFactor; } - public Task>> ApplyAsync(IEnumerable> rules) + public Task> ApplyAsync(IEnumerable rules) { - IEnumerable> updatedPrioritiesRules = rules.Select(r => + IEnumerable updatedPrioritiesRules = rules.Select(r => { r.Priority += this.priorityMoveFactor; return r; diff --git a/src/Rules.Framework/Management/Operations/SetRuleForUpdateManagementOperation.cs b/src/Rules.Framework/Management/Operations/SetRuleForUpdateManagementOperation.cs index f92fb888..185f7a04 100644 --- a/src/Rules.Framework/Management/Operations/SetRuleForUpdateManagementOperation.cs +++ b/src/Rules.Framework/Management/Operations/SetRuleForUpdateManagementOperation.cs @@ -6,18 +6,18 @@ namespace Rules.Framework.Management.Operations using System.Threading.Tasks; using Rules.Framework.Core; - internal sealed class SetRuleForUpdateManagementOperation : IManagementOperation + internal sealed class SetRuleForUpdateManagementOperation : IManagementOperation { - private readonly Rule updatedRule; + private readonly Rule updatedRule; - public SetRuleForUpdateManagementOperation(Rule updatedRule) + public SetRuleForUpdateManagementOperation(Rule updatedRule) { this.updatedRule = updatedRule; } - public Task>> ApplyAsync(IEnumerable> rules) + public Task> ApplyAsync(IEnumerable rules) { - List> result = new List>(rules); + var result = new List(rules); result.RemoveAll(r => string.Equals(r.Name, this.updatedRule.Name, StringComparison.InvariantCultureIgnoreCase)); result.Add(this.updatedRule); diff --git a/src/Rules.Framework/Management/Operations/UpdateRulesManagementOperation.cs b/src/Rules.Framework/Management/Operations/UpdateRulesManagementOperation.cs index e1d9d7f6..49eddbcf 100644 --- a/src/Rules.Framework/Management/Operations/UpdateRulesManagementOperation.cs +++ b/src/Rules.Framework/Management/Operations/UpdateRulesManagementOperation.cs @@ -2,23 +2,22 @@ namespace Rules.Framework.Management.Operations { using System.Collections.Generic; using System.Threading.Tasks; - using Rules.Framework.Core; using Rules.Framework.Source; - internal sealed class UpdateRulesManagementOperation : IManagementOperation + internal sealed class UpdateRulesManagementOperation : IManagementOperation { - private readonly IRulesSource rulesSource; + private readonly IRulesSource rulesSource; - public UpdateRulesManagementOperation(IRulesSource rulesSource) + public UpdateRulesManagementOperation(IRulesSource rulesSource) { this.rulesSource = rulesSource; } - public async Task>> ApplyAsync(IEnumerable> rules) + public async Task> ApplyAsync(IEnumerable rules) { - foreach (Rule existentRule in rules) + foreach (var existentRule in rules) { - UpdateRuleArgs updateRuleArgs = new() + var updateRuleArgs = new UpdateRuleArgs { Rule = existentRule, }; diff --git a/src/Rules.Framework/OperationResult.cs b/src/Rules.Framework/OperationResult.cs new file mode 100644 index 00000000..4352aa34 --- /dev/null +++ b/src/Rules.Framework/OperationResult.cs @@ -0,0 +1,49 @@ +namespace Rules.Framework +{ + using System; + using System.Collections.Generic; + using System.Linq; + + /// + /// Represents the result of an operation performed on the rules engine. + /// + public class OperationResult + { + /// + /// Initializes a new instance of the class. + /// + /// if set to true it represents a success operation result. + /// The errors. + protected OperationResult(bool isSuccess, IEnumerable errors) + { + this.IsSuccess = isSuccess; + this.Errors = errors; + } + + /// + /// Gets the errors occurred during the operation. + /// + /// The errors. + public IEnumerable Errors { get; } + + /// + /// Gets a value indicating whether the operation was successfull or not. + /// + /// true if rule operation was successfull; otherwise, false. + public bool IsSuccess { get; } + + internal static OperationResult Failure(string error) => Failure(new[] { error }); + + internal static OperationResult Failure(IEnumerable errors) + { + if (errors is null) + { + throw new ArgumentNullException(nameof(errors)); + } + + return new OperationResult(isSuccess: false, errors: errors); + } + + internal static OperationResult Success() => new(isSuccess: true, errors: Enumerable.Empty()); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Core/Operators.cs b/src/Rules.Framework/Operators.cs similarity index 98% rename from src/Rules.Framework/Core/Operators.cs rename to src/Rules.Framework/Operators.cs index d7101a08..d49b3d4c 100644 --- a/src/Rules.Framework/Core/Operators.cs +++ b/src/Rules.Framework/Operators.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Core +namespace Rules.Framework { /// /// Defines the set of operators supported for rule's conditions. diff --git a/src/Rules.Framework/Providers/InMemory/DataModel/ComposedConditionNodeDataModel.cs b/src/Rules.Framework/Providers/InMemory/DataModel/ComposedConditionNodeDataModel.cs index 18a0966b..cb788cef 100644 --- a/src/Rules.Framework/Providers/InMemory/DataModel/ComposedConditionNodeDataModel.cs +++ b/src/Rules.Framework/Providers/InMemory/DataModel/ComposedConditionNodeDataModel.cs @@ -1,7 +1,7 @@ namespace Rules.Framework.Providers.InMemory.DataModel { - internal sealed class ComposedConditionNodeDataModel : ConditionNodeDataModel + internal sealed class ComposedConditionNodeDataModel : ConditionNodeDataModel { - public ConditionNodeDataModel[] ChildConditionNodes { get; set; } + public ConditionNodeDataModel[] ChildConditionNodes { get; set; } } } \ No newline at end of file diff --git a/src/Rules.Framework/Providers/InMemory/DataModel/ConditionNodeDataModel.cs b/src/Rules.Framework/Providers/InMemory/DataModel/ConditionNodeDataModel.cs index 75dfd00a..775cdc0b 100644 --- a/src/Rules.Framework/Providers/InMemory/DataModel/ConditionNodeDataModel.cs +++ b/src/Rules.Framework/Providers/InMemory/DataModel/ConditionNodeDataModel.cs @@ -1,9 +1,9 @@ namespace Rules.Framework.Providers.InMemory.DataModel { using System.Collections.Generic; - using Rules.Framework.Core; + using Rules.Framework; - internal class ConditionNodeDataModel + internal class ConditionNodeDataModel { public LogicalOperators LogicalOperator { get; set; } diff --git a/src/Rules.Framework/Providers/InMemory/DataModel/RuleDataModel.cs b/src/Rules.Framework/Providers/InMemory/DataModel/RuleDataModel.cs index 02efa123..ce4f5ea4 100644 --- a/src/Rules.Framework/Providers/InMemory/DataModel/RuleDataModel.cs +++ b/src/Rules.Framework/Providers/InMemory/DataModel/RuleDataModel.cs @@ -2,14 +2,12 @@ namespace Rules.Framework.Providers.InMemory.DataModel { using System; - internal sealed class RuleDataModel + internal sealed class RuleDataModel { public bool Active { get; set; } = true; public dynamic Content { get; set; } - public TContentType ContentType { get; set; } - public DateTime DateBegin { get; set; } public DateTime? DateEnd { get; set; } @@ -18,6 +16,8 @@ internal sealed class RuleDataModel public int Priority { get; set; } - public ConditionNodeDataModel RootCondition { get; set; } + public ConditionNodeDataModel RootCondition { get; set; } + + public string Ruleset { get; set; } } -} +} \ No newline at end of file diff --git a/src/Rules.Framework/Providers/InMemory/DataModel/RulesetDataModel.cs b/src/Rules.Framework/Providers/InMemory/DataModel/RulesetDataModel.cs new file mode 100644 index 00000000..390c1919 --- /dev/null +++ b/src/Rules.Framework/Providers/InMemory/DataModel/RulesetDataModel.cs @@ -0,0 +1,14 @@ +namespace Rules.Framework.Providers.InMemory.DataModel +{ + using System; + using System.Collections.Generic; + + internal class RulesetDataModel + { + public DateTime Creation { get; set; } + + public string Name { get; set; } + + public List Rules { get; set; } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Providers/InMemory/DataModel/ValueConditionNodeDataModel.cs b/src/Rules.Framework/Providers/InMemory/DataModel/ValueConditionNodeDataModel.cs index a0d43179..a6c9f53d 100644 --- a/src/Rules.Framework/Providers/InMemory/DataModel/ValueConditionNodeDataModel.cs +++ b/src/Rules.Framework/Providers/InMemory/DataModel/ValueConditionNodeDataModel.cs @@ -1,10 +1,10 @@ namespace Rules.Framework.Providers.InMemory.DataModel { - using Rules.Framework.Core; + using Rules.Framework; - internal sealed class ValueConditionNodeDataModel : ConditionNodeDataModel + internal sealed class ValueConditionNodeDataModel : ConditionNodeDataModel { - public TConditionType ConditionType { get; set; } + public string Condition { get; set; } public DataTypes DataType { get; set; } diff --git a/src/Rules.Framework/Providers/InMemory/IInMemoryRulesStorage.cs b/src/Rules.Framework/Providers/InMemory/IInMemoryRulesStorage.cs index 0ae7d5e6..f2bf79e1 100644 --- a/src/Rules.Framework/Providers/InMemory/IInMemoryRulesStorage.cs +++ b/src/Rules.Framework/Providers/InMemory/IInMemoryRulesStorage.cs @@ -3,14 +3,18 @@ namespace Rules.Framework.Providers.InMemory using System.Collections.Generic; using Rules.Framework.Providers.InMemory.DataModel; - internal interface IInMemoryRulesStorage + internal interface IInMemoryRulesStorage { - void AddRule(RuleDataModel ruleDataModel); + void AddRule(RuleDataModel ruleDataModel); - IReadOnlyCollection> GetAllRules(); + void CreateRuleset(string contentType); - IReadOnlyCollection> GetRulesBy(TContentType contentType); + IReadOnlyCollection GetAllRules(); - void UpdateRule(RuleDataModel ruleDataModel); + IReadOnlyCollection GetRulesBy(string ruleset); + + IReadOnlyCollection GetRulesets(); + + void UpdateRule(RuleDataModel ruleDataModel); } } \ No newline at end of file diff --git a/src/Rules.Framework/Providers/InMemory/IRuleFactory.cs b/src/Rules.Framework/Providers/InMemory/IRuleFactory.cs index 57f6b6d3..08936dd9 100644 --- a/src/Rules.Framework/Providers/InMemory/IRuleFactory.cs +++ b/src/Rules.Framework/Providers/InMemory/IRuleFactory.cs @@ -1,12 +1,11 @@ namespace Rules.Framework.Providers.InMemory { - using Rules.Framework.Core; using Rules.Framework.Providers.InMemory.DataModel; - internal interface IRuleFactory + internal interface IRuleFactory { - Rule CreateRule(RuleDataModel ruleDataModel); + Rule CreateRule(RuleDataModel ruleDataModel); - RuleDataModel CreateRule(Rule rule); + RuleDataModel CreateRule(Rule rule); } } \ No newline at end of file diff --git a/src/Rules.Framework/Providers/InMemory/InMemoryProviderRulesDataSource.cs b/src/Rules.Framework/Providers/InMemory/InMemoryProviderRulesDataSource.cs index 2232e333..5d307282 100644 --- a/src/Rules.Framework/Providers/InMemory/InMemoryProviderRulesDataSource.cs +++ b/src/Rules.Framework/Providers/InMemory/InMemoryProviderRulesDataSource.cs @@ -2,23 +2,21 @@ namespace Rules.Framework.Providers.InMemory { using System; using System.Collections.Generic; + using System.Linq; using System.Threading.Tasks; - using Rules.Framework.Core; /// /// The rules data source implementation for usage backed with a in-memory database. /// - /// The type of the content type. - /// The type of the condition type. - /// - public class InMemoryProviderRulesDataSource : IRulesDataSource + /// + public class InMemoryProviderRulesDataSource : IRulesDataSource { - private readonly IInMemoryRulesStorage inMemoryRulesStorage; - private readonly IRuleFactory ruleFactory; + private readonly IInMemoryRulesStorage inMemoryRulesStorage; + private readonly IRuleFactory ruleFactory; internal InMemoryProviderRulesDataSource( - IInMemoryRulesStorage inMemoryRulesStorage, - IRuleFactory ruleFactory) + IInMemoryRulesStorage inMemoryRulesStorage, + IRuleFactory ruleFactory) { this.inMemoryRulesStorage = inMemoryRulesStorage ?? throw new ArgumentNullException(nameof(inMemoryRulesStorage)); this.ruleFactory = ruleFactory ?? throw new ArgumentNullException(nameof(ruleFactory)); @@ -30,7 +28,7 @@ internal InMemoryProviderRulesDataSource( /// The rule. /// /// rule - public Task AddRuleAsync(Rule rule) + public Task AddRuleAsync(Rule rule) { if (rule is null) { @@ -45,20 +43,48 @@ public Task AddRuleAsync(Rule rule) } /// - /// Gets the rules categorized with specified between - /// and . + /// Creates a new ruleset on the data source. /// - /// the content type categorization. + /// the ruleset name. + /// + /// ruleset + /// + /// The ruleset '{ruleset}' already exists. + /// + public Task CreateRulesetAsync(string ruleset) + { + if (string.IsNullOrWhiteSpace(ruleset)) + { + throw new ArgumentNullException(nameof(ruleset)); + } + + var rulesets = this.inMemoryRulesStorage.GetRulesets(); + + if (rulesets.Any(rs => string.Equals(rs.Name, ruleset, StringComparison.Ordinal))) + { + throw new InvalidOperationException($"The ruleset '{ruleset}' already exists."); + } + + this.inMemoryRulesStorage.CreateRuleset(ruleset); + + return Task.CompletedTask; + } + + /// + /// Gets the rules categorized with specified between and . + /// + /// the ruleset. /// the filtering begin date. /// the filtering end date. /// - public Task>> GetRulesAsync(TContentType contentType, DateTime dateBegin, DateTime dateEnd) + public Task> GetRulesAsync(string ruleset, DateTime dateBegin, DateTime dateEnd) { - var filteredByContent = this.inMemoryRulesStorage.GetRulesBy(contentType); + var filteredByRuleset = this.inMemoryRulesStorage.GetRulesBy(ruleset); - var filteredRules = new Rule[filteredByContent.Count]; + var filteredRules = new Rule[filteredByRuleset.Count]; var i = 0; - foreach (var ruleDataModel in filteredByContent) + foreach (var ruleDataModel in filteredByRuleset) { if (ruleDataModel.DateBegin <= dateEnd && (ruleDataModel.DateEnd is null || ruleDataModel.DateEnd > dateBegin)) { @@ -72,7 +98,7 @@ public Task>> GetRulesAsync(TCont Array.Resize(ref filteredRules, i); } - return Task.FromResult>>(filteredRules); + return Task.FromResult>(filteredRules); } /// @@ -81,7 +107,7 @@ public Task>> GetRulesAsync(TCont /// The rules filter arguments. /// /// rulesFilterArgs - public Task>> GetRulesByAsync(RulesFilterArgs rulesFilterArgs) + public Task> GetRulesByAsync(RulesFilterArgs rulesFilterArgs) { if (rulesFilterArgs is null) { @@ -90,12 +116,12 @@ public Task>> GetRulesByAsync(Rul var ruleDataModels = this.inMemoryRulesStorage.GetAllRules(); - var filteredRules = new Rule[ruleDataModels.Count]; + var filteredRules = new Rule[ruleDataModels.Count]; var i = 0; foreach (var ruleDataModel in ruleDataModels) { - if (!object.Equals(rulesFilterArgs.ContentType, default(TContentType)) - && !object.Equals(ruleDataModel.ContentType, rulesFilterArgs.ContentType)) + if (!object.Equals(rulesFilterArgs.Ruleset, default(string)) + && !object.Equals(ruleDataModel.Ruleset, rulesFilterArgs.Ruleset)) { continue; } @@ -121,7 +147,24 @@ public Task>> GetRulesByAsync(Rul Array.Resize(ref filteredRules, i); } - return Task.FromResult>>(filteredRules); + return Task.FromResult>(filteredRules); + } + + /// + /// Gets the rulesets from the data source. + /// + /// + public Task> GetRulesetsAsync() + { + var rulesetDataModels = this.inMemoryRulesStorage.GetRulesets(); + var rulesets = new Ruleset[rulesetDataModels.Count]; + var i = 0; + foreach (var rulesetDataModel in rulesetDataModels) + { + rulesets[i++] = new Ruleset(rulesetDataModel.Name, rulesetDataModel.Creation); + } + + return Task.FromResult>(rulesets); } /// @@ -130,7 +173,7 @@ public Task>> GetRulesByAsync(Rul /// The rule. /// /// rule - public Task UpdateRuleAsync(Rule rule) + public Task UpdateRuleAsync(Rule rule) { if (rule is null) { @@ -144,4 +187,4 @@ public Task UpdateRuleAsync(Rule rule) return Task.CompletedTask; } } -} +} \ No newline at end of file diff --git a/src/Rules.Framework/Providers/InMemory/InMemoryProviderRulesDataSourceSelectorExtensions.cs b/src/Rules.Framework/Providers/InMemory/InMemoryProviderRulesDataSourceSelectorExtensions.cs index 4c0cb8f0..377557e6 100644 --- a/src/Rules.Framework/Providers/InMemory/InMemoryProviderRulesDataSourceSelectorExtensions.cs +++ b/src/Rules.Framework/Providers/InMemory/InMemoryProviderRulesDataSourceSelectorExtensions.cs @@ -12,26 +12,22 @@ public static class InMemoryProviderRulesDataSourceSelectorExtensions /// /// Sets the rules engine data source from a in-memory data source. /// - /// The type of the content type. - /// The type of the condition type. /// The rules data source selector. /// /// rulesDataSourceSelector - public static IConfiguredRulesEngineBuilder SetInMemoryDataSource( - this IRulesDataSourceSelector rulesDataSourceSelector) - => rulesDataSourceSelector.SetInMemoryDataSource(new InMemoryRulesStorage()); + public static IConfiguredRulesEngineBuilder SetInMemoryDataSource( + this IRulesDataSourceSelector rulesDataSourceSelector) + => rulesDataSourceSelector.SetInMemoryDataSource(new InMemoryRulesStorage()); /// /// Sets the rules engine data source from a in-memory data source. /// - /// The type of the content type. - /// The type of the condition type. /// The rules data source selector. /// The service provider. /// /// rulesDataSourceSelector or serviceProvider - public static IConfiguredRulesEngineBuilder SetInMemoryDataSource( - this IRulesDataSourceSelector rulesDataSourceSelector, + public static IConfiguredRulesEngineBuilder SetInMemoryDataSource( + this IRulesDataSourceSelector rulesDataSourceSelector, IServiceProvider serviceProvider) { if (serviceProvider is null) @@ -39,24 +35,24 @@ public static IConfiguredRulesEngineBuilder SetInM throw new ArgumentNullException(nameof(serviceProvider)); } - var inMemoryRulesStorage = (IInMemoryRulesStorage)serviceProvider - .GetService(typeof(IInMemoryRulesStorage)); + var inMemoryRulesStorage = (IInMemoryRulesStorage)serviceProvider + .GetService(typeof(IInMemoryRulesStorage)); return rulesDataSourceSelector.SetInMemoryDataSource(inMemoryRulesStorage); } - private static IConfiguredRulesEngineBuilder SetInMemoryDataSource( - this IRulesDataSourceSelector rulesDataSourceSelector, - IInMemoryRulesStorage inMemoryRulesStorage) + private static IConfiguredRulesEngineBuilder SetInMemoryDataSource( + this IRulesDataSourceSelector rulesDataSourceSelector, + IInMemoryRulesStorage inMemoryRulesStorage) { if (rulesDataSourceSelector is null) { throw new ArgumentNullException(nameof(rulesDataSourceSelector)); } - var ruleFactory = new RuleFactory(); + var ruleFactory = new RuleFactory(); var inMemoryProviderRulesDataSource - = new InMemoryProviderRulesDataSource(inMemoryRulesStorage, ruleFactory); + = new InMemoryProviderRulesDataSource(inMemoryRulesStorage, ruleFactory); return rulesDataSourceSelector.SetDataSource(inMemoryProviderRulesDataSource); } diff --git a/src/Rules.Framework/Providers/InMemory/InMemoryRulesStorage.cs b/src/Rules.Framework/Providers/InMemory/InMemoryRulesStorage.cs index 14a6a70f..1db0c3b8 100644 --- a/src/Rules.Framework/Providers/InMemory/InMemoryRulesStorage.cs +++ b/src/Rules.Framework/Providers/InMemory/InMemoryRulesStorage.cs @@ -6,18 +6,18 @@ namespace Rules.Framework.Providers.InMemory using System.Linq; using Rules.Framework.Providers.InMemory.DataModel; - internal sealed class InMemoryRulesStorage : IInMemoryRulesStorage + internal sealed class InMemoryRulesStorage : IInMemoryRulesStorage { - private readonly ConcurrentDictionary>> rulesByContentType; + private readonly ConcurrentDictionary rulesets; public InMemoryRulesStorage() { - this.rulesByContentType = new ConcurrentDictionary>>(); + this.rulesets = new ConcurrentDictionary(StringComparer.Ordinal); } - public void AddRule(RuleDataModel ruleDataModel) + public void AddRule(RuleDataModel ruleDataModel) { - List> contentTypeRules = GetRulesCollectionByContentType(ruleDataModel.ContentType); + var contentTypeRules = this.GetRulesCollectionByRuleset(ruleDataModel.Ruleset); lock (contentTypeRules) { @@ -30,23 +30,36 @@ public void AddRule(RuleDataModel ruleDataModel) } } - public IReadOnlyCollection> GetAllRules() - => this.rulesByContentType.SelectMany(kvp => kvp.Value).ToList().AsReadOnly(); + public void CreateRuleset(string ruleset) + { + _ = this.rulesets.TryAdd(ruleset, new RulesetDataModel + { + Creation = DateTime.UtcNow, + Name = ruleset, + Rules = new List(), + }); + } - public IReadOnlyCollection> GetRulesBy(TContentType contentType) + public IReadOnlyCollection GetAllRules() + => this.rulesets.SelectMany(kvp => kvp.Value.Rules).ToList().AsReadOnly(); + + public IReadOnlyCollection GetRulesBy(string contentType) { - List> contentTypeRules = GetRulesCollectionByContentType(contentType); + var rules = this.GetRulesCollectionByRuleset(contentType); - return contentTypeRules.AsReadOnly(); + return rules.AsReadOnly(); } - public void UpdateRule(RuleDataModel ruleDataModel) + public IReadOnlyCollection GetRulesets() + => this.rulesets.Values.ToList().AsReadOnly(); + + public void UpdateRule(RuleDataModel ruleDataModel) { - List> contentTypeRules = GetRulesCollectionByContentType(ruleDataModel.ContentType); + var contentTypeRules = this.GetRulesCollectionByRuleset(ruleDataModel.Ruleset); lock (contentTypeRules) { - RuleDataModel existent = contentTypeRules.Find(r => string.Equals(r.Name, ruleDataModel.Name, StringComparison.Ordinal)); + var existent = contentTypeRules.Find(r => string.Equals(r.Name, ruleDataModel.Name, StringComparison.Ordinal)); if (existent is null) { throw new InvalidOperationException($"Rule with name '{ruleDataModel.Name}' does not exist, no update can be done."); @@ -57,7 +70,14 @@ public void UpdateRule(RuleDataModel ruleDataModel } } - private List> GetRulesCollectionByContentType(TContentType contentType) => this.rulesByContentType - .GetOrAdd(contentType, (ct) => new List>()); + private List GetRulesCollectionByRuleset(string ruleset) + { + if (this.rulesets.TryGetValue(ruleset, out var rulesetDataModel)) + { + return rulesetDataModel.Rules; + } + + throw new InvalidOperationException($"A ruleset with name '{ruleset}' does not exist."); + } } } \ No newline at end of file diff --git a/src/Rules.Framework/Providers/InMemory/RuleFactory.cs b/src/Rules.Framework/Providers/InMemory/RuleFactory.cs index bf099b8a..ccd12984 100644 --- a/src/Rules.Framework/Providers/InMemory/RuleFactory.cs +++ b/src/Rules.Framework/Providers/InMemory/RuleFactory.cs @@ -3,25 +3,27 @@ namespace Rules.Framework.Providers.InMemory using System; using System.Linq; using System.Runtime.CompilerServices; + using Rules.Framework; + using Rules.Framework.ConditionNodes; using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; using Rules.Framework.Providers.InMemory.DataModel; - internal sealed class RuleFactory : IRuleFactory + internal sealed class RuleFactory : IRuleFactory { - public Rule CreateRule(RuleDataModel ruleDataModel) + public Rule CreateRule(RuleDataModel ruleDataModel) { if (ruleDataModel is null) { throw new ArgumentNullException(nameof(ruleDataModel)); } - var contentContainer = new ContentContainer(ruleDataModel.ContentType, (_) => ruleDataModel.Content); + var contentContainer = new ContentContainer((_) => ruleDataModel.Content); - var rule = new Rule + var rule = new Rule { Active = ruleDataModel.Active, ContentContainer = contentContainer, + Ruleset = ruleDataModel.Ruleset, DateBegin = ruleDataModel.DateBegin, DateEnd = ruleDataModel.DateEnd, Name = ruleDataModel.Name, @@ -32,7 +34,7 @@ public Rule CreateRule(RuleDataModel CreateRule(Rule rule) + public RuleDataModel CreateRule(Rule rule) { if (rule is null) { @@ -41,10 +43,10 @@ public RuleDataModel CreateRule(Rule(); - var ruleDataModel = new RuleDataModel + var ruleDataModel = new RuleDataModel { Content = content, - ContentType = rule.ContentContainer.ContentType, + Ruleset = rule.Ruleset, DateBegin = rule.DateBegin, DateEnd = rule.DateEnd, Name = rule.Name, @@ -56,17 +58,17 @@ public RuleDataModel CreateRule(Rule ConvertConditionNode(ConditionNodeDataModel conditionNodeDataModel) + private static IConditionNode ConvertConditionNode(ConditionNodeDataModel conditionNodeDataModel) { if (conditionNodeDataModel.LogicalOperator == LogicalOperators.Eval) { - return CreateValueConditionNode((ValueConditionNodeDataModel)conditionNodeDataModel); + return CreateValueConditionNode((ValueConditionNodeDataModel)conditionNodeDataModel); } - var composedConditionNodeDataModel = (ComposedConditionNodeDataModel)conditionNodeDataModel; + var composedConditionNodeDataModel = (ComposedConditionNodeDataModel)conditionNodeDataModel; var count = composedConditionNodeDataModel.ChildConditionNodes.Length; var childConditionNodeDataModels = composedConditionNodeDataModel.ChildConditionNodes; - var childConditionNodes = new IConditionNode[count]; + var childConditionNodes = new IConditionNode[count]; var i = -1; while (++i < count) @@ -74,18 +76,18 @@ private static IConditionNode ConvertConditionNode(ConditionNode childConditionNodes[i] = ConvertConditionNode(childConditionNodeDataModels[i]); } - return new ComposedConditionNode( + return new ComposedConditionNode( composedConditionNodeDataModel.LogicalOperator, childConditionNodes, new PropertiesDictionary(conditionNodeDataModel.Properties)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ValueConditionNodeDataModel ConvertValueConditionNode(ValueConditionNode valueConditionNode) + private static ValueConditionNodeDataModel ConvertValueConditionNode(ValueConditionNode valueConditionNode) { - return new ValueConditionNodeDataModel + return new ValueConditionNodeDataModel { - ConditionType = valueConditionNode.ConditionType, + Condition = valueConditionNode.Condition, LogicalOperator = LogicalOperators.Eval, DataType = valueConditionNode.DataType, Operand = valueConditionNode.Operand, @@ -95,28 +97,28 @@ private static ValueConditionNodeDataModel ConvertValueCondition } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static IConditionNode CreateValueConditionNode(ValueConditionNodeDataModel conditionNodeDataModel) + private static IConditionNode CreateValueConditionNode(ValueConditionNodeDataModel conditionNodeDataModel) { - return new ValueConditionNode( + return new ValueConditionNode( conditionNodeDataModel.DataType, - conditionNodeDataModel.ConditionType, + conditionNodeDataModel.Condition, conditionNodeDataModel.Operator, conditionNodeDataModel.Operand, new PropertiesDictionary(conditionNodeDataModel.Properties)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ConditionNodeDataModel ConvertComposedConditionNode(ComposedConditionNode composedConditionNode) + private ConditionNodeDataModel ConvertComposedConditionNode(ComposedConditionNode composedConditionNode) { - var conditionNodeDataModels = new ConditionNodeDataModel[composedConditionNode.ChildConditionNodes.Count()]; + var conditionNodeDataModels = new ConditionNodeDataModel[composedConditionNode.ChildConditionNodes.Count()]; var i = 0; - foreach (IConditionNode child in composedConditionNode.ChildConditionNodes) + foreach (var child in composedConditionNode.ChildConditionNodes) { conditionNodeDataModels[i++] = ConvertConditionNode(child); } - return new ComposedConditionNodeDataModel + return new ComposedConditionNodeDataModel { ChildConditionNodes = conditionNodeDataModels, LogicalOperator = composedConditionNode.LogicalOperator, @@ -124,14 +126,14 @@ private ConditionNodeDataModel ConvertComposedConditionNode(Comp }; } - private ConditionNodeDataModel ConvertConditionNode(IConditionNode conditionNode) + private ConditionNodeDataModel ConvertConditionNode(IConditionNode conditionNode) { if (conditionNode.LogicalOperator == LogicalOperators.Eval) { - return ConvertValueConditionNode((ValueConditionNode)conditionNode); + return ConvertValueConditionNode((ValueConditionNode)conditionNode); } - return ConvertComposedConditionNode((ComposedConditionNode)conditionNode); + return ConvertComposedConditionNode((ComposedConditionNode)conditionNode); } } } \ No newline at end of file diff --git a/src/Rules.Framework/Providers/InMemory/ServiceCollectionExtensions.cs b/src/Rules.Framework/Providers/InMemory/ServiceCollectionExtensions.cs index 51cb7c06..577c57d8 100644 --- a/src/Rules.Framework/Providers/InMemory/ServiceCollectionExtensions.cs +++ b/src/Rules.Framework/Providers/InMemory/ServiceCollectionExtensions.cs @@ -10,16 +10,14 @@ public static class ServiceCollectionExtensions /// /// Adds the in memory rules data source with specified service lifetime. /// - /// The type of the content type. - /// The type of the condition type. /// The service descriptors. /// The service lifetime. /// - public static IServiceCollection AddInMemoryRulesDataSource(this IServiceCollection serviceDescriptors, ServiceLifetime serviceLifetime) + public static IServiceCollection AddInMemoryRulesDataSource(this IServiceCollection serviceDescriptors, ServiceLifetime serviceLifetime) { - ServiceDescriptor item = ServiceDescriptor.Describe( - typeof(IInMemoryRulesStorage), - (sp) => new InMemoryRulesStorage(), + var item = ServiceDescriptor.Describe( + typeof(IInMemoryRulesStorage), + _ => new InMemoryRulesStorage(), serviceLifetime); serviceDescriptors.Add(item); diff --git a/src/Rules.Framework/Core/Rule.cs b/src/Rules.Framework/Rule.cs similarity index 52% rename from src/Rules.Framework/Core/Rule.cs rename to src/Rules.Framework/Rule.cs index 31f8009b..528e924d 100644 --- a/src/Rules.Framework/Core/Rule.cs +++ b/src/Rules.Framework/Rule.cs @@ -1,25 +1,23 @@ -namespace Rules.Framework.Core +namespace Rules.Framework { using System; + using Rules.Framework.Builder.Generic.RulesBuilder; + using Rules.Framework.Builder.RulesBuilder; /// /// Defines a rule. /// - /// The content type that allows to categorize rules. - /// - /// The condition type that allows to filter rules based on a set of conditions. - /// - public class Rule + public class Rule { /// - /// Gets and sets the if the rules ia active. + /// Gets and sets the if the rules is active. /// public bool Active { get; internal set; } = true; /// /// Gets the content container which contains the rule content. /// - public ContentContainer ContentContainer { get; internal set; } + public ContentContainer ContentContainer { get; internal set; } /// /// Gets the date from which the rule begins being applicable. @@ -44,22 +42,44 @@ public class Rule /// /// Gets the rule root condition. This property is null when rule has no conditions. /// - public IConditionNode RootCondition { get; internal set; } + public IConditionNode RootCondition { get; internal set; } + + /// + /// Gets the ruleset to which the rule belongs to. + /// + public string Ruleset { get; internal set; } + + /// + /// Creates a new rule with generic ruleset type and condition type. + /// + /// The type of the ruleset. + /// The type of the conditions. + /// + public static IRuleConfigureRuleset Create(string name) + => new RuleBuilder(name); + + /// + /// Creates a new rule. + /// + /// + public static IRuleConfigureRuleset Create(string name) + => new RuleBuilder(name); /// /// Clones the rule into a different instance. /// /// - public virtual Rule Clone() - => new Rule + public virtual Rule Clone() + => new Rule { + Active = this.Active, ContentContainer = this.ContentContainer, DateBegin = this.DateBegin, DateEnd = this.DateEnd, Name = this.Name, Priority = this.Priority, - RootCondition = this.RootCondition?.Clone(), - Active = this.Active, + RootCondition = this.RootCondition?.Clone()!, + Ruleset = this.Ruleset, }; } -} +} \ No newline at end of file diff --git a/src/Rules.Framework/RuleBuilder.cs b/src/Rules.Framework/RuleBuilder.cs deleted file mode 100644 index 8cf16a57..00000000 --- a/src/Rules.Framework/RuleBuilder.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Rules.Framework -{ - using Rules.Framework.Builder; - - /// - /// The builder to create a new rule. - /// - public static class RuleBuilder - { - /// - /// Creates a new rule. - /// - /// The type of the content type. - /// The type of the condition type. - /// - public static IRuleBuilder NewRule() - => new RuleBuilder(); - } -} \ No newline at end of file diff --git a/src/Rules.Framework/RuleConditionsExtractor.cs b/src/Rules.Framework/RuleConditionsExtractor.cs new file mode 100644 index 00000000..37d3ac44 --- /dev/null +++ b/src/Rules.Framework/RuleConditionsExtractor.cs @@ -0,0 +1,59 @@ +namespace Rules.Framework +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Rules.Framework.ConditionNodes; + + /// + /// Defines a extractor of conditions from rules. + /// + public class RuleConditionsExtractor : IRuleConditionsExtractor + { + /// + public IEnumerable GetConditions(IEnumerable matchedRules) + { + var conditions = new HashSet(StringComparer.Ordinal); + + if (!matchedRules.Any()) + { + return conditions; + } + + foreach (var rootCondition in matchedRules.Select(r => r.RootCondition)) + { + if (rootCondition is null) + { + continue; + } + + VisitConditionNode(rootCondition, conditions); + } + + return conditions; + } + + private static void VisitConditionNode(IConditionNode conditionNode, HashSet conditions) + { + switch (conditionNode) + { + case IValueConditionNode valueConditionNode: + + conditions.Add(valueConditionNode.Condition); + break; + + case ComposedConditionNode composedConditionNode: + + foreach (var childConditionNode in composedConditionNode.ChildConditionNodes) + { + VisitConditionNode(childConditionNode, conditions); + } + + break; + + default: + throw new NotSupportedException($"Unsupported condition node: '{conditionNode.GetType().Name}'."); + } + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/RuleOperationResult.cs b/src/Rules.Framework/RuleOperationResult.cs deleted file mode 100644 index c3e68edf..00000000 --- a/src/Rules.Framework/RuleOperationResult.cs +++ /dev/null @@ -1,51 +0,0 @@ -namespace Rules.Framework -{ - using System; - using System.Collections.Generic; - using System.Linq; - - /// - /// Represents the result of a rule operation. - /// - public class RuleOperationResult - { - /// - /// Initializes a new instance of the class. - /// - /// if set to true [is success]. - /// The errors. - protected RuleOperationResult(bool isSuccess, IEnumerable errors) - { - this.IsSuccess = isSuccess; - this.Errors = errors; - } - - /// - /// Gets the errors occurred during rule operation. - /// - /// - /// The errors. - /// - public IEnumerable Errors { get; } - - /// - /// Gets a value indicating whether rule operation was successfull or not. - /// - /// - /// true if rule operation was successfull; otherwise, false. - /// - public bool IsSuccess { get; } - - internal static RuleOperationResult Error(IEnumerable errors) - { - if (errors is null) - { - throw new ArgumentNullException(nameof(errors)); - } - - return new RuleOperationResult(false, errors); - } - - internal static RuleOperationResult Success() => new RuleOperationResult(true, Enumerable.Empty()); - } -} \ No newline at end of file diff --git a/src/Rules.Framework/RulesEngine.cs b/src/Rules.Framework/RulesEngine.cs index f6af512a..9a686e1a 100644 --- a/src/Rules.Framework/RulesEngine.cs +++ b/src/Rules.Framework/RulesEngine.cs @@ -8,7 +8,6 @@ namespace Rules.Framework using System.Text; using System.Threading.Tasks; using Rules.Framework.Builder.Validation; - using Rules.Framework.Core; using Rules.Framework.Evaluation; using Rules.Framework.Extensions; using Rules.Framework.Management; @@ -18,40 +17,33 @@ namespace Rules.Framework /// /// Exposes rules engine logic to provide rule matches to requests. /// - /// The content type that allows to categorize rules. - /// - /// The condition type that allows to filter rules based on a set of conditions. - /// - public class RulesEngine : IRulesEngine + public class RulesEngine : IRulesEngine { - private readonly IConditionsEvalEngine conditionsEvalEngine; - private readonly IConditionTypeExtractor conditionTypeExtractor; - private readonly RulesEngineOptions rulesEngineOptions; - private readonly IRulesSource rulesSource; - private readonly RuleValidator ruleValidator = RuleValidator.Instance; + private readonly IConditionsEvalEngine conditionsEvalEngine; + private readonly IRuleConditionsExtractor ruleConditionsExtractor; + private readonly IRulesSource rulesSource; + private readonly RuleValidator ruleValidator = RuleValidator.Instance; private readonly IValidatorProvider validatorProvider; internal RulesEngine( - IConditionsEvalEngine conditionsEvalEngine, - IRulesSource rulesSource, + IConditionsEvalEngine conditionsEvalEngine, + IRulesSource rulesSource, IValidatorProvider validatorProvider, RulesEngineOptions rulesEngineOptions, - IConditionTypeExtractor conditionTypeExtractor) + IRuleConditionsExtractor ruleConditionsExtractor) { this.conditionsEvalEngine = conditionsEvalEngine; this.rulesSource = rulesSource; this.validatorProvider = validatorProvider; - this.rulesEngineOptions = rulesEngineOptions; - this.conditionTypeExtractor = conditionTypeExtractor; + this.Options = rulesEngineOptions; + this.ruleConditionsExtractor = ruleConditionsExtractor; } - /// - /// Activates the specified existing rule. - /// - /// The rule. - /// - /// rule - public Task ActivateRuleAsync(Rule rule) + /// + public IRulesEngineOptions Options { get; } + + /// + public Task ActivateRuleAsync(Rule rule) { if (rule is null) { @@ -63,17 +55,8 @@ public Task ActivateRuleAsync(Rule - /// Adds a new rule. - /// - /// The rule. - /// The rule add priority option. - /// - /// rule or rule - /// - /// The placement option '{ruleAddPriorityOption.PriorityOption}' is not supported. - /// - public Task AddRuleAsync(Rule rule, RuleAddPriorityOption ruleAddPriorityOption) + /// + public Task AddRuleAsync(Rule rule, RuleAddPriorityOption ruleAddPriorityOption) { if (rule is null) { @@ -82,19 +65,32 @@ public Task AddRuleAsync(Rule if (ruleAddPriorityOption is null) { - throw new ArgumentNullException(nameof(rule)); + throw new ArgumentNullException(nameof(ruleAddPriorityOption)); } return this.AddRuleInternalAsync(rule, ruleAddPriorityOption); } - /// - /// Deactivates the specified existing rule. - /// - /// The rule. - /// - /// rule - public Task DeactivateRuleAsync(Rule rule) + /// + public async Task CreateRulesetAsync(string ruleset) + { + if (string.IsNullOrWhiteSpace(ruleset)) + { + throw new ArgumentNullException(nameof(ruleset)); + } + + var getContentTypesArgs = new GetRulesetsArgs(); + var existentRulesets = await this.rulesSource.GetRulesetsAsync(getContentTypesArgs).ConfigureAwait(false); + if (existentRulesets.Any(rs => string.Equals(rs.Name, ruleset, StringComparison.Ordinal))) + { + return OperationResult.Failure($"The ruleset '{ruleset}' already exists."); + } + + return await this.CreateRulesetInternalAsync(ruleset).ConfigureAwait(false); + } + + /// + public Task DeactivateRuleAsync(Rule rule) { if (rule is null) { @@ -106,143 +102,99 @@ public Task DeactivateRuleAsync(Rule - /// Gets the priority criterias. - /// - /// Rules engine priority criterias - public PriorityCriterias GetPriorityCriteria() + /// + public Task> GetRulesetsAsync() { - return this.rulesEngineOptions.PriorityCriteria; + return this.rulesSource.GetRulesetsAsync(new GetRulesetsArgs()); } - /// - /// Get the unique condition types associated with rules of a specific content type. - /// - /// - /// - /// - /// - /// - /// A set of rules is requested to rules data source and all conditions are evaluated - /// against them to provide a set of matches. - /// - /// All rules matching supplied conditions are returned. - /// - /// the matched rule; otherwise, empty. - public async Task> GetUniqueConditionTypesAsync(TContentType contentType, DateTime dateBegin, DateTime dateEnd) + /// + public async Task> GetUniqueConditionsAsync(string ruleset, DateTime dateBegin, DateTime dateEnd) { - var getRulesArgs = new GetRulesArgs + if (string.IsNullOrWhiteSpace(ruleset)) { - ContentType = contentType, + throw new ArgumentNullException(nameof(ruleset)); + } + + var getRulesArgs = new GetRulesArgs + { + ContentType = ruleset, DateBegin = dateBegin, DateEnd = dateEnd, }; var matchedRules = await this.rulesSource.GetRulesAsync(getRulesArgs).ConfigureAwait(false); - return this.conditionTypeExtractor.GetConditionTypes(matchedRules); + return this.ruleConditionsExtractor.GetConditions(matchedRules); } - /// - /// Provides all rule matches (if any) to the given content type at the specified and satisfying the supplied . - /// - /// - /// - /// - /// - /// - /// A set of rules is requested to rules data source and all conditions are evaluated - /// against them to provide a set of matches. - /// - /// All rules matching supplied conditions are returned. - /// - /// the matched rule; otherwise, null. - public async Task>> MatchManyAsync( - TContentType contentType, + /// + public async Task> MatchManyAsync( + string ruleset, DateTime matchDateTime, - IEnumerable> conditions) + IDictionary conditions) { + if (string.IsNullOrWhiteSpace(ruleset)) + { + throw new ArgumentNullException(nameof(ruleset)); + } + var evaluationOptions = new EvaluationOptions { ExcludeRulesWithoutSearchConditions = false, MatchMode = MatchModes.Exact, }; - var getRulesArgs = new GetRulesArgs + var getRulesArgs = new GetRulesArgs { - ContentType = contentType, + ContentType = ruleset, DateBegin = matchDateTime, DateEnd = matchDateTime, }; - var conditionsAsDictionary = conditions.ToDictionary(ks => ks.Type, ks => ks.Value); var orderedRules = await this.GetRulesOrderedAscendingAsync(getRulesArgs).ConfigureAwait(false); - return this.EvalAll(orderedRules, evaluationOptions, conditionsAsDictionary, active: true); + return this.EvalAll(orderedRules, evaluationOptions, conditions, active: true); } - /// - /// Provides a rule match (if any) to the given content type at the specified and satisfying the supplied . - /// - /// - /// - /// - /// - /// - /// A set of rules is requested to rules data source and all conditions are evaluated - /// against them to provide a set of matches. - /// - /// - /// If there's more than one match, a rule is selected based on the priority criteria and - /// value: topmost selects the lowest priority number and bottommost selects highest priority. - /// - /// - /// the matched rule; otherwise, null. - public async Task> MatchOneAsync( - TContentType contentType, + /// + public async Task MatchOneAsync( + string ruleset, DateTime matchDateTime, - IEnumerable> conditions) + IDictionary conditions) { + if (string.IsNullOrWhiteSpace(ruleset)) + { + throw new ArgumentNullException(nameof(ruleset)); + } + var evaluationOptions = new EvaluationOptions { ExcludeRulesWithoutSearchConditions = false, MatchMode = MatchModes.Exact, }; - var getRulesArgs = new GetRulesArgs + var getRulesArgs = new GetRulesArgs { - ContentType = contentType, + ContentType = ruleset, DateBegin = matchDateTime, DateEnd = matchDateTime, }; - var conditionsAsDictionary = conditions.ToDictionary(ks => ks.Type, ks => ks.Value); var orderedRules = await this.GetRulesOrderedAscendingAsync(getRulesArgs).ConfigureAwait(false); - return this.rulesEngineOptions.PriorityCriteria == PriorityCriterias.TopmostRuleWins - ? EvalOneTraverse(orderedRules, evaluationOptions, conditionsAsDictionary, active: true) - : EvalOneReverse(orderedRules, evaluationOptions, conditionsAsDictionary, active: true); + return this.Options.PriorityCriteria == PriorityCriterias.TopmostRuleWins + ? EvalOneTraverse(orderedRules, evaluationOptions, conditions, active: true) + : EvalOneReverse(orderedRules, evaluationOptions, conditions, active: true); } - /// - /// Searches for rules on given content type that match on supplied . - /// - /// - /// - /// - /// Only the condition types supplied on input conditions are evaluated, the remaining - /// conditions are ignored. - /// - /// - /// the set of rules matching the conditions. - public async Task>> SearchAsync(SearchArgs searchArgs) + /// + public async Task> SearchAsync(SearchArgs searchArgs) { if (searchArgs is null) { throw new ArgumentNullException(nameof(searchArgs)); } - var validator = this.validatorProvider.GetValidatorFor>(); + var validator = this.validatorProvider.GetValidatorFor>(); var validationResult = await validator.ValidateAsync(searchArgs).ConfigureAwait(false); if (!validationResult.IsValid) { @@ -265,25 +217,19 @@ public async Task>> SearchAsync(S MatchMode = MatchModes.Search, }; - var getRulesArgs = new GetRulesArgs + var getRulesArgs = new GetRulesArgs { - ContentType = searchArgs.ContentType, + ContentType = searchArgs.Ruleset, DateBegin = searchArgs.DateBegin, 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); } - /// - /// Updates the specified existing rule. - /// - /// The rule. - /// - /// rule - public Task UpdateRuleAsync(Rule rule) + /// + public Task UpdateRuleAsync(Rule rule) { if (rule is null) { @@ -293,12 +239,26 @@ public Task UpdateRuleAsync(Rule AddRuleInternalAsync(Rule rule, RuleAddPriorityOption ruleAddPriorityOption) + private async Task AddRuleInternalAsync(Rule rule, RuleAddPriorityOption ruleAddPriorityOption) { var errors = new List(); - var rulesFilterArgs = new GetRulesFilteredArgs + var rulesets = await this.rulesSource.GetRulesetsAsync(new GetRulesetsArgs()).ConfigureAwait(false); + + if (!rulesets.Any(rs => string.Equals(rs.Name, rule.Ruleset, StringComparison.Ordinal))) { - ContentType = rule.ContentContainer.ContentType, + if (!this.Options.AutoCreateRulesets) + { + errors.Add($"Specified ruleset '{rule.Ruleset}' does not exist. " + + $"Please create the ruleset first or set the rules engine option '{nameof(this.Options.AutoCreateRulesets)}' to true."); + return OperationResult.Failure(errors); + } + + await this.CreateRulesetInternalAsync(rule.Ruleset).ConfigureAwait(false); + } + + var rulesFilterArgs = new GetRulesFilteredArgs + { + Ruleset = rule.Ruleset, }; var existentRules = await this.rulesSource.GetRulesFilteredAsync(rulesFilterArgs).ConfigureAwait(false); @@ -316,7 +276,7 @@ private async Task AddRuleInternalAsync(Rule AddRuleInternalAsync(Rule rule, IEnumerable> existentRules) + private async Task AddRuleInternalAtBottomAsync(Rule rule, IEnumerable existentRules) { rule.Priority = !existentRules.Any() ? 1 : existentRules.Max(r => r.Priority) + 1; @@ -360,7 +320,7 @@ await ManagementOperations.Manage(existentRules) .ConfigureAwait(false); } - private async Task AddRuleInternalAtPriorityNumberAsync(Rule rule, RuleAddPriorityOption ruleAddPriorityOption, IEnumerable> existentRules) + private async Task AddRuleInternalAtPriorityNumberAsync(Rule rule, RuleAddPriorityOption ruleAddPriorityOption, IEnumerable existentRules) { var priorityMin = existentRules.MinOrDefault(r => r.Priority); var priorityMax = existentRules.MaxOrDefault(r => r.Priority); @@ -381,7 +341,7 @@ await ManagementOperations.Manage(existentRules) .ConfigureAwait(false); } - private async Task AddRuleInternalAtRuleNameAsync(Rule rule, RuleAddPriorityOption ruleAddPriorityOption, IEnumerable> existentRules) + private async Task AddRuleInternalAtRuleNameAsync(Rule rule, RuleAddPriorityOption ruleAddPriorityOption, IEnumerable existentRules) { var firstPriorityToIncrement = existentRules .FirstOrDefault(r => string.Equals(r.Name, ruleAddPriorityOption.AtRuleNameOptionValue, StringComparison.OrdinalIgnoreCase)) @@ -398,7 +358,7 @@ await ManagementOperations.Manage(existentRules) .ConfigureAwait(false); } - private async Task AddRuleInternalAtTopAsync(Rule rule, IEnumerable> existentRules) + private async Task AddRuleInternalAtTopAsync(Rule rule, IEnumerable existentRules) { rule.Priority = 1; @@ -411,15 +371,22 @@ await ManagementOperations.Manage(existentRules) .ConfigureAwait(false); } - private IEnumerable> EvalAll( - List> orderedRules, + private async Task CreateRulesetInternalAsync(string ruleset) + { + var createContentTypeArgs = new CreateRulesetArgs { Name = ruleset }; + await this.rulesSource.CreateRulesetAsync(createContentTypeArgs).ConfigureAwait(false); + return OperationResult.Success(); + } + + private IEnumerable EvalAll( + List orderedRules, EvaluationOptions evaluationOptions, - Dictionary conditionsAsDictionary, + IDictionary conditionsAsDictionary, bool? active) { // Begins evaluation at the first element of the given list as parameter. Returns all // rules that match. Assumes given list is ordered. - var matchedRules = new List>(orderedRules.Count); + var matchedRules = new List(orderedRules.Count); foreach (var rule in orderedRules) { if (this.EvalRule(rule, evaluationOptions, conditionsAsDictionary, active)) @@ -431,15 +398,15 @@ private IEnumerable> EvalAll( return matchedRules.AsReadOnly(); } - private Rule EvalOneReverse( - List> rules, + private Rule EvalOneReverse( + List rules, EvaluationOptions evaluationOptions, - Dictionary conditionsAsDictionary, + IDictionary conditionsAsDictionary, bool? active) { // Begins evaluation at the last element of the given list as parameter. Returns the // first rule that matches. Assumes given list is ordered. - for (int i = rules.Count - 1; i >= 0; i--) + for (var i = rules.Count - 1; i >= 0; i--) { var rule = rules[i]; if (this.EvalRule(rule, evaluationOptions, conditionsAsDictionary, active)) @@ -451,15 +418,15 @@ private Rule EvalOneReverse( return null!; } - private Rule EvalOneTraverse( - List> rules, + private Rule EvalOneTraverse( + List rules, EvaluationOptions evaluationOptions, - Dictionary conditionsAsDictionary, + IDictionary conditionsAsDictionary, bool? active) { // Begins evaluation at the first element of the given list as parameter. Returns the // first rule that matches. Assumes given list is ordered. - for (int i = 0; i < rules.Count; i++) + for (var i = 0; i < rules.Count; i++) { var rule = rules[i]; if (this.EvalRule(rule, evaluationOptions, conditionsAsDictionary, active)) @@ -473,16 +440,16 @@ private Rule EvalOneTraverse( [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool EvalRule( - Rule rule, + Rule rule, EvaluationOptions evaluationOptions, - Dictionary conditionsAsDictionary, + IDictionary conditionsAsDictionary, bool? active) => rule.Active == active.GetValueOrDefault(defaultValue: true) && (rule.RootCondition == null || this.conditionsEvalEngine.Eval(rule.RootCondition, conditionsAsDictionary, evaluationOptions)); - private async Task>> GetRulesOrderedAscendingAsync(GetRulesArgs getRulesArgs) + private async Task> GetRulesOrderedAscendingAsync(GetRulesArgs getRulesArgs) { var rules = await this.rulesSource.GetRulesAsync(getRulesArgs).ConfigureAwait(false); - var orderedRules = new List>(rules.Count()); + var orderedRules = new List(rules.Count()); var greatestPriority = 0; foreach (var rule in rules) { @@ -493,7 +460,7 @@ private async Task>> GetRulesOrderedAsce continue; } - for (int i = 0; i < orderedRules.Count; i++) + for (var i = 0; i < orderedRules.Count; i++) { var currentRule = orderedRules[i]; if (rule.Priority < currentRule.Priority) @@ -507,11 +474,11 @@ private async Task>> GetRulesOrderedAsce return orderedRules; } - private async Task UpdateRuleInternalAsync(Rule rule) + private async Task UpdateRuleInternalAsync(Rule rule) { - var rulesFilterArgs = new GetRulesFilteredArgs + var rulesFilterArgs = new GetRulesFilteredArgs { - ContentType = rule.ContentContainer.ContentType, + Ruleset = rule.Ruleset, }; var existentRules = await this.rulesSource.GetRulesFilteredAsync(rulesFilterArgs).ConfigureAwait(false); @@ -519,14 +486,14 @@ private async Task UpdateRuleInternalAsync(Rule string.Equals(r.Name, rule.Name, StringComparison.OrdinalIgnoreCase)); if (existentRule is null) { - return RuleOperationResult.Error(new[] { $"Rule with name '{rule.Name}' does not exist." }); + return OperationResult.Failure($"Rule with name '{rule.Name}' does not exist."); } var validationResult = this.ruleValidator.Validate(rule); if (!validationResult.IsValid) { - return RuleOperationResult.Error(validationResult.Errors.Select(ve => ve.ErrorMessage)); + return OperationResult.Failure(validationResult.Errors.Select(ve => ve.ErrorMessage)); } var topPriorityThreshold = Math.Min(rule.Priority, existentRule.Priority); @@ -567,7 +534,7 @@ await ManagementOperations.Manage(existentRules) break; } - return RuleOperationResult.Success(); + return OperationResult.Success(); } } } \ No newline at end of file diff --git a/src/Rules.Framework/RulesEngineBuilder.cs b/src/Rules.Framework/RulesEngineBuilder.cs index 12053412..b3089c61 100644 --- a/src/Rules.Framework/RulesEngineBuilder.cs +++ b/src/Rules.Framework/RulesEngineBuilder.cs @@ -11,7 +11,7 @@ public static class RulesEngineBuilder /// /// Starts building a rules engine. /// - /// a content type selector. - public static IContentTypeSelector CreateRulesEngine() => new ContentTypeSelector(); + /// the rules data source selector. + public static IRulesDataSourceSelector CreateRulesEngine() => new RulesDataSourceSelector(); } } \ No newline at end of file diff --git a/src/Rules.Framework/RulesEngineOptions.cs b/src/Rules.Framework/RulesEngineOptions.cs index f812c667..11cf4207 100644 --- a/src/Rules.Framework/RulesEngineOptions.cs +++ b/src/Rules.Framework/RulesEngineOptions.cs @@ -1,8 +1,6 @@ namespace Rules.Framework { - using System; using System.Collections.Generic; - using Rules.Framework.Core; /// /// The set of rules engine options that influence rules engine rules matching. @@ -14,43 +12,21 @@ private RulesEngineOptions() this.DataTypeDefaults = new Dictionary(); } - /// - /// Gets the default values for each of the supported data types. - /// + /// + public bool AutoCreateRulesets { get; set; } + + /// public IDictionary DataTypeDefaults { get; } - /// - /// Gets or sets whether rules' conditions is enabled or not. - /// + /// public bool EnableCompilation { get; set; } - /// - /// - /// Gets or sets the rules engine behavior when no condition with a specific type is - /// provided to rules engine to match with a rule's condition with the same type. - /// - /// - /// e.g. a rule with a condition of type "Age" is under evaluation but no condition of type - /// "Age" was supplied. - /// - /// + /// public MissingConditionBehaviors MissingConditionBehavior { get; set; } - /// - /// Gets or sets the priority criteria to untie when multiples rules are matched. - /// + /// public PriorityCriterias PriorityCriteria { get; set; } - /// - /// Gets or sets the priority criteria to untie when multiples rules are matched. - /// - [Obsolete("This property has a typo and has been replaced by PriorityCriteria.")] - public PriorityCriterias PriotityCriteria - { - get { return this.PriorityCriteria; } - set { this.PriorityCriteria = value; } - } - /// /// Creates a new set of rules engine options with framework-configured defaults. /// @@ -70,19 +46,19 @@ public static RulesEngineOptions NewWithDefaults() MissingConditionBehavior = MissingConditionBehaviors.UseDataTypeDefault, PriorityCriteria = PriorityCriterias.TopmostRuleWins, DataTypeDefaults = - { - [DataTypes.Boolean] = default(bool), - [DataTypes.Decimal] = default(decimal), - [DataTypes.Integer] = default(int), - [DataTypes.String] = string.Empty, - [DataTypes.ArrayBoolean] = default(bool), - [DataTypes.ArrayDecimal] = default(decimal), - [DataTypes.ArrayInteger] = default(int), - [DataTypes.ArrayString] = string.Empty, - }, + { + [DataTypes.Boolean] = default(bool), + [DataTypes.Decimal] = default(decimal), + [DataTypes.Integer] = default(int), + [DataTypes.String] = string.Empty, + [DataTypes.ArrayBoolean] = default(bool), + [DataTypes.ArrayDecimal] = default(decimal), + [DataTypes.ArrayInteger] = default(int), + [DataTypes.ArrayString] = string.Empty, + }, + AutoCreateRulesets = false, }; - return rulesEngineOptions; } } diff --git a/src/Rules.Framework/RulesFilterArgs.cs b/src/Rules.Framework/RulesFilterArgs.cs index fa3993c7..bc72dec0 100644 --- a/src/Rules.Framework/RulesFilterArgs.cs +++ b/src/Rules.Framework/RulesFilterArgs.cs @@ -5,32 +5,25 @@ namespace Rules.Framework /// /// The set of arguments to filter rules. /// - /// The type of the content type. [ExcludeFromCodeCoverage] - public class RulesFilterArgs + public class RulesFilterArgs { - /// - /// Gets or sets the content type to filter. - /// - /// - /// The type of the content. - /// - public TContentType ContentType { get; set; } - /// /// Gets or sets the name to filter. /// - /// - /// The name. - /// - public string Name { get; set; } + /// The name. + public string? Name { get; set; } /// /// Gets or sets the priority to filter. /// - /// - /// The priority. - /// + /// The priority. public int? Priority { get; set; } + + /// + /// Gets or sets the ruleset to filter. + /// + /// The ruleset name. + public string? Ruleset { get; set; } } } \ No newline at end of file diff --git a/src/Rules.Framework/Ruleset.cs b/src/Rules.Framework/Ruleset.cs new file mode 100644 index 00000000..aecd6f83 --- /dev/null +++ b/src/Rules.Framework/Ruleset.cs @@ -0,0 +1,28 @@ +namespace Rules.Framework +{ + using System; + + /// + /// The representation of a ruleset and associated metadata. + /// + public class Ruleset + { + internal Ruleset(string name, DateTime creation) + { + this.Name = name; + this.Creation = creation; + } + + /// + /// Gets the creation date and time of the ruleset. + /// + /// The creation. + public DateTime Creation { get; } + + /// + /// Gets the name of the ruleset. + /// + /// The name. + public string Name { get; } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/SearchArgs.cs b/src/Rules.Framework/SearchArgs.cs index a5d7ec8b..f8157e2e 100644 --- a/src/Rules.Framework/SearchArgs.cs +++ b/src/Rules.Framework/SearchArgs.cs @@ -2,25 +2,24 @@ namespace Rules.Framework { using System; using System.Collections.Generic; - using System.Linq; /// /// The set of search arguments to find rules. /// - /// The type of the content type. - /// The type of the condition type. - public class SearchArgs + /// The type of the ruleset. + /// The type of the condition. + public class SearchArgs { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// Type of the content. + /// The ruleset name. /// The date begin. /// The date end. - public SearchArgs(TContentType contentType, DateTime dateBegin, DateTime dateEnd) + public SearchArgs(TRuleset ruleset, DateTime dateBegin, DateTime dateEnd) { - this.Conditions = Enumerable.Empty>(); - this.ContentType = contentType; + this.Conditions = new Dictionary(); + this.Ruleset = ruleset; this.DateBegin = dateBegin; this.DateEnd = dateEnd; this.ExcludeRulesWithoutSearchConditions = false; @@ -28,16 +27,16 @@ public SearchArgs(TContentType contentType, DateTime dateBegin, DateTime dateEnd } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// Type of the content. + /// The ruleset name. /// The date begin. /// The date end. /// The active status. - public SearchArgs(TContentType contentType, DateTime dateBegin, DateTime dateEnd, bool active) + public SearchArgs(TRuleset ruleset, DateTime dateBegin, DateTime dateEnd, bool active) { - this.Conditions = Enumerable.Empty>(); - this.ContentType = contentType; + this.Conditions = new Dictionary(); + this.Ruleset = ruleset; this.DateBegin = dateBegin; this.DateEnd = dateEnd; this.ExcludeRulesWithoutSearchConditions = false; @@ -54,13 +53,7 @@ public SearchArgs(TContentType contentType, DateTime dateBegin, DateTime dateEnd /// Gets or sets the search conditions. /// /// The conditions. - public IEnumerable> Conditions { get; set; } - - /// - /// Gets or sets the content type to search. - /// - /// The type of the content. - public TContentType ContentType { get; } + public IDictionary Conditions { get; set; } /// /// Gets or sets the date begin. @@ -79,5 +72,11 @@ public SearchArgs(TContentType contentType, DateTime dateBegin, DateTime dateEnd /// /// true if [exclude rules without search conditions]; otherwise, false. public bool ExcludeRulesWithoutSearchConditions { get; set; } + + /// + /// Gets or sets the ruleset to search. + /// + /// The ruleset name. + public TRuleset Ruleset { get; } } -} +} \ No newline at end of file diff --git a/src/Rules.Framework/Serialization/IContentSerializationProvider.cs b/src/Rules.Framework/Serialization/IContentSerializationProvider.cs index d8a866b8..bf88bf96 100644 --- a/src/Rules.Framework/Serialization/IContentSerializationProvider.cs +++ b/src/Rules.Framework/Serialization/IContentSerializationProvider.cs @@ -1,17 +1,17 @@ namespace Rules.Framework.Serialization { /// - /// Defines the interface contract for a content serialization provider. Provides content serializers per - /// value, allowing for customization of serializers per each content type. + /// Defines the interface contract for a content serialization provider. Provides content + /// serializers per content type value, allowing for customization of serializers per each + /// content type. /// - /// The content type that allows to categorize rules. - public interface IContentSerializationProvider + public interface IContentSerializationProvider { /// /// Gets the content serializer associated with the given . /// /// the content type. /// the content serializer to deal with contents for specified content type. - IContentSerializer GetContentSerializer(TContentType contentType); + IContentSerializer GetContentSerializer(string contentType); } } \ No newline at end of file diff --git a/src/Rules.Framework/Serialization/SerializedContentContainer.cs b/src/Rules.Framework/Serialization/SerializedContentContainer.cs index 7d3892df..47d1cddb 100644 --- a/src/Rules.Framework/Serialization/SerializedContentContainer.cs +++ b/src/Rules.Framework/Serialization/SerializedContentContainer.cs @@ -1,24 +1,22 @@ namespace Rules.Framework.Serialization { - using Rules.Framework.Core; - /// - /// Defines a content container that relies on a to get a content serializer to deserialize container content. + /// Defines a content container that relies on a to + /// get a content serializer to deserialize container content. /// - /// The content type that allows to categorize rules. - public class SerializedContentContainer : ContentContainer + public class SerializedContentContainer : ContentContainer { /// - /// Creates a new . + /// Creates a new . /// /// the content type. /// the serialized content. /// the content serialization provider. public SerializedContentContainer( - TContentType contentType, + string contentType, object serializedContent, - IContentSerializationProvider contentSerializationProvider) - : base(contentType, (t) => contentSerializationProvider.GetContentSerializer(contentType).Deserialize(serializedContent, t)) + IContentSerializationProvider contentSerializationProvider) + : base((t) => contentSerializationProvider.GetContentSerializer(contentType).Deserialize(serializedContent, t)) { } } diff --git a/src/Rules.Framework/Source/AddRuleArgs.cs b/src/Rules.Framework/Source/AddRuleArgs.cs index 220349b2..f4fac87d 100644 --- a/src/Rules.Framework/Source/AddRuleArgs.cs +++ b/src/Rules.Framework/Source/AddRuleArgs.cs @@ -1,9 +1,7 @@ namespace Rules.Framework.Source { - using Rules.Framework.Core; - - internal sealed class AddRuleArgs + internal sealed class AddRuleArgs { - public Rule Rule { get; set; } + public Rule Rule { get; set; } } } \ No newline at end of file diff --git a/src/Rules.Framework/Source/AddRuleDelegate.cs b/src/Rules.Framework/Source/AddRuleDelegate.cs index d14822ab..90b1ed5d 100644 --- a/src/Rules.Framework/Source/AddRuleDelegate.cs +++ b/src/Rules.Framework/Source/AddRuleDelegate.cs @@ -2,6 +2,5 @@ namespace Rules.Framework.Source { using System.Threading.Tasks; - internal delegate Task AddRuleDelegate( - AddRuleArgs args); -} + internal delegate Task AddRuleDelegate(AddRuleArgs args); +} \ No newline at end of file diff --git a/src/Rules.Framework/Source/CreateRulesetArgs.cs b/src/Rules.Framework/Source/CreateRulesetArgs.cs new file mode 100644 index 00000000..b093cd3a --- /dev/null +++ b/src/Rules.Framework/Source/CreateRulesetArgs.cs @@ -0,0 +1,7 @@ +namespace Rules.Framework.Source +{ + internal sealed class CreateRulesetArgs + { + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Source/CreateRulesetDelegate.cs b/src/Rules.Framework/Source/CreateRulesetDelegate.cs new file mode 100644 index 00000000..e80ccc08 --- /dev/null +++ b/src/Rules.Framework/Source/CreateRulesetDelegate.cs @@ -0,0 +1,6 @@ +namespace Rules.Framework.Source +{ + using System.Threading.Tasks; + + internal delegate Task CreateRulesetDelegate(CreateRulesetArgs args); +} \ No newline at end of file diff --git a/src/Rules.Framework/Source/GetRulesArgs.cs b/src/Rules.Framework/Source/GetRulesArgs.cs index 39f7259a..1d7cc200 100644 --- a/src/Rules.Framework/Source/GetRulesArgs.cs +++ b/src/Rules.Framework/Source/GetRulesArgs.cs @@ -2,12 +2,12 @@ namespace Rules.Framework.Source { using System; - internal sealed class GetRulesArgs + internal sealed class GetRulesArgs { - public TContentType ContentType { get; set; } + public string ContentType { get; set; } public DateTime DateBegin { get; set; } public DateTime DateEnd { get; set; } } -} +} \ No newline at end of file diff --git a/src/Rules.Framework/Source/GetRulesDelegate.cs b/src/Rules.Framework/Source/GetRulesDelegate.cs index 7ca02458..19b3bf8e 100644 --- a/src/Rules.Framework/Source/GetRulesDelegate.cs +++ b/src/Rules.Framework/Source/GetRulesDelegate.cs @@ -1,9 +1,7 @@ namespace Rules.Framework.Source { - using Rules.Framework.Core; using System.Collections.Generic; using System.Threading.Tasks; - internal delegate Task>> GetRulesDelegate( - GetRulesArgs args); -} + internal delegate Task> GetRulesDelegate(GetRulesArgs args); +} \ No newline at end of file diff --git a/src/Rules.Framework/Source/GetRulesFilteredArgs.cs b/src/Rules.Framework/Source/GetRulesFilteredArgs.cs index 0a9e1eb7..e06fd0fc 100644 --- a/src/Rules.Framework/Source/GetRulesFilteredArgs.cs +++ b/src/Rules.Framework/Source/GetRulesFilteredArgs.cs @@ -1,8 +1,8 @@ namespace Rules.Framework.Source { - internal sealed class GetRulesFilteredArgs + internal sealed class GetRulesFilteredArgs { - public TContentType ContentType { get; set; } + public string Ruleset { get; set; } public string Name { get; set; } diff --git a/src/Rules.Framework/Source/GetRulesFilteredDelegate.cs b/src/Rules.Framework/Source/GetRulesFilteredDelegate.cs index 06a1fd98..f9ae2302 100644 --- a/src/Rules.Framework/Source/GetRulesFilteredDelegate.cs +++ b/src/Rules.Framework/Source/GetRulesFilteredDelegate.cs @@ -1,9 +1,7 @@ namespace Rules.Framework.Source { - using Rules.Framework.Core; using System.Collections.Generic; using System.Threading.Tasks; - internal delegate Task>> GetRulesFilteredDelegate( - GetRulesFilteredArgs args); -} + internal delegate Task> GetRulesFilteredDelegate(GetRulesFilteredArgs args); +} \ No newline at end of file diff --git a/src/Rules.Framework/Source/GetRulesetsArgs.cs b/src/Rules.Framework/Source/GetRulesetsArgs.cs new file mode 100644 index 00000000..3b5ba599 --- /dev/null +++ b/src/Rules.Framework/Source/GetRulesetsArgs.cs @@ -0,0 +1,6 @@ +namespace Rules.Framework.Source +{ + internal sealed class GetRulesetsArgs + { + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Source/GetRulesetsDelegate.cs b/src/Rules.Framework/Source/GetRulesetsDelegate.cs new file mode 100644 index 00000000..f3ca41fd --- /dev/null +++ b/src/Rules.Framework/Source/GetRulesetsDelegate.cs @@ -0,0 +1,7 @@ +namespace Rules.Framework.Source +{ + using System.Collections.Generic; + using System.Threading.Tasks; + + internal delegate Task> GetRulesetsDelegate(GetRulesetsArgs args); +} \ No newline at end of file diff --git a/src/Rules.Framework/Source/IRulesSource.cs b/src/Rules.Framework/Source/IRulesSource.cs index f711a60f..2c79a812 100644 --- a/src/Rules.Framework/Source/IRulesSource.cs +++ b/src/Rules.Framework/Source/IRulesSource.cs @@ -1,17 +1,20 @@ namespace Rules.Framework.Source { - using Rules.Framework.Core; using System.Collections.Generic; using System.Threading.Tasks; - internal interface IRulesSource + internal interface IRulesSource { - Task AddRuleAsync(AddRuleArgs args); + Task AddRuleAsync(AddRuleArgs args); - Task>> GetRulesAsync(GetRulesArgs args); + Task CreateRulesetAsync(CreateRulesetArgs args); - Task>> GetRulesFilteredAsync(GetRulesFilteredArgs args); + Task> GetRulesAsync(GetRulesArgs args); - Task UpdateRuleAsync(UpdateRuleArgs args); + Task> GetRulesetsAsync(GetRulesetsArgs args); + + Task> GetRulesFilteredAsync(GetRulesFilteredArgs args); + + Task UpdateRuleAsync(UpdateRuleArgs args); } -} +} \ No newline at end of file diff --git a/src/Rules.Framework/Source/IRulesSourceMiddleware.cs b/src/Rules.Framework/Source/IRulesSourceMiddleware.cs index 470ec214..fc0d95db 100644 --- a/src/Rules.Framework/Source/IRulesSourceMiddleware.cs +++ b/src/Rules.Framework/Source/IRulesSourceMiddleware.cs @@ -1,25 +1,32 @@ namespace Rules.Framework.Source { - using Rules.Framework.Core; using System.Collections.Generic; using System.Threading.Tasks; - internal interface IRulesSourceMiddleware + internal interface IRulesSourceMiddleware { Task HandleAddRuleAsync( - AddRuleArgs args, - AddRuleDelegate next); + AddRuleArgs args, + AddRuleDelegate next); - Task>> HandleGetRulesAsync( - GetRulesArgs args, - GetRulesDelegate next); + Task HandleCreateRulesetAsync( + CreateRulesetArgs args, + CreateRulesetDelegate next); - Task>> HandleGetRulesFilteredAsync( - GetRulesFilteredArgs args, - GetRulesFilteredDelegate next); + Task> HandleGetRulesAsync( + GetRulesArgs args, + GetRulesDelegate next); + + Task> HandleGetRulesetsAsync( + GetRulesetsArgs args, + GetRulesetsDelegate next); + + Task> HandleGetRulesFilteredAsync( + GetRulesFilteredArgs args, + GetRulesFilteredDelegate next); Task HandleUpdateRuleAsync( - UpdateRuleArgs args, - UpdateRuleDelegate next); + UpdateRuleArgs args, + UpdateRuleDelegate next); } -} +} \ No newline at end of file diff --git a/src/Rules.Framework/Source/RulesSource.cs b/src/Rules.Framework/Source/RulesSource.cs index c21618be..554b8b5a 100644 --- a/src/Rules.Framework/Source/RulesSource.cs +++ b/src/Rules.Framework/Source/RulesSource.cs @@ -2,51 +2,66 @@ namespace Rules.Framework.Source { using System.Collections.Generic; using System.Threading.Tasks; - using Rules.Framework.Core; - internal sealed class RulesSource : IRulesSource + internal sealed class RulesSource : IRulesSource { - private readonly AddRuleDelegate addRuleDelegate; - private readonly GetRulesDelegate getRulesDelegate; - private readonly GetRulesFilteredDelegate getRulesFilteredDelegate; - private readonly UpdateRuleDelegate updateRuleDelegate; + private readonly AddRuleDelegate addRuleDelegate; + private readonly CreateRulesetDelegate createRulesetDelegate; + private readonly GetRulesDelegate getRulesDelegate; + private readonly GetRulesetsDelegate getRulesetsDelegate; + private readonly GetRulesFilteredDelegate getRulesFilteredDelegate; + private readonly IRulesDataSource rulesDataSource; + private readonly UpdateRuleDelegate updateRuleDelegate; public RulesSource( - IRulesDataSource rulesDataSource, - IEnumerable> middlewares) + IRulesDataSource rulesDataSource, + IEnumerable middlewares) { - var middlewaresLinkedList = new LinkedList>(middlewares); + var middlewaresLinkedList = new LinkedList(middlewares); this.addRuleDelegate = CreateAddRulePipelineDelegate(rulesDataSource, middlewaresLinkedList); + this.createRulesetDelegate = CreateCreateRulesetPipelineDelegate(rulesDataSource, middlewaresLinkedList); + this.getRulesetsDelegate = CreateGetRulesetsPipelineDelegate(rulesDataSource, middlewaresLinkedList); this.getRulesDelegate = CreateGetRulesPipelineDelegate(rulesDataSource, middlewaresLinkedList); this.getRulesFilteredDelegate = CreateGetRulesFilteredPipelineDelegate(rulesDataSource, middlewaresLinkedList); this.updateRuleDelegate = CreateUpdateRulePipelineDelegate(rulesDataSource, middlewaresLinkedList); + this.rulesDataSource = rulesDataSource; } - public async Task AddRuleAsync(AddRuleArgs args) + public async Task AddRuleAsync(AddRuleArgs args) { await this.addRuleDelegate.Invoke(args).ConfigureAwait(false); } - public async Task>> GetRulesAsync(GetRulesArgs args) + public async Task CreateRulesetAsync(CreateRulesetArgs args) + { + await this.createRulesetDelegate(args).ConfigureAwait(false); + } + + public async Task> GetRulesAsync(GetRulesArgs args) { return await this.getRulesDelegate.Invoke(args).ConfigureAwait(false); } - public async Task>> GetRulesFilteredAsync(GetRulesFilteredArgs args) + public async Task> GetRulesetsAsync(GetRulesetsArgs args) + { + return await this.getRulesetsDelegate(args).ConfigureAwait(false); + } + + public async Task> GetRulesFilteredAsync(GetRulesFilteredArgs args) { return await this.getRulesFilteredDelegate.Invoke(args).ConfigureAwait(false); } - public async Task UpdateRuleAsync(UpdateRuleArgs args) + public async Task UpdateRuleAsync(UpdateRuleArgs args) { await this.updateRuleDelegate.Invoke(args).ConfigureAwait(false); } - private static AddRuleDelegate CreateAddRulePipelineDelegate( - IRulesDataSource rulesDataSource, - LinkedList> middlewares) + private static AddRuleDelegate CreateAddRulePipelineDelegate( + IRulesDataSource rulesDataSource, + LinkedList middlewares) { - AddRuleDelegate action = async (args) => await rulesDataSource.AddRuleAsync(args.Rule).ConfigureAwait(false); + AddRuleDelegate action = async (args) => await rulesDataSource.AddRuleAsync(args.Rule).ConfigureAwait(false); if (middlewares.Count > 0) { @@ -66,16 +81,64 @@ private static AddRuleDelegate CreateAddRulePipeli return action; } - private static GetRulesFilteredDelegate CreateGetRulesFilteredPipelineDelegate( - IRulesDataSource rulesDataSource, - LinkedList> middlewares) + private static CreateRulesetDelegate CreateCreateRulesetPipelineDelegate( + IRulesDataSource rulesDataSource, + LinkedList middlewares) + { + CreateRulesetDelegate action = async (args) => await rulesDataSource.CreateRulesetAsync(args.Name).ConfigureAwait(false); + + if (middlewares.Count > 0) + { + var middlewareNode = middlewares.Last; + + while (middlewareNode is { }) + { + var middleware = middlewareNode.Value; + var immutableAction = action; + action = async (args) => await middleware.HandleCreateRulesetAsync(args, immutableAction).ConfigureAwait(false); + + // Get previous middleware node. + middlewareNode = middlewareNode.Previous; + } + } + + return action; + } + + private static GetRulesetsDelegate CreateGetRulesetsPipelineDelegate( + IRulesDataSource rulesDataSource, + LinkedList middlewares) + { + GetRulesetsDelegate action = async (_) => await rulesDataSource.GetRulesetsAsync().ConfigureAwait(false); + + if (middlewares.Count > 0) + { + var middlewareNode = middlewares.Last; + + while (middlewareNode is { }) + { + var middleware = middlewareNode.Value; + var immutableAction = action; + action = async (args) => await middleware.HandleGetRulesetsAsync(args, immutableAction).ConfigureAwait(false); + + // Get previous middleware node. + middlewareNode = middlewareNode.Previous; + } + } + + return action; + } + + private static GetRulesFilteredDelegate CreateGetRulesFilteredPipelineDelegate( + IRulesDataSource rulesDataSource, + LinkedList middlewares) { - GetRulesFilteredDelegate action = + GetRulesFilteredDelegate action = async (args) => { - RulesFilterArgs rulesFilterArgs = new() + RulesFilterArgs rulesFilterArgs = new() { - ContentType = args.ContentType, + Ruleset = args.Ruleset, Name = args.Name, Priority = args.Priority, }; @@ -101,11 +164,11 @@ private static GetRulesFilteredDelegate CreateGetR return action; } - private static GetRulesDelegate CreateGetRulesPipelineDelegate( - IRulesDataSource rulesDataSource, - LinkedList> middlewares) + private static GetRulesDelegate CreateGetRulesPipelineDelegate( + IRulesDataSource rulesDataSource, + LinkedList middlewares) { - GetRulesDelegate action = + GetRulesDelegate action = async (args) => await rulesDataSource.GetRulesAsync(args.ContentType, args.DateBegin, args.DateEnd).ConfigureAwait(false); @@ -127,11 +190,11 @@ private static GetRulesDelegate CreateGetRulesPipe return action; } - private static UpdateRuleDelegate CreateUpdateRulePipelineDelegate( - IRulesDataSource rulesDataSource, - LinkedList> middlewares) + private static UpdateRuleDelegate CreateUpdateRulePipelineDelegate( + IRulesDataSource rulesDataSource, + LinkedList middlewares) { - UpdateRuleDelegate action = + UpdateRuleDelegate action = async (args) => await rulesDataSource.UpdateRuleAsync(args.Rule).ConfigureAwait(false); diff --git a/src/Rules.Framework/Source/UpdateRuleArgs.cs b/src/Rules.Framework/Source/UpdateRuleArgs.cs index 69ae0981..26df9b5d 100644 --- a/src/Rules.Framework/Source/UpdateRuleArgs.cs +++ b/src/Rules.Framework/Source/UpdateRuleArgs.cs @@ -1,9 +1,7 @@ namespace Rules.Framework.Source { - using Rules.Framework.Core; - - internal sealed class UpdateRuleArgs + internal sealed class UpdateRuleArgs { - public Rule Rule { get; set; } + public Rule Rule { get; set; } } } \ No newline at end of file diff --git a/src/Rules.Framework/Source/UpdateRuleDelegate.cs b/src/Rules.Framework/Source/UpdateRuleDelegate.cs index 650d981f..8428516c 100644 --- a/src/Rules.Framework/Source/UpdateRuleDelegate.cs +++ b/src/Rules.Framework/Source/UpdateRuleDelegate.cs @@ -2,6 +2,5 @@ namespace Rules.Framework.Source { using System.Threading.Tasks; - internal delegate Task UpdateRuleDelegate( - UpdateRuleArgs args); -} + internal delegate Task UpdateRuleDelegate(UpdateRuleArgs args); +} \ No newline at end of file diff --git a/src/Rules.Framework/Validation/SearchArgsValidator.cs b/src/Rules.Framework/Validation/SearchArgsValidator.cs index 46eed88d..0ecad407 100644 --- a/src/Rules.Framework/Validation/SearchArgsValidator.cs +++ b/src/Rules.Framework/Validation/SearchArgsValidator.cs @@ -3,17 +3,17 @@ namespace Rules.Framework.Validation using System; using FluentValidation; - internal sealed class SearchArgsValidator : AbstractValidator> + internal sealed class SearchArgsValidator : AbstractValidator> { private readonly Type conditionTypeRuntimeType; private readonly Type contentTypeRuntimeType; public SearchArgsValidator() { - this.conditionTypeRuntimeType = typeof(TConditionType); - this.contentTypeRuntimeType = typeof(TContentType); + this.conditionTypeRuntimeType = typeof(TCondition); + this.contentTypeRuntimeType = typeof(TRuleset); - this.RuleFor(searchArgs => searchArgs.ContentType).Must(ct => + this.RuleFor(searchArgs => searchArgs.Ruleset).Must(ct => { if (this.contentTypeRuntimeType.IsClass && ct is null) { @@ -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; } diff --git a/tests/Rules.Framework.BenchmarkTests/Program.cs b/tests/Rules.Framework.BenchmarkTests/Program.cs index bc12cfa0..65b5885c 100644 --- a/tests/Rules.Framework.BenchmarkTests/Program.cs +++ b/tests/Rules.Framework.BenchmarkTests/Program.cs @@ -8,7 +8,7 @@ using BenchmarkDotNet.Running; using McMaster.Extensions.CommandLineUtils; -[assembly: SimpleJob(RuntimeMoniker.Net60)] +[assembly: SimpleJob(RuntimeMoniker.Net80)] internal static class Program { diff --git a/tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj b/tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj index c6a32a4a..49bda302 100644 --- a/tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj +++ b/tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj @@ -11,8 +11,8 @@ - - + + diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/Benchmark1.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/Benchmark1.cs index b7d87530..569e6e9f 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/Benchmark1.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/Benchmark1.cs @@ -2,12 +2,13 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark1 { using System.Threading.Tasks; using BenchmarkDotNet.Attributes; + using Rules.Framework.Generic; [SkewnessColumn, KurtosisColumn] public class Benchmark1 : IBenchmark { private readonly Scenario6Data benchmarkData = new Scenario6Data(); - private RulesEngine? rulesEngine; + private IRulesEngine? genericRulesEngine; [ParamsAllValues] public bool EnableCompilation { get; set; } @@ -18,15 +19,13 @@ public class Benchmark1 : IBenchmark [Benchmark] public async Task RunAsync() { - await this.rulesEngine!.MatchOneAsync(ContentTypes.ContentType1, this.benchmarkData.MatchDate, this.benchmarkData.Conditions).ConfigureAwait(false); + await this.genericRulesEngine!.MatchOneAsync(Rulesets.Sample1, this.benchmarkData.MatchDate, this.benchmarkData.Conditions).ConfigureAwait(false); } [GlobalSetup] public async Task SetUpAsync() { - this.rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() + var rulesEngine = RulesEngineBuilder.CreateRulesEngine() .SetDataSourceForBenchmark(this.Provider!, nameof(Benchmark1)) .Configure(options => { @@ -34,17 +33,21 @@ public async Task SetUpAsync() }) .Build(); + await rulesEngine.CreateRulesetAsync(nameof(Rulesets.Sample1)); + foreach (var rule in this.benchmarkData.Rules) { - await this.rulesEngine.AddRuleAsync(rule, RuleAddPriorityOption.AtTop).ConfigureAwait(false); + await rulesEngine.AddRuleAsync(rule, RuleAddPriorityOption.AtTop); } + + this.genericRulesEngine = rulesEngine.MakeGeneric(); } [GlobalCleanup] public async Task TearDownAsync() { await Extensions.TearDownProviderAsync(this.Provider!, nameof(Benchmark1)).ConfigureAwait(false); - this.rulesEngine = null; + this.genericRulesEngine = null; } } } \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/Benchmark2.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/Benchmark2.cs index 66444194..0ff35ef1 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/Benchmark2.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/Benchmark2.cs @@ -2,12 +2,13 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark2 { using System.Threading.Tasks; using BenchmarkDotNet.Attributes; + using Rules.Framework.Generic; [SkewnessColumn, KurtosisColumn] public class Benchmark2 : IBenchmark { private readonly Scenario7Data benchmarkData = new Scenario7Data(); - private RulesEngine? rulesEngine; + private IRulesEngine? genericRulesEngine; [ParamsAllValues] public bool EnableCompilation { get; set; } @@ -18,15 +19,13 @@ public class Benchmark2 : IBenchmark [Benchmark] public async Task RunAsync() { - await this.rulesEngine!.MatchOneAsync(ContentTypes.Songs, this.benchmarkData.MatchDate, this.benchmarkData.Conditions).ConfigureAwait(false); + await this.genericRulesEngine!.MatchOneAsync(Rulesets.Songs, this.benchmarkData.MatchDate, this.benchmarkData.Conditions).ConfigureAwait(false); } [GlobalSetup] public async Task SetUpAsync() { - this.rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() + var rulesEngine = RulesEngineBuilder.CreateRulesEngine() .SetDataSourceForBenchmark(this.Provider!, nameof(Benchmark2)) .Configure(options => { @@ -34,17 +33,21 @@ public async Task SetUpAsync() }) .Build(); + await rulesEngine.CreateRulesetAsync(nameof(Rulesets.Songs)); + foreach (var rule in this.benchmarkData.Rules) { - await this.rulesEngine.AddRuleAsync(rule, RuleAddPriorityOption.AtTop).ConfigureAwait(false); + await rulesEngine.AddRuleAsync(rule, RuleAddPriorityOption.AtTop).ConfigureAwait(false); } + + this.genericRulesEngine = rulesEngine.MakeGeneric(); } [GlobalCleanup] public async Task TearDownAsync() { await Extensions.TearDownProviderAsync(this.Provider!, nameof(Benchmark2)).ConfigureAwait(false); - this.rulesEngine = null; + this.genericRulesEngine = null; } } } \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3.cs index 900f0e91..bd4effd9 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3.cs @@ -2,12 +2,13 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 { using System.Threading.Tasks; using BenchmarkDotNet.Attributes; + using Rules.Framework.Generic; [SkewnessColumn, KurtosisColumn] public class Benchmark3 : IBenchmark { private readonly Scenario8Data benchmarkData = new Scenario8Data(); - private RulesEngine? rulesEngine; + private IRulesEngine? genericRulesEngine; [ParamsAllValues] public bool EnableCompilation { get; set; } @@ -18,15 +19,13 @@ public class Benchmark3 : IBenchmark [Benchmark] public async Task RunAsync() { - await this.rulesEngine!.MatchOneAsync(ContentTypes.TexasHoldemPokerSingleCombinations, this.benchmarkData.MatchDate, this.benchmarkData.Conditions).ConfigureAwait(false); + await this.genericRulesEngine!.MatchOneAsync(PokerRulesets.TexasHoldemPokerSingleCombinations, this.benchmarkData.MatchDate, this.benchmarkData.Conditions).ConfigureAwait(false); } [GlobalSetup] public async Task SetUpAsync() { - this.rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() + var rulesEngine = RulesEngineBuilder.CreateRulesEngine() .SetDataSourceForBenchmark(this.Provider!, nameof(Benchmark3)) .Configure(options => { @@ -34,17 +33,21 @@ public async Task SetUpAsync() }) .Build(); + await rulesEngine.CreateRulesetAsync(nameof(PokerRulesets.TexasHoldemPokerSingleCombinations)); + foreach (var rule in this.benchmarkData.Rules) { - await this.rulesEngine.AddRuleAsync(rule, RuleAddPriorityOption.AtTop).ConfigureAwait(false); + await rulesEngine.AddRuleAsync(rule, RuleAddPriorityOption.AtTop).ConfigureAwait(false); } + + this.genericRulesEngine = rulesEngine.MakeGeneric(); } [GlobalCleanup] public async Task TearDownAsync() { await Extensions.TearDownProviderAsync(this.Provider!, nameof(Benchmark3)).ConfigureAwait(false); - this.rulesEngine = null; + this.genericRulesEngine = null; } } } \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Extensions.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Extensions.cs index 581a959c..a7459ac4 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Extensions.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Extensions.cs @@ -2,15 +2,14 @@ namespace Rules.Framework.BenchmarkTests.Tests { using MongoDB.Driver; using Rules.Framework.Builder; - using Rules.Framework.Providers.InMemory; using Rules.Framework.Providers.MongoDb; internal static class Extensions { private const string DatabaseName = "benchmarks-database"; - public static IConfiguredRulesEngineBuilder SetDataSourceForBenchmark( - this IRulesDataSourceSelector rulesDataSourceSelector, + public static IConfiguredRulesEngineBuilder SetDataSourceForBenchmark( + this IRulesDataSourceSelector rulesDataSourceSelector, string dataSourceName, string benchmarkName) { return dataSourceName switch diff --git a/tests/Rules.Framework.IntegrationTests.Common/Features/RuleSpecification.cs b/tests/Rules.Framework.IntegrationTests.Common/Features/RuleSpecification.cs index 2dfcfdfc..c789a285 100644 --- a/tests/Rules.Framework.IntegrationTests.Common/Features/RuleSpecification.cs +++ b/tests/Rules.Framework.IntegrationTests.Common/Features/RuleSpecification.cs @@ -1,18 +1,17 @@ namespace Rules.Framework.IntegrationTests.Common.Features { - using Rules.Framework.Core; + using Rules.Framework.Generic; using Rules.Framework.Tests.Stubs; public class RuleSpecification { - public RuleSpecification(Rule ruleBuilderResult, RuleAddPriorityOption ruleAddPriorityOption) + public RuleSpecification(Rule ruleBuilderResult, RuleAddPriorityOption ruleAddPriorityOption) { this.Rule = ruleBuilderResult; this.RuleAddPriorityOption = ruleAddPriorityOption; } + public Rule Rule { get; set; } public RuleAddPriorityOption RuleAddPriorityOption { get; set; } - - public Rule Rule { get; set; } } } \ No newline at end of file diff --git a/tests/Rules.Framework.IntegrationTests.Common/Features/Stubs/ConditionNames.cs b/tests/Rules.Framework.IntegrationTests.Common/Features/Stubs/ConditionNames.cs new file mode 100644 index 00000000..df4c5ca4 --- /dev/null +++ b/tests/Rules.Framework.IntegrationTests.Common/Features/Stubs/ConditionNames.cs @@ -0,0 +1,11 @@ +namespace Rules.Framework.Tests.Stubs +{ + public enum ConditionNames + { + Condition1 = 1, + + Condition2 = 2, + + Condition3 = 3 + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.IntegrationTests.Common/Features/Stubs/ConditionType.cs b/tests/Rules.Framework.IntegrationTests.Common/Features/Stubs/ConditionType.cs deleted file mode 100644 index 5eda2e4b..00000000 --- a/tests/Rules.Framework.IntegrationTests.Common/Features/Stubs/ConditionType.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Rules.Framework.Tests.Stubs -{ - public enum ConditionType - { - ConditionType1 = 1, - - ConditionType2 = 2, - - ConditionType3 = 3 - } -} \ No newline at end of file diff --git a/tests/Rules.Framework.IntegrationTests.Common/Features/Stubs/ContentType.cs b/tests/Rules.Framework.IntegrationTests.Common/Features/Stubs/ContentType.cs deleted file mode 100644 index eb3dc765..00000000 --- a/tests/Rules.Framework.IntegrationTests.Common/Features/Stubs/ContentType.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Rules.Framework.Tests.Stubs -{ - public enum ContentType - { - ContentType1 = 1, - - ContentType2 = 2 - } -} \ No newline at end of file diff --git a/tests/Rules.Framework.IntegrationTests.Common/Features/Stubs/RulesetNames.cs b/tests/Rules.Framework.IntegrationTests.Common/Features/Stubs/RulesetNames.cs new file mode 100644 index 00000000..41d001e5 --- /dev/null +++ b/tests/Rules.Framework.IntegrationTests.Common/Features/Stubs/RulesetNames.cs @@ -0,0 +1,9 @@ +namespace Rules.Framework.Tests.Stubs +{ + public enum RulesetNames + { + Sample1 = 1, + + Sample2 = 2 + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/IScenarioData.cs b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/IScenarioData.cs index 3d080c4c..ef924c41 100644 --- a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/IScenarioData.cs +++ b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/IScenarioData.cs @@ -2,15 +2,14 @@ namespace Rules.Framework.BenchmarkTests.Tests { using System; using System.Collections.Generic; - using Rules.Framework; - using Rules.Framework.Core; + using Rules.Framework.Generic; - public interface IScenarioData + public interface IScenarioData { - IEnumerable> Conditions { get; } + IDictionary Conditions { get; } DateTime MatchDate { get; } - IEnumerable> Rules { get; } + IEnumerable> Rules { get; } } } \ No newline at end of file diff --git a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario1/ConditionTypes.cs b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario1/Scenario1ConditionNames.cs similarity index 71% rename from tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario1/ConditionTypes.cs rename to tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario1/Scenario1ConditionNames.cs index 1ebdcb2b..340342f4 100644 --- a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario1/ConditionTypes.cs +++ b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario1/Scenario1ConditionNames.cs @@ -1,6 +1,6 @@ namespace Rules.Framework.IntegrationTests.Common.Scenarios.Scenario1 { - public enum ConditionTypes + public enum Scenario1ConditionNames { Age = 1 } diff --git a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario1/ContentTypes.cs b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario1/Scenario1RulesetNames.cs similarity index 75% rename from tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario1/ContentTypes.cs rename to tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario1/Scenario1RulesetNames.cs index 4fbb49f9..ce3f5edc 100644 --- a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario1/ContentTypes.cs +++ b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario1/Scenario1RulesetNames.cs @@ -1,6 +1,6 @@ namespace Rules.Framework.IntegrationTests.Common.Scenarios.Scenario1 { - public enum ContentTypes + public enum Scenario1RulesetNames { BodyMassIndexFormula = 1 } diff --git a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario1/rules-framework-tests.body-mass-index.datasource.json b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario1/rules-framework-tests.body-mass-index.datasource.json index 9ebb3ea0..f799ad2b 100644 --- a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario1/rules-framework-tests.body-mass-index.datasource.json +++ b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario1/rules-framework-tests.body-mass-index.datasource.json @@ -1,11 +1,11 @@ [ - { - "Content": "{\"Description\":\"Body Mass Index default formula\",\"Value\":\"weight / (height ^ 2)\"}", - "ContentTypeCode": 1, - "DateBegin": "2018-01-01", - "DateEnd": null, - "Name": "Body Mass Index default", - "Priority": 1, - "RootCondition": null - } + { + "Content": "{\"Description\":\"Body Mass Index default formula\",\"Value\":\"weight / (height ^ 2)\"}", + "Ruleset": "BodyMassIndexFormula", + "DateBegin": "2018-01-01", + "DateEnd": null, + "Name": "Body Mass Index default", + "Priority": 1, + "RootCondition": null + } ] \ No newline at end of file diff --git a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario2/ConditionTypes.cs b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario2/CarInsuranceConditionNames.cs similarity index 83% rename from tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario2/ConditionTypes.cs rename to tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario2/CarInsuranceConditionNames.cs index 6593d132..5b4025b0 100644 --- a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario2/ConditionTypes.cs +++ b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario2/CarInsuranceConditionNames.cs @@ -1,6 +1,6 @@ namespace Rules.Framework.IntegrationTests.Common.Scenarios.Scenario2 { - public enum ConditionTypes + public enum CarInsuranceConditionNames { RepairCosts = 1, diff --git a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario2/ContentTypes.cs b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario2/CarInsuranceRulesetNames.cs similarity index 73% rename from tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario2/ContentTypes.cs rename to tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario2/CarInsuranceRulesetNames.cs index 55e026c6..76b748a6 100644 --- a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario2/ContentTypes.cs +++ b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario2/CarInsuranceRulesetNames.cs @@ -1,6 +1,6 @@ namespace Rules.Framework.IntegrationTests.Common.Scenarios.Scenario2 { - public enum ContentTypes + public enum CarInsuranceRulesetNames { CarInsuranceAdvice = 1 } diff --git a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario2/rules-framework-tests.car-insurance-advisor.json b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario2/rules-framework-tests.car-insurance-advisor.json index 4cefc6fd..5dfdd6be 100644 --- a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario2/rules-framework-tests.car-insurance-advisor.json +++ b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario2/rules-framework-tests.car-insurance-advisor.json @@ -1,86 +1,86 @@ [ - { - "Content": "RefusePaymentPerFranchise", - "ContentTypeCode": 1, - "DateBegin": "2018-01-01", - "DateEnd": null, - "Name": "Car Insurance Advise on repair costs lower than franchise boundary", - "Priority": 4, - "RootCondition": { - "LogicalOperator": "And", - "ChildConditionNodes": [ - { - "ConditionType": "RepairCosts", - "DataType": "Decimal", - "LogicalOperator": "Eval", - "Operator": "LesserThan", - "Operand": "1000" - }, - { - "ConditionType": "RepairCostsCommercialValueRate", - "DataType": "Decimal", - "LogicalOperator": "Eval", - "Operator": "LesserThan", - "Operand": "80" + { + "Content": "RefusePaymentPerFranchise", + "Ruleset": "CarInsuranceAdvice", + "DateBegin": "2018-01-01", + "DateEnd": null, + "Name": "Car Insurance Advise on repair costs lower than franchise boundary", + "Priority": 4, + "RootCondition": { + "LogicalOperator": "And", + "ChildConditionNodes": [ + { + "Condition": "RepairCosts", + "DataType": "Decimal", + "LogicalOperator": "Eval", + "Operator": "LesserThan", + "Operand": "1000" + }, + { + "Condition": "RepairCostsCommercialValueRate", + "DataType": "Decimal", + "LogicalOperator": "Eval", + "Operator": "LesserThan", + "Operand": "80" + } + ] } - ] - } - }, - { - "Content": "PayOldCar", - "ContentTypeCode": 1, - "DateBegin": "2010-01-01", - "DateEnd": "2016-06-01 23:59:59.999", - "Name": "Car Insurance Advise on repair costs equal to 0", - "Priority": 3, - "RootCondition": { - "LogicalOperator": "And", - "ChildConditionNodes": [ - { - "ConditionType": "RepairCosts", - "DataType": "Decimal", - "LogicalOperator": "Eval", - "Operator": "Equal", - "Operand": "0.0" - }, - { - "ConditionType": "RepairCostsCommercialValueRate", - "DataType": "Decimal", - "LogicalOperator": "Eval", - "Operator": "Equal", - "Operand": "0.0" + }, + { + "Content": "PayOldCar", + "Ruleset": "CarInsuranceAdvice", + "DateBegin": "2010-01-01", + "DateEnd": "2016-06-01 23:59:59.999", + "Name": "Car Insurance Advise on repair costs equal to 0", + "Priority": 3, + "RootCondition": { + "LogicalOperator": "And", + "ChildConditionNodes": [ + { + "Condition": "RepairCosts", + "DataType": "Decimal", + "LogicalOperator": "Eval", + "Operator": "Equal", + "Operand": "0.0" + }, + { + "Condition": "RepairCostsCommercialValueRate", + "DataType": "Decimal", + "LogicalOperator": "Eval", + "Operator": "Equal", + "Operand": "0.0" + } + ] + } + }, + { + "Content": "PayNewCar", + "Ruleset": "CarInsuranceAdvice", + "DateBegin": "2018-01-01", + "DateEnd": null, + "Name": "Car Insurance Advise on repair costs greater than 80% of commercial value", + "Priority": 2, + "RootCondition": { + "Condition": "RepairCostsCommercialValueRate", + "DataType": "Decimal", + "LogicalOperator": "Eval", + "Operator": "GreaterThanOrEqual", + "Operand": "80" + } + }, + { + "Content": "Pay", + "Ruleset": "CarInsuranceAdvice", + "DateBegin": "2018-01-01", + "DateEnd": null, + "Name": "Car Insurance Advise on repair costs lesser than 80% of commercial value", + "Priority": 1, + "RootCondition": { + "Condition": "RepairCostsCommercialValueRate", + "DataType": "Decimal", + "LogicalOperator": "Eval", + "Operator": "LesserThan", + "Operand": "80" } - ] - } - }, - { - "Content": "PayNewCar", - "ContentTypeCode": 1, - "DateBegin": "2018-01-01", - "DateEnd": null, - "Name": "Car Insurance Advise on repair costs greater than 80% of commercial value", - "Priority": 2, - "RootCondition": { - "ConditionType": "RepairCostsCommercialValueRate", - "DataType": "Decimal", - "LogicalOperator": "Eval", - "Operator": "GreaterThanOrEqual", - "Operand": "80" - } - }, - { - "Content": "Pay", - "ContentTypeCode": 1, - "DateBegin": "2018-01-01", - "DateEnd": null, - "Name": "Car Insurance Advise on repair costs lesser than 80% of commercial value", - "Priority": 1, - "RootCondition": { - "ConditionType": "RepairCostsCommercialValueRate", - "DataType": "Decimal", - "LogicalOperator": "Eval", - "Operator": "LesserThan", - "Operand": "80" } - } ] \ No newline at end of file diff --git a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario3/rules-framework-tests.security-system-actionables.json b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario3/rules-framework-tests.security-system-actionables.json index 15901c4f..f827a206 100644 --- a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario3/rules-framework-tests.security-system-actionables.json +++ b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario3/rules-framework-tests.security-system-actionables.json @@ -1,140 +1,140 @@ [ - { - "Content": "{\"ActionId\":\"7367d5d8-bac1-48bb-9b88-28cfe534aed2\",\"ActionName\":\"ActivateSprinklers\"}", - "ContentTypeCode": 1, - "DateBegin": "2018-01-01", - "DateEnd": null, - "Name": "Sprinkler system activation rule", - "Priority": 2, - "RootCondition": { - "LogicalOperator": "Or", - "ChildConditionNodes": [ - { - "ConditionType": "TemperatureCelsius", - "DataType": "Decimal", - "LogicalOperator": "Eval", - "Operator": "GreaterThanOrEqual", - "Operand": "50" - }, - { - "ConditionType": "SmokeRate", - "DataType": "Decimal", - "LogicalOperator": "Eval", - "Operator": "GreaterThan", - "Operand": "40" + { + "Content": "{\"ActionId\":\"7367d5d8-bac1-48bb-9b88-28cfe534aed2\",\"ActionName\":\"ActivateSprinklers\"}", + "Ruleset": "FireSystem", + "DateBegin": "2018-01-01", + "DateEnd": null, + "Name": "Sprinkler system activation rule", + "Priority": 2, + "RootCondition": { + "LogicalOperator": "Or", + "ChildConditionNodes": [ + { + "Condition": "TemperatureCelsius", + "DataType": "Decimal", + "LogicalOperator": "Eval", + "Operator": "GreaterThanOrEqual", + "Operand": "50" + }, + { + "Condition": "SmokeRate", + "DataType": "Decimal", + "LogicalOperator": "Eval", + "Operator": "GreaterThan", + "Operand": "40" + } + ] } - ] - } - }, - { - "Content": "{\"ActionId\":\"96cce7f0-f8ba-4e8a-a3ce-b0d200d49ab2\",\"ActionName\":\"CallFireBrigade\"}", - "ContentTypeCode": 1, - "DateBegin": "2018-01-01", - "DateEnd": null, - "Name": "Fire brigade call rule", - "Priority": 1, - "RootCondition": { - "LogicalOperator": "Or", - "ChildConditionNodes":[ - { - "ConditionType": "TemperatureCelsius", - "DataType": "Decimal", - "LogicalOperator": "Eval", - "Operator": "GreaterThanOrEqual", - "Operand": "70" - }, - { - "ConditionType": "SmokeRate", - "DataType": "Decimal", - "LogicalOperator": "Eval", - "Operator": "GreaterThanOrEqual", - "Operand": "50" + }, + { + "Content": "{\"ActionId\":\"96cce7f0-f8ba-4e8a-a3ce-b0d200d49ab2\",\"ActionName\":\"CallFireBrigade\"}", + "Ruleset": "FireSystem", + "DateBegin": "2018-01-01", + "DateEnd": null, + "Name": "Fire brigade call rule", + "Priority": 1, + "RootCondition": { + "LogicalOperator": "Or", + "ChildConditionNodes": [ + { + "Condition": "TemperatureCelsius", + "DataType": "Decimal", + "LogicalOperator": "Eval", + "Operator": "GreaterThanOrEqual", + "Operand": "70" + }, + { + "Condition": "SmokeRate", + "DataType": "Decimal", + "LogicalOperator": "Eval", + "Operator": "GreaterThanOrEqual", + "Operand": "50" + } + ] } - ] - } - }, - { - "Content": "{\"ActionId\":\"4f771d96-43c7-4939-a8a8-aabd9ad0eee5\",\"ActionName\":\"CallPolice\"}", - "ContentTypeCode": 1, - "DateBegin": "2018-01-01", - "DateEnd": null, - "Name": "Police call rule", - "Priority": 3, - "RootCondition": { - "LogicalOperator": "Or", - "ChildConditionNodes":[ - { - "ConditionType": "TemperatureCelsius", - "DataType": "Decimal", - "LogicalOperator": "Eval", - "Operator": "GreaterThanOrEqual", - "Operand": "70" - }, - { - "ConditionType": "SmokeRate", - "DataType": "Decimal", - "LogicalOperator": "Eval", - "Operator": "GreaterThanOrEqual", - "Operand": "50" + }, + { + "Content": "{\"ActionId\":\"4f771d96-43c7-4939-a8a8-aabd9ad0eee5\",\"ActionName\":\"CallPolice\"}", + "Ruleset": "FireSystem", + "DateBegin": "2018-01-01", + "DateEnd": null, + "Name": "Police call rule", + "Priority": 3, + "RootCondition": { + "LogicalOperator": "Or", + "ChildConditionNodes": [ + { + "Condition": "TemperatureCelsius", + "DataType": "Decimal", + "LogicalOperator": "Eval", + "Operator": "GreaterThanOrEqual", + "Operand": "70" + }, + { + "Condition": "SmokeRate", + "DataType": "Decimal", + "LogicalOperator": "Eval", + "Operator": "GreaterThanOrEqual", + "Operand": "50" + } + ] } - ] - } - }, - { - "Content": "{\"ActionId\":\"39958de2-1201-4904-9555-65f97e8a1d1d\",\"ActionName\":\"EnableEmergencyPower\"}", - "ContentTypeCode": 2, - "DateBegin": "2018-01-01", - "DateEnd": null, - "Name": "Emergency power activation rule", - "Priority": 4, - "RootCondition": { - "ConditionType": "PowerStatus", - "DataType": "String", - "LogicalOperator": "Eval", - "Operator": "Equal", - "Operand": "Offline" - } - }, - { - "Content": "{\"ActionId\":\"5f1c1e0c-6f49-448d-b9ce-7b154436fe5c\",\"ActionName\":\"EnableEmergencyLights\"}", - "ContentTypeCode": 2, - "DateBegin": "2018-01-01", - "DateEnd": null, - "Name": "Enable emergency lights rule", - "Priority": 5, - "RootCondition": { - "LogicalOperator": "Or", - "ChildConditionNodes":[ - { - "ConditionType": "PowerStatus", - "DataType": "String", - "LogicalOperator": "Eval", - "Operator": "Equal", - "Operand": "Offline" - }, - { - "ConditionType": "PowerStatus", - "DataType": "String", - "LogicalOperator": "Eval", - "Operator": "Equal", - "Operand": "Shutdown" + }, + { + "Content": "{\"ActionId\":\"39958de2-1201-4904-9555-65f97e8a1d1d\",\"ActionName\":\"EnableEmergencyPower\"}", + "Ruleset": "PowerSystem", + "DateBegin": "2018-01-01", + "DateEnd": null, + "Name": "Emergency power activation rule", + "Priority": 4, + "RootCondition": { + "Condition": "PowerStatus", + "DataType": "String", + "LogicalOperator": "Eval", + "Operator": "Equal", + "Operand": "Offline" + } + }, + { + "Content": "{\"ActionId\":\"5f1c1e0c-6f49-448d-b9ce-7b154436fe5c\",\"ActionName\":\"EnableEmergencyLights\"}", + "Ruleset": "PowerSystem", + "DateBegin": "2018-01-01", + "DateEnd": null, + "Name": "Enable emergency lights rule", + "Priority": 5, + "RootCondition": { + "LogicalOperator": "Or", + "ChildConditionNodes": [ + { + "Condition": "PowerStatus", + "DataType": "String", + "LogicalOperator": "Eval", + "Operator": "Equal", + "Operand": "Offline" + }, + { + "Condition": "PowerStatus", + "DataType": "String", + "LogicalOperator": "Eval", + "Operator": "Equal", + "Operand": "Shutdown" + } + ] + } + }, + { + "Content": "{\"ActionId\":\"3dd3eadc-15c2-4f66-9e01-4cdf106fd9d6\",\"ActionName\":\"CallPowerGridPicket\"}", + "Ruleset": "PowerSystem", + "DateBegin": "2018-01-01", + "DateEnd": null, + "Name": "Call power grid picket rule", + "Priority": 6, + "RootCondition": { + "Condition": "PowerStatus", + "DataType": "String", + "LogicalOperator": "Eval", + "Operator": "Equal", + "Operand": "Offline" } - ] - } - }, - { - "Content": "{\"ActionId\":\"3dd3eadc-15c2-4f66-9e01-4cdf106fd9d6\",\"ActionName\":\"CallPowerGridPicket\"}", - "ContentTypeCode": 2, - "DateBegin": "2018-01-01", - "DateEnd": null, - "Name": "Call power grid picket rule", - "Priority": 6, - "RootCondition": { - "ConditionType": "PowerStatus", - "DataType": "String", - "LogicalOperator": "Eval", - "Operator": "Equal", - "Operand": "Offline" } - } ] \ No newline at end of file diff --git a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario6/ConditionTypes.cs b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario6/ConditionNames.cs similarity index 86% rename from tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario6/ConditionTypes.cs rename to tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario6/ConditionNames.cs index 1563be26..3126cadc 100644 --- a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario6/ConditionTypes.cs +++ b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario6/ConditionNames.cs @@ -1,6 +1,6 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark1 { - public enum ConditionTypes + public enum ConditionNames { IntegerCondition = 1, DecimalCondition = 2, diff --git a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario6/ContentTypes.cs b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario6/Rulesets.cs similarity index 57% rename from tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario6/ContentTypes.cs rename to tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario6/Rulesets.cs index c7239b3a..47264faa 100644 --- a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario6/ContentTypes.cs +++ b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario6/Rulesets.cs @@ -1,7 +1,7 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark1 { - public enum ContentTypes + public enum Rulesets { - ContentType1 = 1, + Sample1 = 1, } } \ No newline at end of file diff --git a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario6/Scenario6Data.cs b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario6/Scenario6Data.cs index 430e9183..13e0c88d 100644 --- a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario6/Scenario6Data.cs +++ b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario6/Scenario6Data.cs @@ -2,27 +2,28 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark1 { using System; using System.Collections.Generic; + using Rules.Framework; using Rules.Framework.BenchmarkTests.Tests; - using Rules.Framework.Core; + using Rules.Framework.Generic; - public class Scenario6Data : IScenarioData + public class Scenario6Data : IScenarioData { - public IEnumerable> Conditions => new[] + public IDictionary Conditions => new Dictionary { - new Condition(ConditionTypes.StringCondition, "Let's benchmark this!") + { ConditionNames.StringCondition, "Let's benchmark this!" }, }; public DateTime MatchDate => DateTime.Parse("2022-10-01"); - public IEnumerable> Rules => this.GetRules(); + public IEnumerable> Rules => this.GetRules(); - private IEnumerable> GetRules() + private IEnumerable> GetRules() { - var ruleResult = RuleBuilder.NewRule() - .WithName("Benchmark 1 - Test rule") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.ContentType1, "Dummy Content") - .WithCondition(ConditionTypes.StringCondition, Operators.Equal, "Let's benchmark this!") + var ruleResult = Rule.Create("Benchmark 1 - Test rule") + .InRuleset(Rulesets.Sample1) + .SetContent("Dummy Content") + .Since(DateTime.Parse("2000-01-01")) + .ApplyWhen(ConditionNames.StringCondition, Operators.Equal, "Let's benchmark this!") .Build(); return new[] { ruleResult.Rule }; diff --git a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario7/ConditionTypes.cs b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario7/ConditionNames.cs similarity index 81% rename from tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario7/ConditionTypes.cs rename to tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario7/ConditionNames.cs index dfaee2e6..ce7628bb 100644 --- a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario7/ConditionTypes.cs +++ b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario7/ConditionNames.cs @@ -1,6 +1,6 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark2 { - public enum ConditionTypes + public enum ConditionNames { Lyrics = 1, Artist = 2, diff --git a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario7/ContentTypes.cs b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario7/Rulesets.cs similarity index 76% rename from tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario7/ContentTypes.cs rename to tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario7/Rulesets.cs index 1b8dfd30..8858b78f 100644 --- a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario7/ContentTypes.cs +++ b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario7/Rulesets.cs @@ -1,6 +1,6 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark2 { - public enum ContentTypes + public enum Rulesets { Songs = 1, } diff --git a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario7/Scenario7Data.cs b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario7/Scenario7Data.cs index 028e4b6e..6cfc0a5c 100644 --- a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario7/Scenario7Data.cs +++ b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario7/Scenario7Data.cs @@ -3,50 +3,50 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark2 using System; using System.Collections.Generic; using Rules.Framework; - using Rules.Framework.Core; + using Rules.Framework.Generic; - public class Scenario7Data : IScenarioData + public class Scenario7Data : IScenarioData { - public IEnumerable> Conditions => new[] + public IDictionary Conditions => new Dictionary { - new Condition(ConditionTypes.Artist, "Queen"), - new Condition(ConditionTypes.Lyrics, "Is this the real life?\nIs this just fantasy?\nCaught in a landside,\nNo escape from reality" ), - new Condition(ConditionTypes.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"); - public IEnumerable> Rules => this.GetRules(); + public IEnumerable> Rules => this.GetRules(); - private IEnumerable> GetRules() + private IEnumerable> GetRules() { - var rule1Result = RuleBuilder.NewRule() - .WithName("Benchmark 2 - Bohemian Rapsody") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.Songs, "Bohemian Rapsody") - .WithCondition(c => c + var rule1Result = Rule.Create("Benchmark 2 - Bohemian Rapsody") + .InRuleset(Rulesets.Songs) + .SetContent("Bohemian Rapsody") + .SinceUtc(2000, 1, 1) + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.Artist, Operators.Equal, "Queen") - .Value(ConditionTypes.Lyrics, Operators.Contains, "real life") - .Value(ConditionTypes.ReleaseYear, Operators.GreaterThanOrEqual, 1973) - .Value(ConditionTypes.ReleaseYear, Operators.GreaterThanOrEqual, 1977) + .Value(ConditionNames.Artist, Operators.Equal, "Queen") + .Value(ConditionNames.Lyrics, Operators.Contains, "real life") + .Value(ConditionNames.ReleaseYear, Operators.GreaterThanOrEqual, 1973) + .Value(ConditionNames.ReleaseYear, Operators.GreaterThanOrEqual, 1977) ) ) .Build(); - var rule2Result = RuleBuilder.NewRule() - .WithName("Benchmark 2 - Stairway to Heaven") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.Songs, "Stairway to Heaven") - .WithCondition(c => c + var rule2Result = Rule.Create("Benchmark 2 - Stairway to Heaven") + .InRuleset(Rulesets.Songs) + .SetContent("Stairway to Heaven") + .SinceUtc(2000, 1, 1) + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.Artist, Operators.Equal, "Led Zeppelin") + .Value(ConditionNames.Artist, Operators.Equal, "Led Zeppelin") .Or(sub => sub - .Value(ConditionTypes.Lyrics, Operators.Contains, "all that glitters is gold") - .Value(ConditionTypes.Lyrics, Operators.Contains, "it makes me wonder") + .Value(ConditionNames.Lyrics, Operators.Contains, "all that glitters is gold") + .Value(ConditionNames.Lyrics, Operators.Contains, "it makes me wonder") ) - .Value(ConditionTypes.ReleaseYear, Operators.GreaterThanOrEqual, 1973) - .Value(ConditionTypes.ReleaseYear, Operators.GreaterThanOrEqual, 1977) + .Value(ConditionNames.ReleaseYear, Operators.GreaterThanOrEqual, 1973) + .Value(ConditionNames.ReleaseYear, Operators.GreaterThanOrEqual, 1977) ) ) .Build(); diff --git a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/ConditionTypes.cs b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/PokerConditions.cs similarity index 98% rename from tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/ConditionTypes.cs rename to tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/PokerConditions.cs index 4f478167..ed6e7611 100644 --- a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/ConditionTypes.cs +++ b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/PokerConditions.cs @@ -1,6 +1,6 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 { - public enum ConditionTypes + public enum PokerConditions { DeuceOfClubs = 1, TreyOfClubs = 2, diff --git a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/ContentTypes.cs b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/PokerRulesets.cs similarity index 80% rename from tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/ContentTypes.cs rename to tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/PokerRulesets.cs index 48432a66..2a911cd7 100644 --- a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/ContentTypes.cs +++ b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/PokerRulesets.cs @@ -1,6 +1,6 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 { - public enum ContentTypes + public enum PokerRulesets { TexasHoldemPokerSingleCombinations = 1, } diff --git a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.Flush.cs b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.Flush.cs index 2efa715e..9ede6652 100644 --- a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.Flush.cs +++ b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.Flush.cs @@ -1,39 +1,38 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 { - using System; using System.Collections.Generic; - using Rules.Framework.Builder; - using Rules.Framework.Core; + using Rules.Framework; + using Rules.Framework.Generic; - public partial class Scenario8Data : IScenarioData + public partial class Scenario8Data : IScenarioData { - private IEnumerable> GetFlushRules() + private IEnumerable> GetFlushRules() { return new[] { - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Flush of Clubs") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Flush" }) - .WithCondition(ConditionTypes.NumberOfClubs, Operators.GreaterThanOrEqual, 5) + Rule.Create("Benchmark 3 - Flush of Clubs") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Flush" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfClubs, Operators.GreaterThanOrEqual, 5) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Flush of Diamonds") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Flush" }) - .WithCondition(ConditionTypes.NumberOfDiamonds, Operators.GreaterThanOrEqual, 5) + Rule.Create("Benchmark 3 - Flush of Diamonds") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Flush" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfDiamonds, Operators.GreaterThanOrEqual, 5) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Flush of Hearts") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Flush" }) - .WithCondition(ConditionTypes.NumberOfHearts, Operators.GreaterThanOrEqual, 5) + Rule.Create("Benchmark 3 - Flush of Hearts") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Flush" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfHearts, Operators.GreaterThanOrEqual, 5) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Flush of Spades") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Flush" }) - .WithCondition(ConditionTypes.NumberOfSpades, Operators.GreaterThanOrEqual, 5) + Rule.Create("Benchmark 3 - Flush of Spades") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Flush" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfSpades, Operators.GreaterThanOrEqual, 5) .Build().Rule, }; } diff --git a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.FourOfAKind.cs b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.FourOfAKind.cs index fcbbc45a..da0b6c5e 100644 --- a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.FourOfAKind.cs +++ b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.FourOfAKind.cs @@ -1,93 +1,92 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 { - using System; using System.Collections.Generic; - using Rules.Framework.Builder; - using Rules.Framework.Core; + using Rules.Framework; + using Rules.Framework.Generic; - public partial class Scenario8Data : IScenarioData + public partial class Scenario8Data : IScenarioData { - private IEnumerable> GetFourOfAKindRules() + private IEnumerable> GetFourOfAKindRules() { return new[] { - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Four Of A Kind Deuces") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) - .WithCondition(ConditionTypes.NumberOfDeuces, Operators.Equal, 4) + Rule.Create("Benchmark 3 - Four Of A Kind Deuces") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfDeuces, Operators.Equal, 4) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Four Of A Kind Treys") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) - .WithCondition(ConditionTypes.NumberOfTreys, Operators.Equal, 4) + Rule.Create("Benchmark 3 - Four Of A Kind Treys") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfTreys, Operators.Equal, 4) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Four Of A Kind Fours") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) - .WithCondition(ConditionTypes.NumberOfFours, Operators.Equal, 4) + Rule.Create("Benchmark 3 - Four Of A Kind Fours") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfFours, Operators.Equal, 4) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Four Of A Kind Fives") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) - .WithCondition(ConditionTypes.NumberOfFives, Operators.Equal, 4) + Rule.Create("Benchmark 3 - Four Of A Kind Fives") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfFives, Operators.Equal, 4) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Four Of A Kind Sixes") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) - .WithCondition(ConditionTypes.NumberOfSixes, Operators.Equal, 4) + Rule.Create("Benchmark 3 - Four Of A Kind Sixes") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfSixes, Operators.Equal, 4) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Four Of A Kind Sevens") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) - .WithCondition(ConditionTypes.NumberOfSevens, Operators.Equal, 4) + Rule.Create("Benchmark 3 - Four Of A Kind Sevens") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfSevens, Operators.Equal, 4) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Four Of A Kind Eights") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) - .WithCondition(ConditionTypes.NumberOfEigths, Operators.Equal, 4) + Rule.Create("Benchmark 3 - Four Of A Kind Eights") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfEigths, Operators.Equal, 4) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Four Of A Kind Nines") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) - .WithCondition(ConditionTypes.NumberOfNines, Operators.Equal, 4) + Rule.Create("Benchmark 3 - Four Of A Kind Nines") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfNines, Operators.Equal, 4) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Four Of A Kind Tens") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) - .WithCondition(ConditionTypes.NumberOfTens, Operators.Equal, 4) + Rule.Create("Benchmark 3 - Four Of A Kind Tens") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfTens, Operators.Equal, 4) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Four Of A Kind Jacks") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) - .WithCondition(ConditionTypes.NumberOfJacks, Operators.Equal, 4) + Rule.Create("Benchmark 3 - Four Of A Kind Jacks") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfJacks, Operators.Equal, 4) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Four Of A Kind Queens") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) - .WithCondition(ConditionTypes.NumberOfQueens, Operators.Equal, 4) + Rule.Create("Benchmark 3 - Four Of A Kind Queens") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfQueens, Operators.Equal, 4) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Four Of A Kind Kings") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) - .WithCondition(ConditionTypes.NumberOfKings, Operators.Equal, 4) + Rule.Create("Benchmark 3 - Four Of A Kind Kings") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfKings, Operators.Equal, 4) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Four Of A Kind Aces") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) - .WithCondition(ConditionTypes.NumberOfAces, Operators.Equal, 4) + Rule.Create("Benchmark 3 - Four Of A Kind Aces") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfAces, Operators.Equal, 4) .Build().Rule, }; } diff --git a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.HighCard.cs b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.HighCard.cs index d1851617..684756f9 100644 --- a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.HighCard.cs +++ b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.HighCard.cs @@ -1,93 +1,92 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 { - using System; using System.Collections.Generic; - using Rules.Framework.Builder; - using Rules.Framework.Core; + using Rules.Framework; + using Rules.Framework.Generic; - public partial class Scenario8Data : IScenarioData + public partial class Scenario8Data : IScenarioData { - private IEnumerable> GetHighCardsRules() + private IEnumerable> GetHighCardsRules() { return new[] { - RuleBuilder.NewRule() - .WithName("Benchmark 3 - High Card Deuces") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) - .WithCondition(ConditionTypes.NumberOfDeuces, Operators.Equal, 1) + Rule.Create("Benchmark 3 - High Card Deuces") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "High Card" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfDeuces, Operators.Equal, 1) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - High Card Treys") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) - .WithCondition(ConditionTypes.NumberOfTreys, Operators.Equal, 1) + Rule.Create("Benchmark 3 - High Card Treys") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "High Card" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfTreys, Operators.Equal, 1) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - High Card Fours") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) - .WithCondition(ConditionTypes.NumberOfFours, Operators.Equal, 1) + Rule.Create("Benchmark 3 - High Card Fours") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "High Card" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfFours, Operators.Equal, 1) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - High Card Fives") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) - .WithCondition(ConditionTypes.NumberOfFives, Operators.Equal, 1) + Rule.Create("Benchmark 3 - High Card Fives") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "High Card" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfFives, Operators.Equal, 1) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - High Card Sixes") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) - .WithCondition(ConditionTypes.NumberOfSixes, Operators.Equal, 1) + Rule.Create("Benchmark 3 - High Card Sixes") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "High Card" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfSixes, Operators.Equal, 1) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - High Card Sevens") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) - .WithCondition(ConditionTypes.NumberOfSevens, Operators.Equal, 1) + Rule.Create("Benchmark 3 - High Card Sevens") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "High Card" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfSevens, Operators.Equal, 1) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - High Card Eights") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) - .WithCondition(ConditionTypes.NumberOfEigths, Operators.Equal, 1) + Rule.Create("Benchmark 3 - High Card Eights") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "High Card" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfEigths, Operators.Equal, 1) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - High Card Nines") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) - .WithCondition(ConditionTypes.NumberOfNines, Operators.Equal, 1) + Rule.Create("Benchmark 3 - High Card Nines") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "High Card" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfNines, Operators.Equal, 1) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - High Card Tens") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) - .WithCondition(ConditionTypes.NumberOfTens, Operators.Equal, 1) + Rule.Create("Benchmark 3 - High Card Tens") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "High Card" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfTens, Operators.Equal, 1) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - High Card Jacks") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) - .WithCondition(ConditionTypes.NumberOfJacks, Operators.Equal, 1) + Rule.Create("Benchmark 3 - High Card Jacks") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "High Card" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfJacks, Operators.Equal, 1) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - High Card Queens") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) - .WithCondition(ConditionTypes.NumberOfQueens, Operators.Equal, 1) + Rule.Create("Benchmark 3 - High Card Queens") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "High Card" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfQueens, Operators.Equal, 1) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - High Card Kings") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) - .WithCondition(ConditionTypes.NumberOfKings, Operators.Equal, 1) + Rule.Create("Benchmark 3 - High Card Kings") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "High Card" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfKings, Operators.Equal, 1) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - High Card Aces") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) - .WithCondition(ConditionTypes.NumberOfAces, Operators.Equal, 1) + Rule.Create("Benchmark 3 - High Card Aces") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "High Card" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfAces, Operators.Equal, 1) .Build().Rule, }; } diff --git a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.Pair.cs b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.Pair.cs index 72649fd8..ae0b04fd 100644 --- a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.Pair.cs +++ b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.Pair.cs @@ -1,93 +1,92 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 { - using System; using System.Collections.Generic; - using Rules.Framework.Builder; - using Rules.Framework.Core; + using Rules.Framework; + using Rules.Framework.Generic; - public partial class Scenario8Data : IScenarioData + public partial class Scenario8Data : IScenarioData { - private IEnumerable> GetPairsRules() + private IEnumerable> GetPairsRules() { return new[] { - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Pair Deuces") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Pair" }) - .WithCondition(ConditionTypes.NumberOfDeuces, Operators.Equal, 2) + Rule.Create("Benchmark 3 - Pair Deuces") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Pair" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfDeuces, Operators.Equal, 2) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Pair Treys") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Pair" }) - .WithCondition(ConditionTypes.NumberOfTreys, Operators.Equal, 2) + Rule.Create("Benchmark 3 - Pair Treys") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Pair" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfTreys, Operators.Equal, 2) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Pair Fours") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Pair" }) - .WithCondition(ConditionTypes.NumberOfFours, Operators.Equal, 2) + Rule.Create("Benchmark 3 - Pair Fours") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Pair" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfFours, Operators.Equal, 2) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Pair Fives") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Pair" }) - .WithCondition(ConditionTypes.NumberOfFives, Operators.Equal, 2) + Rule.Create("Benchmark 3 - Pair Fives") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Pair" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfFives, Operators.Equal, 2) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Pair Sixes") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Pair" }) - .WithCondition(ConditionTypes.NumberOfSixes, Operators.Equal, 2) + Rule.Create("Benchmark 3 - Pair Sixes") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Pair" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfSixes, Operators.Equal, 2) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Pair Sevens") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Pair" }) - .WithCondition(ConditionTypes.NumberOfSevens, Operators.Equal, 2) + Rule.Create("Benchmark 3 - Pair Sevens") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Pair" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfSevens, Operators.Equal, 2) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Pair Eights") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Pair" }) - .WithCondition(ConditionTypes.NumberOfEigths, Operators.Equal, 2) + Rule.Create("Benchmark 3 - Pair Eights") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Pair" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfEigths, Operators.Equal, 2) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Pair Nines") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Pair" }) - .WithCondition(ConditionTypes.NumberOfNines, Operators.Equal, 2) + Rule.Create("Benchmark 3 - Pair Nines") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Pair" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfNines, Operators.Equal, 2) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Pair Tens") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Pair" }) - .WithCondition(ConditionTypes.NumberOfTens, Operators.Equal, 2) + Rule.Create("Benchmark 3 - Pair Tens") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Pair" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfTens, Operators.Equal, 2) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Pair Jacks") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Pair" }) - .WithCondition(ConditionTypes.NumberOfJacks, Operators.Equal, 2) + Rule.Create("Benchmark 3 - Pair Jacks") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Pair" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfJacks, Operators.Equal, 2) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Pair Queens") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Pair" }) - .WithCondition(ConditionTypes.NumberOfQueens, Operators.Equal, 2) + Rule.Create("Benchmark 3 - Pair Queens") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Pair" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfQueens, Operators.Equal, 2) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Pair Kings") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Pair" }) - .WithCondition(ConditionTypes.NumberOfKings, Operators.Equal, 2) + Rule.Create("Benchmark 3 - Pair Kings") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Pair" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfKings, Operators.Equal, 2) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Pair Aces") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Pair" }) - .WithCondition(ConditionTypes.NumberOfAces, Operators.Equal, 2) + Rule.Create("Benchmark 3 - Pair Aces") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Pair" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfAces, Operators.Equal, 2) .Build().Rule, }; } diff --git a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.RoyalFlush.cs b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.RoyalFlush.cs index 57297782..b4ac0195 100644 --- a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.RoyalFlush.cs +++ b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.RoyalFlush.cs @@ -1,68 +1,68 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 { - using System; using System.Collections.Generic; - using Rules.Framework.Core; + using Rules.Framework; + using Rules.Framework.Generic; - public partial class Scenario8Data : IScenarioData + public partial class Scenario8Data : IScenarioData { - private IEnumerable> GetRoyalFlushRules() + private IEnumerable> GetRoyalFlushRules() { return new[] { - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Royal flush of Clubs: Ace, King, Queen, Jack, 10") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Royal Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Royal flush of Clubs: Ace, King, Queen, Jack, 10") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Royal Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.TenOfClubs, Operators.Equal, true) - .Value(ConditionTypes.JackOfClubs, Operators.Equal, true) - .Value(ConditionTypes.QueenOfClubs, Operators.Equal, true) - .Value(ConditionTypes.KingOfClubs, Operators.Equal, true) - .Value(ConditionTypes.AceOfClubs, Operators.Equal, true) + .Value(PokerConditions.TenOfClubs, Operators.Equal, true) + .Value(PokerConditions.JackOfClubs, Operators.Equal, true) + .Value(PokerConditions.QueenOfClubs, Operators.Equal, true) + .Value(PokerConditions.KingOfClubs, Operators.Equal, true) + .Value(PokerConditions.AceOfClubs, Operators.Equal, true) ) ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Royal flush of Diamonds: Ace, King, Queen, Jack, 10") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Royal Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Royal flush of Diamonds: Ace, King, Queen, Jack, 10") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Royal Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.TenOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.JackOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.QueenOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.KingOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.AceOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.TenOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.JackOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.QueenOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.KingOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.AceOfDiamonds, Operators.Equal, true) ) ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Royal flush of Hearts: Ace, King, Queen, Jack, 10") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Royal Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Royal flush of Hearts: Ace, King, Queen, Jack, 10") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Royal Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.TenOfHearts, Operators.Equal, true) - .Value(ConditionTypes.JackOfHearts, Operators.Equal, true) - .Value(ConditionTypes.QueenOfHearts, Operators.Equal, true) - .Value(ConditionTypes.KingOfHearts, Operators.Equal, true) - .Value(ConditionTypes.AceOfHearts, Operators.Equal, true) + .Value(PokerConditions.TenOfHearts, Operators.Equal, true) + .Value(PokerConditions.JackOfHearts, Operators.Equal, true) + .Value(PokerConditions.QueenOfHearts, Operators.Equal, true) + .Value(PokerConditions.KingOfHearts, Operators.Equal, true) + .Value(PokerConditions.AceOfHearts, Operators.Equal, true) ) ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Royal flush of Spades: Ace, King, Queen, Jack, 10") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Royal Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Royal flush of Spades: Ace, King, Queen, Jack, 10") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Royal Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.TenOfSpades, Operators.Equal, true) - .Value(ConditionTypes.JackOfSpades, Operators.Equal, true) - .Value(ConditionTypes.QueenOfSpades, Operators.Equal, true) - .Value(ConditionTypes.KingOfSpades, Operators.Equal, true) - .Value(ConditionTypes.AceOfSpades, Operators.Equal, true) + .Value(PokerConditions.TenOfSpades, Operators.Equal, true) + .Value(PokerConditions.JackOfSpades, Operators.Equal, true) + .Value(PokerConditions.QueenOfSpades, Operators.Equal, true) + .Value(PokerConditions.KingOfSpades, Operators.Equal, true) + .Value(PokerConditions.AceOfSpades, Operators.Equal, true) ) ) .Build().Rule, diff --git a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.Straight.cs b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.Straight.cs index 8cad58e1..d17885e7 100644 --- a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.Straight.cs +++ b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.Straight.cs @@ -1,126 +1,126 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 { - using System; using System.Collections.Generic; - using Rules.Framework.Core; + using Rules.Framework; + using Rules.Framework.Generic; - public partial class Scenario8Data : IScenarioData + public partial class Scenario8Data : IScenarioData { - private IEnumerable> GetStraightRules() + private IEnumerable> GetStraightRules() { return new[] { - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight 6, 5, 4, 3, 2") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight 6, 5, 4, 3, 2") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.NumberOfDeuces, Operators.GreaterThanOrEqual, 1) - .Value(ConditionTypes.NumberOfTreys, Operators.GreaterThanOrEqual, 1) - .Value(ConditionTypes.NumberOfFours, Operators.GreaterThanOrEqual, 1) - .Value(ConditionTypes.NumberOfFives, Operators.GreaterThanOrEqual, 1) - .Value(ConditionTypes.NumberOfSixes, Operators.GreaterThanOrEqual, 1) - ) - ) + .Value(PokerConditions.NumberOfDeuces, Operators.GreaterThanOrEqual, 1) + .Value(PokerConditions.NumberOfTreys, Operators.GreaterThanOrEqual, 1) + .Value(PokerConditions.NumberOfFours, Operators.GreaterThanOrEqual, 1) + .Value(PokerConditions.NumberOfFives, Operators.GreaterThanOrEqual, 1) + .Value(PokerConditions.NumberOfSixes, Operators.GreaterThanOrEqual, 1) + ) + ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight 7, 6, 5, 4, 3") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight 7, 6, 5, 4, 3") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.NumberOfTreys, Operators.GreaterThanOrEqual, 1) - .Value(ConditionTypes.NumberOfFours, Operators.GreaterThanOrEqual, 1) - .Value(ConditionTypes.NumberOfFives, Operators.GreaterThanOrEqual, 1) - .Value(ConditionTypes.NumberOfSixes, Operators.GreaterThanOrEqual, 1) - .Value(ConditionTypes.NumberOfSevens, Operators.GreaterThanOrEqual, 1) - ) - ) + .Value(PokerConditions.NumberOfTreys, Operators.GreaterThanOrEqual, 1) + .Value(PokerConditions.NumberOfFours, Operators.GreaterThanOrEqual, 1) + .Value(PokerConditions.NumberOfFives, Operators.GreaterThanOrEqual, 1) + .Value(PokerConditions.NumberOfSixes, Operators.GreaterThanOrEqual, 1) + .Value(PokerConditions.NumberOfSevens, Operators.GreaterThanOrEqual, 1) + ) + ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight 8, 7, 6, 5, 4") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight 8, 7, 6, 5, 4") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.NumberOfFours, Operators.GreaterThanOrEqual, 1) - .Value(ConditionTypes.NumberOfFives, Operators.GreaterThanOrEqual, 1) - .Value(ConditionTypes.NumberOfSixes, Operators.GreaterThanOrEqual, 1) - .Value(ConditionTypes.NumberOfSevens, Operators.GreaterThanOrEqual, 1) - .Value(ConditionTypes.NumberOfEigths, Operators.GreaterThanOrEqual, 1) - ) - ) + .Value(PokerConditions.NumberOfFours, Operators.GreaterThanOrEqual, 1) + .Value(PokerConditions.NumberOfFives, Operators.GreaterThanOrEqual, 1) + .Value(PokerConditions.NumberOfSixes, Operators.GreaterThanOrEqual, 1) + .Value(PokerConditions.NumberOfSevens, Operators.GreaterThanOrEqual, 1) + .Value(PokerConditions.NumberOfEigths, Operators.GreaterThanOrEqual, 1) + ) + ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight 9, 8, 7, 6, 5") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight 9, 8, 7, 6, 5") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.NumberOfFives, Operators.GreaterThanOrEqual, 1) - .Value(ConditionTypes.NumberOfSixes, Operators.GreaterThanOrEqual, 1) - .Value(ConditionTypes.NumberOfSevens, Operators.GreaterThanOrEqual, 1) - .Value(ConditionTypes.NumberOfEigths, Operators.GreaterThanOrEqual, 1) - .Value(ConditionTypes.NumberOfNines, Operators.GreaterThanOrEqual, 1) - ) - ) + .Value(PokerConditions.NumberOfFives, Operators.GreaterThanOrEqual, 1) + .Value(PokerConditions.NumberOfSixes, Operators.GreaterThanOrEqual, 1) + .Value(PokerConditions.NumberOfSevens, Operators.GreaterThanOrEqual, 1) + .Value(PokerConditions.NumberOfEigths, Operators.GreaterThanOrEqual, 1) + .Value(PokerConditions.NumberOfNines, Operators.GreaterThanOrEqual, 1) + ) + ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight 10, 9, 8, 7, 6") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight 10, 9, 8, 7, 6") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.NumberOfSixes, Operators.GreaterThanOrEqual, 1) - .Value(ConditionTypes.NumberOfSevens, Operators.GreaterThanOrEqual, 1) - .Value(ConditionTypes.NumberOfEigths, Operators.GreaterThanOrEqual, 1) - .Value(ConditionTypes.NumberOfNines, Operators.GreaterThanOrEqual, 1) - .Value(ConditionTypes.NumberOfTens, Operators.GreaterThanOrEqual, 1) - ) + .Value(PokerConditions.NumberOfSixes, Operators.GreaterThanOrEqual, 1) + .Value(PokerConditions.NumberOfSevens, Operators.GreaterThanOrEqual, 1) + .Value(PokerConditions.NumberOfEigths, Operators.GreaterThanOrEqual, 1) + .Value(PokerConditions.NumberOfNines, Operators.GreaterThanOrEqual, 1) + .Value(PokerConditions.NumberOfTens, Operators.GreaterThanOrEqual, 1) ) + ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight Jack, 10, 9, 8, 7") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight Jack, 10, 9, 8, 7") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.NumberOfSevens, Operators.GreaterThanOrEqual, 1) - .Value(ConditionTypes.NumberOfEigths, Operators.GreaterThanOrEqual, 1) - .Value(ConditionTypes.NumberOfNines, Operators.GreaterThanOrEqual, 1) - .Value(ConditionTypes.NumberOfTens, Operators.GreaterThanOrEqual, 1) - .Value(ConditionTypes.NumberOfJacks, Operators.GreaterThanOrEqual, 1) - ) + .Value(PokerConditions.NumberOfSevens, Operators.GreaterThanOrEqual, 1) + .Value(PokerConditions.NumberOfEigths, Operators.GreaterThanOrEqual, 1) + .Value(PokerConditions.NumberOfNines, Operators.GreaterThanOrEqual, 1) + .Value(PokerConditions.NumberOfTens, Operators.GreaterThanOrEqual, 1) + .Value(PokerConditions.NumberOfJacks, Operators.GreaterThanOrEqual, 1) ) + ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight Queen, Jack, 10, 9, 8") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight Queen, Jack, 10, 9, 8") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.NumberOfEigths, Operators.GreaterThanOrEqual, 1) - .Value(ConditionTypes.NumberOfNines, Operators.GreaterThanOrEqual, 1) - .Value(ConditionTypes.NumberOfTens, Operators.GreaterThanOrEqual, 1) - .Value(ConditionTypes.NumberOfJacks, Operators.GreaterThanOrEqual, 1) - .Value(ConditionTypes.NumberOfQueens, Operators.GreaterThanOrEqual, 1) - ) + .Value(PokerConditions.NumberOfEigths, Operators.GreaterThanOrEqual, 1) + .Value(PokerConditions.NumberOfNines, Operators.GreaterThanOrEqual, 1) + .Value(PokerConditions.NumberOfTens, Operators.GreaterThanOrEqual, 1) + .Value(PokerConditions.NumberOfJacks, Operators.GreaterThanOrEqual, 1) + .Value(PokerConditions.NumberOfQueens, Operators.GreaterThanOrEqual, 1) ) + ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight King, Queen, Jack, 10, 9") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight King, Queen, Jack, 10, 9") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.NumberOfNines, Operators.GreaterThanOrEqual, 1) - .Value(ConditionTypes.NumberOfTens, Operators.GreaterThanOrEqual, 1) - .Value(ConditionTypes.NumberOfJacks, Operators.GreaterThanOrEqual, 1) - .Value(ConditionTypes.NumberOfQueens, Operators.GreaterThanOrEqual, 1) - .Value(ConditionTypes.NumberOfKings, Operators.GreaterThanOrEqual, 1) - ) + .Value(PokerConditions.NumberOfNines, Operators.GreaterThanOrEqual, 1) + .Value(PokerConditions.NumberOfTens, Operators.GreaterThanOrEqual, 1) + .Value(PokerConditions.NumberOfJacks, Operators.GreaterThanOrEqual, 1) + .Value(PokerConditions.NumberOfQueens, Operators.GreaterThanOrEqual, 1) + .Value(PokerConditions.NumberOfKings, Operators.GreaterThanOrEqual, 1) ) + ) .Build().Rule, }; } diff --git a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.StraightFlush.cs b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.StraightFlush.cs index 2cbcb60a..159b6b15 100644 --- a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.StraightFlush.cs +++ b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.StraightFlush.cs @@ -1,468 +1,467 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 { - using System; using System.Collections.Generic; - using Rules.Framework.Builder; - using Rules.Framework.Core; + using Rules.Framework; + using Rules.Framework.Generic; - public partial class Scenario8Data : IScenarioData + public partial class Scenario8Data : IScenarioData { - private IEnumerable> GetStraightFlushRules() + private IEnumerable> GetStraightFlushRules() { return new[] { // Straight flush of Clubs: - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight flush of Clubs: 6, 5, 4, 3, 2") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight flush of Clubs: 6, 5, 4, 3, 2") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.DeuceOfClubs, Operators.Equal, true) - .Value(ConditionTypes.TreyOfClubs, Operators.Equal, true) - .Value(ConditionTypes.FourOfClubs, Operators.Equal, true) - .Value(ConditionTypes.FiveOfClubs, Operators.Equal, true) - .Value(ConditionTypes.SixOfClubs, Operators.Equal, true) + .Value(PokerConditions.DeuceOfClubs, Operators.Equal, true) + .Value(PokerConditions.TreyOfClubs, Operators.Equal, true) + .Value(PokerConditions.FourOfClubs, Operators.Equal, true) + .Value(PokerConditions.FiveOfClubs, Operators.Equal, true) + .Value(PokerConditions.SixOfClubs, Operators.Equal, true) ) ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight flush of Clubs: 7, 6, 5, 4, 3") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight flush of Clubs: 7, 6, 5, 4, 3") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.TreyOfClubs, Operators.Equal, true) - .Value(ConditionTypes.FourOfClubs, Operators.Equal, true) - .Value(ConditionTypes.FiveOfClubs, Operators.Equal, true) - .Value(ConditionTypes.SixOfClubs, Operators.Equal, true) - .Value(ConditionTypes.SevenOfClubs, Operators.Equal, true) + .Value(PokerConditions.TreyOfClubs, Operators.Equal, true) + .Value(PokerConditions.FourOfClubs, Operators.Equal, true) + .Value(PokerConditions.FiveOfClubs, Operators.Equal, true) + .Value(PokerConditions.SixOfClubs, Operators.Equal, true) + .Value(PokerConditions.SevenOfClubs, Operators.Equal, true) ) ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight flush of Clubs: 8, 7, 6, 5, 4") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight flush of Clubs: 8, 7, 6, 5, 4") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.FourOfClubs, Operators.Equal, true) - .Value(ConditionTypes.FiveOfClubs, Operators.Equal, true) - .Value(ConditionTypes.SixOfClubs, Operators.Equal, true) - .Value(ConditionTypes.SevenOfClubs, Operators.Equal, true) - .Value(ConditionTypes.EightOfClubs, Operators.Equal, true) + .Value(PokerConditions.FourOfClubs, Operators.Equal, true) + .Value(PokerConditions.FiveOfClubs, Operators.Equal, true) + .Value(PokerConditions.SixOfClubs, Operators.Equal, true) + .Value(PokerConditions.SevenOfClubs, Operators.Equal, true) + .Value(PokerConditions.EightOfClubs, Operators.Equal, true) ) ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight flush of Clubs: 9, 8, 7, 6, 5") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight flush of Clubs: 9, 8, 7, 6, 5") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.FiveOfClubs, Operators.Equal, true) - .Value(ConditionTypes.SixOfClubs, Operators.Equal, true) - .Value(ConditionTypes.SevenOfClubs, Operators.Equal, true) - .Value(ConditionTypes.EightOfClubs, Operators.Equal, true) - .Value(ConditionTypes.NineOfClubs, Operators.Equal, true) + .Value(PokerConditions.FiveOfClubs, Operators.Equal, true) + .Value(PokerConditions.SixOfClubs, Operators.Equal, true) + .Value(PokerConditions.SevenOfClubs, Operators.Equal, true) + .Value(PokerConditions.EightOfClubs, Operators.Equal, true) + .Value(PokerConditions.NineOfClubs, Operators.Equal, true) ) ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight flush of Clubs: 10, 9, 8, 7, 6") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight flush of Clubs: 10, 9, 8, 7, 6") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.SixOfClubs, Operators.Equal, true) - .Value(ConditionTypes.SevenOfClubs, Operators.Equal, true) - .Value(ConditionTypes.EightOfClubs, Operators.Equal, true) - .Value(ConditionTypes.NineOfClubs, Operators.Equal, true) - .Value(ConditionTypes.TenOfClubs, Operators.Equal, true) + .Value(PokerConditions.SixOfClubs, Operators.Equal, true) + .Value(PokerConditions.SevenOfClubs, Operators.Equal, true) + .Value(PokerConditions.EightOfClubs, Operators.Equal, true) + .Value(PokerConditions.NineOfClubs, Operators.Equal, true) + .Value(PokerConditions.TenOfClubs, Operators.Equal, true) ) ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight flush of Clubs: Jack, 10, 9, 8, 7") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight flush of Clubs: Jack, 10, 9, 8, 7") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.SevenOfClubs, Operators.Equal, true) - .Value(ConditionTypes.EightOfClubs, Operators.Equal, true) - .Value(ConditionTypes.NineOfClubs, Operators.Equal, true) - .Value(ConditionTypes.TenOfClubs, Operators.Equal, true) - .Value(ConditionTypes.JackOfClubs, Operators.Equal, true) + .Value(PokerConditions.SevenOfClubs, Operators.Equal, true) + .Value(PokerConditions.EightOfClubs, Operators.Equal, true) + .Value(PokerConditions.NineOfClubs, Operators.Equal, true) + .Value(PokerConditions.TenOfClubs, Operators.Equal, true) + .Value(PokerConditions.JackOfClubs, Operators.Equal, true) ) ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight flush of Clubs: Queen, Jack, 10, 9, 8") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight flush of Clubs: Queen, Jack, 10, 9, 8") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.EightOfClubs, Operators.Equal, true) - .Value(ConditionTypes.NineOfClubs, Operators.Equal, true) - .Value(ConditionTypes.TenOfClubs, Operators.Equal, true) - .Value(ConditionTypes.JackOfClubs, Operators.Equal, true) - .Value(ConditionTypes.QueenOfClubs, Operators.Equal, true) + .Value(PokerConditions.EightOfClubs, Operators.Equal, true) + .Value(PokerConditions.NineOfClubs, Operators.Equal, true) + .Value(PokerConditions.TenOfClubs, Operators.Equal, true) + .Value(PokerConditions.JackOfClubs, Operators.Equal, true) + .Value(PokerConditions.QueenOfClubs, Operators.Equal, true) ) ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight flush of Clubs: King, Queen, Jack, 10, 9") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight flush of Clubs: King, Queen, Jack, 10, 9") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.NineOfClubs, Operators.Equal, true) - .Value(ConditionTypes.TenOfClubs, Operators.Equal, true) - .Value(ConditionTypes.JackOfClubs, Operators.Equal, true) - .Value(ConditionTypes.QueenOfClubs, Operators.Equal, true) - .Value(ConditionTypes.KingOfClubs, Operators.Equal, true) + .Value(PokerConditions.NineOfClubs, Operators.Equal, true) + .Value(PokerConditions.TenOfClubs, Operators.Equal, true) + .Value(PokerConditions.JackOfClubs, Operators.Equal, true) + .Value(PokerConditions.QueenOfClubs, Operators.Equal, true) + .Value(PokerConditions.KingOfClubs, Operators.Equal, true) ) ) .Build().Rule, // Straight flush of Diamonds: - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight flush of Diamonds: 6, 5, 4, 3, 2") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight flush of Diamonds: 6, 5, 4, 3, 2") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.DeuceOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.TreyOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.FourOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.FiveOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.SixOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.DeuceOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.TreyOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.FourOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.FiveOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.SixOfDiamonds, Operators.Equal, true) ) ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight flush of Diamonds: 7, 6, 5, 4, 3") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight flush of Diamonds: 7, 6, 5, 4, 3") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.TreyOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.FourOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.FiveOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.SixOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.SevenOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.TreyOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.FourOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.FiveOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.SixOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.SevenOfDiamonds, Operators.Equal, true) ) ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight flush of Diamonds: 8, 7, 6, 5, 4") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight flush of Diamonds: 8, 7, 6, 5, 4") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.FourOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.FiveOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.SixOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.SevenOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.EightOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.FourOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.FiveOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.SixOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.SevenOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.EightOfDiamonds, Operators.Equal, true) ) ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight flush of Diamonds: 9, 8, 7, 6, 5") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight flush of Diamonds: 9, 8, 7, 6, 5") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.FiveOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.SixOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.SevenOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.EightOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.NineOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.FiveOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.SixOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.SevenOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.EightOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.NineOfDiamonds, Operators.Equal, true) ) ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight flush of Diamonds: 10, 9, 8, 7, 6") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight flush of Diamonds: 10, 9, 8, 7, 6") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.SixOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.SevenOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.EightOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.NineOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.TenOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.SixOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.SevenOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.EightOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.NineOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.TenOfDiamonds, Operators.Equal, true) ) ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight flush of Diamonds: Jack, 10, 9, 8, 7") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight flush of Diamonds: Jack, 10, 9, 8, 7") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.SevenOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.EightOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.NineOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.TenOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.JackOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.SevenOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.EightOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.NineOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.TenOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.JackOfDiamonds, Operators.Equal, true) ) ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight flush of Diamonds: Queen, Jack, 10, 9, 8") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight flush of Diamonds: Queen, Jack, 10, 9, 8") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.EightOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.NineOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.TenOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.JackOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.QueenOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.EightOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.NineOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.TenOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.JackOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.QueenOfDiamonds, Operators.Equal, true) ) ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight flush of Diamonds: King, Queen, Jack, 10, 9") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight flush of Diamonds: King, Queen, Jack, 10, 9") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.NineOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.TenOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.JackOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.QueenOfDiamonds, Operators.Equal, true) - .Value(ConditionTypes.KingOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.NineOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.TenOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.JackOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.QueenOfDiamonds, Operators.Equal, true) + .Value(PokerConditions.KingOfDiamonds, Operators.Equal, true) ) ) .Build().Rule, // Straight flush of Hearts: - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight flush of Hearts: 6, 5, 4, 3, 2") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight flush of Hearts: 6, 5, 4, 3, 2") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.DeuceOfHearts, Operators.Equal, true) - .Value(ConditionTypes.TreyOfHearts, Operators.Equal, true) - .Value(ConditionTypes.FourOfHearts, Operators.Equal, true) - .Value(ConditionTypes.FiveOfHearts, Operators.Equal, true) - .Value(ConditionTypes.SixOfHearts, Operators.Equal, true) + .Value(PokerConditions.DeuceOfHearts, Operators.Equal, true) + .Value(PokerConditions.TreyOfHearts, Operators.Equal, true) + .Value(PokerConditions.FourOfHearts, Operators.Equal, true) + .Value(PokerConditions.FiveOfHearts, Operators.Equal, true) + .Value(PokerConditions.SixOfHearts, Operators.Equal, true) ) ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight flush of Hearts: 7, 6, 5, 4, 3") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight flush of Hearts: 7, 6, 5, 4, 3") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.TreyOfHearts, Operators.Equal, true) - .Value(ConditionTypes.FourOfHearts, Operators.Equal, true) - .Value(ConditionTypes.FiveOfHearts, Operators.Equal, true) - .Value(ConditionTypes.SixOfHearts, Operators.Equal, true) - .Value(ConditionTypes.SevenOfHearts, Operators.Equal, true) + .Value(PokerConditions.TreyOfHearts, Operators.Equal, true) + .Value(PokerConditions.FourOfHearts, Operators.Equal, true) + .Value(PokerConditions.FiveOfHearts, Operators.Equal, true) + .Value(PokerConditions.SixOfHearts, Operators.Equal, true) + .Value(PokerConditions.SevenOfHearts, Operators.Equal, true) ) ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight flush of Hearts: 8, 7, 6, 5, 4") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight flush of Hearts: 8, 7, 6, 5, 4") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.FourOfHearts, Operators.Equal, true) - .Value(ConditionTypes.FiveOfHearts, Operators.Equal, true) - .Value(ConditionTypes.SixOfHearts, Operators.Equal, true) - .Value(ConditionTypes.SevenOfHearts, Operators.Equal, true) - .Value(ConditionTypes.EightOfHearts, Operators.Equal, true) + .Value(PokerConditions.FourOfHearts, Operators.Equal, true) + .Value(PokerConditions.FiveOfHearts, Operators.Equal, true) + .Value(PokerConditions.SixOfHearts, Operators.Equal, true) + .Value(PokerConditions.SevenOfHearts, Operators.Equal, true) + .Value(PokerConditions.EightOfHearts, Operators.Equal, true) ) ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight flush of Hearts: 9, 8, 7, 6, 5") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight flush of Hearts: 9, 8, 7, 6, 5") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.FiveOfHearts, Operators.Equal, true) - .Value(ConditionTypes.SixOfHearts, Operators.Equal, true) - .Value(ConditionTypes.SevenOfHearts, Operators.Equal, true) - .Value(ConditionTypes.EightOfHearts, Operators.Equal, true) - .Value(ConditionTypes.NineOfHearts, Operators.Equal, true) + .Value(PokerConditions.FiveOfHearts, Operators.Equal, true) + .Value(PokerConditions.SixOfHearts, Operators.Equal, true) + .Value(PokerConditions.SevenOfHearts, Operators.Equal, true) + .Value(PokerConditions.EightOfHearts, Operators.Equal, true) + .Value(PokerConditions.NineOfHearts, Operators.Equal, true) ) ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight flush of Hearts: 10, 9, 8, 7, 6") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight flush of Hearts: 10, 9, 8, 7, 6") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.SixOfHearts, Operators.Equal, true) - .Value(ConditionTypes.SevenOfHearts, Operators.Equal, true) - .Value(ConditionTypes.EightOfHearts, Operators.Equal, true) - .Value(ConditionTypes.NineOfHearts, Operators.Equal, true) - .Value(ConditionTypes.TenOfHearts, Operators.Equal, true) + .Value(PokerConditions.SixOfHearts, Operators.Equal, true) + .Value(PokerConditions.SevenOfHearts, Operators.Equal, true) + .Value(PokerConditions.EightOfHearts, Operators.Equal, true) + .Value(PokerConditions.NineOfHearts, Operators.Equal, true) + .Value(PokerConditions.TenOfHearts, Operators.Equal, true) ) ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight flush of Hearts: Jack, 10, 9, 8, 7") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight flush of Hearts: Jack, 10, 9, 8, 7") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.SevenOfHearts, Operators.Equal, true) - .Value(ConditionTypes.EightOfHearts, Operators.Equal, true) - .Value(ConditionTypes.NineOfHearts, Operators.Equal, true) - .Value(ConditionTypes.TenOfHearts, Operators.Equal, true) - .Value(ConditionTypes.JackOfHearts, Operators.Equal, true) + .Value(PokerConditions.SevenOfHearts, Operators.Equal, true) + .Value(PokerConditions.EightOfHearts, Operators.Equal, true) + .Value(PokerConditions.NineOfHearts, Operators.Equal, true) + .Value(PokerConditions.TenOfHearts, Operators.Equal, true) + .Value(PokerConditions.JackOfHearts, Operators.Equal, true) ) ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight flush of Hearts: Queen, Jack, 10, 9, 8") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight flush of Hearts: Queen, Jack, 10, 9, 8") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.EightOfHearts, Operators.Equal, true) - .Value(ConditionTypes.NineOfHearts, Operators.Equal, true) - .Value(ConditionTypes.TenOfHearts, Operators.Equal, true) - .Value(ConditionTypes.JackOfHearts, Operators.Equal, true) - .Value(ConditionTypes.QueenOfHearts, Operators.Equal, true) + .Value(PokerConditions.EightOfHearts, Operators.Equal, true) + .Value(PokerConditions.NineOfHearts, Operators.Equal, true) + .Value(PokerConditions.TenOfHearts, Operators.Equal, true) + .Value(PokerConditions.JackOfHearts, Operators.Equal, true) + .Value(PokerConditions.QueenOfHearts, Operators.Equal, true) ) ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight flush of Hearts: King, Queen, Jack, 10, 9") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight flush of Hearts: King, Queen, Jack, 10, 9") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.NineOfHearts, Operators.Equal, true) - .Value(ConditionTypes.TenOfHearts, Operators.Equal, true) - .Value(ConditionTypes.JackOfHearts, Operators.Equal, true) - .Value(ConditionTypes.QueenOfHearts, Operators.Equal, true) - .Value(ConditionTypes.KingOfHearts, Operators.Equal, true) + .Value(PokerConditions.NineOfHearts, Operators.Equal, true) + .Value(PokerConditions.TenOfHearts, Operators.Equal, true) + .Value(PokerConditions.JackOfHearts, Operators.Equal, true) + .Value(PokerConditions.QueenOfHearts, Operators.Equal, true) + .Value(PokerConditions.KingOfHearts, Operators.Equal, true) ) ) .Build().Rule, // Straight flush of Spades: - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight flush of Spades: 6, 5, 4, 3, 2") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight flush of Spades: 6, 5, 4, 3, 2") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.DeuceOfSpades, Operators.Equal, true) - .Value(ConditionTypes.TreyOfSpades, Operators.Equal, true) - .Value(ConditionTypes.FourOfSpades, Operators.Equal, true) - .Value(ConditionTypes.FiveOfSpades, Operators.Equal, true) - .Value(ConditionTypes.SixOfSpades, Operators.Equal, true) + .Value(PokerConditions.DeuceOfSpades, Operators.Equal, true) + .Value(PokerConditions.TreyOfSpades, Operators.Equal, true) + .Value(PokerConditions.FourOfSpades, Operators.Equal, true) + .Value(PokerConditions.FiveOfSpades, Operators.Equal, true) + .Value(PokerConditions.SixOfSpades, Operators.Equal, true) ) ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight flush of Spades: 7, 6, 5, 4, 3") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight flush of Spades: 7, 6, 5, 4, 3") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.TreyOfSpades, Operators.Equal, true) - .Value(ConditionTypes.FourOfSpades, Operators.Equal, true) - .Value(ConditionTypes.FiveOfSpades, Operators.Equal, true) - .Value(ConditionTypes.SixOfSpades, Operators.Equal, true) - .Value(ConditionTypes.SevenOfSpades, Operators.Equal, true) + .Value(PokerConditions.TreyOfSpades, Operators.Equal, true) + .Value(PokerConditions.FourOfSpades, Operators.Equal, true) + .Value(PokerConditions.FiveOfSpades, Operators.Equal, true) + .Value(PokerConditions.SixOfSpades, Operators.Equal, true) + .Value(PokerConditions.SevenOfSpades, Operators.Equal, true) ) ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight flush of Spades: 8, 7, 6, 5, 4") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight flush of Spades: 8, 7, 6, 5, 4") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.FourOfSpades, Operators.Equal, true) - .Value(ConditionTypes.FiveOfSpades, Operators.Equal, true) - .Value(ConditionTypes.SixOfSpades, Operators.Equal, true) - .Value(ConditionTypes.SevenOfSpades, Operators.Equal, true) - .Value(ConditionTypes.EightOfSpades, Operators.Equal, true) + .Value(PokerConditions.FourOfSpades, Operators.Equal, true) + .Value(PokerConditions.FiveOfSpades, Operators.Equal, true) + .Value(PokerConditions.SixOfSpades, Operators.Equal, true) + .Value(PokerConditions.SevenOfSpades, Operators.Equal, true) + .Value(PokerConditions.EightOfSpades, Operators.Equal, true) ) ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight flush of Spades: 9, 8, 7, 6, 5") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight flush of Spades: 9, 8, 7, 6, 5") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.FiveOfSpades, Operators.Equal, true) - .Value(ConditionTypes.SixOfSpades, Operators.Equal, true) - .Value(ConditionTypes.SevenOfSpades, Operators.Equal, true) - .Value(ConditionTypes.EightOfSpades, Operators.Equal, true) - .Value(ConditionTypes.NineOfSpades, Operators.Equal, true) + .Value(PokerConditions.FiveOfSpades, Operators.Equal, true) + .Value(PokerConditions.SixOfSpades, Operators.Equal, true) + .Value(PokerConditions.SevenOfSpades, Operators.Equal, true) + .Value(PokerConditions.EightOfSpades, Operators.Equal, true) + .Value(PokerConditions.NineOfSpades, Operators.Equal, true) ) ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight flush of Spades: 10, 9, 8, 7, 6") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight flush of Spades: 10, 9, 8, 7, 6") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.SixOfSpades, Operators.Equal, true) - .Value(ConditionTypes.SevenOfSpades, Operators.Equal, true) - .Value(ConditionTypes.EightOfSpades, Operators.Equal, true) - .Value(ConditionTypes.NineOfSpades, Operators.Equal, true) - .Value(ConditionTypes.TenOfSpades, Operators.Equal, true) + .Value(PokerConditions.SixOfSpades, Operators.Equal, true) + .Value(PokerConditions.SevenOfSpades, Operators.Equal, true) + .Value(PokerConditions.EightOfSpades, Operators.Equal, true) + .Value(PokerConditions.NineOfSpades, Operators.Equal, true) + .Value(PokerConditions.TenOfSpades, Operators.Equal, true) ) ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight flush of Spades: Jack, 10, 9, 8, 7") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight flush of Spades: Jack, 10, 9, 8, 7") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.SevenOfSpades, Operators.Equal, true) - .Value(ConditionTypes.EightOfSpades, Operators.Equal, true) - .Value(ConditionTypes.NineOfSpades, Operators.Equal, true) - .Value(ConditionTypes.TenOfSpades, Operators.Equal, true) - .Value(ConditionTypes.JackOfSpades, Operators.Equal, true) + .Value(PokerConditions.SevenOfSpades, Operators.Equal, true) + .Value(PokerConditions.EightOfSpades, Operators.Equal, true) + .Value(PokerConditions.NineOfSpades, Operators.Equal, true) + .Value(PokerConditions.TenOfSpades, Operators.Equal, true) + .Value(PokerConditions.JackOfSpades, Operators.Equal, true) ) ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight flush of Spades: Queen, Jack, 10, 9, 8") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight flush of Spades: Queen, Jack, 10, 9, 8") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.EightOfSpades, Operators.Equal, true) - .Value(ConditionTypes.NineOfSpades, Operators.Equal, true) - .Value(ConditionTypes.TenOfSpades, Operators.Equal, true) - .Value(ConditionTypes.JackOfSpades, Operators.Equal, true) - .Value(ConditionTypes.QueenOfSpades, Operators.Equal, true) + .Value(PokerConditions.EightOfSpades, Operators.Equal, true) + .Value(PokerConditions.NineOfSpades, Operators.Equal, true) + .Value(PokerConditions.TenOfSpades, Operators.Equal, true) + .Value(PokerConditions.JackOfSpades, Operators.Equal, true) + .Value(PokerConditions.QueenOfSpades, Operators.Equal, true) ) ) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Straight flush of Spades: King, Queen, Jack, 10, 9") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) - .WithCondition(c => c + Rule.Create("Benchmark 3 - Straight flush of Spades: King, Queen, Jack, 10, 9") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .Since("2000-01-01") + .ApplyWhen(c => c .And(x => x - .Value(ConditionTypes.NineOfSpades, Operators.Equal, true) - .Value(ConditionTypes.TenOfSpades, Operators.Equal, true) - .Value(ConditionTypes.JackOfSpades, Operators.Equal, true) - .Value(ConditionTypes.QueenOfSpades, Operators.Equal, true) - .Value(ConditionTypes.KingOfSpades, Operators.Equal, true) + .Value(PokerConditions.NineOfSpades, Operators.Equal, true) + .Value(PokerConditions.TenOfSpades, Operators.Equal, true) + .Value(PokerConditions.JackOfSpades, Operators.Equal, true) + .Value(PokerConditions.QueenOfSpades, Operators.Equal, true) + .Value(PokerConditions.KingOfSpades, Operators.Equal, true) ) ) .Build().Rule, diff --git a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.ThreeOfAKind.cs b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.ThreeOfAKind.cs index 637c3cb4..e2405a13 100644 --- a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.ThreeOfAKind.cs +++ b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.ThreeOfAKind.cs @@ -1,93 +1,92 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 { - using System; using System.Collections.Generic; - using Rules.Framework.Builder; - using Rules.Framework.Core; + using Rules.Framework; + using Rules.Framework.Generic; - public partial class Scenario8Data : IScenarioData + public partial class Scenario8Data : IScenarioData { - private IEnumerable> GetThreeOfAKindRules() + private IEnumerable> GetThreeOfAKindRules() { return new[] { - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Three Of A Kind Deuces") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) - .WithCondition(ConditionTypes.NumberOfDeuces, Operators.Equal, 3) + Rule.Create("Benchmark 3 - Three Of A Kind Deuces") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfDeuces, Operators.Equal, 3) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Three Of A Kind Treys") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) - .WithCondition(ConditionTypes.NumberOfTreys, Operators.Equal, 3) + Rule.Create("Benchmark 3 - Three Of A Kind Treys") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfTreys, Operators.Equal, 3) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Three Of A Kind Fours") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) - .WithCondition(ConditionTypes.NumberOfDeuces, Operators.Equal, 3) + Rule.Create("Benchmark 3 - Three Of A Kind Fours") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfDeuces, Operators.Equal, 3) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Three Of A Kind Fives") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) - .WithCondition(ConditionTypes.NumberOfFives, Operators.Equal, 3) + Rule.Create("Benchmark 3 - Three Of A Kind Fives") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfFives, Operators.Equal, 3) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Three Of A Kind Sixes") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) - .WithCondition(ConditionTypes.NumberOfSixes, Operators.Equal, 3) + Rule.Create("Benchmark 3 - Three Of A Kind Sixes") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfSixes, Operators.Equal, 3) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Three Of A Kind Sevens") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) - .WithCondition(ConditionTypes.NumberOfSevens, Operators.Equal, 3) + Rule.Create("Benchmark 3 - Three Of A Kind Sevens") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfSevens, Operators.Equal, 3) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Three Of A Kind Eights") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) - .WithCondition(ConditionTypes.NumberOfEigths, Operators.Equal, 3) + Rule.Create("Benchmark 3 - Three Of A Kind Eights") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfEigths, Operators.Equal, 3) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Three Of A Kind Nines") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) - .WithCondition(ConditionTypes.NumberOfNines, Operators.Equal, 3) + Rule.Create("Benchmark 3 - Three Of A Kind Nines") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfNines, Operators.Equal, 3) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Three Of A Kind Tens") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) - .WithCondition(ConditionTypes.NumberOfTens, Operators.Equal, 3) + Rule.Create("Benchmark 3 - Three Of A Kind Tens") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfTens, Operators.Equal, 3) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Three Of A Kind Jacks") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) - .WithCondition(ConditionTypes.NumberOfJacks, Operators.Equal, 3) + Rule.Create("Benchmark 3 - Three Of A Kind Jacks") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfJacks, Operators.Equal, 3) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Three Of A Kind Queens") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) - .WithCondition(ConditionTypes.NumberOfQueens, Operators.Equal, 3) + Rule.Create("Benchmark 3 - Three Of A Kind Queens") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfQueens, Operators.Equal, 3) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Three Of A Kind Kings") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) - .WithCondition(ConditionTypes.NumberOfKings, Operators.Equal, 3) + Rule.Create("Benchmark 3 - Three Of A Kind Kings") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfKings, Operators.Equal, 3) .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - Three Of A Kind Aces") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) - .WithCondition(ConditionTypes.NumberOfAces, Operators.Equal, 3) + Rule.Create("Benchmark 3 - Three Of A Kind Aces") + .InRuleset(PokerRulesets.TexasHoldemPokerSingleCombinations) + .SetContent(new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) + .Since("2000-01-01") + .ApplyWhen(PokerConditions.NumberOfAces, Operators.Equal, 3) .Build().Rule, }; } diff --git a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.cs b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.cs index 47c1eb16..354549be 100644 --- a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.cs +++ b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/Scenario8/Scenario8Data.cs @@ -3,30 +3,29 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 using System; using System.Collections.Generic; using System.Linq; - using Rules.Framework; - using Rules.Framework.Core; + using Rules.Framework.Generic; - public partial class Scenario8Data : IScenarioData + public partial class Scenario8Data : IScenarioData { - public IEnumerable> Conditions => new[] + public IDictionary Conditions => new Dictionary { - new Condition(ConditionTypes.NumberOfKings, 1), - new Condition(ConditionTypes.NumberOfQueens, 1 ), - new Condition(ConditionTypes.NumberOfJacks, 1), - new Condition(ConditionTypes.NumberOfTens, 1 ), - new Condition(ConditionTypes.NumberOfNines, 1 ), - new Condition(ConditionTypes.KingOfClubs, true ), - new Condition(ConditionTypes.QueenOfDiamonds, true ), - new Condition(ConditionTypes.JackOfClubs, true ), - new Condition(ConditionTypes.TenOfHearts, true ), - new Condition(ConditionTypes.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"); - public IEnumerable> Rules => this.GetRules(); + public IEnumerable> Rules => this.GetRules(); - private IEnumerable> GetRules() + private IEnumerable> GetRules() { // Does not consider the double pairs and full house combinations, as they would imply a // combinatorial explosion. For the purpose of the benchmark, scenario already simulates diff --git a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/ScenarioLoader.cs b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/ScenarioLoader.cs index df2e64a2..ddec42b8 100644 --- a/tests/Rules.Framework.IntegrationTests.Common/Scenarios/ScenarioLoader.cs +++ b/tests/Rules.Framework.IntegrationTests.Common/Scenarios/ScenarioLoader.cs @@ -4,10 +4,16 @@ namespace Rules.Framework.IntegrationTests.Common.Scenarios public static class ScenarioLoader { - public static async Task LoadScenarioAsync( - RulesEngine rulesEngine, - IScenarioData scenarioData) + public static async Task LoadScenarioAsync( + IRulesEngine rulesEngine, + IScenarioData scenarioData) { + var rulesets = scenarioData.Rules.Select(r => ((Rule)r).Ruleset).Distinct().ToArray(); + foreach (var ruleset in rulesets) + { + await rulesEngine.CreateRulesetAsync(ruleset).ConfigureAwait(false); + } + foreach (var rule in scenarioData.Rules) { await rulesEngine.AddRuleAsync(rule, RuleAddPriorityOption.AtTop).ConfigureAwait(false); diff --git a/tests/Rules.Framework.IntegrationTests/DataSource/ConditionNodeDataModel.cs b/tests/Rules.Framework.IntegrationTests/DataSource/ConditionNodeDataModel.cs index 50e57b0c..67391b88 100644 --- a/tests/Rules.Framework.IntegrationTests/DataSource/ConditionNodeDataModel.cs +++ b/tests/Rules.Framework.IntegrationTests/DataSource/ConditionNodeDataModel.cs @@ -6,7 +6,7 @@ internal class ConditionNodeDataModel { public IEnumerable ChildConditionNodes { get; set; } - public string ConditionType { get; set; } + public string Condition { get; set; } public string DataType { get; set; } diff --git a/tests/Rules.Framework.IntegrationTests/DataSource/RuleDataModel.cs b/tests/Rules.Framework.IntegrationTests/DataSource/RuleDataModel.cs index 67808701..2f30063c 100644 --- a/tests/Rules.Framework.IntegrationTests/DataSource/RuleDataModel.cs +++ b/tests/Rules.Framework.IntegrationTests/DataSource/RuleDataModel.cs @@ -5,16 +5,11 @@ namespace Rules.Framework.IntegrationTests.DataSource internal class RuleDataModel { public string Content { get; set; } - public short ContentTypeCode { get; set; } - public DateTime DateBegin { get; set; } - public DateTime? DateEnd { get; set; } - public string Name { get; set; } - public int Priority { get; set; } - public ConditionNodeDataModel RootCondition { get; set; } + public string Ruleset { get; set; } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.IntegrationTests/JsonContentSerializationProvider.cs b/tests/Rules.Framework.IntegrationTests/JsonContentSerializationProvider.cs index 44dfdc6b..52897fbd 100644 --- a/tests/Rules.Framework.IntegrationTests/JsonContentSerializationProvider.cs +++ b/tests/Rules.Framework.IntegrationTests/JsonContentSerializationProvider.cs @@ -2,9 +2,9 @@ namespace Rules.Framework.IntegrationTests { using Rules.Framework.Serialization; - internal class JsonContentSerializationProvider : IContentSerializationProvider + internal class JsonContentSerializationProvider : IContentSerializationProvider { - public IContentSerializer GetContentSerializer(TContentType contentType) + public IContentSerializer GetContentSerializer(string contentType) { return new JsonContentSerializer(); } diff --git a/tests/Rules.Framework.IntegrationTests/RulesFromJsonFile.cs b/tests/Rules.Framework.IntegrationTests/RulesFromJsonFile.cs index 8a567d1f..a90725cd 100644 --- a/tests/Rules.Framework.IntegrationTests/RulesFromJsonFile.cs +++ b/tests/Rules.Framework.IntegrationTests/RulesFromJsonFile.cs @@ -6,8 +6,9 @@ namespace Rules.Framework.IntegrationTests using System.IO; using System.Threading.Tasks; using Newtonsoft.Json; - using Rules.Framework.Builder; - using Rules.Framework.Core; + using Rules.Framework; + using Rules.Framework.Builder.Generic.RulesBuilder; + using Rules.Framework.Generic; using Rules.Framework.IntegrationTests.DataSource; internal class RulesFromJsonFile @@ -16,26 +17,23 @@ internal class RulesFromJsonFile public static RulesFromJsonFile Load => instance; - public async Task FromJsonFileAsync(RulesEngine rulesEngine, string filePath, Type contentRuntimeType, bool serializedContent = true) - where TContentType : new() + public async Task FromJsonFileAsync(IRulesEngine rulesEngine, string filePath, Type contentRuntimeType, bool serializedContent = true) + where TRuleset : new() { using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read)) using (var streamReader = new StreamReader(fileStream)) { var contents = await streamReader.ReadToEndAsync(); var ruleDataModels = JsonConvert.DeserializeObject>(contents); + var addedContentTypes = new HashSet(); foreach (var ruleDataModel in ruleDataModels) { - var contentType = GetContentType(ruleDataModel.ContentTypeCode); - - var ruleBuilder = RuleBuilder.NewRule() - .WithName(ruleDataModel.Name) - .WithDatesInterval(ruleDataModel.DateBegin, ruleDataModel.DateEnd); - - if (ruleDataModel.RootCondition is { }) + var contentType = GetRuleset(ruleDataModel.Ruleset); + if (!addedContentTypes.Contains(contentType)) { - ruleBuilder.WithCondition(b => this.ConvertConditionNode(b, ruleDataModel.RootCondition)); + await rulesEngine.CreateRulesetAsync(contentType); + addedContentTypes.Add(contentType); } object content; @@ -48,7 +46,16 @@ public async Task FromJsonFileAsync(RulesEngine(ruleDataModel.Name) + .InRuleset(contentType) + .SetContent(content) + .Since(ruleDataModel.DateBegin) + .Until(ruleDataModel.DateEnd); + + if (ruleDataModel.RootCondition is { }) + { + ruleBuilder.ApplyWhen(b => this.ConvertConditionNode(b, ruleDataModel.RootCondition)); + } var ruleBuilderResult = ruleBuilder.Build(); if (ruleBuilderResult.IsSuccess) @@ -64,82 +71,82 @@ public async Task FromJsonFileAsync(RulesEngine CreateValueConditionNode(IFluentComposedConditionNodeBuilder conditionNodeBuilder, ConditionNodeDataModel conditionNodeDataModel) + private static IFluentConditionNodeBuilder CreateValueConditionNode(IFluentConditionNodeBuilder conditionNodeBuilder, ConditionNodeDataModel conditionNodeDataModel) { var dataType = RulesFromJsonFile.Parse(conditionNodeDataModel.DataType); - var integrationTestsConditionType = RulesFromJsonFile.Parse(conditionNodeDataModel.ConditionType); + var condition = RulesFromJsonFile.Parse(conditionNodeDataModel.Condition); var @operator = RulesFromJsonFile.Parse(conditionNodeDataModel.Operator); switch (dataType) { case DataTypes.Integer: return conditionNodeBuilder.Value( - integrationTestsConditionType, + condition, @operator, Convert.ToInt32(conditionNodeDataModel.Operand, CultureInfo.InvariantCulture)); case DataTypes.Decimal: return conditionNodeBuilder.Value( - integrationTestsConditionType, + condition, @operator, Convert.ToDecimal(conditionNodeDataModel.Operand, CultureInfo.InvariantCulture)); case DataTypes.String: return conditionNodeBuilder.Value( - integrationTestsConditionType, + condition, @operator, conditionNodeDataModel.Operand); case DataTypes.Boolean: return conditionNodeBuilder.Value( - integrationTestsConditionType, + condition, @operator, Convert.ToBoolean(conditionNodeDataModel.Operand, CultureInfo.InvariantCulture)); default: - throw new NotSupportedException($"Unsupported data type: {dataType.ToString()}."); + throw new NotSupportedException($"Unsupported data type: {dataType}."); } } - private static IConditionNode CreateValueConditionNode(IRootConditionNodeBuilder conditionNodeBuilder, ConditionNodeDataModel conditionNodeDataModel) + private static IConditionNode CreateValueConditionNode(IRootConditionNodeBuilder conditionNodeBuilder, ConditionNodeDataModel conditionNodeDataModel) { var dataType = RulesFromJsonFile.Parse(conditionNodeDataModel.DataType); - var integrationTestsConditionType = RulesFromJsonFile.Parse(conditionNodeDataModel.ConditionType); + var condition = RulesFromJsonFile.Parse(conditionNodeDataModel.Condition); var @operator = RulesFromJsonFile.Parse(conditionNodeDataModel.Operator); switch (dataType) { case DataTypes.Integer: return conditionNodeBuilder.Value( - integrationTestsConditionType, + condition, @operator, Convert.ToInt32(conditionNodeDataModel.Operand, CultureInfo.InvariantCulture)); case DataTypes.Decimal: return conditionNodeBuilder.Value( - integrationTestsConditionType, + condition, @operator, Convert.ToDecimal(conditionNodeDataModel.Operand, CultureInfo.InvariantCulture)); case DataTypes.String: return conditionNodeBuilder.Value( - integrationTestsConditionType, + condition, @operator, conditionNodeDataModel.Operand); case DataTypes.Boolean: return conditionNodeBuilder.Value( - integrationTestsConditionType, + condition, @operator, Convert.ToBoolean(conditionNodeDataModel.Operand, CultureInfo.InvariantCulture)); default: - throw new NotSupportedException($"Unsupported data type: {dataType.ToString()}."); + throw new NotSupportedException($"Unsupported data type: {dataType}."); } } - private static TContentType GetContentType(short contentTypeCode) where TContentType : new() - => RulesFromJsonFile.Parse(contentTypeCode.ToString()); + private static TRuleset GetRuleset(string ruleset) where TRuleset : new() + => RulesFromJsonFile.Parse(ruleset); private static T Parse(string value) => (T)Parse(value, typeof(T)); @@ -147,7 +154,7 @@ private static T Parse(string value) private static object Parse(string value, Type type) => type.IsEnum ? Enum.Parse(type, value) : Convert.ChangeType(value, type, CultureInfo.InvariantCulture); - private IConditionNode ConvertConditionNode(IRootConditionNodeBuilder conditionNodeBuilder, ConditionNodeDataModel conditionNodeDataModel) + private IConditionNode ConvertConditionNode(IRootConditionNodeBuilder conditionNodeBuilder, ConditionNodeDataModel conditionNodeDataModel) { var logicalOperator = RulesFromJsonFile.Parse(conditionNodeDataModel.LogicalOperator); @@ -167,7 +174,7 @@ private IConditionNode ConvertConditionNode(IRoo } } - private IFluentComposedConditionNodeBuilder ConvertConditionNode(IFluentComposedConditionNodeBuilder conditionNodeBuilder, ConditionNodeDataModel conditionNodeDataModel) + private IFluentConditionNodeBuilder ConvertConditionNode(IFluentConditionNodeBuilder conditionNodeBuilder, ConditionNodeDataModel conditionNodeDataModel) { var logicalOperator = RulesFromJsonFile.Parse(conditionNodeDataModel.LogicalOperator); @@ -187,7 +194,7 @@ private IFluentComposedConditionNodeBuilder ConvertConditionNode } } - private IFluentComposedConditionNodeBuilder HandleChildConditionNodes(IFluentComposedConditionNodeBuilder conditionNodeBuilder, ConditionNodeDataModel conditionNodeDataModel) + private IFluentConditionNodeBuilder HandleChildConditionNodes(IFluentConditionNodeBuilder conditionNodeBuilder, ConditionNodeDataModel conditionNodeDataModel) { foreach (var child in conditionNodeDataModel.ChildConditionNodes) { diff --git a/tests/Rules.Framework.IntegrationTests/Scenarios/Scenario1/BodyMassIndexTests.cs b/tests/Rules.Framework.IntegrationTests/Scenarios/Scenario1/BodyMassIndexTests.cs index 3a8fc253..4935dde7 100644 --- a/tests/Rules.Framework.IntegrationTests/Scenarios/Scenario1/BodyMassIndexTests.cs +++ b/tests/Rules.Framework.IntegrationTests/Scenarios/Scenario1/BodyMassIndexTests.cs @@ -1,15 +1,15 @@ namespace Rules.Framework.IntegrationTests.Scenarios.Scenario1 { using System; + using System.Collections.Generic; using System.Threading.Tasks; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.IntegrationTests.Common.Scenarios.Scenario1; using Rules.Framework.Providers.InMemory; using Xunit; - [System.Runtime.InteropServices.Guid("309D98C5-4007-4116-92B1-9FEAD18B9DC3")] public class BodyMassIndexTests { private static string DataSourceFilePath => $@"{Environment.CurrentDirectory}/Scenarios/Scenario1/rules-framework-tests.body-mass-index.datasource.json"; @@ -21,49 +21,50 @@ public async Task AddRule_AddingNewRuleFromScratchWithAgeConditionAtPriority1And { // Arrange var serviceProvider = new ServiceCollection() - .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) + .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) .BuildServiceProvider(); var rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetInMemoryDataSource(serviceProvider) .Configure(options => { options.EnableCompilation = enableCompilation; }) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); - var newRuleResult1 = RuleBuilder.NewRule() - .WithName("Body Mass Index up to 18 years formula") - .WithDateBegin(DateTime.Parse("2018-01-01")) - .WithContent(ContentTypes.BodyMassIndexFormula, new Formula + await genericRulesEngine.CreateRulesetAsync(Scenario1RulesetNames.BodyMassIndexFormula); + + var newRuleResult1 = Rule.Create("Body Mass Index up to 18 years formula") + .InRuleset(Scenario1RulesetNames.BodyMassIndexFormula) + .SetContent(new Formula { Description = "Body Mass Index up to 18 years formula", Value = "weight / ((height + 1) ^ 2)" // Not real, for the sake of the test. }) - .WithCondition(ConditionTypes.Age, Operators.LesserThanOrEqual, 18) + .Since(DateTime.Parse("2018-01-01")) + .ApplyWhen(Scenario1ConditionNames.Age, Operators.LesserThanOrEqual, 18) .Build(); var newRule1 = newRuleResult1.Rule; var ruleAddPriorityOption1 = RuleAddPriorityOption.ByPriorityNumber(1); - var ruleBuilderResult2 = RuleBuilder.NewRule() - .WithName("Sample rule") - .WithDateBegin(DateTime.Parse("2021-01-01")) - .WithContent(ContentTypes.BodyMassIndexFormula, new Formula + var ruleBuilderResult2 = Rule.Create("Sample rule") + .InRuleset(Scenario1RulesetNames.BodyMassIndexFormula) + .SetContent(new Formula { Description = "Sample formula", Value = "0" }) + .Since(DateTime.Parse("2021-01-01")) .Build(); var newRule2 = ruleBuilderResult2.Rule; var ruleAddPriorityOption2 = RuleAddPriorityOption.ByPriorityNumber(4); // Act - var ruleOperationResult1 = await rulesEngine.AddRuleAsync(newRule1, ruleAddPriorityOption1).ConfigureAwait(false); - var ruleOperationResult2 = await rulesEngine.AddRuleAsync(newRule2, ruleAddPriorityOption2).ConfigureAwait(false); + var ruleOperationResult1 = await genericRulesEngine.AddRuleAsync(newRule1, ruleAddPriorityOption1); + var ruleOperationResult2 = await genericRulesEngine.AddRuleAsync(newRule2, ruleAddPriorityOption2); // Assert ruleOperationResult1.Should().NotBeNull(); @@ -72,11 +73,11 @@ public async Task AddRule_AddingNewRuleFromScratchWithAgeConditionAtPriority1And ruleOperationResult2.Should().NotBeNull(); ruleOperationResult2.IsSuccess.Should().BeTrue(); - var inMemoryRulesStorage = serviceProvider.GetService>(); + var inMemoryRulesStorage = serviceProvider.GetService(); var rulesDataSource = CreateRulesDataSource(inMemoryRulesStorage); - var rules = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs()).ConfigureAwait(false); + var rules = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs()); rules.Should().NotBeNull().And.HaveCount(2); - rules.Should().ContainEquivalentOf(newRule1); + rules.Should().ContainEquivalentOf((Rule)newRule1); newRule1.Priority.Should().Be(1, "rule should to priority 1 if inserted at priority 1"); newRule2.Priority.Should().Be(2, "rule should have priority 2 if inserted at priority 2, given that last rule after insert was at priority 2."); } @@ -88,52 +89,51 @@ public async Task AddRule_AddingNewRuleWithAgeConditionAtPriority1AndNewRuleAtPr { // Arrange var serviceProvider = new ServiceCollection() - .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) + .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) .BuildServiceProvider(); var rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetInMemoryDataSource(serviceProvider) .Configure(options => { options.EnableCompilation = enableCompilation; }) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); await RulesFromJsonFile.Load - .FromJsonFileAsync(rulesEngine, DataSourceFilePath, typeof(Formula)); + .FromJsonFileAsync(genericRulesEngine, DataSourceFilePath, typeof(Formula)); - var newRuleResult1 = RuleBuilder.NewRule() - .WithName("Body Mass Index up to 18 years formula") - .WithDateBegin(DateTime.Parse("2018-01-01")) - .WithContent(ContentTypes.BodyMassIndexFormula, new Formula + var newRuleResult1 = Rule.Create("Body Mass Index up to 18 years formula") + .InRuleset(Scenario1RulesetNames.BodyMassIndexFormula) + .SetContent(new Formula { Description = "Body Mass Index up to 18 years formula", - Value = "weight / ((height + 1) ^ 2)" // Not real, for the sake of the test. + Value = "weight / ((height + 1) ^ 2)", }) - .WithCondition(ConditionTypes.Age, Operators.LesserThanOrEqual, 18) + .Since(DateTime.Parse("2018-01-01")) + .ApplyWhen(Scenario1ConditionNames.Age, Operators.LesserThanOrEqual, 18) .Build(); var newRule1 = newRuleResult1.Rule; var ruleAddPriorityOption1 = RuleAddPriorityOption.ByPriorityNumber(1); - var ruleBuilderResult2 = RuleBuilder.NewRule() - .WithName("Sample rule") - .WithDateBegin(DateTime.Parse("2021-01-01")) - .WithContent(ContentTypes.BodyMassIndexFormula, new Formula + var ruleBuilderResult2 = Rule.Create("Sample rule") + .InRuleset(Scenario1RulesetNames.BodyMassIndexFormula) + .SetContent(new Formula { Description = "Sample formula", - Value = "0" + Value = "0", }) + .Since(DateTime.Parse("2021-01-01")) .Build(); var newRule2 = ruleBuilderResult2.Rule; var ruleAddPriorityOption2 = RuleAddPriorityOption.ByPriorityNumber(4); // Act - var ruleOperationResult1 = await rulesEngine.AddRuleAsync(newRule1, ruleAddPriorityOption1).ConfigureAwait(false); - var ruleOperationResult2 = await rulesEngine.AddRuleAsync(newRule2, ruleAddPriorityOption2).ConfigureAwait(false); + var ruleOperationResult1 = await genericRulesEngine.AddRuleAsync(newRule1, ruleAddPriorityOption1); + var ruleOperationResult2 = await genericRulesEngine.AddRuleAsync(newRule2, ruleAddPriorityOption2); // Assert ruleOperationResult1.Should().NotBeNull(); @@ -142,11 +142,11 @@ await RulesFromJsonFile.Load ruleOperationResult2.Should().NotBeNull(); ruleOperationResult2.IsSuccess.Should().BeTrue(); - var inMemoryRulesStorage = serviceProvider.GetService>(); + var inMemoryRulesStorage = serviceProvider.GetService(); var rulesDataSource = CreateRulesDataSource(inMemoryRulesStorage); - var rules = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs()).ConfigureAwait(false); + var rules = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs()); rules.Should().NotBeNull().And.HaveCount(3); - rules.Should().ContainEquivalentOf(newRule1); + rules.Should().ContainEquivalentOf((Rule)newRule1); newRule1.Priority.Should().Be(1, "rule should to priority 1 if inserted at priority 1"); newRule2.Priority.Should().Be(3, "rule should have priority 3 if inserted at priority 3, given that last rule after insert was at priority 2."); } @@ -158,31 +158,30 @@ public async Task AddRule_AddingNewRuleWithAgeConditionOnTop_NewRuleIsInsertedAn { // Arrange var serviceProvider = new ServiceCollection() - .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) + .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) .BuildServiceProvider(); var rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetInMemoryDataSource(serviceProvider) .Configure(options => { options.EnableCompilation = enableCompilation; }) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); await RulesFromJsonFile.Load - .FromJsonFileAsync(rulesEngine, DataSourceFilePath, typeof(Formula)); + .FromJsonFileAsync(genericRulesEngine, DataSourceFilePath, typeof(Formula)); - var newRuleResult = RuleBuilder.NewRule() - .WithName("Body Mass Index up to 18 years formula") - .WithDateBegin(DateTime.Parse("2018-01-01")) - .WithContent(ContentTypes.BodyMassIndexFormula, new Formula + var newRuleResult = Rule.Create("Body Mass Index up to 18 years formula") + .InRuleset(Scenario1RulesetNames.BodyMassIndexFormula) + .SetContent(new Formula { Description = "Body Mass Index up to 18 years formula", - Value = "weight / ((height + 1) ^ 2)" // Not real, for the sake of the test. + Value = "weight / ((height + 1) ^ 2)", }) - .WithCondition(ConditionTypes.Age, Operators.LesserThanOrEqual, 18) + .Since(DateTime.Parse("2018-01-01")) + .ApplyWhen(Scenario1ConditionNames.Age, Operators.LesserThanOrEqual, 18) .Build(); var newRule = newRuleResult.Rule; @@ -192,17 +191,17 @@ await RulesFromJsonFile.Load }; // Act - var ruleOperationResult = await rulesEngine.AddRuleAsync(newRule, ruleAddPriorityOption).ConfigureAwait(false); + var ruleOperationResult = await genericRulesEngine.AddRuleAsync(newRule, ruleAddPriorityOption); // Assert ruleOperationResult.Should().NotBeNull(); ruleOperationResult.IsSuccess.Should().BeTrue(); - var inMemoryRulesStorage = serviceProvider.GetService>(); + var inMemoryRulesStorage = serviceProvider.GetService(); var rulesDataSource = CreateRulesDataSource(inMemoryRulesStorage); - var rules = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs()).ConfigureAwait(false); + var rules = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs()); rules.Should().NotBeNull().And.HaveCount(2); - rules.Should().ContainEquivalentOf(newRule, o => o.Excluding(x => x.RootCondition.Properties)); + rules.Should().ContainEquivalentOf((Rule)newRule, o => o.Excluding(x => x.RootCondition.Properties)); newRule.Priority.Should().Be(1, "rule should to priority 1 if inserted at top."); } @@ -212,31 +211,30 @@ await RulesFromJsonFile.Load public async Task BodyMassIndex_NoConditions_ReturnsDefaultFormula(bool enableCompilation) { // Arrange - string expectedFormulaDescription = "Body Mass Index default formula"; - string expectedFormulaValue = "weight / (height ^ 2)"; - const ContentTypes expectedContent = ContentTypes.BodyMassIndexFormula; + var expectedFormulaDescription = "Body Mass Index default formula"; + var expectedFormulaValue = "weight / (height ^ 2)"; + const Scenario1RulesetNames expectedContent = Scenario1RulesetNames.BodyMassIndexFormula; var expectedMatchDate = new DateTime(2018, 06, 01); - var expectedConditions = new Condition[0]; + var expectedConditions = new Dictionary(); var serviceProvider = new ServiceCollection() - .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) + .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) .BuildServiceProvider(); var rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetInMemoryDataSource(serviceProvider) .Configure(options => { options.EnableCompilation = enableCompilation; }) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); await RulesFromJsonFile.Load - .FromJsonFileAsync(rulesEngine, DataSourceFilePath, typeof(Formula)); + .FromJsonFileAsync(genericRulesEngine, DataSourceFilePath, typeof(Formula)); // Act - var actual = await rulesEngine.MatchOneAsync(expectedContent, expectedMatchDate, expectedConditions); + var actual = await genericRulesEngine.MatchOneAsync(expectedContent, expectedMatchDate, expectedConditions); // Assert actual.Should().NotBeNull(); @@ -245,10 +243,10 @@ await RulesFromJsonFile.Load actualFormula.Value.Should().Be(expectedFormulaValue); } - private static IRulesDataSource CreateRulesDataSource(IInMemoryRulesStorage inMemoryRulesStorage) + private static InMemoryProviderRulesDataSource CreateRulesDataSource(IInMemoryRulesStorage inMemoryRulesStorage) { - var ruleFactory = new RuleFactory(); - return new InMemoryProviderRulesDataSource(inMemoryRulesStorage, ruleFactory); + var ruleFactory = new RuleFactory(); + return new InMemoryProviderRulesDataSource(inMemoryRulesStorage, ruleFactory); } } } \ No newline at end of file diff --git a/tests/Rules.Framework.IntegrationTests/Scenarios/Scenario2/CarInsuranceAdvisorTests.cs b/tests/Rules.Framework.IntegrationTests/Scenarios/Scenario2/CarInsuranceAdvisorTests.cs index 5107d60f..5b6bf362 100644 --- a/tests/Rules.Framework.IntegrationTests/Scenarios/Scenario2/CarInsuranceAdvisorTests.cs +++ b/tests/Rules.Framework.IntegrationTests/Scenarios/Scenario2/CarInsuranceAdvisorTests.cs @@ -1,11 +1,12 @@ namespace Rules.Framework.IntegrationTests.Scenarios.Scenario2 { using System; + using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.IntegrationTests.Common.Scenarios.Scenario2; using Rules.Framework.Providers.InMemory; using Xunit; @@ -21,22 +22,20 @@ public async Task GetCarInsuranceAdvice_ClaimDescriptionContionsAlcoholOrDrugs_R { // Arrange var expected = CarInsuranceAdvices.PerformInvestigation; - const ContentTypes expectedContent = ContentTypes.CarInsuranceAdvice; + const CarInsuranceRulesetNames expectedRuleset = CarInsuranceRulesetNames.CarInsuranceAdvice; var expectedMatchDate = new DateTime(2020, 06, 01); - var expectedConditions = new[] + var expectedConditions = new Dictionary { - new Condition(ConditionTypes.RepairCosts,800.00000m), - new Condition(ConditionTypes.RepairCostsCommercialValueRate,23.45602m), - new Condition(ConditionTypes.ClaimDescription,"Driver A claims that Driver B appeared to be under the effect of alcohol.") + { CarInsuranceConditionNames.RepairCosts, 800.00000m }, + { CarInsuranceConditionNames.RepairCostsCommercialValueRate, 23.45602m }, + { CarInsuranceConditionNames.ClaimDescription, "Driver A claims that Driver B appeared to be under the effect of alcohol." }, }; var serviceProvider = new ServiceCollection() - .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) + .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) .BuildServiceProvider(); var rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetInMemoryDataSource(serviceProvider) .Configure(opt => { @@ -44,25 +43,27 @@ public async Task GetCarInsuranceAdvice_ClaimDescriptionContionsAlcoholOrDrugs_R opt.EnableCompilation = enableCompilation; }) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); await RulesFromJsonFile.Load - .FromJsonFileAsync(rulesEngine, DataSourceFilePath, typeof(CarInsuranceAdvices), serializedContent: false); + .FromJsonFileAsync(genericRulesEngine, DataSourceFilePath, typeof(CarInsuranceAdvices), serializedContent: false); - var ruleBuilderResult = RuleBuilder.NewRule() - .WithName("Car Insurance Advise on on accident under the effect of drugs or alcohol") - .WithDateBegin(DateTime.Parse("2020-01-01")) - .WithCondition(c => c + var ruleBuilderResult = Rule.Create("Car Insurance Advise on on accident under the effect of drugs or alcohol") + .InRuleset(expectedRuleset) + .SetContent(CarInsuranceAdvices.PerformInvestigation) + .Since(DateTime.Parse("2020-01-01")) + .ApplyWhen(c => c .Or(o => o - .Value(ConditionTypes.ClaimDescription, Operators.Contains, "alcohol") - .Value(ConditionTypes.ClaimDescription, Operators.Contains, "drugs") - )) - .WithContent(expectedContent, CarInsuranceAdvices.PerformInvestigation) + .Value(CarInsuranceConditionNames.ClaimDescription, Operators.Contains, "alcohol") + .Value(CarInsuranceConditionNames.ClaimDescription, Operators.Contains, "drugs") + ) + ) .Build(); // Act - await rulesEngine.AddRuleAsync(ruleBuilderResult.Rule, RuleAddPriorityOption.AtBottom).ConfigureAwait(false); + await genericRulesEngine.AddRuleAsync(ruleBuilderResult.Rule, RuleAddPriorityOption.AtBottom); - var actual = await rulesEngine.MatchOneAsync(expectedContent, expectedMatchDate, expectedConditions); + var actual = await genericRulesEngine.MatchOneAsync(expectedRuleset, expectedMatchDate, expectedConditions); // Assert actual.Should().NotBeNull(); @@ -77,21 +78,19 @@ public async Task GetCarInsuranceAdvice_RepairCostsNotWorthIt_ReturnsRefusePayme { // Arrange var expected = CarInsuranceAdvices.RefusePaymentPerFranchise; - const ContentTypes expectedContent = ContentTypes.CarInsuranceAdvice; + const CarInsuranceRulesetNames expectedContent = CarInsuranceRulesetNames.CarInsuranceAdvice; var expectedMatchDate = new DateTime(2018, 06, 01); - var expectedConditions = new[] + var expectedConditions = new Dictionary { - new Condition(ConditionTypes.RepairCosts,800.00000m), - new Condition(ConditionTypes.RepairCostsCommercialValueRate,23.45602m) + { CarInsuranceConditionNames.RepairCosts,800.00000m }, + { CarInsuranceConditionNames.RepairCostsCommercialValueRate,23.45602m }, }; var serviceProvider = new ServiceCollection() - .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) + .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) .BuildServiceProvider(); var rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetInMemoryDataSource(serviceProvider) .Configure(opt => { @@ -99,12 +98,13 @@ public async Task GetCarInsuranceAdvice_RepairCostsNotWorthIt_ReturnsRefusePayme opt.EnableCompilation = enableCompilation; }) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); await RulesFromJsonFile.Load - .FromJsonFileAsync(rulesEngine, DataSourceFilePath, typeof(CarInsuranceAdvices), serializedContent: false); + .FromJsonFileAsync(genericRulesEngine, DataSourceFilePath, typeof(CarInsuranceAdvices), serializedContent: false); // Act - var actual = await rulesEngine.MatchOneAsync(expectedContent, expectedMatchDate, expectedConditions); + var actual = await genericRulesEngine.MatchOneAsync(expectedContent, expectedMatchDate, expectedConditions); // Assert actual.Should().NotBeNull(); @@ -118,25 +118,23 @@ await RulesFromJsonFile.Load public async Task GetCarInsuranceAdvice_SearchForRulesExcludingRulesWithoutSearchConditions_ReturnsNoRules(bool enableCompilation) { // Arrange - const ContentTypes expectedContent = ContentTypes.CarInsuranceAdvice; + const CarInsuranceRulesetNames expectedContent = CarInsuranceRulesetNames.CarInsuranceAdvice; var expectedMatchDate = new DateTime(2018, 06, 01); - var searchArgs = new SearchArgs(expectedContent, expectedMatchDate, expectedMatchDate) + var searchArgs = new SearchArgs(expectedContent, expectedMatchDate, expectedMatchDate) { - Conditions = new[] + Conditions = new Dictionary { - new Condition(ConditionTypes.RepairCosts, 800.00000m), - new Condition(ConditionTypes.RepairCostsCommercialValueRate, 86.33m) + { CarInsuranceConditionNames.RepairCosts, 800.00000m }, + { CarInsuranceConditionNames.RepairCostsCommercialValueRate, 86.33m }, }, - ExcludeRulesWithoutSearchConditions = true + ExcludeRulesWithoutSearchConditions = true, }; var serviceProvider = new ServiceCollection() - .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) + .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) .BuildServiceProvider(); var rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetInMemoryDataSource(serviceProvider) .Configure(opt => { @@ -144,12 +142,13 @@ public async Task GetCarInsuranceAdvice_SearchForRulesExcludingRulesWithoutSearc opt.EnableCompilation = enableCompilation; }) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); await RulesFromJsonFile.Load - .FromJsonFileAsync(rulesEngine, DataSourceFilePath, typeof(CarInsuranceAdvices), serializedContent: false); + .FromJsonFileAsync(genericRulesEngine, DataSourceFilePath, typeof(CarInsuranceAdvices), serializedContent: false); // Act - var actual = await rulesEngine.SearchAsync(searchArgs); + var actual = await genericRulesEngine.SearchAsync(searchArgs); // Assert actual.Should().NotBeNull(); @@ -162,24 +161,22 @@ await RulesFromJsonFile.Load public async Task GetCarInsuranceAdvice_SearchForRulesWithRepairCostsGreaterThan1000_Returns2Rules(bool enableCompilation) { // Arrange - const ContentTypes expectedContent = ContentTypes.CarInsuranceAdvice; + const CarInsuranceRulesetNames expectedContent = CarInsuranceRulesetNames.CarInsuranceAdvice; var expectedMatchDate = new DateTime(2018, 06, 01); - var searchArgs = new SearchArgs(expectedContent, expectedMatchDate, expectedMatchDate) + var searchArgs = new SearchArgs(expectedContent, expectedMatchDate, expectedMatchDate) { - Conditions = new[] + Conditions = new Dictionary { - new Condition(ConditionTypes.RepairCosts, 1200.00000m) + { CarInsuranceConditionNames.RepairCosts, 1200.00000m }, }, - ExcludeRulesWithoutSearchConditions = false + ExcludeRulesWithoutSearchConditions = false, }; var serviceProvider = new ServiceCollection() - .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) + .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) .BuildServiceProvider(); var rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetInMemoryDataSource(serviceProvider) .Configure(opt => { @@ -187,12 +184,13 @@ public async Task GetCarInsuranceAdvice_SearchForRulesWithRepairCostsGreaterThan opt.EnableCompilation = enableCompilation; }) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); await RulesFromJsonFile.Load - .FromJsonFileAsync(rulesEngine, DataSourceFilePath, typeof(CarInsuranceAdvices), serializedContent: false); + .FromJsonFileAsync(genericRulesEngine, DataSourceFilePath, typeof(CarInsuranceAdvices), serializedContent: false); // Act - var actual = await rulesEngine.SearchAsync(searchArgs); + var actual = await genericRulesEngine.SearchAsync(searchArgs); // Assert actual.Should().NotBeNull(); @@ -207,21 +205,19 @@ await RulesFromJsonFile.Load public async Task GetCarInsuranceAdvice_UpdatesRuleAndAddsNewOneAndEvaluates_ReturnsPay(bool enableCompilation) { // Arrange - const ContentTypes expectedContent = ContentTypes.CarInsuranceAdvice; + const CarInsuranceRulesetNames expectedContent = CarInsuranceRulesetNames.CarInsuranceAdvice; var expectedMatchDate = new DateTime(2018, 06, 01); - var expectedConditions = new[] + var expectedConditions = new Dictionary { - new Condition(ConditionTypes.RepairCosts,800.00000m), - new Condition(ConditionTypes.RepairCostsCommercialValueRate,23.45602m) + { CarInsuranceConditionNames.RepairCosts,800.00000m }, + { CarInsuranceConditionNames.RepairCostsCommercialValueRate,23.45602m }, }; var serviceProvider = new ServiceCollection() - .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) + .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) .BuildServiceProvider(); var rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetInMemoryDataSource(serviceProvider) .Configure(opt => { @@ -229,20 +225,20 @@ public async Task GetCarInsuranceAdvice_UpdatesRuleAndAddsNewOneAndEvaluates_Ret opt.EnableCompilation = enableCompilation; }) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); await RulesFromJsonFile.Load - .FromJsonFileAsync(rulesEngine, DataSourceFilePath, typeof(CarInsuranceAdvices), serializedContent: false); + .FromJsonFileAsync(genericRulesEngine, DataSourceFilePath, typeof(CarInsuranceAdvices), serializedContent: false); - var ruleBuilderResult = RuleBuilder - .NewRule() - .WithName("Car Insurance Advise on self damage coverage") - .WithDateBegin(DateTime.Parse("2018-01-01")) - .WithContent(ContentTypes.CarInsuranceAdvice, CarInsuranceAdvices.Pay) + var ruleBuilderResult = Rule.Create("Car Insurance Advise on self damage coverage") + .InRuleset(expectedContent) + .SetContent(CarInsuranceAdvices.Pay) + .Since(DateTime.Parse("2018-01-01")) .Build(); - var inMemoryRulesStorage = serviceProvider.GetService>(); + var inMemoryRulesStorage = serviceProvider.GetService(); var rulesDataSource = CreateRulesDataSource(inMemoryRulesStorage); - var existentRules1 = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs + var existentRules1 = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs { Name = "Car Insurance Advise on repair costs lower than franchise boundary" }); @@ -254,9 +250,9 @@ await RulesFromJsonFile.Load // Act 1 var updateOperationResult1 = await rulesEngine.UpdateRuleAsync(ruleToUpdate1); - var eval1 = await rulesEngine.MatchOneAsync(expectedContent, expectedMatchDate, expectedConditions); + var eval1 = await genericRulesEngine.MatchOneAsync(expectedContent, expectedMatchDate, expectedConditions); - var rules1 = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs()); + var rules1 = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs()); // Assert 1 updateOperationResult1.Should().NotBeNull(); @@ -279,15 +275,15 @@ await RulesFromJsonFile.Load rule13.Priority.Should().Be(3); // Act 2 - var addOperationResult = await rulesEngine.AddRuleAsync(ruleToAdd, new RuleAddPriorityOption + var addOperationResult = await genericRulesEngine.AddRuleAsync(ruleToAdd, new RuleAddPriorityOption { PriorityOption = PriorityOptions.AtRuleName, AtRuleNameOptionValue = "Car Insurance Advise on repair costs lower than franchise boundary" }); - var eval2 = await rulesEngine.MatchOneAsync(expectedContent, expectedMatchDate, expectedConditions).ConfigureAwait(false); + var eval2 = await genericRulesEngine.MatchOneAsync(expectedContent, expectedMatchDate, expectedConditions); - var rules2 = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs()); + var rules2 = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs()); // Assert 2 addOperationResult.Should().NotBeNull(); @@ -314,7 +310,7 @@ await RulesFromJsonFile.Load rule24.Priority.Should().Be(4); // Act 3 - var existentRules2 = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs + var existentRules2 = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs { Name = "Car Insurance Advise on repair costs lower than franchise boundary" }); @@ -324,9 +320,9 @@ await RulesFromJsonFile.Load var updateOperationResult2 = await rulesEngine.UpdateRuleAsync(ruleToUpdate2); - var eval3 = await rulesEngine.MatchOneAsync(expectedContent, expectedMatchDate, expectedConditions); + var eval3 = await genericRulesEngine.MatchOneAsync(expectedContent, expectedMatchDate, expectedConditions); - var rules3 = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs()); + var rules3 = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs()); // Assert 3 updateOperationResult2.Should().NotBeNull(); @@ -353,10 +349,10 @@ await RulesFromJsonFile.Load rule34.Priority.Should().Be(4); } - private static IRulesDataSource CreateRulesDataSource(IInMemoryRulesStorage inMemoryRulesStorage) + private static IRulesDataSource CreateRulesDataSource(IInMemoryRulesStorage inMemoryRulesStorage) { - var ruleFactory = new RuleFactory(); - return new InMemoryProviderRulesDataSource(inMemoryRulesStorage, ruleFactory); + var ruleFactory = new RuleFactory(); + return new InMemoryProviderRulesDataSource(inMemoryRulesStorage, ruleFactory); } } } \ No newline at end of file diff --git a/tests/Rules.Framework.IntegrationTests/Scenarios/Scenario3/BuildingSecuritySystemControlTests.cs b/tests/Rules.Framework.IntegrationTests/Scenarios/Scenario3/BuildingSecuritySystemControlTests.cs index cf01a8a8..78c92a69 100644 --- a/tests/Rules.Framework.IntegrationTests/Scenarios/Scenario3/BuildingSecuritySystemControlTests.cs +++ b/tests/Rules.Framework.IntegrationTests/Scenarios/Scenario3/BuildingSecuritySystemControlTests.cs @@ -1,6 +1,7 @@ namespace Rules.Framework.IntegrationTests.Scenarios.Scenario3 { using System; + using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using FluentAssertions; @@ -19,28 +20,27 @@ public async Task BuildingSecuritySystem_FireScenario_ReturnsActionsToTrigger() const SecuritySystemActionables securitySystemActionable = SecuritySystemActionables.FireSystem; var expectedMatchDate = new DateTime(2018, 06, 01); - var expectedConditions = new[] + var expectedConditions = new Dictionary { - new Condition(SecuritySystemConditions.TemperatureCelsius,100.0m), - new Condition(SecuritySystemConditions.SmokeRate,55), - new Condition(SecuritySystemConditions.PowerStatus,"Online") + { SecuritySystemConditions.TemperatureCelsius, 100.0m }, + { SecuritySystemConditions.SmokeRate, 55 }, + { SecuritySystemConditions.PowerStatus, "Online" }, }; var serviceProvider = new ServiceCollection() - .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) + .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) .BuildServiceProvider(); var rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetInMemoryDataSource(serviceProvider) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); await RulesFromJsonFile.Load - .FromJsonFileAsync(rulesEngine, DataSourceFilePath, typeof(SecuritySystemAction)); + .FromJsonFileAsync(genericRulesEngine, DataSourceFilePath, typeof(SecuritySystemAction)); // Act - var actual = await rulesEngine.MatchManyAsync(securitySystemActionable, expectedMatchDate, expectedConditions); + var actual = await genericRulesEngine.MatchManyAsync(securitySystemActionable, expectedMatchDate, expectedConditions); // Assert actual.Should().NotBeNull(); @@ -59,28 +59,27 @@ public async Task BuildingSecuritySystem_PowerFailureScenario_ReturnsActionsToTr const SecuritySystemActionables securitySystemActionable = SecuritySystemActionables.PowerSystem; var expectedMatchDate = new DateTime(2018, 06, 01); - var expectedConditions = new[] + var expectedConditions = new Dictionary { - new Condition(SecuritySystemConditions.TemperatureCelsius,100.0m), - new Condition(SecuritySystemConditions.SmokeRate,55), - new Condition(SecuritySystemConditions.PowerStatus,"Offline") + { SecuritySystemConditions.TemperatureCelsius, 100.0m }, + { SecuritySystemConditions.SmokeRate, 55 }, + { SecuritySystemConditions.PowerStatus, "Offline" }, }; var serviceProvider = new ServiceCollection() - .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) + .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) .BuildServiceProvider(); var rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetInMemoryDataSource(serviceProvider) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); await RulesFromJsonFile.Load - .FromJsonFileAsync(rulesEngine, DataSourceFilePath, typeof(SecuritySystemAction)); + .FromJsonFileAsync(genericRulesEngine, DataSourceFilePath, typeof(SecuritySystemAction)); // Act - var actual = await rulesEngine.MatchManyAsync(securitySystemActionable, expectedMatchDate, expectedConditions); + var actual = await genericRulesEngine.MatchManyAsync(securitySystemActionable, expectedMatchDate, expectedConditions); // Assert actual.Should().NotBeNull(); @@ -98,28 +97,27 @@ public async Task BuildingSecuritySystem_PowerShutdownScenario_ReturnsActionsToT const SecuritySystemActionables securitySystemActionable = SecuritySystemActionables.PowerSystem; var expectedMatchDate = new DateTime(2018, 06, 01); - var expectedConditions = new[] + var expectedConditions = new Dictionary { - new Condition(SecuritySystemConditions.TemperatureCelsius,100.0m), - new Condition(SecuritySystemConditions.SmokeRate,55), - new Condition(SecuritySystemConditions.PowerStatus,"Shutdown") + { SecuritySystemConditions.TemperatureCelsius,100.0m }, + { SecuritySystemConditions.SmokeRate,55 }, + { SecuritySystemConditions.PowerStatus,"Shutdown" }, }; var serviceProvider = new ServiceCollection() - .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) + .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) .BuildServiceProvider(); var rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetInMemoryDataSource(serviceProvider) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); await RulesFromJsonFile.Load - .FromJsonFileAsync(rulesEngine, DataSourceFilePath, typeof(SecuritySystemAction)); + .FromJsonFileAsync(genericRulesEngine, DataSourceFilePath, typeof(SecuritySystemAction)); // Act - var actual = await rulesEngine.MatchManyAsync(securitySystemActionable, expectedMatchDate, expectedConditions); + var actual = await genericRulesEngine.MatchManyAsync(securitySystemActionable, expectedMatchDate, expectedConditions); // Assert actual.Should().NotBeNull(); diff --git a/tests/Rules.Framework.IntegrationTests/Scenarios/Scenario4/DiscountCampaignTests.cs b/tests/Rules.Framework.IntegrationTests/Scenarios/Scenario4/DiscountCampaignTests.cs index 601c47b7..c35fe02e 100644 --- a/tests/Rules.Framework.IntegrationTests/Scenarios/Scenario4/DiscountCampaignTests.cs +++ b/tests/Rules.Framework.IntegrationTests/Scenarios/Scenario4/DiscountCampaignTests.cs @@ -6,7 +6,7 @@ namespace Rules.Framework.IntegrationTests.Scenarios.Scenario4 using System.Threading.Tasks; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.IntegrationTests.Common.Scenarios.Scenario4; using Xunit; @@ -26,29 +26,31 @@ public async Task DiscountsWeekend_Adding15PercentRulePerBrandAndEvaluatingOneOf { // Arrange var serviceProvider = new ServiceCollection() - .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) + .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) .BuildServiceProvider(); var rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetInMemoryDataSource(serviceProvider) .Configure(options => { + options.AutoCreateRulesets = true; options.EnableCompilation = enableCompilation; }) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); // Act 1 - Create rule with "in" operator - var ruleBuilderResult = RuleBuilder - .NewRule() - .WithName("Discounts Weekend MAY2021") - .WithDatesInterval(DateTime.Parse("2021-05-29Z"), DateTime.Parse("2021-05-31Z")) - .WithContent(DiscountConfigurations.DiscountCampaigns, 15m) - .WithCondition(c => c + var ruleBuilderResult = Rule.Create("Discounts Weekend MAY2021") + .InRuleset(DiscountConfigurations.DiscountCampaigns) + .SetContent(15m) + .Since(DateTime.Parse("2021-05-29Z")) + .Until(DateTime.Parse("2021-05-31Z")) + .ApplyWhen(c => c .And(a => a .Value(DiscountConditions.ProductRecommendedRetailPrice, Operators.GreaterThanOrEqual, 1000) - .Value(DiscountConditions.ProductBrand, Operators.In, new[] { "ASUS", "HP", "Dell", "Toshiba", "Acer" }))) + .Value(DiscountConditions.ProductBrand, Operators.In, new[] { "ASUS", "HP", "Dell", "Toshiba", "Acer" }) + ) + ) .Build(); // Assert 1 @@ -60,7 +62,7 @@ public async Task DiscountsWeekend_Adding15PercentRulePerBrandAndEvaluatingOneOf // Act 2 - Add new rule with "in" operator var rule = ruleBuilderResult.Rule; - var addRuleResult = await rulesEngine.AddRuleAsync(rule, RuleAddPriorityOption.AtTop).ConfigureAwait(false); + var addRuleResult = await genericRulesEngine.AddRuleAsync(rule, RuleAddPriorityOption.AtTop); // Assert 2 - Verify if rule was added addRuleResult.Should().NotBeNull(); @@ -68,13 +70,13 @@ public async Task DiscountsWeekend_Adding15PercentRulePerBrandAndEvaluatingOneOf // Act 3 - Evaluate new rule with "in" operator var matchDateTime = DateTime.Parse("2021-05-29T12:34:52Z"); - var conditions = new[] + var conditions = new Dictionary { - new Condition(DiscountConditions.ProductBrand,"ASUS"), - new Condition(DiscountConditions.ProductRecommendedRetailPrice,1249.90m) + { DiscountConditions.ProductBrand,"ASUS" }, + { DiscountConditions.ProductRecommendedRetailPrice,1249.90m }, }; - var actual = await rulesEngine.MatchOneAsync(DiscountConfigurations.DiscountCampaigns, matchDateTime, conditions).ConfigureAwait(false); + var actual = await genericRulesEngine.MatchOneAsync(DiscountConfigurations.DiscountCampaigns, matchDateTime, conditions); // Assert 3 actual.Should().NotBeNull(); @@ -88,28 +90,31 @@ public async Task DiscountsWeekend_Adding20PercentRulePerProductTierAndEvaluatin { // Arrange var serviceProvider = new ServiceCollection() - .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) + .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) .BuildServiceProvider(); var rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetInMemoryDataSource(serviceProvider) .Configure(options => { + options.AutoCreateRulesets = true; options.EnableCompilation = enableCompilation; }) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); // Act 1 - Create rule with "in" operator - var ruleBuilderResult = RuleBuilder.NewRule() - .WithName("Discounts Weekend MAY2021 - Tiered discount") - .WithDatesInterval(DateTime.Parse("2021-05-29Z"), DateTime.Parse("2021-05-31Z")) - .WithContent(DiscountConfigurations.DiscountCampaigns, 15m) - .WithCondition(c => c + var ruleBuilderResult = Rule.Create("Discounts Weekend MAY2021 - Tiered discount") + .InRuleset(DiscountConfigurations.DiscountCampaigns) + .SetContent(15m) + .Since(DateTime.Parse("2021-05-29Z")) + .Until(DateTime.Parse("2021-05-31Z")) + .ApplyWhen(c => c .And(a => a .Value(DiscountConditions.ProductRecommendedRetailPrice, Operators.GreaterThanOrEqual, 1000) - .Value(DiscountConditions.ProductTier, Operators.In, new[] { 1, 3 }))) + .Value(DiscountConditions.ProductTier, Operators.In, new[] { 1, 3 }) + ) + ) .Build(); // Assert 1 @@ -121,7 +126,7 @@ public async Task DiscountsWeekend_Adding20PercentRulePerProductTierAndEvaluatin // Act 2 - Add new rule with "in" operator var rule = ruleBuilderResult.Rule; - var addRuleResult = await rulesEngine.AddRuleAsync(rule, RuleAddPriorityOption.AtTop).ConfigureAwait(false); + var addRuleResult = await genericRulesEngine.AddRuleAsync(rule, RuleAddPriorityOption.AtTop); // Assert 2 - Verify if rule was added addRuleResult.Should().NotBeNull(); @@ -129,14 +134,14 @@ public async Task DiscountsWeekend_Adding20PercentRulePerProductTierAndEvaluatin // Act 3 - Evaluate new rule with "in" operator var matchDateTime = DateTime.Parse("2021-05-29T12:34:52Z"); - var conditions = new[] + var conditions = new Dictionary { - new Condition(DiscountConditions.ProductBrand, "ASUS"), - new Condition(DiscountConditions.ProductTier, 1), - new Condition(DiscountConditions.ProductRecommendedRetailPrice, 1249.90m) + { DiscountConditions.ProductBrand, "ASUS" }, + { DiscountConditions.ProductTier, 1 }, + { DiscountConditions.ProductRecommendedRetailPrice, 1249.90m }, }; - var actual = await rulesEngine.MatchOneAsync(DiscountConfigurations.DiscountCampaigns, matchDateTime, conditions).ConfigureAwait(false); + var actual = await genericRulesEngine.MatchOneAsync(DiscountConfigurations.DiscountCampaigns, matchDateTime, conditions); // Assert 3 actual.Should().NotBeNull(); @@ -150,28 +155,26 @@ public async Task DiscountsWeekend_Adding5PercentRuleWithNotContainsTestConditio { // Arrange var serviceProvider = new ServiceCollection() - .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) + .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) .BuildServiceProvider(); var rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetInMemoryDataSource(serviceProvider) .Configure(options => { + options.AutoCreateRulesets = true; options.EnableCompilation = enableCompilation; }) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); // Act 1 - Create rule with "not contains" operator - var ruleBuilderResult = - RuleBuilder - .NewRule() - .WithName("Not a staff discount") - .WithContent(DiscountConfigurations.DiscountCampaigns, 5m) - .WithDateBegin(DateTime.Parse("2021-05-29Z")) - .WithCondition(DiscountConditions.CustomerEmail, Operators.NotContains, "@staff.com") - .Build(); + var ruleBuilderResult = Rule.Create("Not a staff discount") + .InRuleset(DiscountConfigurations.DiscountCampaigns) + .SetContent(5m) + .Since(DateTime.Parse("2021-05-29Z")) + .ApplyWhen(DiscountConditions.CustomerEmail, Operators.NotContains, "@staff.com") + .Build(); // Assert 1 ruleBuilderResult.Should().NotBeNull(); @@ -182,7 +185,7 @@ public async Task DiscountsWeekend_Adding5PercentRuleWithNotContainsTestConditio // Act 2 - Add new rule with "not contains" operator var rule = ruleBuilderResult.Rule; - var addRuleResult = await rulesEngine.AddRuleAsync(rule, RuleAddPriorityOption.AtTop).ConfigureAwait(false); + var addRuleResult = await genericRulesEngine.AddRuleAsync(rule, RuleAddPriorityOption.AtTop); // Assert 2 - Verify if rule was added addRuleResult.Should().NotBeNull(); @@ -190,12 +193,12 @@ public async Task DiscountsWeekend_Adding5PercentRuleWithNotContainsTestConditio // Act 3 - Evaluate new rule with "not contains" operator var matchDateTime = DateTime.Parse("2021-05-29T12:34:52Z"); - var conditions = new[] + var conditions = new Dictionary { - new Condition(DiscountConditions.CustomerEmail, "user12345@somewhere.com") + { DiscountConditions.CustomerEmail, "user12345@somewhere.com" }, }; - var actual = await rulesEngine.MatchOneAsync(DiscountConfigurations.DiscountCampaigns, matchDateTime, conditions).ConfigureAwait(false); + var actual = await genericRulesEngine.MatchOneAsync(DiscountConfigurations.DiscountCampaigns, matchDateTime, conditions); // Assert 3 actual.Should().NotBeNull(); @@ -209,28 +212,26 @@ public async Task DiscountsWeekend_AddingRuleWithNullTestConditionAndInputWithMa { // Arrange var serviceProvider = new ServiceCollection() - .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) + .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) .BuildServiceProvider(); var rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetInMemoryDataSource(serviceProvider) .Configure(options => { + options.AutoCreateRulesets = true; options.EnableCompilation = enableCompilation; }) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); // Act 1 - Create rule with "equal" operator - var ruleBuilderResult = - RuleBuilder - .NewRule() - .WithName("Blue Product") - .WithContent(DiscountConfigurations.DiscountCampaigns, ProductColor.Blue.ToString()) - .WithDateBegin(DateTime.Parse("2021-05-29Z")) - .WithCondition(DiscountConditions.ProductColor, Operators.Equal, ProductColor.Blue.ToString()) - .Build(); + var ruleBuilderResult = Rule.Create("Blue Product") + .InRuleset(DiscountConfigurations.DiscountCampaigns) + .SetContent(ProductColor.Blue.ToString()) + .Since(DateTime.Parse("2021-05-29Z")) + .ApplyWhen(DiscountConditions.ProductColor, Operators.Equal, ProductColor.Blue.ToString()) + .Build(); // Assert 1 ruleBuilderResult.Should().NotBeNull(); @@ -241,7 +242,7 @@ public async Task DiscountsWeekend_AddingRuleWithNullTestConditionAndInputWithMa // Act 2 - Add new rule with "in" operator var rule = ruleBuilderResult.Rule; - var addRuleResult = await rulesEngine.AddRuleAsync(rule, RuleAddPriorityOption.AtTop).ConfigureAwait(false); + var addRuleResult = await genericRulesEngine.AddRuleAsync(rule, RuleAddPriorityOption.AtTop); // Assert 2 - Verify if rule was added addRuleResult.Should().NotBeNull(); @@ -249,12 +250,12 @@ public async Task DiscountsWeekend_AddingRuleWithNullTestConditionAndInputWithMa // Act 3 - Evaluate new rule with "in" operator var matchDateTime = DateTime.Parse("2021-05-29T12:34:52Z"); - var conditions = new[] + var conditions = new Dictionary { - new Condition(DiscountConditions.ProductColor, ProductColor.Blue.ToString()) + { DiscountConditions.ProductColor, ProductColor.Blue.ToString() }, }; - var actual = await rulesEngine.MatchOneAsync(DiscountConfigurations.DiscountCampaigns, matchDateTime, conditions).ConfigureAwait(false); + var actual = await genericRulesEngine.MatchOneAsync(DiscountConfigurations.DiscountCampaigns, matchDateTime, conditions); // Assert 3 actual.Should().NotBeNull(); @@ -268,28 +269,26 @@ public async Task DiscountsWeekend_AddingRuleWithNullTestConditionAndInputWithou { // Arrange var serviceProvider = new ServiceCollection() - .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) + .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) .BuildServiceProvider(); var rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetInMemoryDataSource(serviceProvider) .Configure(options => { + options.AutoCreateRulesets = true; options.EnableCompilation = enableCompilation; }) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); // Act 1 - Create rule with "equal" operator - var ruleBuilderResult = - RuleBuilder - .NewRule() - .WithName("Blue Product") - .WithContent(DiscountConfigurations.DiscountCampaigns, ProductColor.Blue.ToString()) - .WithDateBegin(DateTime.Parse("2021-05-29Z")) - .WithCondition(DiscountConditions.ProductColor, Operators.Equal, ProductColor.Blue.ToString()) - .Build(); + var ruleBuilderResult = Rule.Create("Blue Product") + .InRuleset(DiscountConfigurations.DiscountCampaigns) + .SetContent(ProductColor.Blue.ToString()) + .Since(DateTime.Parse("2021-05-29Z")) + .ApplyWhen(DiscountConditions.ProductColor, Operators.Equal, ProductColor.Blue.ToString()) + .Build(); // Assert 1 ruleBuilderResult.Should().NotBeNull(); @@ -300,7 +299,7 @@ public async Task DiscountsWeekend_AddingRuleWithNullTestConditionAndInputWithou // Act 2 - Add new rule with "in" operator var rule = ruleBuilderResult.Rule; - var addRuleResult = await rulesEngine.AddRuleAsync(rule, RuleAddPriorityOption.AtTop).ConfigureAwait(false); + var addRuleResult = await genericRulesEngine.AddRuleAsync(rule, RuleAddPriorityOption.AtTop); // Assert 2 - Verify if rule was added addRuleResult.Should().NotBeNull(); @@ -308,9 +307,9 @@ public async Task DiscountsWeekend_AddingRuleWithNullTestConditionAndInputWithou // Act 3 - Evaluate new rule with "in" operator var matchDateTime = DateTime.Parse("2021-05-29T12:34:52Z"); - var conditions = new List>(); + var conditions = new Dictionary(); - var actual = await rulesEngine.MatchOneAsync(DiscountConfigurations.DiscountCampaigns, matchDateTime, conditions).ConfigureAwait(false); + var actual = await genericRulesEngine.MatchOneAsync(DiscountConfigurations.DiscountCampaigns, matchDateTime, conditions); // Assert 3 actual.Should().BeNull(); @@ -323,28 +322,26 @@ public async Task DiscountsWeekend_AddingRuleWithNullTestConditionAndInputWithou { // Arrange var serviceProvider = new ServiceCollection() - .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) + .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) .BuildServiceProvider(); var rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetInMemoryDataSource(serviceProvider) .Configure(options => { + options.AutoCreateRulesets = true; options.EnableCompilation = enableCompilation; }) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); // Act 1 - Create rule with "equal" operator - var ruleBuilderResult = - RuleBuilder - .NewRule() - .WithName("Blue Product") - .WithContent(DiscountConfigurations.DiscountCampaigns, ProductColor.Blue.ToString()) - .WithDateBegin(DateTime.Parse("2021-05-29Z")) - .WithCondition(DiscountConditions.ProductColor, Operators.Equal, ProductColor.Blue.ToString()) - .Build(); + var ruleBuilderResult = Rule.Create("Blue Product") + .InRuleset(DiscountConfigurations.DiscountCampaigns) + .SetContent(ProductColor.Blue.ToString()) + .Since(DateTime.Parse("2021-05-29Z")) + .ApplyWhen(DiscountConditions.ProductColor, Operators.Equal, ProductColor.Blue.ToString()) + .Build(); // Assert 1 ruleBuilderResult.Should().NotBeNull(); @@ -355,7 +352,7 @@ public async Task DiscountsWeekend_AddingRuleWithNullTestConditionAndInputWithou // Act 2 - Add new rule with "in" operator var rule = ruleBuilderResult.Rule; - var addRuleResult = await rulesEngine.AddRuleAsync(rule, RuleAddPriorityOption.AtTop).ConfigureAwait(false); + var addRuleResult = await genericRulesEngine.AddRuleAsync(rule, RuleAddPriorityOption.AtTop); // Assert 2 - Verify if rule was added addRuleResult.Should().NotBeNull(); @@ -363,12 +360,12 @@ public async Task DiscountsWeekend_AddingRuleWithNullTestConditionAndInputWithou // Act 3 - Evaluate new rule with "in" operator var matchDateTime = DateTime.Parse("2021-05-29T12:34:52Z"); - var conditions = new[] + var conditions = new Dictionary { - new Condition(DiscountConditions.ProductColor, ProductColor.White.ToString()) + { DiscountConditions.ProductColor, ProductColor.White.ToString() }, }; - var actual = await rulesEngine.MatchOneAsync(DiscountConfigurations.DiscountCampaigns, matchDateTime, conditions).ConfigureAwait(false); + var actual = await genericRulesEngine.MatchOneAsync(DiscountConfigurations.DiscountCampaigns, matchDateTime, conditions); // Assert 3 actual.Should().BeNull(); diff --git a/tests/Rules.Framework.IntegrationTests/Scenarios/Scenario5/BestServerTests.cs b/tests/Rules.Framework.IntegrationTests/Scenarios/Scenario5/BestServerTests.cs index 00c6f389..cd545839 100644 --- a/tests/Rules.Framework.IntegrationTests/Scenarios/Scenario5/BestServerTests.cs +++ b/tests/Rules.Framework.IntegrationTests/Scenarios/Scenario5/BestServerTests.cs @@ -1,11 +1,12 @@ namespace Rules.Framework.IntegrationTests.Scenarios.Scenario5 { using System; + using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.IntegrationTests.Common.Scenarios.Scenario5; using Xunit; @@ -16,28 +17,30 @@ public async Task BestServer_DeactivatingBestServerTop5_ReturnsBestServerDefault { // Arrange var serviceProvider = new ServiceCollection() - .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) + .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) .BuildServiceProvider(); var rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetInMemoryDataSource(serviceProvider) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); + + await genericRulesEngine.CreateRulesetAsync(BestServerConfigurations.BestServerEvaluation); // Act 1 - Create rule with "in" operator - var ruleBuilderResult = RuleBuilder.NewRule() - .WithName("Best Server Top5") - .WithDatesInterval(DateTime.Parse("2021-05-29T11:00:00Z"), DateTime.Parse("2021-05-31Z")) - .WithContent(BestServerConfigurations.BestServerEvaluation, "Top5") - .WithCondition(c => c + var ruleBuilderResult = Rule.Create("Best Server Top5") + .InRuleset(BestServerConfigurations.BestServerEvaluation) + .SetContent("Top5") + .Since(DateTime.Parse("2021-05-29T11:00:00Z")).Until(DateTime.Parse("2021-05-31Z")) + .ApplyWhen(c => c .And(a => a .Value(BestServerConditions.Price, Operators.In, new[] { 100m, 200m, 300m }) .Value(BestServerConditions.Memory, Operators.In, new[] { 12, 16, 24, 36 }) .Value(BestServerConditions.Memory, Operators.NotIn, new[] { 4, 8 }) .Value(BestServerConditions.StoragePartionable, Operators.In, new[] { true }) .Value(BestServerConditions.Brand, Operators.In, new[] { "AMD", "Intel", "Cisco" }) - )) + ) + ) .Build(); // Assert 1 @@ -49,18 +52,17 @@ public async Task BestServer_DeactivatingBestServerTop5_ReturnsBestServerDefault // Act 2 - Add new rule with "in" operator var rule = ruleBuilderResult.Rule; - var addRuleResult = await rulesEngine.AddRuleAsync(rule, RuleAddPriorityOption.AtTop).ConfigureAwait(false); + var addRuleResult = await genericRulesEngine.AddRuleAsync(rule, RuleAddPriorityOption.AtTop); // Assert 2 - Verify if rule was added addRuleResult.Should().NotBeNull(); addRuleResult.IsSuccess.Should().BeTrue(); // Act 3 - Create rule default - var ruleBuilderResultDefault = RuleBuilder - .NewRule() - .WithName("Best Server Default") - .WithDatesInterval(DateTime.Parse("2021-05-29Z"), DateTime.Parse("2021-05-31Z")) - .WithContent(BestServerConfigurations.BestServerEvaluation, "Default") + var ruleBuilderResultDefault = Rule.Create("Best Server Default") + .InRuleset(BestServerConfigurations.BestServerEvaluation) + .SetContent("Default") + .Since(DateTime.Parse("2021-05-29Z")).Until(DateTime.Parse("2021-05-31Z")) .Build(); // Assert 3 @@ -70,7 +72,7 @@ public async Task BestServer_DeactivatingBestServerTop5_ReturnsBestServerDefault $"errors have occurred while creating rule: \n[\n- {errors}\n]"); // Act 4 - Add new default rule - addRuleResult = await rulesEngine.AddRuleAsync(ruleBuilderResultDefault.Rule, RuleAddPriorityOption.AtBottom).ConfigureAwait(false); + addRuleResult = await genericRulesEngine.AddRuleAsync(ruleBuilderResultDefault.Rule, RuleAddPriorityOption.AtBottom); // Assert 4 - Verify if rule was added addRuleResult.Should().NotBeNull(); @@ -78,29 +80,29 @@ public async Task BestServer_DeactivatingBestServerTop5_ReturnsBestServerDefault // Act 5 - Evaluate new rule with "in" operator var matchDateTime = DateTime.Parse("2021-05-29T12:34:52Z"); - var conditions = new[] + var conditions = new Dictionary { - new Condition(BestServerConditions.Price, 100), - new Condition(BestServerConditions.Memory, 12), - new Condition(BestServerConditions.StoragePartionable, true), - new Condition(BestServerConditions.Brand, "AMD") + { BestServerConditions.Price, 100 }, + { BestServerConditions.Memory, 12 }, + { BestServerConditions.StoragePartionable, true }, + { BestServerConditions.Brand, "AMD" }, }; - var actual = await rulesEngine.MatchOneAsync(BestServerConfigurations.BestServerEvaluation, matchDateTime, conditions).ConfigureAwait(false); + var actual = await genericRulesEngine.MatchOneAsync(BestServerConfigurations.BestServerEvaluation, matchDateTime, conditions); // Assert 5 actual.Should().NotBeNull(); actual.Should().BeEquivalentTo(rule); // Act 6 - Update Best Server Top5 rule deactivate - var updateRuleResult = await rulesEngine.DeactivateRuleAsync(rule).ConfigureAwait(false); + var updateRuleResult = await genericRulesEngine.DeactivateRuleAsync(rule); // Assert 6 updateRuleResult.Should().NotBeNull(); updateRuleResult.IsSuccess.Should().BeTrue(); // Act 7 - Evaluate rule should be default now - actual = await rulesEngine.MatchOneAsync(BestServerConfigurations.BestServerEvaluation, matchDateTime, conditions).ConfigureAwait(false); + actual = await genericRulesEngine.MatchOneAsync(BestServerConfigurations.BestServerEvaluation, matchDateTime, conditions); // Assert 7 actual.Should().NotBeNull(); @@ -112,28 +114,31 @@ public async Task BestServer_UpdatingBestServerTop5_ReturnsBestServerDefault() { // Arrange var serviceProvider = new ServiceCollection() - .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) + .AddInMemoryRulesDataSource(ServiceLifetime.Singleton) .BuildServiceProvider(); var rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetInMemoryDataSource(serviceProvider) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); + + await genericRulesEngine.CreateRulesetAsync(BestServerConfigurations.BestServerEvaluation); // Act 1 - Create rule with "in" operator - var ruleBuilderResult = RuleBuilder.NewRule() - .WithName("Best Server Top5") - .WithDatesInterval(DateTime.Parse("2021-05-29T11:00:00Z"), DateTime.Parse("2021-05-31Z")) - .WithContent(BestServerConfigurations.BestServerEvaluation, "Top5") - .WithCondition(c => c + var ruleBuilderResult = Rule.Create("Best Server Top5") + .InRuleset(BestServerConfigurations.BestServerEvaluation) + .SetContent("Top5") + .Since(DateTime.Parse("2021-05-29T11:00:00Z")) + .Until(DateTime.Parse("2021-05-31Z")) + .ApplyWhen(c => c .And(a => a .Value(BestServerConditions.Price, Operators.In, new[] { 100m, 200m, 300m }) .Value(BestServerConditions.Memory, Operators.In, new[] { 12, 16, 24, 36 }) .Value(BestServerConditions.Memory, Operators.NotIn, new[] { 4, 8 }) .Value(BestServerConditions.StoragePartionable, Operators.In, new[] { true }) .Value(BestServerConditions.Brand, Operators.In, new[] { "AMD", "Intel", "Cisco" }) - )) + ) + ) .Build(); // Assert 1 @@ -145,17 +150,18 @@ public async Task BestServer_UpdatingBestServerTop5_ReturnsBestServerDefault() // Act 2 - Add new rule with "in" operator var rule = ruleBuilderResult.Rule; - var addRuleResult = await rulesEngine.AddRuleAsync(rule, RuleAddPriorityOption.AtTop).ConfigureAwait(false); + var addRuleResult = await genericRulesEngine.AddRuleAsync(rule, RuleAddPriorityOption.AtTop); // Assert 2 - Verify if rule was added addRuleResult.Should().NotBeNull(); addRuleResult.IsSuccess.Should().BeTrue(); // Act 3 - Create rule default - var ruleBuilderResultDefault = RuleBuilder.NewRule() - .WithName("Best Server Default") - .WithDatesInterval(DateTime.Parse("2021-05-29Z"), DateTime.Parse("2021-05-31Z")) - .WithContent(BestServerConfigurations.BestServerEvaluation, "Default") + var ruleBuilderResultDefault = Rule.Create("Best Server Default") + .InRuleset(BestServerConfigurations.BestServerEvaluation) + .SetContent("Default") + .Since(DateTime.Parse("2021-05-29Z")) + .Until(DateTime.Parse("2021-05-31Z")) .Build(); // Assert 3 @@ -165,7 +171,7 @@ public async Task BestServer_UpdatingBestServerTop5_ReturnsBestServerDefault() $"errors have occurred while creating rule: \n[\n- {errors}\n]"); // Act 4 - Add new default rule - addRuleResult = await rulesEngine.AddRuleAsync(ruleBuilderResultDefault.Rule, RuleAddPriorityOption.AtBottom).ConfigureAwait(false); + addRuleResult = await genericRulesEngine.AddRuleAsync(ruleBuilderResultDefault.Rule, RuleAddPriorityOption.AtBottom); // Assert 4 - Verify if rule was added addRuleResult.Should().NotBeNull(); @@ -173,15 +179,15 @@ public async Task BestServer_UpdatingBestServerTop5_ReturnsBestServerDefault() // Act 5 - Evaluate new rule with "in" operator var matchDateTime = DateTime.Parse("2021-05-29T12:34:52Z"); - var conditions = new[] + var conditions = new Dictionary { - new Condition(BestServerConditions.Price,100), - new Condition(BestServerConditions.Memory,12), - new Condition(BestServerConditions.StoragePartionable,true), - new Condition(BestServerConditions.Brand,"AMD") + { BestServerConditions.Price, 100 }, + { BestServerConditions.Memory, 12 }, + { BestServerConditions.StoragePartionable, true }, + { BestServerConditions.Brand, "AMD" }, }; - var actual = await rulesEngine.MatchOneAsync(BestServerConfigurations.BestServerEvaluation, matchDateTime, conditions).ConfigureAwait(false); + var actual = await genericRulesEngine.MatchOneAsync(BestServerConfigurations.BestServerEvaluation, matchDateTime, conditions); // Assert 5 actual.Should().NotBeNull(); @@ -189,14 +195,14 @@ public async Task BestServer_UpdatingBestServerTop5_ReturnsBestServerDefault() // Act 6 - Update Best Server Top5 date end rule.DateEnd = DateTime.Parse("2021-05-29T12:10:00Z"); - var updateRuleResult = await rulesEngine.UpdateRuleAsync(rule).ConfigureAwait(false); + var updateRuleResult = await genericRulesEngine.UpdateRuleAsync(rule); // Assert 6 updateRuleResult.Should().NotBeNull(); updateRuleResult.IsSuccess.Should().BeTrue(); // Act 7 - Evaluate rule should be default now - actual = await rulesEngine.MatchOneAsync(BestServerConfigurations.BestServerEvaluation, matchDateTime, conditions).ConfigureAwait(false); + actual = await genericRulesEngine.MatchOneAsync(BestServerConfigurations.BestServerEvaluation, matchDateTime, conditions); // Assert 7 actual.Should().NotBeNull(); diff --git a/tests/Rules.Framework.IntegrationTests/Scenarios/Scenario8/TexasHoldEmPokerSingleCombinationsTests.cs b/tests/Rules.Framework.IntegrationTests/Scenarios/Scenario8/TexasHoldEmPokerSingleCombinationsTests.cs index 8d9c7e29..b2f448d1 100644 --- a/tests/Rules.Framework.IntegrationTests/Scenarios/Scenario8/TexasHoldEmPokerSingleCombinationsTests.cs +++ b/tests/Rules.Framework.IntegrationTests/Scenarios/Scenario8/TexasHoldEmPokerSingleCombinationsTests.cs @@ -1,6 +1,7 @@ namespace Rules.Framework.IntegrationTests.Scenarios.Scenario8 { using System; + using System.Collections.Generic; using System.Threading.Tasks; using FluentAssertions; using Rules.Framework.BenchmarkTests.Tests.Benchmark3; @@ -16,36 +17,35 @@ public async Task PokerCombinations_Given_EvaluatesStraightCombination(bool enab { // Arrange var matchDate = new DateTime(2023, 1, 1); - var conditions = new[] + var conditions = new Dictionary { - new Condition(ConditionTypes.NumberOfKings, 1), - new Condition(ConditionTypes.NumberOfQueens, 1), - new Condition(ConditionTypes.NumberOfJacks, 1 ), - new Condition(ConditionTypes.NumberOfTens, 1 ), - new Condition(ConditionTypes.NumberOfNines, 1), - new Condition(ConditionTypes.KingOfClubs, true), - new Condition(ConditionTypes.QueenOfDiamonds, true), - new Condition(ConditionTypes.JackOfClubs, true), - new Condition(ConditionTypes.TenOfHearts, true), - new Condition(ConditionTypes.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 }, }; var rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetInMemoryDataSource() .Configure(options => { options.EnableCompilation = enableCompilation; }) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); var scenarioData = new Scenario8Data(); await ScenarioLoader.LoadScenarioAsync(rulesEngine, scenarioData); // Act - var result = await rulesEngine.MatchOneAsync(ContentTypes.TexasHoldemPokerSingleCombinations, matchDate, conditions); + var result = await genericRulesEngine.MatchOneAsync(PokerRulesets.TexasHoldemPokerSingleCombinations, matchDate, conditions); // Assert result.Should().NotBeNull(); diff --git a/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Features/RulesEngine/RulesEngineTestsBase.cs b/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Features/RulesEngine/RulesEngineTestsBase.cs index 642a55fd..90e153df 100644 --- a/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Features/RulesEngine/RulesEngineTestsBase.cs +++ b/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Features/RulesEngine/RulesEngineTestsBase.cs @@ -4,22 +4,20 @@ namespace Rules.Framework.Providers.InMemory.IntegrationTests.Features.RulesEngi using System.Collections.Generic; using System.Globalization; using System.Threading.Tasks; - using Rules.Framework.Core; + using Rules.Framework.Generic; using Rules.Framework.IntegrationTests.Common.Features; using Rules.Framework.Tests.Stubs; public abstract class RulesEngineTestsBase { - private readonly ContentType TestContentType; + private readonly RulesetNames TestRuleset; - protected RulesEngineTestsBase(ContentType testContentType) + protected RulesEngineTestsBase(RulesetNames testRuleset) { - this.TestContentType = testContentType; + this.TestRuleset = testRuleset; - this.CompiledRulesEngine = RulesEngineBuilder + var compiledRulesEngine = RulesEngineBuilder .CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetInMemoryDataSource() .Configure(c => { @@ -27,21 +25,23 @@ protected RulesEngineTestsBase(ContentType testContentType) c.PriorityCriteria = PriorityCriterias.TopmostRuleWins; }) .Build(); + this.CompiledRulesEngine = compiledRulesEngine.MakeGeneric(); + this.CompiledRulesEngine.CreateRulesetAsync(testRuleset).GetAwaiter().GetResult(); - this.InterpretedRulesEngine = RulesEngineBuilder + var interpretedRulesEngine = RulesEngineBuilder .CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetInMemoryDataSource() .Configure(c => c.PriorityCriteria = PriorityCriterias.TopmostRuleWins) .Build(); + this.InterpretedRulesEngine = interpretedRulesEngine.MakeGeneric(); + this.InterpretedRulesEngine.CreateRulesetAsync(testRuleset).GetAwaiter().GetResult(); } - protected RulesEngine CompiledRulesEngine { get; } + protected IRulesEngine CompiledRulesEngine { get; } - protected RulesEngine InterpretedRulesEngine { get; } + protected IRulesEngine InterpretedRulesEngine { get; } - protected async Task ActivateRuleAsync(Rule rule, bool compiled) + protected async Task ActivateRuleAsync(Rule rule, bool compiled) { if (compiled) { @@ -67,7 +67,7 @@ protected void AddRules(IEnumerable ruleSpecifications) } } - protected async Task DeactivateRuleAsync(Rule rule, bool compiled) + protected async Task DeactivateRuleAsync(Rule rule, bool compiled) { if (compiled) { @@ -79,22 +79,22 @@ protected async Task DeactivateRuleAsync(Rule> MatchOneAsync( + protected async Task> MatchOneAsync( DateTime matchDate, - Condition[] conditions, + IDictionary conditions, bool compiled) { if (compiled) { - return await CompiledRulesEngine.MatchOneAsync(TestContentType, matchDate, conditions); + return await CompiledRulesEngine.MatchOneAsync(TestRuleset, matchDate, conditions); } else { - return await InterpretedRulesEngine.MatchOneAsync(TestContentType, matchDate, conditions); + return await InterpretedRulesEngine.MatchOneAsync(TestRuleset, matchDate, conditions); } } - protected async Task UpdateRuleAsync(Rule rule, bool compiled) + protected async Task UpdateRuleAsync(Rule rule, bool compiled) { if (compiled) { diff --git a/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Features/RulesEngine/RulesMatching/OperatorContainsManyToOneTests.cs b/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Features/RulesEngine/RulesMatching/OperatorContainsManyToOneTests.cs index bf846d19..33035b00 100644 --- a/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Features/RulesEngine/RulesMatching/OperatorContainsManyToOneTests.cs +++ b/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Features/RulesEngine/RulesMatching/OperatorContainsManyToOneTests.cs @@ -3,32 +3,33 @@ namespace Rules.Framework.Providers.InMemory.IntegrationTests.Features.RulesEngi using System.Collections.Generic; using System.Threading.Tasks; using FluentAssertions; - using Rules.Framework.Core; + using Rules.Framework; + using Rules.Framework.Generic; 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 expectedMatchRule; - private readonly Rule otherRule; + private static readonly RulesetNames testContentType = RulesetNames.Sample1; + private readonly Rule expectedMatchRule; + private readonly Rule otherRule; public OperatorContainsManyToOneTests() : base(testContentType) { - this.expectedMatchRule = RuleBuilder.NewRule() - .WithName("Expected rule") - .WithDateBegin(UtcDate("2020-01-01Z")) - .WithContent(testContentType, "Just as expected!") - .WithCondition(ConditionType.ConditionType1, Operators.Contains, "Cat") + this.expectedMatchRule = Rule.Create("Expected rule") + .InRuleset(testContentType) + .SetContent("Just as expected!") + .Since(UtcDate("2020-01-01Z")) + .ApplyWhen(ConditionNames.Condition1, Operators.Contains, "Cat") .Build() .Rule; - this.otherRule = RuleBuilder.NewRule() - .WithName("Other rule") - .WithDateBegin(UtcDate("2020-01-01Z")) - .WithContent(testContentType, "Oops! Not expected to be matched.") + this.otherRule = Rule.Create("Other rule") + .InRuleset(testContentType) + .SetContent("Oops! Not expected to be matched.") + .Since(UtcDate("2020-01-01Z")) .Build() .Rule; @@ -41,9 +42,9 @@ public OperatorContainsManyToOneTests() public async Task RulesEngine_GivenConditionType1WithArrayOfStringsContainingCat_MatchesExpectedRule(bool compiled) { // Arrange - var emptyConditions = new[] + var emptyConditions = new Dictionary { - new Condition(ConditionType.ConditionType1, new[]{ "Dog", "Fish", "Cat", "Spider", "Mockingbird", }) + { ConditionNames.Condition1, new[]{ "Dog", "Fish", "Cat", "Spider", "Mockingbird", } }, }; var matchDate = UtcDate("2020-01-02Z"); @@ -60,9 +61,9 @@ public async Task RulesEngine_GivenConditionType1WithArrayOfStringsContainingCat public async Task RulesEngine_GivenConditionType1WithArrayOfStringsNotContainingCat_MatchesOtherRule(bool compiled) { // Arrange - var emptyConditions = new[] + var emptyConditions = new Dictionary { - new Condition(ConditionType.ConditionType1, new[]{ "Dog", "Fish", "Bat", "Spider", "Mockingbird", }) + { ConditionNames.Condition1, new[]{ "Dog", "Fish", "Bat", "Spider", "Mockingbird", } }, }; var matchDate = UtcDate("2020-01-02Z"); diff --git a/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Features/RulesEngine/RulesMatching/RulesDeactivateAndActivateTests.cs b/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Features/RulesEngine/RulesMatching/RulesDeactivateAndActivateTests.cs index 5771bd7b..d8851651 100644 --- a/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Features/RulesEngine/RulesMatching/RulesDeactivateAndActivateTests.cs +++ b/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Features/RulesEngine/RulesMatching/RulesDeactivateAndActivateTests.cs @@ -3,7 +3,7 @@ namespace Rules.Framework.Providers.InMemory.IntegrationTests.Features.RulesEngi using System; using System.Collections.Generic; using System.Threading.Tasks; - using Rules.Framework.Core; + using Rules.Framework.Generic; using Rules.Framework.IntegrationTests.Common.Features; using Rules.Framework.Providers.InMemory.IntegrationTests.Features.RulesEngine; using Rules.Framework.Tests.Stubs; @@ -17,25 +17,23 @@ public class RulesDeactivateAndActivateTests : RulesEngineTestsBase private static readonly string rule2Value = "DummyRule2 Value"; private static readonly DateTime ruleEndDate = new DateTime(2021, 02, 01); private static readonly DateTime ruleStartDate = new DateTime(2020, 01, 01); - private static readonly ContentType TestContentType = ContentType.ContentType1; - private readonly Rule rule1; - private readonly Rule rule2; + private static readonly RulesetNames testRuleset = RulesetNames.Sample1; + private readonly Rule rule1; + private readonly Rule rule2; - public RulesDeactivateAndActivateTests() : base(TestContentType) + public RulesDeactivateAndActivateTests() : base(testRuleset) { - rule1 = RuleBuilder - .NewRule() - .WithName(rule1Name) - .WithContent(TestContentType, rule1Value) - .WithDatesInterval(ruleStartDate, ruleEndDate) + rule1 = Rule.Create(rule1Name) + .InRuleset(testRuleset) + .SetContent(rule1Value) + .Since(ruleStartDate) + .Until(ruleEndDate) .Build().Rule; - rule2 = - RuleBuilder - .NewRule() - .WithName(rule2Name) - .WithContent(TestContentType, rule2Value) - .WithDatesInterval(ruleStartDate, ruleEndDate) + rule2 = Rule.Create(rule2Name) + .InRuleset(testRuleset) + .SetContent(rule2Value) + .Since(ruleStartDate).Until(ruleEndDate) .Build().Rule; this.AddRules(this.CreateTestRules()); @@ -47,7 +45,7 @@ public RulesDeactivateAndActivateTests() : base(TestContentType) public async Task RulesEngine_DeactivateThenActivateRule_Validations(bool compiled) { // Arrange - var emptyConditions = Array.Empty>(); + var emptyConditions = new Dictionary(); var matchDate = new DateTime(2020, 01, 02); // Act 1: Deactivate the rule @@ -73,8 +71,8 @@ private IEnumerable CreateTestRules() { var ruleSpecs = new List { - new RuleSpecification(rule1, RuleAddPriorityOption.ByPriorityNumber(1)), - new RuleSpecification(rule2, RuleAddPriorityOption.ByPriorityNumber(2)) + new(rule1, RuleAddPriorityOption.ByPriorityNumber(1)), + new(rule2, RuleAddPriorityOption.ByPriorityNumber(2)) }; return ruleSpecs; diff --git a/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Features/RulesEngine/RulesMatching/RulesInSequenceTests.cs b/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Features/RulesEngine/RulesMatching/RulesInSequenceTests.cs index 7b411fb3..ed66dbc8 100644 --- a/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Features/RulesEngine/RulesMatching/RulesInSequenceTests.cs +++ b/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Features/RulesEngine/RulesMatching/RulesInSequenceTests.cs @@ -17,11 +17,11 @@ public class RulesInSequenceTests : RulesEngineTestsBase private static readonly string rule2Name = "DummyRule2"; private static readonly string rule2Value = "DummyRule2 Value"; private static readonly DateTime ruleChangeDate = new DateTime(2020, 07, 01, 14, 30, 00); - private static readonly ContentType TestContentType = ContentType.ContentType1; + private static readonly RulesetNames testRuleset = RulesetNames.Sample1; - public RulesInSequenceTests() : base(TestContentType) + public RulesInSequenceTests() : base(testRuleset) { - this.AddRules(this.CreateTestRules()); + this.AddRules(CreateTestRules()); } public static IEnumerable FailureCases => @@ -51,7 +51,7 @@ public RulesInSequenceTests() : base(TestContentType) public async Task RulesEngine_MatchOneAsync_OutsideRulesPeriod_Failure(DateTime matchDate, bool compiled) { // Arrange - var emptyConditions = Array.Empty>(); + var emptyConditions = new Dictionary(); // Act var actualMatch = await this.MatchOneAsync(matchDate, emptyConditions, compiled); @@ -65,37 +65,35 @@ public async Task RulesEngine_MatchOneAsync_OutsideRulesPeriod_Failure(DateTime public async Task RulesEngine_MatchOneAsync_WithRulesInSequence_ReturnsCorrectRule(DateTime matchDate, string expectedName, string expectedValue, bool compiled) { // Arrange - var emptyConditions = Array.Empty>(); + var emptyConditions = new Dictionary(); // Act var actualMatch = await this.MatchOneAsync(matchDate, emptyConditions, compiled); // Assert Assert.Equal(expectedName, actualMatch.Name); - Assert.Equal(TestContentType, actualMatch.ContentContainer.ContentType); + Assert.Equal(testRuleset, actualMatch.Ruleset); Assert.Equal(expectedValue, actualMatch.ContentContainer.GetContentAs()); } - private IEnumerable CreateTestRules() + private static List CreateTestRules() { var ruleSpecs = new List(); - var rule1 = - RuleBuilder - .NewRule() - .WithName(rule1Name) - .WithContent(TestContentType, rule1Value) - .WithDatesInterval(rule1StartDate, ruleChangeDate) + var rule1 = Rule.Create(rule1Name) + .InRuleset(testRuleset) + .SetContent(rule1Value) + .Since(rule1StartDate) + .Until(ruleChangeDate) .Build(); ruleSpecs.Add(new RuleSpecification(rule1.Rule, RuleAddPriorityOption.ByPriorityNumber(1))); - var rule2 = - RuleBuilder - .NewRule() - .WithName(rule2Name) - .WithContent(TestContentType, rule2Value) - .WithDatesInterval(ruleChangeDate, rule2EndDate) + var rule2 = Rule.Create(rule2Name) + .InRuleset(testRuleset) + .SetContent(rule2Value) + .Since(ruleChangeDate) + .Until(rule2EndDate) .Build(); ruleSpecs.Add(new RuleSpecification(rule2.Rule, RuleAddPriorityOption.ByPriorityNumber(2))); diff --git a/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Features/RulesEngine/RulesMatching/RulesUpdateDateEndTests.cs b/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Features/RulesEngine/RulesMatching/RulesUpdateDateEndTests.cs index a265a8aa..1835d713 100644 --- a/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Features/RulesEngine/RulesMatching/RulesUpdateDateEndTests.cs +++ b/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Features/RulesEngine/RulesMatching/RulesUpdateDateEndTests.cs @@ -3,7 +3,7 @@ namespace Rules.Framework.Providers.InMemory.IntegrationTests.Features.RulesEngi using System; using System.Collections.Generic; using System.Threading.Tasks; - using Rules.Framework.Core; + using Rules.Framework.Generic; using Rules.Framework.IntegrationTests.Common.Features; using Rules.Framework.Providers.InMemory.IntegrationTests.Features.RulesEngine; using Rules.Framework.Tests.Stubs; @@ -17,25 +17,24 @@ public class RulesUpdateDateEndTests : RulesEngineTestsBase private static readonly string rule2Value = "DummyRule2 Value"; private static readonly DateTime ruleEndDate = new DateTime(2021, 02, 01); private static readonly DateTime ruleStartDate = new DateTime(2020, 01, 01); - private static readonly ContentType TestContentType = ContentType.ContentType1; - private readonly Rule rule1; - private readonly Rule rule2; + private static readonly RulesetNames testRuleset = RulesetNames.Sample1; + private readonly Rule rule1; + private readonly Rule rule2; - public RulesUpdateDateEndTests() : base(TestContentType) + public RulesUpdateDateEndTests() : base(testRuleset) { - rule1 = RuleBuilder - .NewRule() - .WithName(rule1Name) - .WithContent(TestContentType, rule1Value) - .WithDatesInterval(ruleStartDate, ruleEndDate) + rule1 = Rule.Create(rule1Name) + .InRuleset(testRuleset) + .SetContent(rule1Value) + .Since(ruleStartDate) + .Until(ruleEndDate) .Build().Rule; - rule2 = - RuleBuilder - .NewRule() - .WithName(rule2Name) - .WithContent(TestContentType, rule2Value) - .WithDatesInterval(ruleStartDate, ruleEndDate) + rule2 = Rule.Create(rule2Name) + .InRuleset(testRuleset) + .SetContent(rule2Value) + .Since(ruleStartDate) + .Until(ruleEndDate) .Build().Rule; this.AddRules(this.CreateTestRules()); @@ -55,7 +54,7 @@ public RulesUpdateDateEndTests() : base(TestContentType) public async Task RulesEngine_UpdateRuleDateEnd_Validations(DateTime dateEnd, bool success, bool compiled) { // Arrange - var emptyConditions = Array.Empty>(); + var emptyConditions = new Dictionary(); var matchDate = new DateTime(2020, 01, 02); // Act @@ -76,8 +75,8 @@ private IEnumerable CreateTestRules() { var ruleSpecs = new List { - new RuleSpecification(rule1, RuleAddPriorityOption.ByPriorityNumber(1)), - new RuleSpecification(rule2, RuleAddPriorityOption.ByPriorityNumber(2)) + new(rule1, RuleAddPriorityOption.ByPriorityNumber(1)), + new(rule2, RuleAddPriorityOption.ByPriorityNumber(2)) }; return ruleSpecs; diff --git a/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Scenarios/BaseScenarioTests.cs b/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Scenarios/BaseScenarioTests.cs index 2a741765..0a49c0f8 100644 --- a/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Scenarios/BaseScenarioTests.cs +++ b/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Scenarios/BaseScenarioTests.cs @@ -6,6 +6,7 @@ namespace Rules.Framework.Providers.InMemory.IntegrationTests.Scenarios using System.IO; using System.Linq; using Newtonsoft.Json; + using Rules.Framework; using Rules.Framework.Core; using Rules.Framework.Providers.InMemory.DataModel; @@ -15,7 +16,7 @@ protected BaseScenarioTests() { } - internal ConditionNodeDataModel CreateConditionNodeDataModel(dynamic conditionNode) + internal ConditionNodeDataModel CreateConditionNodeDataModel(dynamic conditionNode) { var logicalOperator = this.Parse((string)conditionNode.LogicalOperator); @@ -34,9 +35,9 @@ internal ConditionNodeDataModel CreateConditionNodeDataModel (IEnumerable)conditionNode.Operand, _ => null, }; - return new ValueConditionNodeDataModel + return new ValueConditionNodeDataModel { - ConditionType = this.Parse((string)conditionNode.ConditionType), + Condition = conditionNode.Condition, DataType = dataType, LogicalOperator = logicalOperator, Operator = this.Parse((string)conditionNode.Operator), @@ -48,14 +49,14 @@ internal ConditionNodeDataModel CreateConditionNodeDataModel; - var conditionNodeDataModels = new ConditionNodeDataModel[childConditionNodes.Count()]; + var conditionNodeDataModels = new ConditionNodeDataModel[childConditionNodes.Count()]; var i = 0; - foreach (dynamic child in childConditionNodes) + foreach (var child in childConditionNodes) { - conditionNodeDataModels[i++] = this.CreateConditionNodeDataModel(child); + conditionNodeDataModels[i++] = this.CreateConditionNodeDataModel(child); } - return new ComposedConditionNodeDataModel + return new ComposedConditionNodeDataModel { ChildConditionNodes = conditionNodeDataModels, LogicalOperator = logicalOperator, @@ -64,20 +65,20 @@ internal ConditionNodeDataModel CreateConditionNodeDataModel CreateRulesDataSourceTest(IInMemoryRulesStorage inMemoryRulesStorage) + internal IRulesDataSource CreateRulesDataSourceTest(IInMemoryRulesStorage inMemoryRulesStorage) { - var ruleFactory = new RuleFactory(); - return new InMemoryProviderRulesDataSource(inMemoryRulesStorage, ruleFactory); + var ruleFactory = new RuleFactory(); + return new InMemoryProviderRulesDataSource(inMemoryRulesStorage, ruleFactory); } - internal void LoadInMemoryStorage( + internal void LoadInMemoryStorage( string dataSourceFilePath, - IInMemoryRulesStorage inMemoryRulesStorage, + IInMemoryRulesStorage inMemoryRulesStorage, Func contentConvertFunc) { var rulesFile = File.OpenRead(dataSourceFilePath); - IEnumerable> rules; + RuleDataModel[] rules; using (var streamReader = new StreamReader(rulesFile ?? throw new InvalidOperationException("Could not load rules file."))) { var json = streamReader.ReadToEnd(); @@ -89,18 +90,24 @@ internal void LoadInMemoryStorage( rules = array.Select(t => { - return new RuleDataModel + return new RuleDataModel { Content = contentConvertFunc.Invoke(t.Content), - ContentType = (TContentType)t.ContentTypeCode, + Ruleset = t.Ruleset, DateBegin = t.DateBegin, DateEnd = t.DateEnd, Name = t.Name, Priority = t.Priority, Active = t.Active ?? true, - RootCondition = this.CreateConditionNodeDataModel(t.RootCondition) + RootCondition = this.CreateConditionNodeDataModel(t.RootCondition) }; - }).ToList(); + }).ToArray(); + } + + var rulesets = rules.Select(r => r.Ruleset).Distinct().ToArray(); + foreach (var ruleset in rulesets) + { + inMemoryRulesStorage.CreateRuleset(ruleset); } foreach (var rule in rules) diff --git a/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Scenarios/Scenario2/CarInsuranceAdvisorTests.cs b/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Scenarios/Scenario2/CarInsuranceAdvisorTests.cs index 6bbb99ca..120f1674 100644 --- a/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Scenarios/Scenario2/CarInsuranceAdvisorTests.cs +++ b/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Scenarios/Scenario2/CarInsuranceAdvisorTests.cs @@ -1,6 +1,7 @@ namespace Rules.Framework.Providers.InMemory.IntegrationTests.Scenarios.Scenario2 { using System; + using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using FluentAssertions; @@ -12,12 +13,12 @@ namespace Rules.Framework.Providers.InMemory.IntegrationTests.Scenarios.Scenario public class CarInsuranceAdvisorTests : BaseScenarioTests { - private readonly IInMemoryRulesStorage inMemoryRulesStorage; + private readonly IInMemoryRulesStorage inMemoryRulesStorage; public CarInsuranceAdvisorTests() { - this.inMemoryRulesStorage = new InMemoryRulesStorage(); - this.LoadInMemoryStorage( + this.inMemoryRulesStorage = new InMemoryRulesStorage(); + this.LoadInMemoryStorage( DataSourceFilePath, this.inMemoryRulesStorage, (c) => this.Parse((string)c)); @@ -30,12 +31,12 @@ public async Task GetCarInsuranceAdvice_RepairCostsNotWorthIt_ReturnsPayOldCar() { // Arrange var expected = CarInsuranceAdvices.PayOldCar; - const ContentTypes expectedContent = ContentTypes.CarInsuranceAdvice; + const CarInsuranceRulesetNames expectedRuleset = CarInsuranceRulesetNames.CarInsuranceAdvice; var expectedMatchDate = new DateTime(2016, 06, 01, 20, 23, 23); - var expectedConditions = new[] + var expectedConditions = new Dictionary { - new Condition(ConditionTypes.RepairCosts, 0.0m), - new Condition(ConditionTypes.RepairCostsCommercialValueRate, 0.0m) + { CarInsuranceConditionNames.RepairCosts, 0.0m }, + { CarInsuranceConditionNames.RepairCostsCommercialValueRate, 0.0m }, }; var serviceDescriptors = new ServiceCollection(); @@ -43,17 +44,16 @@ public async Task GetCarInsuranceAdvice_RepairCostsNotWorthIt_ReturnsPayOldCar() var serviceProvider = serviceDescriptors.BuildServiceProvider(); var rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetInMemoryDataSource(serviceProvider) .Configure(opt => { opt.PriorityCriteria = PriorityCriterias.BottommostRuleWins; }) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); // Act - var actual = await rulesEngine.MatchOneAsync(expectedContent, expectedMatchDate, expectedConditions); + var actual = await genericRulesEngine.MatchOneAsync(expectedRuleset, expectedMatchDate, expectedConditions); // Assert actual.Should().NotBeNull(); @@ -66,12 +66,12 @@ public async Task GetCarInsuranceAdvice_RepairCostsNotWorthIt_ReturnsRefusePayme { // Arrange var expected = CarInsuranceAdvices.RefusePaymentPerFranchise; - const ContentTypes expectedContent = ContentTypes.CarInsuranceAdvice; + const CarInsuranceRulesetNames expectedRuleset = CarInsuranceRulesetNames.CarInsuranceAdvice; var expectedMatchDate = new DateTime(2018, 06, 01); - var expectedConditions = new[] + var expectedConditions = new Dictionary { - new Condition(ConditionTypes.RepairCosts, 800.00000m), - new Condition(ConditionTypes.RepairCostsCommercialValueRate, 23.45602m) + { CarInsuranceConditionNames.RepairCosts, 800.00000m }, + { CarInsuranceConditionNames.RepairCostsCommercialValueRate, 23.45602m }, }; var serviceDescriptors = new ServiceCollection(); @@ -79,17 +79,16 @@ public async Task GetCarInsuranceAdvice_RepairCostsNotWorthIt_ReturnsRefusePayme var serviceProvider = serviceDescriptors.BuildServiceProvider(); var rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetInMemoryDataSource(serviceProvider) .Configure(opt => { opt.PriorityCriteria = PriorityCriterias.BottommostRuleWins; }) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); // Act - var actual = await rulesEngine.MatchOneAsync(expectedContent, expectedMatchDate, expectedConditions); + var actual = await genericRulesEngine.MatchOneAsync(expectedRuleset, expectedMatchDate, expectedConditions); // Assert actual.Should().NotBeNull(); @@ -101,12 +100,12 @@ public async Task GetCarInsuranceAdvice_RepairCostsNotWorthIt_ReturnsRefusePayme public async Task GetCarInsuranceAdvice_UpdatesRuleAndAddsNewOneAndEvaluates_ReturnsPay() { // Arrange - const ContentTypes expectedContent = ContentTypes.CarInsuranceAdvice; + const CarInsuranceRulesetNames expectedRuleset = CarInsuranceRulesetNames.CarInsuranceAdvice; var expectedMatchDate = new DateTime(2018, 06, 01); - var expectedConditions = new[] + var expectedConditions = new Dictionary { - new Condition(ConditionTypes.RepairCosts, 800.00000m), - new Condition(ConditionTypes.RepairCostsCommercialValueRate, 23.45602m) + { CarInsuranceConditionNames.RepairCosts, 800.00000m }, + { CarInsuranceConditionNames.RepairCostsCommercialValueRate, 23.45602m }, }; var serviceDescriptors = new ServiceCollection(); @@ -114,24 +113,23 @@ public async Task GetCarInsuranceAdvice_UpdatesRuleAndAddsNewOneAndEvaluates_Ret var serviceProvider = serviceDescriptors.BuildServiceProvider(); var rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetInMemoryDataSource(serviceProvider) .Configure(opt => { opt.PriorityCriteria = PriorityCriterias.BottommostRuleWins; }) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); - var rulesDataSource = CreateRulesDataSourceTest(this.inMemoryRulesStorage); + var rulesDataSource = CreateRulesDataSourceTest(this.inMemoryRulesStorage); - var ruleBuilderResult = RuleBuilder.NewRule() - .WithName("Car Insurance Advise on self damage coverage") - .WithDateBegin(DateTime.Parse("2018-01-01")) - .WithContent(ContentTypes.CarInsuranceAdvice, CarInsuranceAdvices.Pay) + var ruleBuilderResult = Rule.Create("Car Insurance Advise on self damage coverage") + .InRuleset(expectedRuleset) + .SetContent(CarInsuranceAdvices.Pay) + .Since(DateTime.Parse("2018-01-01")) .Build(); - var existentRules1 = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs + var existentRules1 = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs { Name = "Car Insurance Advise on repair costs lower than franchise boundary" }); @@ -143,9 +141,9 @@ public async Task GetCarInsuranceAdvice_UpdatesRuleAndAddsNewOneAndEvaluates_Ret // Act 1 var updateOperationResult1 = await rulesEngine.UpdateRuleAsync(ruleToUpdate1); - var eval1 = await rulesEngine.MatchOneAsync(expectedContent, expectedMatchDate, expectedConditions); + var eval1 = await genericRulesEngine.MatchOneAsync(expectedRuleset, expectedMatchDate, expectedConditions); - var rules1 = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs()); + var rules1 = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs()); // Assert 1 updateOperationResult1.Should().NotBeNull(); @@ -166,22 +164,22 @@ public async Task GetCarInsuranceAdvice_UpdatesRuleAndAddsNewOneAndEvaluates_Ret rule13.Priority.Should().Be(3); // Act 2 - var addOperationResult = await rulesEngine.AddRuleAsync(ruleToAdd, new RuleAddPriorityOption + var addOperationResult = await genericRulesEngine.AddRuleAsync(ruleToAdd, new RuleAddPriorityOption { PriorityOption = PriorityOptions.AtRuleName, AtRuleNameOptionValue = "Car Insurance Advise on repair costs lower than franchise boundary" }); - var eval2 = await rulesEngine.MatchOneAsync(expectedContent, expectedMatchDate, expectedConditions); + var eval2 = await genericRulesEngine.MatchOneAsync(expectedRuleset, expectedMatchDate, expectedConditions); - var rules2 = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs()); + var rules2 = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs()); // Assert 2 addOperationResult.Should().NotBeNull(); addOperationResult.IsSuccess.Should().BeTrue(); eval2.Priority.Should().Be(3); - CarInsuranceAdvices content2 = eval2.ContentContainer.GetContentAs(); + var content2 = eval2.ContentContainer.GetContentAs(); content2.Should().Be(CarInsuranceAdvices.RefusePaymentPerFranchise); var rule21 = rules2.FirstOrDefault(r => r.Name == "Car Insurance Advise on repair costs lesser than 80% of commercial value"); @@ -198,7 +196,7 @@ public async Task GetCarInsuranceAdvice_UpdatesRuleAndAddsNewOneAndEvaluates_Ret rule24.Priority.Should().Be(4); // Act 3 - var existentRules2 = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs + var existentRules2 = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs { Name = "Car Insurance Advise on repair costs lower than franchise boundary" }); @@ -207,9 +205,9 @@ public async Task GetCarInsuranceAdvice_UpdatesRuleAndAddsNewOneAndEvaluates_Ret var updateOperationResult2 = await rulesEngine.UpdateRuleAsync(ruleToUpdate2); - var eval3 = await rulesEngine.MatchOneAsync(expectedContent, expectedMatchDate, expectedConditions); + var eval3 = await genericRulesEngine.MatchOneAsync(expectedRuleset, expectedMatchDate, expectedConditions); - var rules3 = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs()); + var rules3 = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs()); // Assert 3 updateOperationResult2.Should().NotBeNull(); diff --git a/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Scenarios/Scenario3/BuildingSecuritySystemControlTests.cs b/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Scenarios/Scenario3/BuildingSecuritySystemControlTests.cs index 2f474209..b5c52113 100644 --- a/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Scenarios/Scenario3/BuildingSecuritySystemControlTests.cs +++ b/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Scenarios/Scenario3/BuildingSecuritySystemControlTests.cs @@ -7,20 +7,19 @@ namespace Rules.Framework.Providers.InMemory.IntegrationTests.Scenarios.Scenario using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; - using Rules.Framework.Core; using Rules.Framework.IntegrationTests.Common.Scenarios.Scenario3; using Rules.Framework.Providers.InMemory; using Xunit; public class BuildingSecuritySystemControlTests : BaseScenarioTests { - private readonly IInMemoryRulesStorage inMemoryRulesStorage; + private readonly IInMemoryRulesStorage inMemoryRulesStorage; public BuildingSecuritySystemControlTests() { - this.inMemoryRulesStorage = new InMemoryRulesStorage(); + this.inMemoryRulesStorage = new InMemoryRulesStorage(); - this.LoadInMemoryStorage( + this.LoadInMemoryStorage( DataSourceFilePath, this.inMemoryRulesStorage, (c) => JsonConvert.DeserializeObject((string)c)); @@ -36,30 +35,29 @@ public async Task BuildingSecuritySystem_FireScenario_ReturnsActionsToTrigger(bo // Assert const SecuritySystemActionables securitySystemActionable = SecuritySystemActionables.FireSystem; - DateTime expectedMatchDate = new DateTime(2018, 06, 01); - Condition[] expectedConditions = new Condition[] + var expectedMatchDate = new DateTime(2018, 06, 01); + var expectedConditions = new Dictionary { - new Condition(SecuritySystemConditions.TemperatureCelsius, 100.0m), - new Condition(SecuritySystemConditions.SmokeRate, 55.0m), - new Condition(SecuritySystemConditions.PowerStatus, "Online") + { SecuritySystemConditions.TemperatureCelsius, 100.0m }, + { SecuritySystemConditions.SmokeRate, 55.0m }, + { SecuritySystemConditions.PowerStatus, "Online" }, }; IServiceCollection serviceDescriptors = new ServiceCollection(); serviceDescriptors.AddSingleton(this.inMemoryRulesStorage); IServiceProvider serviceProvider = serviceDescriptors.BuildServiceProvider(); - RulesEngine rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() + var rulesEngine = RulesEngineBuilder.CreateRulesEngine() .SetInMemoryDataSource(serviceProvider) .Configure(options => { options.EnableCompilation = enableCompilation; }) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); // Act - IEnumerable> actual = await rulesEngine.MatchManyAsync(securitySystemActionable, expectedMatchDate, expectedConditions); + var actual = await genericRulesEngine.MatchManyAsync(securitySystemActionable, expectedMatchDate, expectedConditions); // Assert actual.Should().NotBeNull(); @@ -80,30 +78,29 @@ public async Task BuildingSecuritySystem_PowerFailureScenario_ReturnsActionsToTr // Assert const SecuritySystemActionables securitySystemActionable = SecuritySystemActionables.PowerSystem; - DateTime expectedMatchDate = new DateTime(2018, 06, 01); - Condition[] expectedConditions = new Condition[] + var expectedMatchDate = new DateTime(2018, 06, 01); + var expectedConditions = new Dictionary { - new Condition(SecuritySystemConditions.TemperatureCelsius, 100.0m), - new Condition(SecuritySystemConditions.SmokeRate, 55.0m), - new Condition(SecuritySystemConditions.PowerStatus, "Offline") + { SecuritySystemConditions.TemperatureCelsius, 100.0m }, + { SecuritySystemConditions.SmokeRate, 55.0m }, + { SecuritySystemConditions.PowerStatus, "Offline" }, }; IServiceCollection serviceDescriptors = new ServiceCollection(); serviceDescriptors.AddSingleton(this.inMemoryRulesStorage); IServiceProvider serviceProvider = serviceDescriptors.BuildServiceProvider(); - RulesEngine rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() + var rulesEngine = RulesEngineBuilder.CreateRulesEngine() .SetInMemoryDataSource(serviceProvider) .Configure(options => { options.EnableCompilation = enableCompilation; }) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); // Act - IEnumerable> actual = await rulesEngine.MatchManyAsync(securitySystemActionable, expectedMatchDate, expectedConditions); + var actual = await genericRulesEngine.MatchManyAsync(securitySystemActionable, expectedMatchDate, expectedConditions); // Assert actual.Should().NotBeNull(); @@ -123,30 +120,29 @@ public async Task BuildingSecuritySystem_PowerShutdownScenario_ReturnsActionsToT // Assert const SecuritySystemActionables securitySystemActionable = SecuritySystemActionables.PowerSystem; - DateTime expectedMatchDate = new DateTime(2018, 06, 01); - Condition[] expectedConditions = new Condition[] + var expectedMatchDate = new DateTime(2018, 06, 01); + var expectedConditions = new Dictionary { - new Condition(SecuritySystemConditions.TemperatureCelsius, 100.0m), - new Condition(SecuritySystemConditions.SmokeRate, 55.0m), - new Condition(SecuritySystemConditions.PowerStatus, "Shutdown") + { SecuritySystemConditions.TemperatureCelsius, 100.0m }, + { SecuritySystemConditions.SmokeRate, 55.0m }, + { SecuritySystemConditions.PowerStatus, "Shutdown" }, }; IServiceCollection serviceDescriptors = new ServiceCollection(); serviceDescriptors.AddSingleton(this.inMemoryRulesStorage); IServiceProvider serviceProvider = serviceDescriptors.BuildServiceProvider(); - RulesEngine rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() + var rulesEngine = RulesEngineBuilder.CreateRulesEngine() .SetInMemoryDataSource(serviceProvider) .Configure(options => { options.EnableCompilation = enableCompilation; }) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); // Act - IEnumerable> actual = await rulesEngine.MatchManyAsync(securitySystemActionable, expectedMatchDate, expectedConditions); + var actual = await genericRulesEngine.MatchManyAsync(securitySystemActionable, expectedMatchDate, expectedConditions); // Assert actual.Should().NotBeNull(); diff --git a/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Scenarios/Scenario5/BestServerTests.cs b/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Scenarios/Scenario5/BestServerTests.cs index a8560492..738e6b9a 100644 --- a/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Scenarios/Scenario5/BestServerTests.cs +++ b/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Scenarios/Scenario5/BestServerTests.cs @@ -5,9 +5,8 @@ namespace Rules.Framework.Providers.InMemory.IntegrationTests.Scenarios.Scenario using System.Linq; using System.Threading.Tasks; using FluentAssertions; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.IntegrationTests.Common.Scenarios.Scenario5; - using Rules.Framework.Providers.InMemory; using Xunit; public class BestServerTests @@ -16,33 +15,33 @@ public class BestServerTests { new object[] { - new[] + new Dictionary { - new Condition(BestServerConditions.Price,100), - new Condition(BestServerConditions.Memory,12), - new Condition(BestServerConditions.StoragePartionable,true), - new Condition(BestServerConditions.Brand,"AMD") + { BestServerConditions.Price,100 }, + { BestServerConditions.Memory,12 }, + { BestServerConditions.StoragePartionable,true }, + { BestServerConditions.Brand,"AMD" }, }, "Best Server Top5" }, new object[] { - new[] + new Dictionary { - new Condition(BestServerConditions.Price,110), - new Condition(BestServerConditions.Memory,12), - new Condition(BestServerConditions.StoragePartionable,true), - new Condition(BestServerConditions.Brand,"AMD") + { BestServerConditions.Price,110 }, + { BestServerConditions.Memory,12 }, + { BestServerConditions.StoragePartionable,true }, + { BestServerConditions.Brand,"AMD" }, }, "Best Server Default" }, new object[] { - new[] + new Dictionary { - new Condition(BestServerConditions.Price,100), - new Condition(BestServerConditions.Memory,12), - new Condition(BestServerConditions.StoragePartionable,true), + { BestServerConditions.Price,100 }, + { BestServerConditions.Memory,12 }, + { BestServerConditions.StoragePartionable,true }, }, "Best Server Default" } @@ -50,40 +49,43 @@ public class BestServerTests [Theory] [MemberData(nameof(DataTest))] - public async Task BestServer_InEvaluation(IEnumerable> conditions, string expectedRuleName) + public async Task BestServer_InEvaluation(Dictionary conditions, string expectedRuleName) { // Arrange var rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetInMemoryDataSource() .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); + await genericRulesEngine.CreateRulesetAsync(BestServerConfigurations.BestServerEvaluation); // Act 1 - Create rule with "in" operator - var ruleBuilderResult = RuleBuilder.NewRule() - .WithName("Best Server Top5") - .WithDatesInterval(DateTime.Parse("2021-05-29Z"), DateTime.Parse("2021-05-31Z")) - .WithContent(BestServerConfigurations.BestServerEvaluation, "Top5") - .WithCondition(c => c + var ruleBuilderResult = Rule.Create("Best Server Top5") + .InRuleset(BestServerConfigurations.BestServerEvaluation) + .SetContent("Top5") + .Since(DateTime.Parse("2021-05-29Z")) + .Until(DateTime.Parse("2021-05-31Z")) + .ApplyWhen(c => c .And(a => a .Value(BestServerConditions.Price, Operators.In, new[] { 100m, 200m, 300m }) .Value(BestServerConditions.Memory, Operators.In, new[] { 12, 16, 24, 36 }) .Value(BestServerConditions.Memory, Operators.NotIn, new[] { 4, 8 }) .Value(BestServerConditions.StoragePartionable, Operators.In, new[] { true }) .Value(BestServerConditions.Brand, Operators.In, new[] { "AMD", "Intel", "Cisco" }) - )) + ) + ) .Build(); // Act 2 - Create rule default - var ruleBuilderResultDefault = RuleBuilder.NewRule() - .WithName("Best Server Default") - .WithDatesInterval(DateTime.Parse("2021-05-29Z"), DateTime.Parse("2021-05-31Z")) - .WithContent(BestServerConfigurations.BestServerEvaluation, "Default") + var ruleBuilderResultDefault = Rule.Create("Best Server Default") + .InRuleset(BestServerConfigurations.BestServerEvaluation) + .SetContent("Default") + .Since(DateTime.Parse("2021-05-29Z")) + .Until(DateTime.Parse("2021-05-31Z")) .Build(); // Assert 1 ruleBuilderResult.Should().NotBeNull(); - string errors = ruleBuilderResult.Errors.Any() ? ruleBuilderResult.Errors.Aggregate((s1, s2) => $"{s1}\n- {s2}") : string.Empty; + var errors = ruleBuilderResult.Errors.Any() ? ruleBuilderResult.Errors.Aggregate((s1, s2) => $"{s1}\n- {s2}") : string.Empty; ruleBuilderResult.IsSuccess.Should().BeTrue( $"errors have occurred while creating rule: \n[\n- {errors}\n]"); @@ -94,12 +96,12 @@ public async Task BestServer_InEvaluation(IEnumerable() - .WithConditionType() .SetMongoDbDataSource(this.mongoClient, this.mongoDbProviderSettings) .Configure(c => c.PriorityCriteria = PriorityCriterias.TopmostRuleWins) .Build(); + + this.RulesEngine = rulesEngine.MakeGeneric(); + this.RulesEngine.CreateRulesetAsync(testRuleset).GetAwaiter().GetResult(); } - protected RulesEngine RulesEngine { get; } + protected IRulesEngine RulesEngine { get; } public void Dispose() { @@ -47,23 +48,21 @@ protected void AddRules(IEnumerable ruleSpecifications) this.RulesEngine.AddRuleAsync( ruleSpecification.Rule, ruleSpecification.RuleAddPriorityOption) - .ConfigureAwait(false) .GetAwaiter() .GetResult(); } } - protected async Task> MatchOneAsync( + protected async Task> MatchOneAsync( DateTime matchDate, - Condition[] conditions) => await RulesEngine.MatchOneAsync( - TestContentType, + Dictionary conditions) => await RulesEngine.MatchOneAsync( + TestRuleset, matchDate, - conditions) - .ConfigureAwait(false); + conditions); private static MongoClient CreateMongoClient() { - MongoClientSettings settings = MongoClientSettings.FromConnectionString($"mongodb://{SettingsProvider.GetMongoDbHost()}:27017"); + var settings = MongoClientSettings.FromConnectionString($"mongodb://{SettingsProvider.GetMongoDbHost()}:27017"); settings.ClusterConfigurator = (cb) => { cb.Subscribe(e => @@ -74,7 +73,7 @@ private static MongoClient CreateMongoClient() return new MongoClient(settings); } - private MongoDbProviderSettings CreateProviderSettings() => new MongoDbProviderSettings + private static MongoDbProviderSettings CreateProviderSettings() => new MongoDbProviderSettings { DatabaseName = "rules-framework-tests", RulesCollectionName = "features-tests" diff --git a/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Features/RulesEngine/RulesMatching/RulesDeactivateAndActivateTests.cs b/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Features/RulesEngine/RulesMatching/RulesDeactivateAndActivateTests.cs index b15f5623..614266ec 100644 --- a/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Features/RulesEngine/RulesMatching/RulesDeactivateAndActivateTests.cs +++ b/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Features/RulesEngine/RulesMatching/RulesDeactivateAndActivateTests.cs @@ -3,8 +3,7 @@ namespace Rules.Framework.Providers.MongoDb.IntegrationTests.Features.RulesEngin using System; using System.Collections.Generic; using System.Threading.Tasks; - using Rules.Framework.Builder; - using Rules.Framework.Core; + using Rules.Framework.Generic; using Rules.Framework.IntegrationTests.Common.Features; using Rules.Framework.Tests.Stubs; using Xunit; @@ -17,25 +16,24 @@ public class RulesDeactivateAndActivateTests : RulesEngineTestsBase private static readonly string rule2Value = "DummyRule2 Value"; private static readonly DateTime ruleEndDate = new DateTime(2021, 02, 01); private static readonly DateTime ruleStartDate = new DateTime(2020, 01, 01); - private static readonly ContentType TestContentType = ContentType.ContentType1; - private readonly Rule rule1; - private readonly Rule rule2; + private static readonly RulesetNames testRuleset = RulesetNames.Sample1; + private readonly Rule rule1; + private readonly Rule rule2; - public RulesDeactivateAndActivateTests() : base(TestContentType) + public RulesDeactivateAndActivateTests() : base(testRuleset) { - rule1 = RuleBuilder - .NewRule() - .WithName(rule1Name) - .WithContent(TestContentType, rule1Value) - .WithDatesInterval(ruleStartDate, ruleEndDate) + rule1 = Rule.Create(rule1Name) + .InRuleset(testRuleset) + .SetContent(rule1Value) + .Since(ruleStartDate) + .Until(ruleEndDate) .Build().Rule; - rule2 = - RuleBuilder - .NewRule() - .WithName(rule2Name) - .WithContent(TestContentType, rule2Value) - .WithDatesInterval(ruleStartDate, ruleEndDate) + rule2 = Rule.Create(rule2Name) + .InRuleset(testRuleset) + .SetContent(rule2Value) + .Since(ruleStartDate) + .Until(ruleEndDate) .Build().Rule; this.AddRules(this.CreateTestRules()); @@ -45,7 +43,7 @@ public RulesDeactivateAndActivateTests() : base(TestContentType) public async Task RulesEngine_DeactivateThenActivateRule_Validations() { // Arrange - var emptyConditions = Array.Empty>(); + var emptyConditions = new Dictionary(); var matchDate = new DateTime(2020, 01, 02); // Act 1: Deactivate the rule @@ -53,7 +51,7 @@ public async Task RulesEngine_DeactivateThenActivateRule_Validations() // Assert 1: Rule 2 must be found Assert.True(deactivateResult.IsSuccess); - var actualMatch1 = await this.MatchOneAsync(matchDate, emptyConditions).ConfigureAwait(false); + var actualMatch1 = await this.MatchOneAsync(matchDate, emptyConditions); Assert.NotNull(actualMatch1); Assert.Equal(rule2.Name, actualMatch1.Name); @@ -62,7 +60,7 @@ public async Task RulesEngine_DeactivateThenActivateRule_Validations() // Assert 2: Rule 1 must be found Assert.True(activateResult.IsSuccess); - var actualMatch2 = await this.MatchOneAsync(matchDate, emptyConditions).ConfigureAwait(false); + var actualMatch2 = await this.MatchOneAsync(matchDate, emptyConditions); Assert.NotNull(actualMatch2); Assert.Equal(rule1.Name, actualMatch2.Name); } @@ -71,8 +69,8 @@ private IEnumerable CreateTestRules() { var ruleSpecs = new List { - new RuleSpecification(rule1, RuleAddPriorityOption.ByPriorityNumber(1)), - new RuleSpecification(rule2, RuleAddPriorityOption.ByPriorityNumber(2)) + new(rule1, RuleAddPriorityOption.ByPriorityNumber(1)), + new(rule2, RuleAddPriorityOption.ByPriorityNumber(2)) }; return ruleSpecs; diff --git a/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Features/RulesEngine/RulesMatching/RulesInSequenceTests.cs b/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Features/RulesEngine/RulesMatching/RulesInSequenceTests.cs index ba279a38..5c540443 100644 --- a/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Features/RulesEngine/RulesMatching/RulesInSequenceTests.cs +++ b/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Features/RulesEngine/RulesMatching/RulesInSequenceTests.cs @@ -3,7 +3,6 @@ namespace Rules.Framework.Providers.MongoDb.IntegrationTests.Features.RulesEngin using System; using System.Collections.Generic; using System.Threading.Tasks; - using Rules.Framework.Builder; using Rules.Framework.IntegrationTests.Common.Features; using Rules.Framework.Tests.Stubs; using Xunit; @@ -17,11 +16,11 @@ public class RulesInSequenceTests : RulesEngineTestsBase private static readonly string rule2Name = "DummyRule2"; private static readonly string rule2Value = "DummyRule2 Value"; private static readonly DateTime ruleChangeDate = new DateTime(2020, 07, 01, 14, 30, 00); - private static readonly ContentType TestContentType = ContentType.ContentType1; + private static readonly RulesetNames testRuleset = RulesetNames.Sample1; - public RulesInSequenceTests() : base(TestContentType) + public RulesInSequenceTests() : base(testRuleset) { - this.AddRules(this.CreateTestRules()); + this.AddRules(CreateTestRules()); } public static IEnumerable FailureCases => @@ -45,10 +44,10 @@ public RulesInSequenceTests() : base(TestContentType) public async Task RulesEngine_MatchOneAsync_OutsideRulesPeriod_Failure(DateTime matchDate) { // Arrange - var emptyConditions = Array.Empty>(); + var emptyConditions = new Dictionary(); // Act - var actualMatch = await this.MatchOneAsync(matchDate, emptyConditions).ConfigureAwait(false); + var actualMatch = await this.MatchOneAsync(matchDate, emptyConditions); // Assert Assert.Null(actualMatch); @@ -59,37 +58,35 @@ public async Task RulesEngine_MatchOneAsync_OutsideRulesPeriod_Failure(DateTime public async Task RulesEngine_MatchOneAsync_WithRulesInSequence_ReturnsCorrectRule(DateTime matchDate, string expectedName, string expectedValue) { // Arrange - var emptyConditions = Array.Empty>(); + var emptyConditions = new Dictionary(); // Act - var actualMatch = await this.MatchOneAsync(matchDate, emptyConditions).ConfigureAwait(false); + var actualMatch = await this.MatchOneAsync(matchDate, emptyConditions); // Assert Assert.Equal(expectedName, actualMatch.Name); - Assert.Equal(TestContentType, actualMatch.ContentContainer.ContentType); + Assert.Equal(testRuleset, actualMatch.Ruleset); Assert.Equal(expectedValue, actualMatch.ContentContainer.GetContentAs()); } - private IEnumerable CreateTestRules() + private static List CreateTestRules() { var ruleSpecs = new List(); - var rule1 = - RuleBuilder - .NewRule() - .WithName(rule1Name) - .WithContent(TestContentType, rule1Value) - .WithDatesInterval(rule1StartDate, ruleChangeDate) + var rule1 = Rule.Create(rule1Name) + .InRuleset(testRuleset) + .SetContent(rule1Value) + .Since(rule1StartDate) + .Until(ruleChangeDate) .Build(); ruleSpecs.Add(new RuleSpecification(rule1.Rule, RuleAddPriorityOption.ByPriorityNumber(1))); - var rule2 = - RuleBuilder - .NewRule() - .WithName(rule2Name) - .WithContent(TestContentType, rule2Value) - .WithDatesInterval(ruleChangeDate, rule2EndDate) + var rule2 = Rule.Create(rule2Name) + .InRuleset(testRuleset) + .SetContent(rule2Value) + .Since(ruleChangeDate) + .Until(rule2EndDate) .Build(); ruleSpecs.Add(new RuleSpecification(rule2.Rule, RuleAddPriorityOption.ByPriorityNumber(2))); diff --git a/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Features/RulesEngine/RulesMatching/RulesUpdateDateEndTests.cs b/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Features/RulesEngine/RulesMatching/RulesUpdateDateEndTests.cs index 6487c118..cc346c7f 100644 --- a/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Features/RulesEngine/RulesMatching/RulesUpdateDateEndTests.cs +++ b/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Features/RulesEngine/RulesMatching/RulesUpdateDateEndTests.cs @@ -3,8 +3,7 @@ namespace Rules.Framework.Providers.MongoDb.IntegrationTests.Features.RulesEngin using System; using System.Collections.Generic; using System.Threading.Tasks; - using Rules.Framework.Builder; - using Rules.Framework.Core; + using Rules.Framework.Generic; using Rules.Framework.IntegrationTests.Common.Features; using Rules.Framework.Tests.Stubs; using Xunit; @@ -17,25 +16,24 @@ public class RulesUpdateDateEndTests : RulesEngineTestsBase private static readonly string rule2Value = "DummyRule2 Value"; private static readonly DateTime ruleEndDate = new DateTime(2021, 02, 01); private static readonly DateTime ruleStartDate = new DateTime(2020, 01, 01); - private static readonly ContentType TestContentType = ContentType.ContentType1; - private readonly Rule rule1; - private readonly Rule rule2; + private static readonly RulesetNames testRuleset = RulesetNames.Sample1; + private readonly Rule rule1; + private readonly Rule rule2; - public RulesUpdateDateEndTests() : base(TestContentType) + public RulesUpdateDateEndTests() : base(testRuleset) { - rule1 = RuleBuilder - .NewRule() - .WithName(rule1Name) - .WithContent(TestContentType, rule1Value) - .WithDatesInterval(ruleStartDate, ruleEndDate) + rule1 = Rule.Create(rule1Name) + .InRuleset(testRuleset) + .SetContent(rule1Value) + .Since(ruleStartDate) + .Until(ruleEndDate) .Build().Rule; - rule2 = - RuleBuilder - .NewRule() - .WithName(rule2Name) - .WithContent(TestContentType, rule2Value) - .WithDatesInterval(ruleStartDate, ruleEndDate) + rule2 = Rule.Create(rule2Name) + .InRuleset(testRuleset) + .SetContent(rule2Value) + .Since(ruleStartDate) + .Until(ruleEndDate) .Build().Rule; this.AddRules(this.CreateTestRules()); @@ -53,7 +51,7 @@ public RulesUpdateDateEndTests() : base(TestContentType) public async Task RulesEngine_UpdateRuleDateEnd_Validations(DateTime dateEnd, bool success) { // Arrange - var emptyConditions = Array.Empty>(); + var emptyConditions = new Dictionary(); var matchDate = new DateTime(2020, 01, 02); // Act @@ -64,13 +62,13 @@ public async Task RulesEngine_UpdateRuleDateEnd_Validations(DateTime dateEnd, bo Assert.Equal(success, updateResult.IsSuccess); if (success) { - var actualMatch = await this.MatchOneAsync(matchDate, emptyConditions).ConfigureAwait(false); + var actualMatch = await this.MatchOneAsync(matchDate, emptyConditions); Assert.NotNull(actualMatch); Assert.Equal(rule2.Name, actualMatch.Name); } } - private IEnumerable CreateTestRules() + private List CreateTestRules() { var ruleSpecs = new List { @@ -81,4 +79,4 @@ private IEnumerable CreateTestRules() return ruleSpecs; } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Scenarios/Scenario2/CarInsuranceAdvisorTests.cs b/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Scenarios/Scenario2/CarInsuranceAdvisorTests.cs index 5c7dd99f..51fcdba9 100644 --- a/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Scenarios/Scenario2/CarInsuranceAdvisorTests.cs +++ b/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Scenarios/Scenario2/CarInsuranceAdvisorTests.cs @@ -30,20 +30,20 @@ public CarInsuranceAdvisorTests() this.mongoClient = CreateMongoClient(); this.mongoDbProviderSettings = CreateProviderSettings(); - Stream? rulesFile = Assembly.GetExecutingAssembly() + var rulesFile = Assembly.GetExecutingAssembly() .GetManifestResourceStream("Rules.Framework.Providers.MongoDb.IntegrationTests.Scenarios.Scenario2.rules-framework-tests.car-insurance-advisor.json"); IEnumerable rules; - using (StreamReader streamReader = new StreamReader(rulesFile ?? throw new InvalidOperationException("Could not load rules file."))) + using (var streamReader = new StreamReader(rulesFile ?? throw new InvalidOperationException("Could not load rules file."))) { - string json = streamReader.ReadToEnd(); + var json = streamReader.ReadToEnd(); - IEnumerable array = JsonConvert.DeserializeObject>(json, new JsonSerializerSettings + var array = JsonConvert.DeserializeObject>(json, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All }); - rules = array.Select(t => + rules = array!.Select(t => { t.Content = t.Content as string; @@ -51,11 +51,25 @@ public CarInsuranceAdvisorTests() }).ToList(); } + var rulesets = rules + .Select(r => new RulesetDataModel + { + Creation = DateTime.UtcNow, + Id = Guid.NewGuid(), + Name = r.Ruleset, + }) + .Distinct() + .ToArray(); + var mongoDatabase = this.mongoClient.GetDatabase(this.mongoDbProviderSettings.DatabaseName); - mongoDatabase.DropCollection(this.mongoDbProviderSettings.RulesCollectionName); - var mongoCollection = mongoDatabase.GetCollection(this.mongoDbProviderSettings.RulesCollectionName); - mongoCollection.InsertMany(rules); + mongoDatabase.DropCollection(this.mongoDbProviderSettings.RulesetsCollectionName); + var contentTypesMongoCollection = mongoDatabase.GetCollection(this.mongoDbProviderSettings.RulesetsCollectionName); + contentTypesMongoCollection.InsertMany(rulesets); + + mongoDatabase.DropCollection(this.mongoDbProviderSettings.RulesCollectionName); + var rulesMongoCollection = mongoDatabase.GetCollection(this.mongoDbProviderSettings.RulesCollectionName); + rulesMongoCollection.InsertMany(rules); } public void Dispose() @@ -71,17 +85,15 @@ public async Task GetCarInsuranceAdvice_RepairCostsNotWorthIt_ReturnsRefusePayme { // Arrange var expected = CarInsuranceAdvices.RefusePaymentPerFranchise; - const ContentTypes expectedContent = ContentTypes.CarInsuranceAdvice; + const CarInsuranceRulesetNames expectedRuleset = CarInsuranceRulesetNames.CarInsuranceAdvice; var expectedMatchDate = new DateTime(2018, 06, 01); - var expectedConditions = new[] + var expectedConditions = new Dictionary { - new Condition(ConditionTypes.RepairCosts, 800.00000m), - new Condition(ConditionTypes.RepairCostsCommercialValueRate, 23.45602m) + { CarInsuranceConditionNames.RepairCosts, 800.00000m }, + { CarInsuranceConditionNames.RepairCostsCommercialValueRate, 23.45602m }, }; var rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetMongoDbDataSource(this.mongoClient, this.mongoDbProviderSettings) .Configure(opt => { @@ -89,9 +101,10 @@ public async Task GetCarInsuranceAdvice_RepairCostsNotWorthIt_ReturnsRefusePayme opt.PriorityCriteria = PriorityCriterias.BottommostRuleWins; }) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); // Act - var actual = await rulesEngine.MatchOneAsync(expectedContent, expectedMatchDate, expectedConditions).ConfigureAwait(false); + var actual = await genericRulesEngine.MatchOneAsync(expectedRuleset, expectedMatchDate, expectedConditions); // Assert actual.Should().NotBeNull(); @@ -105,17 +118,15 @@ public async Task GetCarInsuranceAdvice_RepairCostsNotWorthIt_ReturnsRefusePayme public async Task GetCarInsuranceAdvice_UpdatesRuleAndAddsNewOneAndEvaluates_ReturnsPay(bool enableCompilation) { // Arrange - const ContentTypes expectedContent = ContentTypes.CarInsuranceAdvice; + const CarInsuranceRulesetNames expectedContent = CarInsuranceRulesetNames.CarInsuranceAdvice; var expectedMatchDate = new DateTime(2018, 06, 01); - var expectedConditions = new[] + var expectedConditions = new Dictionary { - new Condition(ConditionTypes.RepairCosts, 800.00000m), - new Condition(ConditionTypes.RepairCostsCommercialValueRate, 23.45602m) + { CarInsuranceConditionNames.RepairCosts, 800.00000m }, + { CarInsuranceConditionNames.RepairCostsCommercialValueRate, 23.45602m }, }; var rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetMongoDbDataSource(this.mongoClient, this.mongoDbProviderSettings) .Configure(opt => { @@ -123,29 +134,30 @@ public async Task GetCarInsuranceAdvice_UpdatesRuleAndAddsNewOneAndEvaluates_Ret opt.PriorityCriteria = PriorityCriterias.BottommostRuleWins; }) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); - var rulesDataSource = CreateRulesDataSourceTest(this.mongoClient, this.mongoDbProviderSettings); + var rulesDataSource = CreateRulesDataSourceTest(this.mongoClient, this.mongoDbProviderSettings); - var ruleBuilderResult = RuleBuilder.NewRule() - .WithName("Car Insurance Advise on self damage coverage") - .WithDateBegin(DateTime.Parse("2018-01-01")) - .WithContent(ContentTypes.CarInsuranceAdvice, CarInsuranceAdvices.Pay) + var ruleBuilderResult = Rule.Create("Car Insurance Advise on self damage coverage") + .InRuleset(CarInsuranceRulesetNames.CarInsuranceAdvice) + .SetContent(CarInsuranceAdvices.Pay) + .Since(DateTime.Parse("2018-01-01")) .Build(); - var existentRules1 = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs + var existentRules1 = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs { Name = "Car Insurance Advise on repair costs lower than franchise boundary" - }).ConfigureAwait(false); + }); var ruleToAdd = ruleBuilderResult.Rule; var ruleToUpdate1 = existentRules1.First(); ruleToUpdate1.Priority = 2; // Act 1 - var updateOperationResult1 = await rulesEngine.UpdateRuleAsync(ruleToUpdate1).ConfigureAwait(false); + var updateOperationResult1 = await rulesEngine.UpdateRuleAsync(ruleToUpdate1); - var eval1 = await rulesEngine.MatchOneAsync(expectedContent, expectedMatchDate, expectedConditions).ConfigureAwait(false); + var eval1 = await genericRulesEngine.MatchOneAsync(expectedContent, expectedMatchDate, expectedConditions); - var rules1 = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs()).ConfigureAwait(false); + var rules1 = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs()); // Assert 1 updateOperationResult1.Should().NotBeNull(); @@ -172,16 +184,16 @@ public async Task GetCarInsuranceAdvice_UpdatesRuleAndAddsNewOneAndEvaluates_Ret AtRuleNameOptionValue = "Car Insurance Advise on repair costs lower than franchise boundary" }); - var eval2 = await rulesEngine.MatchOneAsync(expectedContent, expectedMatchDate, expectedConditions).ConfigureAwait(false); + var eval2 = await genericRulesEngine.MatchOneAsync(expectedContent, expectedMatchDate, expectedConditions); - var rules2 = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs()).ConfigureAwait(false); + var rules2 = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs()); // Assert 2 addOperationResult.Should().NotBeNull(); addOperationResult.IsSuccess.Should().BeTrue(); eval2.Priority.Should().Be(3); - CarInsuranceAdvices content2 = eval2.ContentContainer.GetContentAs(); + var content2 = eval2.ContentContainer.GetContentAs(); content2.Should().Be(CarInsuranceAdvices.RefusePaymentPerFranchise); var rule21 = rules2.First(r => r.Name == "Car Insurance Advise on repair costs lesser than 80% of commercial value"); @@ -198,18 +210,18 @@ public async Task GetCarInsuranceAdvice_UpdatesRuleAndAddsNewOneAndEvaluates_Ret rule24.Priority.Should().Be(4); // Act 3 - var existentRules2 = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs + var existentRules2 = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs { Name = "Car Insurance Advise on repair costs lower than franchise boundary" - }).ConfigureAwait(false); + }); var ruleToUpdate2 = existentRules2.First(); ruleToUpdate2.Priority = 4; - var updateOperationResult2 = await rulesEngine.UpdateRuleAsync(ruleToUpdate2).ConfigureAwait(false); + var updateOperationResult2 = await rulesEngine.UpdateRuleAsync(ruleToUpdate2); - var eval3 = await rulesEngine.MatchOneAsync(expectedContent, expectedMatchDate, expectedConditions).ConfigureAwait(false); + var eval3 = await genericRulesEngine.MatchOneAsync(expectedContent, expectedMatchDate, expectedConditions); - var rules3 = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs()).ConfigureAwait(false); + var rules3 = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs()); // Assert 3 updateOperationResult2.Should().NotBeNull(); @@ -246,19 +258,19 @@ private static MongoClient CreateMongoClient() return new MongoClient(settings); } - private static IRulesDataSource CreateRulesDataSourceTest( + private static MongoDbProviderRulesDataSource CreateRulesDataSourceTest( IMongoClient mongoClient, MongoDbProviderSettings mongoDbProviderSettings) { - var contentSerializationProvider = new DynamicToStrongTypeContentSerializationProvider(); - var ruleFactory = new RuleFactory(contentSerializationProvider); - return new MongoDbProviderRulesDataSource( + var contentSerializationProvider = new DynamicToStrongTypeContentSerializationProvider(); + var ruleFactory = new RuleFactory(contentSerializationProvider); + return new MongoDbProviderRulesDataSource( mongoClient, mongoDbProviderSettings, ruleFactory); } - private MongoDbProviderSettings CreateProviderSettings() => new MongoDbProviderSettings + private MongoDbProviderSettings CreateProviderSettings() => new() { DatabaseName = "rules-framework-tests", RulesCollectionName = "car-insurance-advisor" diff --git a/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Scenarios/Scenario2/rules-framework-tests.car-insurance-advisor.json b/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Scenarios/Scenario2/rules-framework-tests.car-insurance-advisor.json index c25b626b..08937c0c 100644 --- a/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Scenarios/Scenario2/rules-framework-tests.car-insurance-advisor.json +++ b/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Scenarios/Scenario2/rules-framework-tests.car-insurance-advisor.json @@ -1,72 +1,72 @@ [ - { - "_id": "5e9c659bf1b6421fe0eee8e2", - "Content": "Pay", - "ContentType": "CarInsuranceAdvice", - "DateBegin": "2018-01-01T00:00:00Z", - "DateEnd": null, - "Name": "Car Insurance Advise on repair costs lesser than 80% of commercial value", - "Priority": 1, - "RootCondition": { - "$type": "Rules.Framework.Providers.MongoDb.DataModel.ValueConditionNodeDataModel, Rules.Framework.Providers.MongoDb", - "_t": "value", - "ConditionType": "RepairCostsCommercialValueRate", - "DataType": "Decimal", - "LogicalOperator": "Eval", - "Operator": "LesserThan", - "Operand": 80 - } - }, - { - "_id": "5e9c659bf1b6421fe0eee8e1", - "Content": "PayNewCar", - "ContentType": "CarInsuranceAdvice", - "DateBegin": "2018-01-01T00:00:00Z", - "DateEnd": null, - "Name": "Car Insurance Advise on repair costs greater than 80% of commercial value", - "Priority": 2, - "RootCondition": { - "$type": "Rules.Framework.Providers.MongoDb.DataModel.ValueConditionNodeDataModel, Rules.Framework.Providers.MongoDb", - "_t": "value", - "ConditionType": "RepairCostsCommercialValueRate", - "DataType": "Decimal", - "LogicalOperator": "Eval", - "Operator": "GreaterThanOrEqual", - "Operand": 80 - } - }, - { - "_id": "5e9c659bf1b6421fe0eee8e0", - "Content": "RefusePaymentPerFranchise", - "ContentType": "CarInsuranceAdvice", - "DateBegin": "2018-01-01T00:00:00Z", - "DateEnd": null, - "Name": "Car Insurance Advise on repair costs lower than franchise boundary", - "Priority": 3, - "RootCondition": { - "$type": "Rules.Framework.Providers.MongoDb.DataModel.ComposedConditionNodeDataModel, Rules.Framework.Providers.MongoDb", - "_t": "composed", - "LogicalOperator": "And", - "ChildConditionNodes": [ - { - "$type": "Rules.Framework.Providers.MongoDb.DataModel.ValueConditionNodeDataModel, Rules.Framework.Providers.MongoDb", - "_t": "value", - "ConditionType": "RepairCosts", - "DataType": "Decimal", - "LogicalOperator": "Eval", - "Operator": "LesserThan", - "Operand": 1000 - }, - { - "$type": "Rules.Framework.Providers.MongoDb.DataModel.ValueConditionNodeDataModel, Rules.Framework.Providers.MongoDb", - "_t": "value", - "ConditionType": "RepairCostsCommercialValueRate", - "DataType": "Decimal", - "LogicalOperator": "Eval", - "Operator": "LesserThan", - "Operand": 80 + { + "_id": "5e9c659bf1b6421fe0eee8e2", + "Content": "Pay", + "Ruleset": "CarInsuranceAdvice", + "DateBegin": "2018-01-01T00:00:00Z", + "DateEnd": null, + "Name": "Car Insurance Advise on repair costs lesser than 80% of commercial value", + "Priority": 1, + "RootCondition": { + "$type": "Rules.Framework.Providers.MongoDb.DataModel.ValueConditionNodeDataModel, Rules.Framework.Providers.MongoDb", + "_t": "value", + "Condition": "RepairCostsCommercialValueRate", + "DataType": "Decimal", + "LogicalOperator": "Eval", + "Operator": "LesserThan", + "Operand": 80 + } + }, + { + "_id": "5e9c659bf1b6421fe0eee8e1", + "Content": "PayNewCar", + "Ruleset": "CarInsuranceAdvice", + "DateBegin": "2018-01-01T00:00:00Z", + "DateEnd": null, + "Name": "Car Insurance Advise on repair costs greater than 80% of commercial value", + "Priority": 2, + "RootCondition": { + "$type": "Rules.Framework.Providers.MongoDb.DataModel.ValueConditionNodeDataModel, Rules.Framework.Providers.MongoDb", + "_t": "value", + "Condition": "RepairCostsCommercialValueRate", + "DataType": "Decimal", + "LogicalOperator": "Eval", + "Operator": "GreaterThanOrEqual", + "Operand": 80 + } + }, + { + "_id": "5e9c659bf1b6421fe0eee8e0", + "Content": "RefusePaymentPerFranchise", + "Ruleset": "CarInsuranceAdvice", + "DateBegin": "2018-01-01T00:00:00Z", + "DateEnd": null, + "Name": "Car Insurance Advise on repair costs lower than franchise boundary", + "Priority": 3, + "RootCondition": { + "$type": "Rules.Framework.Providers.MongoDb.DataModel.ComposedConditionNodeDataModel, Rules.Framework.Providers.MongoDb", + "_t": "composed", + "LogicalOperator": "And", + "ChildConditionNodes": [ + { + "$type": "Rules.Framework.Providers.MongoDb.DataModel.ValueConditionNodeDataModel, Rules.Framework.Providers.MongoDb", + "_t": "value", + "Condition": "RepairCosts", + "DataType": "Decimal", + "LogicalOperator": "Eval", + "Operator": "LesserThan", + "Operand": 1000 + }, + { + "$type": "Rules.Framework.Providers.MongoDb.DataModel.ValueConditionNodeDataModel, Rules.Framework.Providers.MongoDb", + "_t": "value", + "Condition": "RepairCostsCommercialValueRate", + "DataType": "Decimal", + "LogicalOperator": "Eval", + "Operator": "LesserThan", + "Operand": 80 + } + ] } - ] } - } ] \ No newline at end of file diff --git a/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Scenarios/Scenario3/BuildingSecuritySystemControlTests.cs b/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Scenarios/Scenario3/BuildingSecuritySystemControlTests.cs index a9d151c9..5fb6d81b 100644 --- a/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Scenarios/Scenario3/BuildingSecuritySystemControlTests.cs +++ b/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Scenarios/Scenario3/BuildingSecuritySystemControlTests.cs @@ -10,6 +10,7 @@ namespace Rules.Framework.Providers.MongoDb.IntegrationTests.Scenarios.Scenario3 using FluentAssertions; using MongoDB.Driver; using Newtonsoft.Json; + using Rules.Framework; using Rules.Framework.IntegrationTests.Common.Scenarios.Scenario3; using Rules.Framework.Providers.MongoDb; using Rules.Framework.Providers.MongoDb.DataModel; @@ -17,7 +18,7 @@ namespace Rules.Framework.Providers.MongoDb.IntegrationTests.Scenarios.Scenario3 public sealed class BuildingSecuritySystemControlTests : IDisposable { - private readonly IMongoClient mongoClient; + private readonly MongoClient mongoClient; private readonly MongoDbProviderSettings mongoDbProviderSettings; public BuildingSecuritySystemControlTests() @@ -25,20 +26,20 @@ public BuildingSecuritySystemControlTests() this.mongoClient = CreateMongoClient(); this.mongoDbProviderSettings = CreateProviderSettings(); - Stream? rulesFile = Assembly.GetExecutingAssembly() + var rulesFile = Assembly.GetExecutingAssembly() .GetManifestResourceStream("Rules.Framework.Providers.MongoDb.IntegrationTests.Scenarios.Scenario3.rules-framework-tests.security-system-actionables.json"); IEnumerable rules; using (var streamReader = new StreamReader(rulesFile ?? throw new InvalidOperationException("Could not load rules file."))) { - string json = streamReader.ReadToEnd(); + var json = streamReader.ReadToEnd(); var array = JsonConvert.DeserializeObject>(json, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All }); - rules = array.Select(t => + rules = array!.Select(t => { SecuritySystemAction securitySystemAction = t.Content.ToObject(); dynamic dynamicContent = new ExpandoObject(); @@ -50,11 +51,25 @@ public BuildingSecuritySystemControlTests() }).ToList(); } + var contentTypes = rules + .Select(r => new RulesetDataModel + { + Creation = DateTime.UtcNow, + Id = Guid.NewGuid(), + Name = r.Ruleset, + }) + .Distinct() + .ToArray(); + var mongoDatabase = this.mongoClient.GetDatabase(this.mongoDbProviderSettings.DatabaseName); - mongoDatabase.DropCollection(this.mongoDbProviderSettings.RulesCollectionName); - var mongoCollection = mongoDatabase.GetCollection(this.mongoDbProviderSettings.RulesCollectionName); - mongoCollection.InsertMany(rules); + mongoDatabase.DropCollection(this.mongoDbProviderSettings.RulesetsCollectionName); + var contentTypesMongoCollection = mongoDatabase.GetCollection(this.mongoDbProviderSettings.RulesetsCollectionName); + contentTypesMongoCollection.InsertMany(contentTypes); + + mongoDatabase.DropCollection(this.mongoDbProviderSettings.RulesCollectionName); + var rulesMongoCollection = mongoDatabase.GetCollection(this.mongoDbProviderSettings.RulesCollectionName); + rulesMongoCollection.InsertMany(rules); } [Theory] @@ -66,43 +81,38 @@ public async Task BuildingSecuritySystem_FireScenario_ReturnsActionsToTrigger(bo const SecuritySystemActionables securitySystemActionable = SecuritySystemActionables.FireSystem; var expectedMatchDate = new DateTime(2018, 06, 01); - var expectedConditions = new[] + var expectedConditions = new Dictionary { - new Condition(SecuritySystemConditions.TemperatureCelsius, 100.0m), - new Condition(SecuritySystemConditions.SmokeRate, 55.0m), - new Condition(SecuritySystemConditions.PowerStatus, "Online") + { SecuritySystemConditions.TemperatureCelsius, 100.0m }, + { SecuritySystemConditions.SmokeRate, 55.0m }, + { SecuritySystemConditions.PowerStatus, "Online" }, }; var rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetMongoDbDataSource(this.mongoClient, this.mongoDbProviderSettings) .Configure(opt => { opt.EnableCompilation = enableCompilation; }) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); // Act - var newRuleResult = RuleBuilder.NewRule() - .WithName("Activate ventilation system rule") - .WithDateBegin(new DateTime(2018, 01, 01)) - .WithContent(SecuritySystemActionables.FireSystem, new SecuritySystemAction + var newRuleResult = Rule.Create("Activate ventilation system rule") + .InRuleset(SecuritySystemActionables.FireSystem) + .SetContent(new SecuritySystemAction { ActionId = new Guid("ef0d65ae-ec76-492a-84db-5cb9090c3eaa"), ActionName = "ActivateVentilationSystem" }) - .WithCondition(b => b.AsValued(SecuritySystemConditions.SmokeRate) - .OfDataType() - .WithComparisonOperator(Core.Operators.GreaterThanOrEqual) - .SetOperand(30.0m) - .Build()) + .Since(new DateTime(2018, 01, 01)) + .ApplyWhen(b => b.Value(SecuritySystemConditions.SmokeRate, Operators.GreaterThanOrEqual, 30.0m)) .Build(); var newRule = newRuleResult.Rule; - _ = await rulesEngine.AddRuleAsync(newRule, RuleAddPriorityOption.AtBottom); + _ = await genericRulesEngine.AddRuleAsync(newRule, RuleAddPriorityOption.AtBottom); - var actual = await rulesEngine.MatchManyAsync(securitySystemActionable, expectedMatchDate, expectedConditions); + var actual = await genericRulesEngine.MatchManyAsync(securitySystemActionable, expectedMatchDate, expectedConditions); // Assert actual.Should().NotBeNull(); @@ -125,25 +135,24 @@ public async Task BuildingSecuritySystem_PowerFailureScenario_ReturnsActionsToTr const SecuritySystemActionables securitySystemActionable = SecuritySystemActionables.PowerSystem; var expectedMatchDate = new DateTime(2018, 06, 01); - var expectedConditions = new[] + var expectedConditions = new Dictionary { - new Condition(SecuritySystemConditions.TemperatureCelsius, 100.0m), - new Condition(SecuritySystemConditions.SmokeRate, 55.0m), - new Condition(SecuritySystemConditions.PowerStatus, "Offline") + { SecuritySystemConditions.TemperatureCelsius, 100.0m }, + { SecuritySystemConditions.SmokeRate, 55.0m }, + { SecuritySystemConditions.PowerStatus, "Offline" }, }; var rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetMongoDbDataSource(this.mongoClient, this.mongoDbProviderSettings) .Configure(opt => { opt.EnableCompilation = enableCompilation; }) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); // Act - var actual = await rulesEngine.MatchManyAsync(securitySystemActionable, expectedMatchDate, expectedConditions); + var actual = await genericRulesEngine.MatchManyAsync(securitySystemActionable, expectedMatchDate, expectedConditions); // Assert actual.Should().NotBeNull(); @@ -164,25 +173,24 @@ public async Task BuildingSecuritySystem_PowerShutdownScenario_ReturnsActionsToT const SecuritySystemActionables securitySystemActionable = SecuritySystemActionables.PowerSystem; var expectedMatchDate = new DateTime(2018, 06, 01); - var expectedConditions = new[] + var expectedConditions = new Dictionary { - new Condition(SecuritySystemConditions.TemperatureCelsius, 100.0m), - new Condition(SecuritySystemConditions.SmokeRate, 55.0m), - new Condition(SecuritySystemConditions.PowerStatus, "Shutdown") + { SecuritySystemConditions.TemperatureCelsius, 100.0m }, + { SecuritySystemConditions.SmokeRate, 55.0m }, + { SecuritySystemConditions.PowerStatus, "Shutdown" }, }; var rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetMongoDbDataSource(this.mongoClient, this.mongoDbProviderSettings) .Configure(opt => { opt.EnableCompilation = enableCompilation; }) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); // Act - var actual = await rulesEngine.MatchManyAsync(securitySystemActionable, expectedMatchDate, expectedConditions); + var actual = await genericRulesEngine.MatchManyAsync(securitySystemActionable, expectedMatchDate, expectedConditions); // Assert actual.Should().NotBeNull(); @@ -197,14 +205,15 @@ public void Dispose() { var mongoDatabase = this.mongoClient.GetDatabase(this.mongoDbProviderSettings.DatabaseName); mongoDatabase.DropCollection(this.mongoDbProviderSettings.RulesCollectionName); + mongoDatabase.DropCollection(this.mongoDbProviderSettings.RulesetsCollectionName); } - private static MongoClient CreateMongoClient() => new MongoClient($"mongodb://{SettingsProvider.GetMongoDbHost()}:27017"); + private static MongoClient CreateMongoClient() => new($"mongodb://{SettingsProvider.GetMongoDbHost()}:27017"); - private static MongoDbProviderSettings CreateProviderSettings() => new MongoDbProviderSettings + private static MongoDbProviderSettings CreateProviderSettings() => new() { DatabaseName = "rules-framework-tests", - RulesCollectionName = "security-system-actionables" + RulesCollectionName = "security-system-actionables", }; } } \ No newline at end of file diff --git a/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Scenarios/Scenario3/rules-framework-tests.security-system-actionables.json b/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Scenarios/Scenario3/rules-framework-tests.security-system-actionables.json index a8cb0503..c7117a23 100644 --- a/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Scenarios/Scenario3/rules-framework-tests.security-system-actionables.json +++ b/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Scenarios/Scenario3/rules-framework-tests.security-system-actionables.json @@ -1,192 +1,192 @@ [ - { - "_id": "5e63fd9361f24b15f4531bea", - "Content": { - "ActionId": "3dd3eadc-15c2-4f66-9e01-4cdf106fd9d6", - "ActionName": "CallPowerGridPicket" - }, - "ContentType": "PowerSystem", - "DateBegin": "\/Date(1527807600000+0100)\/", - "DateEnd": null, - "Name": "Call power grid picket rule", - "Priority": 6, - "RootCondition": { - "$type": "Rules.Framework.Providers.MongoDb.DataModel.ValueConditionNodeDataModel, Rules.Framework.Providers.MongoDb", - "_t": "value", - "ConditionType": "PowerStatus", - "DataType": "String", - "LogicalOperator": "Eval", - "Operator": "Equal", - "Operand": "Offline" - } - }, - { - "_id": "5e63fd9361f24b15f4531be9", - "Content": { - "ActionId": "5f1c1e0c-6f49-448d-b9ce-7b154436fe5c", - "ActionName": "EnableEmergencyLights" + { + "_id": "5e63fd9361f24b15f4531bea", + "Content": { + "ActionId": "3dd3eadc-15c2-4f66-9e01-4cdf106fd9d6", + "ActionName": "CallPowerGridPicket" + }, + "Ruleset": "PowerSystem", + "DateBegin": "\/Date(1527807600000+0100)\/", + "DateEnd": null, + "Name": "Call power grid picket rule", + "Priority": 6, + "RootCondition": { + "$type": "Rules.Framework.Providers.MongoDb.DataModel.ValueConditionNodeDataModel, Rules.Framework.Providers.MongoDb", + "_t": "value", + "Condition": "PowerStatus", + "DataType": "String", + "LogicalOperator": "Eval", + "Operator": "Equal", + "Operand": "Offline" + } }, - "ContentType": "PowerSystem", - "DateBegin": "\/Date(1527807600000+0100)\/", - "DateEnd": null, - "Name": "Enable emergency lights rule", - "Priority": 5, - "RootCondition": { - "$type": "Rules.Framework.Providers.MongoDb.DataModel.ComposedConditionNodeDataModel, Rules.Framework.Providers.MongoDb", - "_t": "composed", - "LogicalOperator": "Or", - "ChildConditionNodes": [ - { - "$type": "Rules.Framework.Providers.MongoDb.DataModel.ValueConditionNodeDataModel, Rules.Framework.Providers.MongoDb", - "_t": "value", - "ConditionType": "PowerStatus", - "DataType": "String", - "LogicalOperator": "Eval", - "Operator": "Equal", - "Operand": "Offline" + { + "_id": "5e63fd9361f24b15f4531be9", + "Content": { + "ActionId": "5f1c1e0c-6f49-448d-b9ce-7b154436fe5c", + "ActionName": "EnableEmergencyLights" }, - { - "$type": "Rules.Framework.Providers.MongoDb.DataModel.ValueConditionNodeDataModel, Rules.Framework.Providers.MongoDb", - "_t": "value", - "ConditionType": "PowerStatus", - "DataType": "String", - "LogicalOperator": "Eval", - "Operator": "Equal", - "Operand": "Shutdown" + "Ruleset": "PowerSystem", + "DateBegin": "\/Date(1527807600000+0100)\/", + "DateEnd": null, + "Name": "Enable emergency lights rule", + "Priority": 5, + "RootCondition": { + "$type": "Rules.Framework.Providers.MongoDb.DataModel.ComposedConditionNodeDataModel, Rules.Framework.Providers.MongoDb", + "_t": "composed", + "LogicalOperator": "Or", + "ChildConditionNodes": [ + { + "$type": "Rules.Framework.Providers.MongoDb.DataModel.ValueConditionNodeDataModel, Rules.Framework.Providers.MongoDb", + "_t": "value", + "Condition": "PowerStatus", + "DataType": "String", + "LogicalOperator": "Eval", + "Operator": "Equal", + "Operand": "Offline" + }, + { + "$type": "Rules.Framework.Providers.MongoDb.DataModel.ValueConditionNodeDataModel, Rules.Framework.Providers.MongoDb", + "_t": "value", + "Condition": "PowerStatus", + "DataType": "String", + "LogicalOperator": "Eval", + "Operator": "Equal", + "Operand": "Shutdown" + } + ] } - ] - } - }, - { - "_id": "5e63fd9361f24b15f4531be8", - "Content": { - "ActionId": "39958de2-1201-4904-9555-65f97e8a1d1d", - "ActionName": "EnableEmergencyPower" }, - "ContentType": "PowerSystem", - "DateBegin": "\/Date(1527807600000+0100)\/", - "DateEnd": null, - "Name": "Emergency power activation rule", - "Priority": 4, - "RootCondition": { - "$type": "Rules.Framework.Providers.MongoDb.DataModel.ValueConditionNodeDataModel, Rules.Framework.Providers.MongoDb", - "_t": "value", - "ConditionType": "PowerStatus", - "DataType": "String", - "LogicalOperator": "Eval", - "Operator": "Equal", - "Operand": "Offline" - } - }, - { - "_id": "5e63fd9361f24b15f4531be7", - "Content": { - "ActionId": "4f771d96-43c7-4939-a8a8-aabd9ad0eee5", - "ActionName": "CallPolice" + { + "_id": "5e63fd9361f24b15f4531be8", + "Content": { + "ActionId": "39958de2-1201-4904-9555-65f97e8a1d1d", + "ActionName": "EnableEmergencyPower" + }, + "Ruleset": "PowerSystem", + "DateBegin": "\/Date(1527807600000+0100)\/", + "DateEnd": null, + "Name": "Emergency power activation rule", + "Priority": 4, + "RootCondition": { + "$type": "Rules.Framework.Providers.MongoDb.DataModel.ValueConditionNodeDataModel, Rules.Framework.Providers.MongoDb", + "_t": "value", + "Condition": "PowerStatus", + "DataType": "String", + "LogicalOperator": "Eval", + "Operator": "Equal", + "Operand": "Offline" + } }, - "ContentType": "FireSystem", - "DateBegin": "\/Date(1527807600000+0100)\/", - "DateEnd": null, - "Name": "Police call rule", - "Priority": 3, - "RootCondition": { - "$type": "Rules.Framework.Providers.MongoDb.DataModel.ComposedConditionNodeDataModel, Rules.Framework.Providers.MongoDb", - "_t": "composed", - "LogicalOperator": "Or", - "ChildConditionNodes": [ - { - "$type": "Rules.Framework.Providers.MongoDb.DataModel.ValueConditionNodeDataModel, Rules.Framework.Providers.MongoDb", - "_t": "value", - "ConditionType": "TemperatureCelsius", - "DataType": "Decimal", - "LogicalOperator": "Eval", - "Operator": "GreaterThanOrEqual", - "Operand": 70 + { + "_id": "5e63fd9361f24b15f4531be7", + "Content": { + "ActionId": "4f771d96-43c7-4939-a8a8-aabd9ad0eee5", + "ActionName": "CallPolice" }, - { - "$type": "Rules.Framework.Providers.MongoDb.DataModel.ValueConditionNodeDataModel, Rules.Framework.Providers.MongoDb", - "_t": "value", - "ConditionType": "SmokeRate", - "DataType": "Decimal", - "LogicalOperator": "Eval", - "Operator": "GreaterThanOrEqual", - "Operand": 50 + "Ruleset": "FireSystem", + "DateBegin": "\/Date(1527807600000+0100)\/", + "DateEnd": null, + "Name": "Police call rule", + "Priority": 3, + "RootCondition": { + "$type": "Rules.Framework.Providers.MongoDb.DataModel.ComposedConditionNodeDataModel, Rules.Framework.Providers.MongoDb", + "_t": "composed", + "LogicalOperator": "Or", + "ChildConditionNodes": [ + { + "$type": "Rules.Framework.Providers.MongoDb.DataModel.ValueConditionNodeDataModel, Rules.Framework.Providers.MongoDb", + "_t": "value", + "Condition": "TemperatureCelsius", + "DataType": "Decimal", + "LogicalOperator": "Eval", + "Operator": "GreaterThanOrEqual", + "Operand": 70 + }, + { + "$type": "Rules.Framework.Providers.MongoDb.DataModel.ValueConditionNodeDataModel, Rules.Framework.Providers.MongoDb", + "_t": "value", + "Condition": "SmokeRate", + "DataType": "Decimal", + "LogicalOperator": "Eval", + "Operator": "GreaterThanOrEqual", + "Operand": 50 + } + ] } - ] - } - }, - { - "_id": "5e63fd9361f24b15f4531be6", - "Content": { - "ActionId": "96cce7f0-f8ba-4e8a-a3ce-b0d200d49ab2", - "ActionName": "CallFireBrigade" }, - "ContentType": "FireSystem", - "DateBegin": "\/Date(1527807600000+0100)\/", - "DateEnd": null, - "Name": "Fire brigade call rule", - "Priority": 1, - "RootCondition": { - "$type": "Rules.Framework.Providers.MongoDb.DataModel.ComposedConditionNodeDataModel, Rules.Framework.Providers.MongoDb", - "_t": "composed", - "LogicalOperator": "Or", - "ChildConditionNodes": [ - { - "$type": "Rules.Framework.Providers.MongoDb.DataModel.ValueConditionNodeDataModel, Rules.Framework.Providers.MongoDb", - "_t": "value", - "ConditionType": "TemperatureCelsius", - "DataType": "Decimal", - "LogicalOperator": "Eval", - "Operator": "GreaterThanOrEqual", - "Operand": 70 + { + "_id": "5e63fd9361f24b15f4531be6", + "Content": { + "ActionId": "96cce7f0-f8ba-4e8a-a3ce-b0d200d49ab2", + "ActionName": "CallFireBrigade" }, - { - "$type": "Rules.Framework.Providers.MongoDb.DataModel.ValueConditionNodeDataModel, Rules.Framework.Providers.MongoDb", - "_t": "value", - "ConditionType": "SmokeRate", - "DataType": "Decimal", - "LogicalOperator": "Eval", - "Operator": "GreaterThanOrEqual", - "Operand": 50 + "Ruleset": "FireSystem", + "DateBegin": "\/Date(1527807600000+0100)\/", + "DateEnd": null, + "Name": "Fire brigade call rule", + "Priority": 1, + "RootCondition": { + "$type": "Rules.Framework.Providers.MongoDb.DataModel.ComposedConditionNodeDataModel, Rules.Framework.Providers.MongoDb", + "_t": "composed", + "LogicalOperator": "Or", + "ChildConditionNodes": [ + { + "$type": "Rules.Framework.Providers.MongoDb.DataModel.ValueConditionNodeDataModel, Rules.Framework.Providers.MongoDb", + "_t": "value", + "Condition": "TemperatureCelsius", + "DataType": "Decimal", + "LogicalOperator": "Eval", + "Operator": "GreaterThanOrEqual", + "Operand": 70 + }, + { + "$type": "Rules.Framework.Providers.MongoDb.DataModel.ValueConditionNodeDataModel, Rules.Framework.Providers.MongoDb", + "_t": "value", + "Condition": "SmokeRate", + "DataType": "Decimal", + "LogicalOperator": "Eval", + "Operator": "GreaterThanOrEqual", + "Operand": 50 + } + ] } - ] - } - }, - { - "_id": "5e63fd9361f24b15f4531be5", - "Content": { - "ActionId": "7367d5d8-bac1-48bb-9b88-28cfe534aed2", - "ActionName": "ActivateSprinklers" }, - "ContentType": "FireSystem", - "DateBegin": "\/Date(1527807600000+0100)\/", - "DateEnd": null, - "Name": "Sprinkler system activation rule", - "Priority": 2, - "RootCondition": { - "$type": "Rules.Framework.Providers.MongoDb.DataModel.ComposedConditionNodeDataModel, Rules.Framework.Providers.MongoDb", - "_t": "composed", - "LogicalOperator": "Or", - "ChildConditionNodes": [ - { - "$type": "Rules.Framework.Providers.MongoDb.DataModel.ValueConditionNodeDataModel, Rules.Framework.Providers.MongoDb", - "_t": "value", - "ConditionType": "TemperatureCelsius", - "DataType": "Decimal", - "LogicalOperator": "Eval", - "Operator": "GreaterThanOrEqual", - "Operand": 50 + { + "_id": "5e63fd9361f24b15f4531be5", + "Content": { + "ActionId": "7367d5d8-bac1-48bb-9b88-28cfe534aed2", + "ActionName": "ActivateSprinklers" }, - { - "$type": "Rules.Framework.Providers.MongoDb.DataModel.ValueConditionNodeDataModel, Rules.Framework.Providers.MongoDb", - "_t": "value", - "ConditionType": "SmokeRate", - "DataType": "Decimal", - "LogicalOperator": "Eval", - "Operator": "GreaterThan", - "Operand": 40 + "Ruleset": "FireSystem", + "DateBegin": "\/Date(1527807600000+0100)\/", + "DateEnd": null, + "Name": "Sprinkler system activation rule", + "Priority": 2, + "RootCondition": { + "$type": "Rules.Framework.Providers.MongoDb.DataModel.ComposedConditionNodeDataModel, Rules.Framework.Providers.MongoDb", + "_t": "composed", + "LogicalOperator": "Or", + "ChildConditionNodes": [ + { + "$type": "Rules.Framework.Providers.MongoDb.DataModel.ValueConditionNodeDataModel, Rules.Framework.Providers.MongoDb", + "_t": "value", + "Condition": "TemperatureCelsius", + "DataType": "Decimal", + "LogicalOperator": "Eval", + "Operator": "GreaterThanOrEqual", + "Operand": 50 + }, + { + "$type": "Rules.Framework.Providers.MongoDb.DataModel.ValueConditionNodeDataModel, Rules.Framework.Providers.MongoDb", + "_t": "value", + "Condition": "SmokeRate", + "DataType": "Decimal", + "LogicalOperator": "Eval", + "Operator": "GreaterThan", + "Operand": 40 + } + ] } - ] } - } ] \ No newline at end of file diff --git a/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Scenarios/Scenario5/BestServerTests.cs b/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Scenarios/Scenario5/BestServerTests.cs index 2c35a6d7..f1138650 100644 --- a/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Scenarios/Scenario5/BestServerTests.cs +++ b/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Scenarios/Scenario5/BestServerTests.cs @@ -6,7 +6,7 @@ namespace Rules.Framework.Providers.MongoDb.IntegrationTests.Scenarios.Scenario5 using System.Threading.Tasks; using FluentAssertions; using MongoDB.Driver; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.IntegrationTests.Common.Scenarios.Scenario5; using Rules.Framework.Providers.MongoDb; using Xunit; @@ -17,29 +17,29 @@ public sealed class BestServerTests : IDisposable { new object[] { - new[] + new Dictionary { - new Condition(BestServerConditions.Price, 100), - new Condition(BestServerConditions.Memory, 12), - new Condition(BestServerConditions.StoragePartionable, true), - new Condition(BestServerConditions.Brand, "AMD") + { BestServerConditions.Price, 100 }, + { BestServerConditions.Memory, 12 }, + { BestServerConditions.StoragePartionable, true }, + { BestServerConditions.Brand, "AMD" }, }, "Best Server Top5" }, new object[] { - new[] + new Dictionary { - new Condition(BestServerConditions.Price, 110), - new Condition(BestServerConditions.Memory, 12), - new Condition(BestServerConditions.StoragePartionable, true), - new Condition(BestServerConditions.Brand, "AMD") + { BestServerConditions.Price, 110 }, + { BestServerConditions.Memory, 12 }, + { BestServerConditions.StoragePartionable, true }, + { BestServerConditions.Brand, "AMD" }, }, "Best Server Default" } }.SelectMany(x => new[] { false, true }.Select(c => new object[] { x[0], x[1], c })); - private readonly IMongoClient mongoClient; + private readonly MongoClient mongoClient; private readonly MongoDbProviderSettings mongoDbProviderSettings; public BestServerTests() @@ -50,44 +50,47 @@ public BestServerTests() [Theory] [MemberData(nameof(DataTest))] - public async Task BestServer_InEvaluation(IEnumerable> conditions, string expectedRuleName, bool enableCompilation) + public async Task BestServer_InEvaluation(Dictionary conditions, string expectedRuleName, bool enableCompilation) { // Arrange var rulesEngine = RulesEngineBuilder.CreateRulesEngine() - .WithContentType() - .WithConditionType() .SetMongoDbDataSource(this.mongoClient, this.mongoDbProviderSettings) .Configure(opt => { opt.EnableCompilation = enableCompilation; }) .Build(); + var genericRulesEngine = rulesEngine.MakeGeneric(); + await genericRulesEngine.CreateRulesetAsync(BestServerConfigurations.BestServerEvaluation); // Act 1 - Create rule with "in" operator - var ruleBuilderResult = RuleBuilder.NewRule() - .WithName("Best Server Top5") - .WithDatesInterval(DateTime.Parse("2021-05-29Z"), DateTime.Parse("2021-05-31Z")) - .WithContent(BestServerConfigurations.BestServerEvaluation, "Top5") - .WithCondition(c => c + var ruleBuilderResult = Rule.Create("Best Server Top5") + .InRuleset(BestServerConfigurations.BestServerEvaluation) + .SetContent("Top5") + .Since(DateTime.Parse("2021-05-29Z")) + .Until(DateTime.Parse("2021-05-31Z")) + .ApplyWhen(c => c .And(a => a .Value(BestServerConditions.Price, Operators.In, new[] { 100m, 200m, 300m }) .Value(BestServerConditions.Memory, Operators.In, new[] { 12, 16, 24, 36 }) .Value(BestServerConditions.Memory, Operators.NotIn, new[] { 4, 8 }) .Value(BestServerConditions.StoragePartionable, Operators.In, new[] { true }) - .Value(BestServerConditions.Brand, Operators.In, new[] { "AMD", "Intel", "Cisco" } - ))) + .Value(BestServerConditions.Brand, Operators.In, new[] { "AMD", "Intel", "Cisco" }) + ) + ) .Build(); // Act 2 - Create rule default - var ruleBuilderResultDefault = RuleBuilder.NewRule() - .WithName("Best Server Default") - .WithDatesInterval(DateTime.Parse("2021-05-29Z"), DateTime.Parse("2021-05-31Z")) - .WithContent(BestServerConfigurations.BestServerEvaluation, "Default") + var ruleBuilderResultDefault = Rule.Create("Best Server Default") + .InRuleset(BestServerConfigurations.BestServerEvaluation) + .SetContent("Default") + .Since(DateTime.Parse("2021-05-29Z")) + .Until(DateTime.Parse("2021-05-31Z")) .Build(); // Assert 1 ruleBuilderResult.Should().NotBeNull(); - string errors = ruleBuilderResult.Errors.Any() ? ruleBuilderResult.Errors.Aggregate((s1, s2) => $"{s1}\n- {s2}") : string.Empty; + var errors = ruleBuilderResult.Errors.Any() ? ruleBuilderResult.Errors.Aggregate((s1, s2) => $"{s1}\n- {s2}") : string.Empty; ruleBuilderResult.IsSuccess.Should().BeTrue( $"errors have occurred while creating rule: \n[\n- {errors}\n]"); @@ -98,12 +101,12 @@ public async Task BestServer_InEvaluation(IEnumerable new($"mongodb://{SettingsProvider.GetMongoDbHost()}:27017"); - private static MongoDbProviderSettings CreateProviderSettings() => new MongoDbProviderSettings + private static MongoDbProviderSettings CreateProviderSettings() => new() { DatabaseName = "rules-framework-tests", - RulesCollectionName = "best-server" + RulesCollectionName = "best-server", }; } } \ No newline at end of file diff --git a/tests/Rules.Framework.Providers.MongoDb.Tests/MongoDbProviderRulesDataSourceTests.cs b/tests/Rules.Framework.Providers.MongoDb.Tests/MongoDbProviderRulesDataSourceTests.cs index ec2571d7..db1c01f0 100644 --- a/tests/Rules.Framework.Providers.MongoDb.Tests/MongoDbProviderRulesDataSourceTests.cs +++ b/tests/Rules.Framework.Providers.MongoDb.Tests/MongoDbProviderRulesDataSourceTests.cs @@ -2,38 +2,144 @@ namespace Rules.Framework.Providers.MongoDb.Tests { using System; using System.Collections.Generic; + using System.Threading; using System.Threading.Tasks; using FluentAssertions; using MongoDB.Driver; using Moq; - using Rules.Framework.Core; using Rules.Framework.Providers.MongoDb.DataModel; using Rules.Framework.Providers.MongoDb.Tests.TestStubs; using Xunit; public class MongoDbProviderRulesDataSourceTests { + [Fact] + public async Task CreateContentTypeAsync_GivenContentTypeName_InsertsContentTypeOnCollection() + { + // Arrange + var contentType = nameof(RulesetNames.RulesetSample); + RulesetDataModel actual = null; + var contentTypesCollection = Mock.Of>(); + Mock.Get(contentTypesCollection) + .Setup(x => x.InsertOneAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((ct, _, _) => actual = ct); + + var mongoDatabase = Mock.Of(); + Mock.Get(mongoDatabase) + .Setup(x => x.GetCollection(It.IsAny(), null)) + .Returns(contentTypesCollection); + + var mongoClient = Mock.Of(); + Mock.Get(mongoClient) + .Setup(x => x.GetDatabase(It.IsAny(), null)) + .Returns(mongoDatabase); + + var mongoDbProviderSettings = new MongoDbProviderSettings + { + DatabaseName = "TestDatabaseName", + RulesCollectionName = "TestCollectionName" + }; + + var ruleFactory = Mock.Of(); + + var mongoDbProviderRulesDataSource = new MongoDbProviderRulesDataSource( + mongoClient, + mongoDbProviderSettings, + ruleFactory); + + // Act + await mongoDbProviderRulesDataSource.CreateRulesetAsync(contentType); + + // Assert + actual.Should().NotBeNull(); + actual.Name.Should().Be(contentType); + actual.Id.Should().NotBeEmpty(); + actual.Creation.Should().BeWithin(TimeSpan.FromSeconds(5)).Before(DateTime.UtcNow); + } + + [Fact] + public async Task GetContentTypesAsync_NoConditions_ReturnsCollectionOfContentTypes() + { + // Arrange + var contentTypeDataModels = new[] + { + new RulesetDataModel + { + Creation = DateTime.UtcNow, + Id = Guid.NewGuid(), + Name = nameof(RulesetNames.RulesetSample), + }, + }; + + var fetchedRulesCursor = Mock.Of>(); + Mock.Get(fetchedRulesCursor) + .SetupSequence(x => x.MoveNextAsync(default)) + .ReturnsAsync(true) + .ReturnsAsync(false); + Mock.Get(fetchedRulesCursor) + .SetupGet(x => x.Current) + .Returns(contentTypeDataModels); + Mock.Get(fetchedRulesCursor) + .Setup(x => x.Dispose()); + + var contentTypesCollection = Mock.Of>(); + Mock.Get(contentTypesCollection) + .Setup(x => x.FindAsync(It.IsAny>(), It.IsAny>(), default)) + .ReturnsAsync(fetchedRulesCursor); + + var mongoDatabase = Mock.Of(); + Mock.Get(mongoDatabase) + .Setup(x => x.GetCollection(It.IsAny(), null)) + .Returns(contentTypesCollection); + + var mongoClient = Mock.Of(); + Mock.Get(mongoClient) + .Setup(x => x.GetDatabase(It.IsAny(), null)) + .Returns(mongoDatabase); + + var mongoDbProviderSettings = new MongoDbProviderSettings + { + DatabaseName = "TestDatabaseName", + RulesCollectionName = "TestCollectionName" + }; + + var ruleFactory = Mock.Of(); + + var mongoDbProviderRulesDataSource = new MongoDbProviderRulesDataSource( + mongoClient, + mongoDbProviderSettings, + ruleFactory); + + // Act + var actual = await mongoDbProviderRulesDataSource.GetRulesetsAsync(); + + // Assert + actual.Should().NotBeNull() + .And.HaveCount(1) + .And.Contain(r => string.Equals(r.Name, nameof(RulesetNames.RulesetSample), StringComparison.Ordinal)); + } + [Fact] public async Task GetRulesAsync_GivenContentTypeAndDatesInterval_ReturnsCollectionOfRules() { // Arrange - ContentType contentType = ContentType.ContentTypeSample; - DateTime dateBegin = new DateTime(2020, 03, 01); - DateTime dateEnd = new DateTime(2020, 04, 01); + var contentType = RulesetNames.RulesetSample.ToString(); + var dateBegin = new DateTime(2020, 03, 01); + var dateEnd = new DateTime(2020, 04, 01); - List ruleDataModels = new List + var ruleDataModels = new List { - new RuleDataModel + new() { Name = "Rule 1" }, - new RuleDataModel + new() { Name = "Rule 2" } }; - IAsyncCursor fetchedRulesCursor = Mock.Of>(); + var fetchedRulesCursor = Mock.Of>(); Mock.Get(fetchedRulesCursor) .SetupSequence(x => x.MoveNextAsync(default)) .ReturnsAsync(true) @@ -44,39 +150,44 @@ public async Task GetRulesAsync_GivenContentTypeAndDatesInterval_ReturnsCollecti Mock.Get(fetchedRulesCursor) .Setup(x => x.Dispose()); - IMongoCollection rulesCollection = Mock.Of>(); + var rulesCollection = Mock.Of>(); Mock.Get(rulesCollection) .Setup(x => x.FindAsync(It.IsAny>(), null, default)) .ReturnsAsync(fetchedRulesCursor); - IMongoDatabase mongoDatabase = Mock.Of(); + var mongoDatabase = Mock.Of(); Mock.Get(mongoDatabase) .Setup(x => x.GetCollection(It.IsAny(), null)) .Returns(rulesCollection); - IMongoClient mongoClient = Mock.Of(); + var mongoClient = Mock.Of(); Mock.Get(mongoClient) .Setup(x => x.GetDatabase(It.IsAny(), null)) .Returns(mongoDatabase); - MongoDbProviderSettings mongoDbProviderSettings = new MongoDbProviderSettings + var mongoDbProviderSettings = new MongoDbProviderSettings { DatabaseName = "TestDatabaseName", RulesCollectionName = "TestCollectionName" }; - IRuleFactory ruleFactory = Mock.Of>(); + var ruleFactory = Mock.Of(); Mock.Get(ruleFactory) .Setup(x => x.CreateRule(It.IsAny())) - .Returns(x => RuleBuilder.NewRule().WithName(x.Name).Build().Rule); + .Returns(x => Rule.Create(x.Name) + .InRuleset("test ruleset") + .SetContent(new object()) + .Since(dateBegin) + .Build() + .Rule); - MongoDbProviderRulesDataSource mongoDbProviderRulesDataSource = new MongoDbProviderRulesDataSource( + var mongoDbProviderRulesDataSource = new MongoDbProviderRulesDataSource( mongoClient, mongoDbProviderSettings, ruleFactory); // Act - IEnumerable> rules = await mongoDbProviderRulesDataSource.GetRulesAsync(contentType, dateBegin, dateEnd); + var rules = await mongoDbProviderRulesDataSource.GetRulesAsync(contentType, dateBegin, dateEnd); // Assert rules.Should().NotBeNull() diff --git a/tests/Rules.Framework.Providers.MongoDb.Tests/RuleFactoryTests.cs b/tests/Rules.Framework.Providers.MongoDb.Tests/RuleFactoryTests.cs index 3075bdaa..c98e5677 100644 --- a/tests/Rules.Framework.Providers.MongoDb.Tests/RuleFactoryTests.cs +++ b/tests/Rules.Framework.Providers.MongoDb.Tests/RuleFactoryTests.cs @@ -5,9 +5,9 @@ namespace Rules.Framework.Providers.MongoDb.Tests using System.Linq; using FluentAssertions; using Moq; + using Rules.Framework; using Rules.Framework.Builder; - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; + using Rules.Framework.ConditionNodes; using Rules.Framework.Providers.MongoDb.DataModel; using Rules.Framework.Providers.MongoDb.Tests.TestStubs; using Rules.Framework.Serialization; @@ -19,11 +19,11 @@ public class RuleFactoryTests public void CreateRule_GivenNullRule_ThrowsArgumentNullException() { // Arrange - Rule rule = null; + Rule rule = null; - var contentSerializationProvider = Mock.Of>(); + var contentSerializationProvider = Mock.Of(); - var ruleFactory = new RuleFactory(contentSerializationProvider); + var ruleFactory = new RuleFactory(contentSerializationProvider); // Act var argumentNullException = Assert.Throws(() => ruleFactory.CreateRule(rule)); @@ -39,9 +39,9 @@ public void CreateRule_GivenNullRuleDataModel_ThrowsArgumentNullException() // Arrange RuleDataModel ruleDataModel = null; - var contentSerializationProvider = Mock.Of>(); + var contentSerializationProvider = Mock.Of(); - var ruleFactory = new RuleFactory(contentSerializationProvider); + var ruleFactory = new RuleFactory(contentSerializationProvider); // Act var argumentNullException = Assert.Throws(() => ruleFactory.CreateRule(ruleDataModel)); @@ -62,7 +62,7 @@ public void CreateRule_GivenRuleDataModelWithComposedNodeAndChildNodesOfEachData var integerConditionNodeDataModel = new ValueConditionNodeDataModel { - ConditionType = "SampleIntegerCondition", + Condition = "SampleIntegerCondition", DataType = DataTypes.Integer, LogicalOperator = LogicalOperators.Eval, Operand = 20, @@ -71,7 +71,7 @@ public void CreateRule_GivenRuleDataModelWithComposedNodeAndChildNodesOfEachData var stringConditionNodeDataModel = new ValueConditionNodeDataModel { - ConditionType = "SampleStringCondition", + Condition = "SampleStringCondition", DataType = DataTypes.String, LogicalOperator = LogicalOperators.Eval, Operand = "TEST", @@ -80,7 +80,7 @@ public void CreateRule_GivenRuleDataModelWithComposedNodeAndChildNodesOfEachData var decimalConditionNodeDataModel = new ValueConditionNodeDataModel { - ConditionType = "SampleDecimalCondition", + Condition = "SampleDecimalCondition", DataType = DataTypes.Decimal, LogicalOperator = LogicalOperators.Eval, Operand = 50.3m, @@ -89,7 +89,7 @@ public void CreateRule_GivenRuleDataModelWithComposedNodeAndChildNodesOfEachData var booleanConditionNodeDataModel = new ValueConditionNodeDataModel { - ConditionType = "SampleBooleanCondition", + Condition = "SampleBooleanCondition", DataType = DataTypes.Boolean, LogicalOperator = LogicalOperators.Eval, Operand = true, @@ -99,7 +99,7 @@ public void CreateRule_GivenRuleDataModelWithComposedNodeAndChildNodesOfEachData var ruleDataModel = new RuleDataModel { Content = content, - ContentType = "ContentTypeSample", + Ruleset = "ContentTypeSample", DateBegin = new System.DateTime(2020, 1, 1), DateEnd = null, Name = "My rule used for testing purposes", @@ -117,32 +117,33 @@ public void CreateRule_GivenRuleDataModelWithComposedNodeAndChildNodesOfEachData } }; - var contentSerializationProvider = Mock.Of>(); + var contentSerializationProvider = Mock.Of(); - var ruleFactory = new RuleFactory(contentSerializationProvider); + var ruleFactory = new RuleFactory(contentSerializationProvider); // Act var rule = ruleFactory.CreateRule(ruleDataModel); // Assert rule.Should().NotBeNull(); - rule.ContentContainer.Should().NotBeNull().And.BeOfType>(); + rule.ContentContainer.Should().NotBeNull().And.BeOfType(); rule.DateBegin.Should().Be(ruleDataModel.DateBegin); rule.DateEnd.Should().BeNull(); rule.Name.Should().Be(ruleDataModel.Name); rule.Priority.Should().Be(ruleDataModel.Priority); - rule.RootCondition.Should().BeOfType>(); + rule.Ruleset.Should().Be(ruleDataModel.Ruleset); + rule.RootCondition.Should().BeOfType(); - var composedConditionNode = rule.RootCondition.As>(); + var composedConditionNode = rule.RootCondition.As(); composedConditionNode.LogicalOperator.Should().Be(LogicalOperators.And); composedConditionNode.ChildConditionNodes.Should().HaveCount(4); - var valueConditionNodes = composedConditionNode.ChildConditionNodes.OfType>(); + var valueConditionNodes = composedConditionNode.ChildConditionNodes.OfType(); valueConditionNodes.Should().HaveCount(4); var integerConditionNode = valueConditionNodes.First(x => x.DataType == DataTypes.Integer); integerConditionNode.Should().NotBeNull(); - integerConditionNode.ConditionType.Should().Match(x => x == Enum.Parse(integerConditionNodeDataModel.ConditionType)); + integerConditionNode.Condition.Should().Be(integerConditionNodeDataModel.Condition); integerConditionNode.DataType.Should().Be(integerConditionNodeDataModel.DataType); integerConditionNode.LogicalOperator.Should().Be(integerConditionNodeDataModel.LogicalOperator); integerConditionNode.Operand.Should().Match(x => object.Equals(x, integerConditionNodeDataModel.Operand)); @@ -150,7 +151,7 @@ public void CreateRule_GivenRuleDataModelWithComposedNodeAndChildNodesOfEachData var stringConditionNode = valueConditionNodes.First(x => x.DataType == DataTypes.String); stringConditionNode.Should().NotBeNull(); - stringConditionNode.ConditionType.Should().Match(x => x == Enum.Parse(stringConditionNodeDataModel.ConditionType)); + stringConditionNode.Condition.Should().Be(stringConditionNodeDataModel.Condition); stringConditionNode.DataType.Should().Be(stringConditionNodeDataModel.DataType); stringConditionNode.LogicalOperator.Should().Be(stringConditionNodeDataModel.LogicalOperator); stringConditionNode.Operand.Should().Match(x => object.Equals(x, stringConditionNodeDataModel.Operand)); @@ -158,7 +159,7 @@ public void CreateRule_GivenRuleDataModelWithComposedNodeAndChildNodesOfEachData var decimalConditionNode = valueConditionNodes.First(x => x.DataType == DataTypes.Decimal); decimalConditionNode.Should().NotBeNull(); - decimalConditionNode.ConditionType.Should().Match(x => x == Enum.Parse(decimalConditionNodeDataModel.ConditionType)); + decimalConditionNode.Condition.Should().Be(decimalConditionNodeDataModel.Condition); decimalConditionNode.DataType.Should().Be(decimalConditionNodeDataModel.DataType); decimalConditionNode.LogicalOperator.Should().Be(decimalConditionNodeDataModel.LogicalOperator); decimalConditionNode.Operand.Should().Match(x => object.Equals(x, decimalConditionNodeDataModel.Operand)); @@ -166,7 +167,7 @@ public void CreateRule_GivenRuleDataModelWithComposedNodeAndChildNodesOfEachData var booleanConditionNode = valueConditionNodes.First(x => x.DataType == DataTypes.Boolean); booleanConditionNode.Should().NotBeNull(); - booleanConditionNode.ConditionType.Should().Match(x => x == Enum.Parse(booleanConditionNodeDataModel.ConditionType)); + booleanConditionNode.Condition.Should().Be(booleanConditionNodeDataModel.Condition); booleanConditionNode.DataType.Should().Be(booleanConditionNodeDataModel.DataType); booleanConditionNode.LogicalOperator.Should().Be(booleanConditionNodeDataModel.LogicalOperator); booleanConditionNode.Operand.Should().Be(Convert.ToBoolean(booleanConditionNodeDataModel.Operand)); @@ -190,25 +191,25 @@ public void CreateRule_GivenRuleWithComposedNodeAndChildNodesOfEachDataType_Retu .Setup(x => x.Serialize(It.IsAny())) .Returns((object)content); - var contentSerializationProvider = Mock.Of>(); + var contentSerializationProvider = Mock.Of(); Mock.Get(contentSerializationProvider) - .Setup(x => x.GetContentSerializer(ContentType.ContentTypeSample)) + .Setup(x => x.GetContentSerializer(RulesetNames.RulesetSample.ToString())) .Returns(contentSerializer); var booleanConditionNode = ConditionNodeFactory - .CreateValueNode(ConditionType.SampleBooleanCondition, Operators.NotEqual, true) as ValueConditionNode; + .CreateValueNode(ConditionNames.SampleBooleanCondition.ToString(), Operators.NotEqual, true); var decimalConditionNode = ConditionNodeFactory - .CreateValueNode(ConditionType.SampleDecimalCondition, Operators.LesserThanOrEqual, 50.3m) as ValueConditionNode; + .CreateValueNode(ConditionNames.SampleDecimalCondition.ToString(), Operators.LesserThanOrEqual, 50.3m); var integerConditionNode = ConditionNodeFactory - .CreateValueNode(ConditionType.SampleIntegerCondition, Operators.GreaterThan, 20) as ValueConditionNode; + .CreateValueNode(ConditionNames.SampleIntegerCondition.ToString(), Operators.GreaterThan, 20); var stringConditionNode = ConditionNodeFactory - .CreateValueNode(ConditionType.SampleStringCondition, Operators.Equal, "TEST") as ValueConditionNode; + .CreateValueNode(ConditionNames.SampleStringCondition.ToString(), Operators.Equal, "TEST"); - var rule = RuleBuilder.NewRule() - .WithName("My rule used for testing purposes") - .WithDateBegin(new DateTime(2020, 1, 1)) - .WithContent(ContentType.ContentTypeSample, (object)content) - .WithCondition(c => c + var rule = Rule.Create("My rule used for testing purposes") + .InRuleset(RulesetNames.RulesetSample) + .SetContent((object)content) + .Since(new DateTime(2020, 1, 1)) + .ApplyWhen(c => c .And(a => a .Condition(booleanConditionNode) .Condition(decimalConditionNode) @@ -217,19 +218,20 @@ public void CreateRule_GivenRuleWithComposedNodeAndChildNodesOfEachDataType_Retu )) .Build().Rule; - var ruleFactory = new RuleFactory(contentSerializationProvider); + var ruleFactory = new RuleFactory(contentSerializationProvider); // Act var ruleDataModel = ruleFactory.CreateRule(rule); // Assert ruleDataModel.Should().NotBeNull(); - object ruleDataModelContent = ruleDataModel.Content; + var ruleDataModelContent = rule.ContentContainer.GetContentAs(); ruleDataModelContent.Should().NotBeNull().And.BeSameAs(content); - ruleDataModel.DateBegin.Should().Be(ruleDataModel.DateBegin); + ruleDataModel.DateBegin.Should().Be(rule.DateBegin); ruleDataModel.DateEnd.Should().BeNull(); - ruleDataModel.Name.Should().Be(ruleDataModel.Name); - ruleDataModel.Priority.Should().Be(ruleDataModel.Priority); + ruleDataModel.Name.Should().Be(rule.Name); + ruleDataModel.Priority.Should().Be(rule.Priority); + ruleDataModel.Ruleset.Should().Be(rule.Ruleset.ToString()); ruleDataModel.RootCondition.Should().BeOfType(); var composedConditionNodeDataModel = ruleDataModel.RootCondition.As(); @@ -241,7 +243,7 @@ public void CreateRule_GivenRuleWithComposedNodeAndChildNodesOfEachDataType_Retu var integerConditionNodeDataModel = valueConditionNodeDataModels.First(v => v.DataType == DataTypes.Integer); integerConditionNodeDataModel.Should().NotBeNull(); - integerConditionNodeDataModel.ConditionType.Should().Match(x => integerConditionNode.ConditionType == Enum.Parse(x)); + integerConditionNodeDataModel.Condition.Should().Be(integerConditionNode.Condition); integerConditionNodeDataModel.DataType.Should().Be(integerConditionNode.DataType); integerConditionNodeDataModel.LogicalOperator.Should().Be(integerConditionNode.LogicalOperator); integerConditionNodeDataModel.Operand.Should().Match(x => object.Equals(x, integerConditionNode.Operand)); @@ -249,7 +251,7 @@ public void CreateRule_GivenRuleWithComposedNodeAndChildNodesOfEachDataType_Retu var stringConditionNodeDataModel = valueConditionNodeDataModels.First(v => v.DataType == DataTypes.String); stringConditionNodeDataModel.Should().NotBeNull(); - stringConditionNodeDataModel.ConditionType.Should().Match(x => stringConditionNode.ConditionType == Enum.Parse(x)); + stringConditionNodeDataModel.Condition.Should().Be(stringConditionNode.Condition); stringConditionNodeDataModel.DataType.Should().Be(stringConditionNode.DataType); stringConditionNodeDataModel.LogicalOperator.Should().Be(stringConditionNode.LogicalOperator); stringConditionNodeDataModel.Operand.Should().Match(x => object.Equals(x, stringConditionNode.Operand)); @@ -257,7 +259,7 @@ public void CreateRule_GivenRuleWithComposedNodeAndChildNodesOfEachDataType_Retu var decimalConditionNodeDataModel = valueConditionNodeDataModels.First(v => v.DataType == DataTypes.Decimal); decimalConditionNodeDataModel.Should().NotBeNull(); - decimalConditionNodeDataModel.ConditionType.Should().Match(x => decimalConditionNode.ConditionType == Enum.Parse(x)); + decimalConditionNodeDataModel.Condition.Should().Be(decimalConditionNode.Condition); decimalConditionNodeDataModel.DataType.Should().Be(decimalConditionNode.DataType); decimalConditionNodeDataModel.LogicalOperator.Should().Be(decimalConditionNode.LogicalOperator); decimalConditionNodeDataModel.Operand.Should().Match(x => object.Equals(x, decimalConditionNode.Operand)); @@ -265,7 +267,7 @@ public void CreateRule_GivenRuleWithComposedNodeAndChildNodesOfEachDataType_Retu var booleanConditionNodeDataModel = valueConditionNodeDataModels.First(v => v.DataType == DataTypes.Boolean); booleanConditionNodeDataModel.Should().NotBeNull(); - booleanConditionNodeDataModel.ConditionType.Should().Match(x => booleanConditionNode.ConditionType == Enum.Parse(x)); + booleanConditionNodeDataModel.Condition.Should().Be(booleanConditionNode.Condition); booleanConditionNodeDataModel.DataType.Should().Be(booleanConditionNode.DataType); booleanConditionNodeDataModel.LogicalOperator.Should().Be(booleanConditionNode.LogicalOperator); booleanConditionNodeDataModel.Operand.Should().Be(Convert.ToBoolean(booleanConditionNode.Operand)); diff --git a/tests/Rules.Framework.Providers.MongoDb.Tests/Serialization/DynamicToStrongTypeContentSerializationProviderTests.cs b/tests/Rules.Framework.Providers.MongoDb.Tests/Serialization/DynamicToStrongTypeContentSerializationProviderTests.cs index f4e911d4..440d6db8 100644 --- a/tests/Rules.Framework.Providers.MongoDb.Tests/Serialization/DynamicToStrongTypeContentSerializationProviderTests.cs +++ b/tests/Rules.Framework.Providers.MongoDb.Tests/Serialization/DynamicToStrongTypeContentSerializationProviderTests.cs @@ -3,7 +3,6 @@ namespace Rules.Framework.Providers.MongoDb.Tests.Serialization using FluentAssertions; using Rules.Framework.Providers.MongoDb.Serialization; using Rules.Framework.Providers.MongoDb.Tests.TestStubs; - using Rules.Framework.Serialization; using Xunit; public class DynamicToStrongTypeContentSerializationProviderTests @@ -12,10 +11,10 @@ public class DynamicToStrongTypeContentSerializationProviderTests public void GetContentSerializer_GivenAnyContentTypeValue_ReturnsDynamicToStrongTypeContentSerializer() { // Arrange - DynamicToStrongTypeContentSerializationProvider dynamicToStrongTypeContentSerializationProvider = new DynamicToStrongTypeContentSerializationProvider(); + var dynamicToStrongTypeContentSerializationProvider = new DynamicToStrongTypeContentSerializationProvider(); // Act - IContentSerializer contentSerializer = dynamicToStrongTypeContentSerializationProvider.GetContentSerializer(ContentType.ContentTypeSample); + var contentSerializer = dynamicToStrongTypeContentSerializationProvider.GetContentSerializer(RulesetNames.RulesetSample.ToString()); // Assert contentSerializer.Should().NotBeNull() diff --git a/tests/Rules.Framework.Providers.MongoDb.Tests/Serialization/DynamicToStrongTypeContentSerializerTests.cs b/tests/Rules.Framework.Providers.MongoDb.Tests/Serialization/DynamicToStrongTypeContentSerializerTests.cs index b31d43dc..7c32d48f 100644 --- a/tests/Rules.Framework.Providers.MongoDb.Tests/Serialization/DynamicToStrongTypeContentSerializerTests.cs +++ b/tests/Rules.Framework.Providers.MongoDb.Tests/Serialization/DynamicToStrongTypeContentSerializerTests.cs @@ -11,83 +11,105 @@ namespace Rules.Framework.Providers.MongoDb.Tests.Serialization public class DynamicToStrongTypeContentSerializerTests { [Fact] - public void Deserialize_GivenNullSerializedContent_ThrowsArgumentNullException() + public void Deserialize_GivenCorrectSerializedContentAndType_ReturnsDeserializedTypeInstance() { // Arrange - object serializedContent = null; - Type type = null; + dynamic serializedContent = new ExpandoObject(); - DynamicToStrongTypeContentSerializer dynamicToStrongTypeContentSerializer = new DynamicToStrongTypeContentSerializer(); + // Remember that InvariantCulture formats is an assumption! + serializedContent.Prop01 = 1; + serializedContent.Prop02 = "TEST"; + serializedContent.Prop03 = 30.3m; + serializedContent.Prop04 = "e986380c-ca88-47dd-b417-15f8beb26d9c"; + serializedContent.Prop05 = "RulesetSample"; + serializedContent.Prop06 = "123"; + serializedContent.Prop07 = "95.78"; + serializedContent.Prop08 = true; + serializedContent.Prop09 = "false"; + serializedContent.Prop10 = DateTime.Parse("2020-03-21Z"); + serializedContent.Prop11 = "2020-03-21 15:26:58Z"; + var type = typeof(ContentStub); + + var dynamicToStrongTypeContentSerializer = new DynamicToStrongTypeContentSerializer(); // Act - ArgumentNullException argumentNullException = Assert.Throws(() => - { - dynamicToStrongTypeContentSerializer.Deserialize(serializedContent, type); - }); + object deserializedContent = dynamicToStrongTypeContentSerializer.Deserialize(serializedContent, type); // Assert - argumentNullException.Should().NotBeNull(); - argumentNullException.ParamName.Should().Be(nameof(serializedContent)); + deserializedContent.Should().BeOfType(type); + + var contentStub = deserializedContent.As(); + contentStub.Prop01.Should().Be(1); + contentStub.Prop02.Should().Be("TEST"); + contentStub.Prop03.Should().Be(30.3m); + contentStub.Prop04.Should().Be(Guid.Parse("e986380c-ca88-47dd-b417-15f8beb26d9c")); + contentStub.Prop05.Should().Be(RulesetNames.RulesetSample); + contentStub.Prop06.Should().Be(123); + contentStub.Prop07.Should().Be(95.78m); + contentStub.Prop08.Should().BeTrue(); + contentStub.Prop09.Should().BeFalse(); + contentStub.Prop10.Should().Be(DateTime.Parse("2020-03-21Z")); + contentStub.Prop11.Should().Be(DateTime.Parse("2020-03-21 15:26:58Z")); } [Fact] - public void Deserialize_GivenNullType_ThrowsArgumentNullException() + public void Deserialize_GivenNonDynamicSerializedContent_ThrowsNotSupportedException() { // Arrange - object serializedContent = new object(); - Type type = null; + var serializedContent = new object(); + var type = typeof(ContentStub); - DynamicToStrongTypeContentSerializer dynamicToStrongTypeContentSerializer = new DynamicToStrongTypeContentSerializer(); + var dynamicToStrongTypeContentSerializer = new DynamicToStrongTypeContentSerializer(); // Act - ArgumentNullException argumentNullException = Assert.Throws(() => + var notSupportedException = Assert.Throws(() => { dynamicToStrongTypeContentSerializer.Deserialize(serializedContent, type); }); // Assert - argumentNullException.Should().NotBeNull(); - argumentNullException.ParamName.Should().Be(nameof(type)); + notSupportedException.Should().NotBeNull(); + notSupportedException.Message.Should().Be($"The serialized content type is not supported for deserialization: {typeof(object).FullName}"); } [Fact] - public void Deserialize_GivenNonDynamicSerializedContent_ThrowsNotSupportedException() + public void Deserialize_GivenNullSerializedContent_ThrowsArgumentNullException() { // Arrange - object serializedContent = new object(); - Type type = typeof(ContentStub); + object serializedContent = null; + Type type = null; - DynamicToStrongTypeContentSerializer dynamicToStrongTypeContentSerializer = new DynamicToStrongTypeContentSerializer(); + var dynamicToStrongTypeContentSerializer = new DynamicToStrongTypeContentSerializer(); // Act - NotSupportedException notSupportedException = Assert.Throws(() => + var argumentNullException = Assert.Throws(() => { dynamicToStrongTypeContentSerializer.Deserialize(serializedContent, type); }); // Assert - notSupportedException.Should().NotBeNull(); - notSupportedException.Message.Should().Be($"The serialized content type is not supported for deserialization: {typeof(object).FullName}"); + argumentNullException.Should().NotBeNull(); + argumentNullException.ParamName.Should().Be(nameof(serializedContent)); } [Fact] - public void Deserialize_GivenTypeNoDefaultCtor_ThrowsNotSupportedException() + public void Deserialize_GivenNullType_ThrowsArgumentNullException() { // Arrange - dynamic serializedContent = new ExpandoObject(); - Type type = typeof(MissingDefaultCtorContentStub); + var serializedContent = new object(); + Type type = null; - DynamicToStrongTypeContentSerializer dynamicToStrongTypeContentSerializer = new DynamicToStrongTypeContentSerializer(); + var dynamicToStrongTypeContentSerializer = new DynamicToStrongTypeContentSerializer(); // Act - NotSupportedException notSupportedException = Assert.Throws(() => + var argumentNullException = Assert.Throws(() => { dynamicToStrongTypeContentSerializer.Deserialize(serializedContent, type); }); // Assert - notSupportedException.Should().NotBeNull(); - notSupportedException.Message.Should().Be($"The target type '{typeof(MissingDefaultCtorContentStub).FullName}' must define a default (no parameters) constructor."); + argumentNullException.Should().NotBeNull(); + argumentNullException.ParamName.Should().Be(nameof(type)); } [Fact] @@ -97,12 +119,12 @@ public void Deserialize_GivenSerializedContentWithPropertyMissingOnType_ThrowsSe dynamic serializedContent = new ExpandoObject(); serializedContent.Prop1 = 1; serializedContent.Prop2 = true; - Type type = typeof(MissingPropertyContentStub); + var type = typeof(MissingPropertyContentStub); - DynamicToStrongTypeContentSerializer dynamicToStrongTypeContentSerializer = new DynamicToStrongTypeContentSerializer(); + var dynamicToStrongTypeContentSerializer = new DynamicToStrongTypeContentSerializer(); // Act - SerializationException serializationException = Assert.Throws(() => + var serializationException = Assert.Throws(() => { dynamicToStrongTypeContentSerializer.Deserialize(serializedContent, type); }); @@ -119,12 +141,12 @@ public void Deserialize_GivenSerializedContentWithPropertyValueTypeDifferentFrom dynamic serializedContent = new ExpandoObject(); serializedContent.Prop01 = 1; serializedContent.Prop03 = "WRONG VALUE"; - Type type = typeof(ContentStub); + var type = typeof(ContentStub); - DynamicToStrongTypeContentSerializer dynamicToStrongTypeContentSerializer = new DynamicToStrongTypeContentSerializer(); + var dynamicToStrongTypeContentSerializer = new DynamicToStrongTypeContentSerializer(); // Act - SerializationException serializationException = Assert.Throws(() => + var serializationException = Assert.Throws(() => { dynamicToStrongTypeContentSerializer.Deserialize(serializedContent, type); }); @@ -136,45 +158,23 @@ public void Deserialize_GivenSerializedContentWithPropertyValueTypeDifferentFrom } [Fact] - public void Deserialize_GivenCorrectSerializedContentAndType_ReturnsDeserializedTypeInstance() + public void Deserialize_GivenTypeNoDefaultCtor_ThrowsNotSupportedException() { // Arrange dynamic serializedContent = new ExpandoObject(); + var type = typeof(MissingDefaultCtorContentStub); - // Remember that InvariantCulture formats is an assumption! - serializedContent.Prop01 = 1; - serializedContent.Prop02 = "TEST"; - serializedContent.Prop03 = 30.3m; - serializedContent.Prop04 = "e986380c-ca88-47dd-b417-15f8beb26d9c"; - serializedContent.Prop05 = "ContentTypeSample"; - serializedContent.Prop06 = "123"; - serializedContent.Prop07 = "95.78"; - serializedContent.Prop08 = true; - serializedContent.Prop09 = "false"; - serializedContent.Prop10 = DateTime.Parse("2020-03-21Z"); - serializedContent.Prop11 = "2020-03-21 15:26:58Z"; - Type type = typeof(ContentStub); - - DynamicToStrongTypeContentSerializer dynamicToStrongTypeContentSerializer = new DynamicToStrongTypeContentSerializer(); + var dynamicToStrongTypeContentSerializer = new DynamicToStrongTypeContentSerializer(); // Act - object deserializedContent = dynamicToStrongTypeContentSerializer.Deserialize(serializedContent, type); + var notSupportedException = Assert.Throws(() => + { + dynamicToStrongTypeContentSerializer.Deserialize(serializedContent, type); + }); // Assert - deserializedContent.Should().BeOfType(type); - - ContentStub contentStub = deserializedContent.As(); - contentStub.Prop01.Should().Be(1); - contentStub.Prop02.Should().Be("TEST"); - contentStub.Prop03.Should().Be(30.3m); - contentStub.Prop04.Should().Be(Guid.Parse("e986380c-ca88-47dd-b417-15f8beb26d9c")); - contentStub.Prop05.Should().Be(ContentType.ContentTypeSample); - contentStub.Prop06.Should().Be(123); - contentStub.Prop07.Should().Be(95.78m); - contentStub.Prop08.Should().BeTrue(); - contentStub.Prop09.Should().BeFalse(); - contentStub.Prop10.Should().Be(DateTime.Parse("2020-03-21Z")); - contentStub.Prop11.Should().Be(DateTime.Parse("2020-03-21 15:26:58Z")); + notSupportedException.Should().NotBeNull(); + notSupportedException.Message.Should().Be($"The target type '{typeof(MissingDefaultCtorContentStub).FullName}' must define a default (no parameters) constructor."); } } } \ No newline at end of file diff --git a/tests/Rules.Framework.Providers.MongoDb.Tests/TestStubs/ConditionType.cs b/tests/Rules.Framework.Providers.MongoDb.Tests/TestStubs/ConditionNames.cs similarity index 86% rename from tests/Rules.Framework.Providers.MongoDb.Tests/TestStubs/ConditionType.cs rename to tests/Rules.Framework.Providers.MongoDb.Tests/TestStubs/ConditionNames.cs index 2793241d..bb603eb1 100644 --- a/tests/Rules.Framework.Providers.MongoDb.Tests/TestStubs/ConditionType.cs +++ b/tests/Rules.Framework.Providers.MongoDb.Tests/TestStubs/ConditionNames.cs @@ -1,6 +1,6 @@ namespace Rules.Framework.Providers.MongoDb.Tests.TestStubs { - internal enum ConditionType + internal enum ConditionNames { SampleIntegerCondition = 1, diff --git a/tests/Rules.Framework.Providers.MongoDb.Tests/TestStubs/ContentStub.cs b/tests/Rules.Framework.Providers.MongoDb.Tests/TestStubs/ContentStub.cs index e4b9947c..7e79bbd0 100644 --- a/tests/Rules.Framework.Providers.MongoDb.Tests/TestStubs/ContentStub.cs +++ b/tests/Rules.Framework.Providers.MongoDb.Tests/TestStubs/ContentStub.cs @@ -17,7 +17,7 @@ internal class ContentStub public Guid Prop04 { get; set; } // For the purpose of testing when type must be parsed to specific enum. - public ContentType Prop05 { get; set; } + public RulesetNames Prop05 { get; set; } // For the purpose of testing when type must be parsed to int. public int Prop06 { get; set; } diff --git a/tests/Rules.Framework.Providers.MongoDb.Tests/TestStubs/ContentType.cs b/tests/Rules.Framework.Providers.MongoDb.Tests/TestStubs/RulesetNames.cs similarity index 55% rename from tests/Rules.Framework.Providers.MongoDb.Tests/TestStubs/ContentType.cs rename to tests/Rules.Framework.Providers.MongoDb.Tests/TestStubs/RulesetNames.cs index a04a62ce..7c269084 100644 --- a/tests/Rules.Framework.Providers.MongoDb.Tests/TestStubs/ContentType.cs +++ b/tests/Rules.Framework.Providers.MongoDb.Tests/TestStubs/RulesetNames.cs @@ -1,7 +1,7 @@ namespace Rules.Framework.Providers.MongoDb.Tests.TestStubs { - internal enum ContentType + internal enum RulesetNames { - ContentTypeSample = 1 + RulesetSample = 1 } } \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Builder/ConfiguredRulesEngineBuilderTests.cs b/tests/Rules.Framework.Tests/Builder/ConfiguredRulesEngineBuilderTests.cs index 224210ba..0e964d26 100644 --- a/tests/Rules.Framework.Tests/Builder/ConfiguredRulesEngineBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Builder/ConfiguredRulesEngineBuilderTests.cs @@ -3,7 +3,6 @@ namespace Rules.Framework.Tests.Builder using FluentAssertions; using Moq; using Rules.Framework.Builder; - using Rules.Framework.Tests.Stubs; using Xunit; public class ConfiguredRulesEngineBuilderTests @@ -12,8 +11,8 @@ public class ConfiguredRulesEngineBuilderTests public void Build_WhenCompilationIsEnabled_ReturnsRulesEngineWithCompiledEvaluation() { // Arrange - var rulesDataSource = Mock.Of>(); - var configuredRulesEngineBuilder = new ConfiguredRulesEngineBuilder(rulesDataSource); + var rulesDataSource = Mock.Of(); + var configuredRulesEngineBuilder = new ConfiguredRulesEngineBuilder(rulesDataSource); configuredRulesEngineBuilder.Configure(opt => { @@ -31,8 +30,8 @@ public void Build_WhenCompilationIsEnabled_ReturnsRulesEngineWithCompiledEvaluat public void Build_WhenCompilationIsNotEnabled_ReturnsRulesEngineWithClassicEvaluation() { // Arrange - var rulesDataSource = Mock.Of>(); - var configuredRulesEngineBuilder = new ConfiguredRulesEngineBuilder(rulesDataSource); + var rulesDataSource = Mock.Of(); + var configuredRulesEngineBuilder = new ConfiguredRulesEngineBuilder(rulesDataSource); // Act var actual = configuredRulesEngineBuilder.Build(); @@ -45,8 +44,8 @@ public void Build_WhenCompilationIsNotEnabled_ReturnsRulesEngineWithClassicEvalu public void Configure_GivenOptionsConfigurationAction_SetsOptionsAndValidates() { // Arrange - var rulesDataSource = Mock.Of>(); - var configuredRulesEngineBuilder = new ConfiguredRulesEngineBuilder(rulesDataSource); + var rulesDataSource = Mock.Of(); + var configuredRulesEngineBuilder = new ConfiguredRulesEngineBuilder(rulesDataSource); // Act var actual = configuredRulesEngineBuilder.Configure(opt => diff --git a/tests/Rules.Framework.Tests/Builder/RuleBuilderResultTests.cs b/tests/Rules.Framework.Tests/Builder/RuleBuilderResultTests.cs index 599462fa..cc8dad7a 100644 --- a/tests/Rules.Framework.Tests/Builder/RuleBuilderResultTests.cs +++ b/tests/Rules.Framework.Tests/Builder/RuleBuilderResultTests.cs @@ -4,8 +4,6 @@ namespace Rules.Framework.Tests.Builder using System.Collections.Generic; using FluentAssertions; using Rules.Framework.Builder; - using Rules.Framework.Core; - using Rules.Framework.Tests.Stubs; using Xunit; public class RuleBuilderResultTests @@ -17,7 +15,7 @@ public void Failure_GivenAllValidParameters_ReturnsRuleBuilderResultWithFailure( IEnumerable expectedErrors = new[] { "Error1", "Error2" }; // Act - RuleBuilderResult ruleBuilderResult = RuleBuilderResult.Failure(expectedErrors); + var ruleBuilderResult = RuleBuilderResult.Failure(expectedErrors); // Assert ruleBuilderResult.Should().NotBeNull(); @@ -33,7 +31,7 @@ public void Failure_GivenNullErrorsCollection_ThrowsArgumentNullException() IEnumerable expectedErrors = null; // Act - ArgumentNullException argumentNullException = Assert.Throws(() => RuleBuilderResult.Failure(expectedErrors)); + var argumentNullException = Assert.Throws(() => RuleBuilderResult.Failure(expectedErrors)); // Arrange argumentNullException.Should().NotBeNull(); @@ -44,10 +42,10 @@ public void Failure_GivenNullErrorsCollection_ThrowsArgumentNullException() public void Success_GivenAllValidParameters_ReturnsRuleBuilderResultWithSuccessAndNewRule() { // Arrange - Rule rule = new Rule(); + var rule = new Rule(); // Act - RuleBuilderResult ruleBuilderResult = RuleBuilderResult.Success(rule); + var ruleBuilderResult = RuleBuilderResult.Success(rule); // Assert ruleBuilderResult.Should().NotBeNull(); @@ -60,10 +58,10 @@ public void Success_GivenAllValidParameters_ReturnsRuleBuilderResultWithSuccessA public void Success_GivenNullErrorsCollection_ThrowsArgumentNullException() { // Arrange - Rule rule = null; + Rule rule = null; // Act - ArgumentNullException argumentNullException = Assert.Throws(() => RuleBuilderResult.Success(rule)); + var argumentNullException = Assert.Throws(() => RuleBuilderResult.Success(rule)); // Arrange argumentNullException.Should().NotBeNull(); diff --git a/tests/Rules.Framework.Tests/Builder/RuleBuilderTests.cs b/tests/Rules.Framework.Tests/Builder/RuleBuilderTests.cs index dacc3d45..a15dc2bd 100644 --- a/tests/Rules.Framework.Tests/Builder/RuleBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Builder/RuleBuilderTests.cs @@ -5,9 +5,8 @@ namespace Rules.Framework.Tests.Builder using FluentAssertions; using Moq; using Rules.Framework; - using Rules.Framework.Builder; - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; + using Rules.Framework.Builder.Generic.RulesBuilder; + using Rules.Framework.Generic.ConditionNodes; using Rules.Framework.Serialization; using Rules.Framework.Tests.Stubs; using Xunit; @@ -18,23 +17,25 @@ public class RuleBuilderTests public void NewRule_GivenRuleWithComposedCondition_BuildsAndReturnsRule() { // Arrange - string ruleName = "Rule 1"; + var ruleName = "Rule 1"; var dateBegin = DateTime.Parse("2021-01-01"); - var contentType = ContentType.Type1; - string content = "Content"; + var ruleset = RulesetNames.Type1; + var content = "Content"; // Act - var ruleBuilderResult = RuleBuilder.NewRule() - .WithName(ruleName) - .WithDateBegin(dateBegin) - .WithContent(contentType, content) - .WithCondition(c => c + var ruleBuilderResult = Rule.Create(ruleName) + .InRuleset(ruleset) + .SetContent(content) + .Since(dateBegin) + .ApplyWhen(c => c .Or(o => o - .Value(ConditionType.IsoCountryCode, Operators.Equal, "PT") + .Value(ConditionNames.IsoCountryCode, Operators.Equal, "PT") .And(a => a - .Value(ConditionType.NumberOfSales, Operators.GreaterThan, 1000) - .Value(ConditionType.IsoCurrency, Operators.In, new[] { "EUR", "USD" }) - ))) + .Value(ConditionNames.NumberOfSales, Operators.GreaterThan, 1000) + .Value(ConditionNames.IsoCurrency, Operators.In, new[] { "EUR", "USD" }) + ) + ) + ) .Build(); // Assert @@ -48,35 +49,35 @@ public void NewRule_GivenRuleWithComposedCondition_BuildsAndReturnsRule() rule.ContentContainer.Should().NotBeNull(); // root node - rule.RootCondition.Should().BeOfType>(); - var rootCondition = rule.RootCondition as ComposedConditionNode; + rule.RootCondition.Should().BeOfType>(); + var rootCondition = rule.RootCondition as ComposedConditionNode; rootCondition.LogicalOperator.Should().Be(LogicalOperators.Or); rootCondition.ChildConditionNodes.Should().HaveCount(2); var childNodes = rootCondition.ChildConditionNodes.ToList(); // first child node - childNodes[0].Should().BeOfType>(); - var isoCountryChildNode = childNodes[0] as ValueConditionNode; - isoCountryChildNode.ConditionType.Should().Be(ConditionType.IsoCountryCode); + childNodes[0].Should().BeOfType>(); + var isoCountryChildNode = childNodes[0] as ValueConditionNode; + isoCountryChildNode.Condition.Should().Be(ConditionNames.IsoCountryCode); isoCountryChildNode.Operator.Should().Be(Operators.Equal); isoCountryChildNode.Operand.Should().Be("PT"); // second child nodes - childNodes[1].Should().BeOfType>(); - var composedChildNode = childNodes[1] as ComposedConditionNode; + childNodes[1].Should().BeOfType>(); + var composedChildNode = childNodes[1] as ComposedConditionNode; composedChildNode.LogicalOperator.Should().Be(LogicalOperators.And); composedChildNode.ChildConditionNodes.Should().HaveCount(2); var composedChildNodes = composedChildNode.ChildConditionNodes.ToList(); - composedChildNodes[0].Should().BeOfType>(); - var numberOfSalesChildNode = composedChildNodes[0] as ValueConditionNode; - numberOfSalesChildNode.ConditionType.Should().Be(ConditionType.NumberOfSales); + composedChildNodes[0].Should().BeOfType>(); + var numberOfSalesChildNode = composedChildNodes[0] as ValueConditionNode; + numberOfSalesChildNode.Condition.Should().Be(ConditionNames.NumberOfSales); numberOfSalesChildNode.Operator.Should().Be(Operators.GreaterThan); numberOfSalesChildNode.Operand.Should().Be(1000); - composedChildNodes[1].Should().BeOfType>(); - var isoCurrencyChildNode = composedChildNodes[1] as ValueConditionNode; - isoCurrencyChildNode.ConditionType.Should().Be(ConditionType.IsoCurrency); + composedChildNodes[1].Should().BeOfType>(); + var isoCurrencyChildNode = composedChildNodes[1] as ValueConditionNode; + isoCurrencyChildNode.Condition.Should().Be(ConditionNames.IsoCurrency); isoCurrencyChildNode.Operator.Should().Be(Operators.In); isoCurrencyChildNode.Operand.Should().BeEquivalentTo(new[] { "EUR", "USD" }); } @@ -89,19 +90,19 @@ public void NewRule_GivenRuleWithIntegerConditionTypeAndContainsOperator_Returns // Arrange var ruleName = "Rule 1"; var dateBegin = DateTime.Parse("2021-01-01"); - var contentType = ContentType.Type1; - string content = "Content"; - const ConditionType conditionType = ConditionType.NumberOfSales; + var ruleset = RulesetNames.Type1; + var content = "Content"; + const ConditionNames conditionType = ConditionNames.NumberOfSales; const int conditionValue = 40; var conditionOperator = containsOperator; const DataTypes dataType = DataTypes.Integer; // Act - RuleBuilderResult ruleBuilderResult = RuleBuilder.NewRule() - .WithName(ruleName) - .WithDateBegin(dateBegin) - .WithContent(contentType, content) - .WithCondition(conditionType, conditionOperator, conditionValue) + var ruleBuilderResult = Rule.Create(ruleName) + .InRuleset(ruleset) + .SetContent(content) + .Since(dateBegin) + .ApplyWhen(conditionType, conditionOperator, conditionValue) .Build(); // Assert @@ -119,22 +120,22 @@ public void NewRule_GivenRuleWithIntegerConditionTypeAndContainsOperator_Returns public void NewRule_GivenRuleWithStringConditionTypeAndContainsOperator_BuildsAndReturnsRule(Operators containsOperator) { // Arrange - string ruleName = "Rule 1"; + var ruleName = "Rule 1"; var dateBegin = DateTime.Parse("2021-01-01"); - var contentType = ContentType.Type1; - string content = "Content"; - const ConditionType conditionType = ConditionType.IsoCountryCode; + var ruleset = RulesetNames.Type1; + var content = "Content"; + const ConditionNames conditionType = ConditionNames.IsoCountryCode; const string conditionValue = "PT"; var conditionOperator = containsOperator; const LogicalOperators logicalOperator = LogicalOperators.Eval; const DataTypes dataType = DataTypes.String; // Act - var ruleBuilderResult = RuleBuilder.NewRule() - .WithName(ruleName) - .WithDateBegin(dateBegin) - .WithContent(contentType, content) - .WithCondition(c => c.Value(conditionType, conditionOperator, conditionValue)) + var ruleBuilderResult = Rule.Create(ruleName) + .InRuleset(ruleset) + .SetContent(content) + .Since(dateBegin) + .ApplyWhen(c => c.Value(conditionType, conditionOperator, conditionValue)) .Build(); // Assert @@ -142,17 +143,17 @@ public void NewRule_GivenRuleWithStringConditionTypeAndContainsOperator_BuildsAn ruleBuilderResult.IsSuccess.Should().BeTrue(); ruleBuilderResult.Rule.Should().NotBeNull(); - Rule rule = ruleBuilderResult.Rule; + var rule = ruleBuilderResult.Rule; rule.Name.Should().Be(ruleName); rule.DateBegin.Should().Be(dateBegin); rule.DateEnd.Should().BeNull(); rule.ContentContainer.Should().NotBeNull(); rule.RootCondition.Should().NotBeNull(); - rule.RootCondition.Should().BeAssignableTo>(); + rule.RootCondition.Should().BeAssignableTo>(); - var rootCondition = rule.RootCondition as IValueConditionNode; - rootCondition.ConditionType.Should().Be(conditionType); + var rootCondition = rule.RootCondition as IValueConditionNode; + rootCondition.Condition.Should().Be(conditionType); rootCondition.DataType.Should().Be(dataType); rootCondition.LogicalOperator.Should().Be(logicalOperator); rootCondition.Operator.Should().Be(conditionOperator); @@ -164,12 +165,12 @@ public void NewRule_GivenRuleWithStringConditionTypeAndContainsOperator_BuildsAn public void NewRule_WithSerializedContent_GivenNullContentSerializationProvider_ThrowsArgumentNullException() { // Arrange - var ruleBuilder = RuleBuilder.NewRule(); - IContentSerializationProvider contentSerializationProvider = null; + var ruleBuilder = new RuleBuilder("My rule used for testing purposes"); + IContentSerializationProvider contentSerializationProvider = null; // Act var argumentNullException = Assert - .Throws(() => ruleBuilder.WithSerializedContent(ContentType.Type1, "TEST", contentSerializationProvider)); + .Throws(() => ruleBuilder.SetContent(new object(), contentSerializationProvider)); // Assert argumentNullException.Should().NotBeNull(); @@ -180,33 +181,32 @@ public void NewRule_WithSerializedContent_GivenNullContentSerializationProvider_ public void NewRule_WithSerializedContent_SetsContentAsSerializedContent() { // Arrange - string ruleName = "Rule 1"; + var ruleName = "Rule 1"; var dateBegin = DateTime.Parse("2021-01-01"); - var contentType = ContentType.Type1; - string content = "TEST"; + var ruleset = RulesetNames.Type1; + var content = "TEST"; var contentSerializer = Mock.Of(); Mock.Get(contentSerializer) .Setup(x => x.Deserialize(It.IsAny(), It.IsAny())) .Returns(content); - var contentSerializationProvider = Mock.Of>(); + var contentSerializationProvider = Mock.Of(); Mock.Get(contentSerializationProvider) - .Setup(x => x.GetContentSerializer(contentType)) + .Setup(x => x.GetContentSerializer(ruleset.ToString())) .Returns(contentSerializer); // Act - var ruleBuilderResult = RuleBuilder.NewRule() - .WithName(ruleName) - .WithDateBegin(dateBegin) - .WithSerializedContent(contentType, content, contentSerializationProvider) + var ruleBuilderResult = Rule.Create(ruleName) + .InRuleset(ruleset) + .SetContent(content, contentSerializationProvider) + .Since(dateBegin) .Build(); // Assert + ruleBuilderResult.Rule.Ruleset.Should().Be(ruleset); var ruleContent = ruleBuilderResult.Rule.ContentContainer; - ruleContent.Should().NotBeNull(); - ruleContent.Should().NotBeNull().And.BeOfType>(); - ruleContent.ContentType.Should().Be(contentType); + ruleContent.Should().NotBeNull().And.BeOfType(); ruleContent.GetContentAs().Should().Be(content); } } diff --git a/tests/Rules.Framework.Tests/Builder/RuleEngineOptionsValidatorTests.cs b/tests/Rules.Framework.Tests/Builder/RuleEngineOptionsValidatorTests.cs index cd81de1e..e0c1034c 100644 --- a/tests/Rules.Framework.Tests/Builder/RuleEngineOptionsValidatorTests.cs +++ b/tests/Rules.Framework.Tests/Builder/RuleEngineOptionsValidatorTests.cs @@ -1,8 +1,8 @@ namespace Rules.Framework.Tests.Builder { using FluentAssertions; + using Rules.Framework; using Rules.Framework.Builder; - using Rules.Framework.Core; using Xunit; public class RuleEngineOptionsValidatorTests diff --git a/tests/Rules.Framework.Tests/Builder/SelectorsTests.cs b/tests/Rules.Framework.Tests/Builder/SelectorsTests.cs index 1a2ba354..e69d3e5b 100644 --- a/tests/Rules.Framework.Tests/Builder/SelectorsTests.cs +++ b/tests/Rules.Framework.Tests/Builder/SelectorsTests.cs @@ -3,44 +3,16 @@ namespace Rules.Framework.Tests.Builder using System; using FluentAssertions; using Moq; - using Rules.Framework.Builder; - using Rules.Framework.Tests.Stubs; using Xunit; using static Rules.Framework.Builder.RulesEngineSelectors; public class SelectorsTests { - [Fact] - public void ConditionTypeSelector_WithConditionType_GivenTypeOfCondition_ReturnsNewRulesDataSourceSelector() - { - // Arrange - ConditionTypeSelector sut = new ConditionTypeSelector(); - - // Act - IRulesDataSourceSelector actual = sut.WithConditionType(); - - // Assert - actual.Should().BeOfType>(); - } - - [Fact] - public void ContentTypeSelector_WithContentType_GivenTypeOfContent_ReturnsNewConditionTypeSelector() - { - // Arrange - ContentTypeSelector sut = new ContentTypeSelector(); - - // Act - IConditionTypeSelector actual = sut.WithContentType(); - - // Assert - actual.Should().BeOfType>(); - } - [Fact] public void RulesDataSourceSelector_SetDataSource_GivenNullRulesDataSource_ThrowsArgumentNullException() { // Arrange - RulesDataSourceSelector sut = new RulesDataSourceSelector(); + var sut = new RulesDataSourceSelector(); // Assert Assert.Throws(() => @@ -54,12 +26,12 @@ public void RulesDataSourceSelector_SetDataSource_GivenNullRulesDataSource_Throw public void RulesDataSourceSelector_SetDataSource_GivenRulesDataSourceInstance_ReturnsRulesEngine() { // Arrange - RulesDataSourceSelector sut = new RulesDataSourceSelector(); + var sut = new RulesDataSourceSelector(); - Mock> mockRulesDataSource = new Mock>(); + var mockRulesDataSource = new Mock(); // Act - IConfiguredRulesEngineBuilder actual = sut.SetDataSource(mockRulesDataSource.Object); + var actual = sut.SetDataSource(mockRulesDataSource.Object); // Assert actual.Should().NotBeNull(); diff --git a/tests/Rules.Framework.Tests/Core/ConditionNodes/ComposedConditionNodeTests.cs b/tests/Rules.Framework.Tests/ConditionNodes/ComposedConditionNodeTests.cs similarity index 70% rename from tests/Rules.Framework.Tests/Core/ConditionNodes/ComposedConditionNodeTests.cs rename to tests/Rules.Framework.Tests/ConditionNodes/ComposedConditionNodeTests.cs index 35cc5cf3..98dffae0 100644 --- a/tests/Rules.Framework.Tests/Core/ConditionNodes/ComposedConditionNodeTests.cs +++ b/tests/Rules.Framework.Tests/ConditionNodes/ComposedConditionNodeTests.cs @@ -1,10 +1,9 @@ -namespace Rules.Framework.Tests.Core.ConditionNodes +namespace Rules.Framework.Tests.ConditionNodes { using FluentAssertions; using Moq; - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; - using Rules.Framework.Tests.Stubs; + using Rules.Framework; + using Rules.Framework.ConditionNodes; using Xunit; public class ComposedConditionNodeTests @@ -14,8 +13,8 @@ public void Clone_NoConditions_ReturnsCloneInstance() { // Arrange var expectedLogicalOperator = LogicalOperators.Eval; - var conditionNode1 = Mock.Of>(); - var conditionNode2 = Mock.Of>(); + var conditionNode1 = Mock.Of(); + var conditionNode2 = Mock.Of(); Mock.Get(conditionNode1) .Setup(x => x.Clone()) .Returns(conditionNode1); @@ -25,7 +24,7 @@ public void Clone_NoConditions_ReturnsCloneInstance() var expectedChildConditionNodes = new[] { conditionNode1, conditionNode2 }; - var sut = new ComposedConditionNode(expectedLogicalOperator, expectedChildConditionNodes); + var sut = new ComposedConditionNode(expectedLogicalOperator, expectedChildConditionNodes); sut.Properties["test"] = "test"; // Act @@ -35,8 +34,8 @@ public void Clone_NoConditions_ReturnsCloneInstance() actual.Should() .NotBeNull() .And - .BeOfType>(); - var valueConditionNode = actual.As>(); + .BeOfType(); + var valueConditionNode = actual.As(); valueConditionNode.LogicalOperator.Should().Be(expectedLogicalOperator); valueConditionNode.ChildConditionNodes.Should().BeEquivalentTo(expectedChildConditionNodes); valueConditionNode.Properties.Should().BeEquivalentTo(sut.Properties); @@ -49,11 +48,11 @@ public void ComposedConditionNode_Init_GivenSetupWithChildConditionsAndLogicalOp var expectedLogicalOperator = LogicalOperators.Eval; var expectedChildConditionNodes = new[] { - new Mock>().Object, - new Mock>().Object + new Mock().Object, + new Mock().Object }; - var sut = new ComposedConditionNode(expectedLogicalOperator, expectedChildConditionNodes); + var sut = new ComposedConditionNode(expectedLogicalOperator, expectedChildConditionNodes); // Act var actualLogicalOperator = sut.LogicalOperator; diff --git a/tests/Rules.Framework.Tests/ConditionNodes/ValueConditionNodeTests.cs b/tests/Rules.Framework.Tests/ConditionNodes/ValueConditionNodeTests.cs new file mode 100644 index 00000000..cf666701 --- /dev/null +++ b/tests/Rules.Framework.Tests/ConditionNodes/ValueConditionNodeTests.cs @@ -0,0 +1,238 @@ +namespace Rules.Framework.Tests.ConditionNodes +{ + using FluentAssertions; + using Rules.Framework.ConditionNodes; + using Rules.Framework.Tests.Stubs; + using Xunit; + + public class ValueConditionNodeTests + { + [Fact] + public void Clone_BooleanDataType_ReturnsCloneInstance() + { + // Arrange + var expectedConditionType = ConditionNames.IsoCountryCode.ToString(); + var expectedOperator = Operators.NotEqual; + var expectedOperand = false; + var expectedLogicalOperator = LogicalOperators.Eval; + var expectedDataType = DataTypes.Boolean; + + var sut = new ValueConditionNode(DataTypes.Boolean, expectedConditionType, expectedOperator, expectedOperand); + sut.Properties["test"] = "test"; + + // Act + var actual = sut.Clone(); + + // Assert + actual.Should() + .NotBeNull() + .And + .BeOfType(); + var valueConditionNode = actual.As(); + valueConditionNode.Condition.Should().Be(expectedConditionType); + valueConditionNode.DataType.Should().Be(expectedDataType); + valueConditionNode.LogicalOperator.Should().Be(expectedLogicalOperator); + valueConditionNode.Operator.Should().Be(expectedOperator); + valueConditionNode.Operand.Should().Be(expectedOperand); + valueConditionNode.Properties.Should().BeEquivalentTo(sut.Properties); + } + + [Fact] + public void Clone_DecimalDataType_ReturnsCloneInstance() + { + // Arrange + var expectedConditionType = ConditionNames.PluviosityRate.ToString(); + var expectedOperator = Operators.NotEqual; + var expectedOperand = 5682.2654m; + var expectedLogicalOperator = LogicalOperators.Eval; + var expectedDataType = DataTypes.Decimal; + + var sut = new ValueConditionNode(expectedDataType, expectedConditionType, expectedOperator, expectedOperand); + sut.Properties["test"] = "test"; + + // Act + var actual = sut.Clone(); + + // Assert + actual.Should() + .NotBeNull() + .And + .BeOfType(); + var valueConditionNode = actual.As(); + valueConditionNode.Condition.Should().Be(expectedConditionType); + valueConditionNode.DataType.Should().Be(expectedDataType); + valueConditionNode.LogicalOperator.Should().Be(expectedLogicalOperator); + valueConditionNode.Operator.Should().Be(expectedOperator); + valueConditionNode.Operand.Should().Be(expectedOperand); + valueConditionNode.Properties.Should().BeEquivalentTo(sut.Properties); + } + + [Fact] + public void Clone_IntegerDataType_ReturnsCloneInstance() + { + // Arrange + var expectedConditionType = ConditionNames.IsoCountryCode.ToString(); + var expectedOperator = Operators.NotEqual; + var expectedOperand = 1616; + var expectedLogicalOperator = LogicalOperators.Eval; + var expectedDataType = DataTypes.Integer; + + var sut = new ValueConditionNode(expectedDataType, expectedConditionType, expectedOperator, expectedOperand); + sut.Properties["test"] = "test"; + + // Act + var actual = sut.Clone(); + + // Assert + actual.Should() + .NotBeNull() + .And + .BeOfType(); + var valueConditionNode = actual.As(); + valueConditionNode.Condition.Should().Be(expectedConditionType); + valueConditionNode.DataType.Should().Be(expectedDataType); + valueConditionNode.LogicalOperator.Should().Be(expectedLogicalOperator); + valueConditionNode.Operator.Should().Be(expectedOperator); + valueConditionNode.Operand.Should().Be(expectedOperand); + valueConditionNode.Properties.Should().BeEquivalentTo(sut.Properties); + } + + [Fact] + public void Clone_StringDataType_ReturnsCloneInstance() + { + // Arrange + var expectedConditionType = ConditionNames.IsoCountryCode.ToString(); + var expectedOperator = Operators.NotEqual; + var expectedOperand = "Such operand, much wow."; + var expectedLogicalOperator = LogicalOperators.Eval; + var expectedDataType = DataTypes.String; + + var sut = new ValueConditionNode(expectedDataType, expectedConditionType, expectedOperator, expectedOperand); + sut.Properties["test"] = "test"; + + // Act + var actual = sut.Clone(); + + // Assert + actual.Should() + .NotBeNull() + .And + .BeOfType(); + var valueConditionNode = actual.As(); + valueConditionNode.Condition.Should().Be(expectedConditionType); + valueConditionNode.DataType.Should().Be(expectedDataType); + valueConditionNode.LogicalOperator.Should().Be(expectedLogicalOperator); + valueConditionNode.Operator.Should().Be(expectedOperator); + valueConditionNode.Operand.Should().Be(expectedOperand); + valueConditionNode.Properties.Should().BeEquivalentTo(sut.Properties); + } + + [Fact] + public void Init_GivenSetupWithBooleanValue_ReturnsSettedValues() + { + // Arrange + var expectedConditionType = ConditionNames.IsoCountryCode.ToString(); + var expectedOperator = Operators.NotEqual; + var expectedOperand = false; + var expectedLogicalOperator = LogicalOperators.Eval; + var expectedDataType = DataTypes.Boolean; + + var sut = new ValueConditionNode(expectedDataType, expectedConditionType, expectedOperator, expectedOperand); + + // Act + var actualConditionType = sut.Condition; + var actualOperator = sut.Operator; + var actualDataType = sut.DataType; + var actualLogicalOperator = sut.LogicalOperator; + var actualOperand = sut.Operand; + + // Assert + actualConditionType.Should().Be(expectedConditionType); + actualOperator.Should().Be(expectedOperator); + actualOperand.Should().Be(expectedOperand); + actualLogicalOperator.Should().Be(expectedLogicalOperator); + actualDataType.Should().Be(expectedDataType); + } + + [Fact] + public void Init_GivenSetupWithDecimalValue_ReturnsSettedValues() + { + // Arrange + var expectedConditionType = ConditionNames.PluviosityRate.ToString(); + var expectedOperator = Operators.NotEqual; + var expectedOperand = 5682.2654m; + var expectedLogicalOperator = LogicalOperators.Eval; + var expectedDataType = DataTypes.Decimal; + + var sut = new ValueConditionNode(DataTypes.Decimal, expectedConditionType, expectedOperator, expectedOperand); + + // Act + var actualConditionType = sut.Condition; + var actualOperator = sut.Operator; + var actualDataType = sut.DataType; + var actualLogicalOperator = sut.LogicalOperator; + var actualOperand = sut.Operand; + + // Assert + actualConditionType.Should().Be(expectedConditionType); + actualOperator.Should().Be(expectedOperator); + actualOperand.Should().Be(expectedOperand); + actualLogicalOperator.Should().Be(expectedLogicalOperator); + actualDataType.Should().Be(expectedDataType); + } + + [Fact] + public void Init_GivenSetupWithIntegerValue_ReturnsSettedValues() + { + // Arrange + var expectedConditionType = ConditionNames.IsoCountryCode.ToString(); + var expectedOperator = Operators.NotEqual; + var expectedOperand = 1616; + var expectedLogicalOperator = LogicalOperators.Eval; + var expectedDataType = DataTypes.Integer; + + var sut = new ValueConditionNode(expectedDataType, expectedConditionType, expectedOperator, expectedOperand); + + // Act + var actualConditionType = sut.Condition; + var actualOperator = sut.Operator; + var actualDataType = sut.DataType; + var actualLogicalOperator = sut.LogicalOperator; + var actualOperand = sut.Operand; + + // Assert + actualConditionType.Should().Be(expectedConditionType); + actualOperator.Should().Be(expectedOperator); + actualOperand.Should().Be(expectedOperand); + actualLogicalOperator.Should().Be(expectedLogicalOperator); + actualDataType.Should().Be(expectedDataType); + } + + [Fact] + public void Init_GivenSetupWithStringValue_ReturnsSettedValues() + { + // Arrange + var expectedConditionType = ConditionNames.IsoCountryCode.ToString(); + var expectedOperator = Operators.NotEqual; + var expectedOperand = "Such operand, much wow."; + var expectedLogicalOperator = LogicalOperators.Eval; + var expectedDataType = DataTypes.String; + + var sut = new ValueConditionNode(expectedDataType, expectedConditionType, expectedOperator, expectedOperand); + + // Act + var actualConditionType = sut.Condition; + var actualOperator = sut.Operator; + var actualDataType = sut.DataType; + var actualLogicalOperator = sut.LogicalOperator; + var actualOperand = sut.Operand; + + // Assert + actualConditionType.Should().Be(expectedConditionType); + actualOperator.Should().Be(expectedOperator); + actualOperand.Should().Be(expectedOperand); + actualLogicalOperator.Should().Be(expectedLogicalOperator); + actualDataType.Should().Be(expectedDataType); + } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/ConditionTests.cs b/tests/Rules.Framework.Tests/ConditionTests.cs deleted file mode 100644 index c2135cf7..00000000 --- a/tests/Rules.Framework.Tests/ConditionTests.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Rules.Framework.Tests -{ - using FluentAssertions; - using Rules.Framework.Tests.Stubs; - using Xunit; - - public class ConditionTests - { - [Fact] - public void Condition_Ctor_Success() - { - // Arrange - var expectedType = ConditionType.IsoCountryCode; - var expectedValue = "abc"; - - // Act - var sut = new Condition(expectedType, expectedValue); - - // Assert - sut.Type.Should().Be(expectedType); - sut.Value.Should().Be(expectedValue); - } - } -} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/ConditionTypeExtractorTests.cs b/tests/Rules.Framework.Tests/ConditionTypeExtractorTests.cs deleted file mode 100644 index 6fdf643f..00000000 --- a/tests/Rules.Framework.Tests/ConditionTypeExtractorTests.cs +++ /dev/null @@ -1,148 +0,0 @@ -namespace Rules.Framework.Tests -{ - using System; - using System.Collections.Generic; - using FluentAssertions; - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; - using Rules.Framework.Tests.Stubs; - using Xunit; - - public class ConditionTypeExtractorTests - { - [Fact] - public void GetConditionTypes_ReturnsCorrectExtraction() - { - // Arrange - - var dateBegin = new DateTime(2018, 01, 01); - var dateEnd = new DateTime(2019, 01, 01); - - var contentType = ContentType.Type1; - - var rule1 = new Rule - { - ContentContainer = new ContentContainer(contentType, _ => new object()), - DateBegin = dateBegin, - DateEnd = dateEnd, - Name = "Rule 1", - Priority = 3, - RootCondition = new ValueConditionNode(DataTypes.String, ConditionType.IsoCountryCode, Operators.Equal, "USA") - }; - - var rule2 = new Rule - { - ContentContainer = new ContentContainer(contentType, _ => new object()), - DateBegin = new DateTime(2020, 01, 01), - DateEnd = new DateTime(2021, 01, 01), - Name = "Rule 2", - Priority = 200, - RootCondition = new ValueConditionNode(DataTypes.String, ConditionType.IsoCountryCode, Operators.Equal, "USA") - }; - - var rule3 = new Rule - { - ContentContainer = new ContentContainer(contentType, _ => new object()), - DateBegin = dateBegin, - DateEnd = dateEnd, - Name = "Rule 3", - Priority = 1, - RootCondition = new ValueConditionNode(DataTypes.String, ConditionType.IsoCurrency, Operators.Equal, "EUR") - }; - - var rule4 = new Rule - { - ContentContainer = new ContentContainer(contentType, _ => new object()), - DateBegin = dateBegin, - DateEnd = dateEnd, - Name = "Rule 4", - Priority = 1, - RootCondition = new ComposedConditionNode( - LogicalOperators.And, - new IConditionNode[] - { - new ValueConditionNode(DataTypes.String,ConditionType.IsVip, Operators.Equal, "true"), - new ValueConditionNode(DataTypes.String,ConditionType.PluviosityRate, Operators.Equal, "15"), - new ValueConditionNode(DataTypes.String,ConditionType.IsoCurrency, Operators.Equal, "JPY") - } - ) - }; - - var matchRules = new[] - { - rule1, - rule2, - rule3, - rule4 - }; - - var expectedConditionTypeList = new List - { - ConditionType.IsoCurrency, - ConditionType.IsoCountryCode, - ConditionType.IsVip, - ConditionType.PluviosityRate - }; - - var conditionTypeExtractor = new ConditionTypeExtractor(); - - // Act - var actual = conditionTypeExtractor.GetConditionTypes(matchRules); - - // Assert - actual.Should().BeEquivalentTo(expectedConditionTypeList); - } - - [Fact] - public void GetConditionTypes_WithEmptyMatchRules_ReturnsEmptyListConditionTypes() - { - // Arrange - - var matchRules = new List>(); - - var expectedConditionTypeList = new List(); - - var conditionTypeExtractor = new ConditionTypeExtractor(); - - // Act - var actual = conditionTypeExtractor.GetConditionTypes(matchRules); - - // Assert - actual.Should().BeEquivalentTo(expectedConditionTypeList); - } - - [Fact] - public void GetConditionTypes_WithNullRootCondition_ReturnsEmptyListConditionTypes() - { - // Arrange - - var dateBegin = new DateTime(2018, 01, 01); - var dateEnd = new DateTime(2019, 01, 01); - - var contentType = ContentType.Type1; - - var matchRules = new List> - { - new Rule - { - ContentContainer = new ContentContainer(contentType, _ => new object()), - DateBegin = dateBegin, - DateEnd = dateEnd, - Name = "Rule 3", - Priority = 1, - RootCondition = null - } - }; - - var expectedConditionTypeList = new List(); - - var conditionTypeExtractor = new ConditionTypeExtractor(); - - // Act - var actual = conditionTypeExtractor.GetConditionTypes(matchRules); - - // Assert - actual.Should().BeEquivalentTo(expectedConditionTypeList); - } - } -} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Core/ContentContainerTests.cs b/tests/Rules.Framework.Tests/ContentContainerTests.cs similarity index 55% rename from tests/Rules.Framework.Tests/Core/ContentContainerTests.cs rename to tests/Rules.Framework.Tests/ContentContainerTests.cs index 9bbb9338..615497df 100644 --- a/tests/Rules.Framework.Tests/Core/ContentContainerTests.cs +++ b/tests/Rules.Framework.Tests/ContentContainerTests.cs @@ -1,35 +1,19 @@ -namespace Rules.Framework.Tests.Core +namespace Rules.Framework.Tests { using System; using FluentAssertions; - using Rules.Framework.Core; - using Rules.Framework.Tests.Stubs; + using Rules.Framework; using Xunit; public class ContentContainerTests { - [Fact] - public void ContentContainer_ContentType_HavingProvidedContentTypeByCtor_ReturnsProvidedValue() - { - // Arrange - ContentType expected = ContentType.Type1; - - ContentContainer sut = new ContentContainer(expected, (t) => new object()); - - // Act - ContentType actual = sut.ContentType; - - // Assert - actual.Should().Be(expected); - } - [Fact] public void ContentContainer_GetContentAs_HavingProvidedContentByCtorAndGivenCorrectRuntimeType_ReturnsProvidedValue() { // Arrange object expected = "Just some content"; - ContentContainer sut = new ContentContainer(ContentType.Type1, (t) => expected); + var sut = new ContentContainer(_ => expected); // Act object actual = sut.GetContentAs(); @@ -44,10 +28,10 @@ public void ContentContainer_GetContentAs_HavingProvidedContentByCtorAndGivenWro // Arrange object expected = "Just some content"; - ContentContainer sut = new ContentContainer(ContentType.Type1, (t) => expected); + var sut = new ContentContainer(_ => expected); // Act - ContentTypeException contentTypeException = Assert.Throws(() => sut.GetContentAs()); + var contentTypeException = Assert.Throws(() => sut.GetContentAs()); // Assert contentTypeException.Should().NotBeNull(); diff --git a/tests/Rules.Framework.Tests/Core/ConditionNodes/BooleanConditionNodeTests.cs b/tests/Rules.Framework.Tests/Core/ConditionNodes/BooleanConditionNodeTests.cs deleted file mode 100644 index 5351c9b1..00000000 --- a/tests/Rules.Framework.Tests/Core/ConditionNodes/BooleanConditionNodeTests.cs +++ /dev/null @@ -1,68 +0,0 @@ -namespace Rules.Framework.Tests.Core.ConditionNodes -{ - using FluentAssertions; - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; - using Rules.Framework.Tests.Stubs; - using Xunit; - - public class BooleanConditionNodeTests - { - [Fact] - public void Clone_NoConditions_ReturnsCloneInstance() - { - // Arrange - var expectedConditionType = ConditionType.IsoCountryCode; - var expectedOperator = Operators.NotEqual; - var expectedOperand = false; - var expectedLogicalOperator = LogicalOperators.Eval; - var expectedDataType = DataTypes.Boolean; - - var sut = new ValueConditionNode(DataTypes.Boolean, expectedConditionType, expectedOperator, expectedOperand); - sut.Properties["test"] = "test"; - - // Act - var actual = sut.Clone(); - - // Assert - actual.Should() - .NotBeNull() - .And - .BeOfType>(); - var valueConditionNode = actual.As>(); - valueConditionNode.ConditionType.Should().Be(expectedConditionType); - valueConditionNode.DataType.Should().Be(expectedDataType); - valueConditionNode.LogicalOperator.Should().Be(expectedLogicalOperator); - valueConditionNode.Operator.Should().Be(expectedOperator); - valueConditionNode.Operand.Should().Be(expectedOperand); - valueConditionNode.Properties.Should().BeEquivalentTo(sut.Properties); - } - - [Fact] - public void Init_GivenSetupWithBooleanValue_ReturnsSettedValues() - { - // Arrange - var expectedConditionType = ConditionType.IsoCountryCode; - var expectedOperator = Operators.NotEqual; - var expectedOperand = false; - var expectedLogicalOperator = LogicalOperators.Eval; - var expectedDataType = DataTypes.Boolean; - - var sut = new ValueConditionNode(expectedDataType, expectedConditionType, expectedOperator, expectedOperand); - - // Act - var actualConditionType = sut.ConditionType; - var actualOperator = sut.Operator; - var actualDataType = sut.DataType; - var actualLogicalOperator = sut.LogicalOperator; - var actualOperand = sut.Operand; - - // Assert - actualConditionType.Should().Be(expectedConditionType); - actualOperator.Should().Be(expectedOperator); - actualOperand.Should().Be(expectedOperand); - actualLogicalOperator.Should().Be(expectedLogicalOperator); - actualDataType.Should().Be(expectedDataType); - } - } -} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Core/ConditionNodes/DecimalConditionNodeTests.cs b/tests/Rules.Framework.Tests/Core/ConditionNodes/DecimalConditionNodeTests.cs deleted file mode 100644 index 92d6e95f..00000000 --- a/tests/Rules.Framework.Tests/Core/ConditionNodes/DecimalConditionNodeTests.cs +++ /dev/null @@ -1,68 +0,0 @@ -namespace Rules.Framework.Tests.Core.ConditionNodes -{ - using FluentAssertions; - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; - using Rules.Framework.Tests.Stubs; - using Xunit; - - public class DecimalConditionNodeTests - { - [Fact] - public void Clone_NoConditions_ReturnsCloneInstance() - { - // Arrange - var expectedConditionType = ConditionType.PluviosityRate; - var expectedOperator = Operators.NotEqual; - var expectedOperand = 5682.2654m; - var expectedLogicalOperator = LogicalOperators.Eval; - var expectedDataType = DataTypes.Decimal; - - var sut = new ValueConditionNode(expectedDataType, expectedConditionType, expectedOperator, expectedOperand); - sut.Properties["test"] = "test"; - - // Act - var actual = sut.Clone(); - - // Assert - actual.Should() - .NotBeNull() - .And - .BeOfType>(); - var valueConditionNode = actual.As>(); - valueConditionNode.ConditionType.Should().Be(expectedConditionType); - valueConditionNode.DataType.Should().Be(expectedDataType); - valueConditionNode.LogicalOperator.Should().Be(expectedLogicalOperator); - valueConditionNode.Operator.Should().Be(expectedOperator); - valueConditionNode.Operand.Should().Be(expectedOperand); - valueConditionNode.Properties.Should().BeEquivalentTo(sut.Properties); - } - - [Fact] - public void Init_GivenSetupWithDecimalValue_ReturnsSettedValues() - { - // Arrange - var expectedConditionType = ConditionType.PluviosityRate; - var expectedOperator = Operators.NotEqual; - var expectedOperand = 5682.2654m; - var expectedLogicalOperator = LogicalOperators.Eval; - var expectedDataType = DataTypes.Decimal; - - var sut = new ValueConditionNode(DataTypes.Decimal, expectedConditionType, expectedOperator, expectedOperand); - - // Act - var actualConditionType = sut.ConditionType; - var actualOperator = sut.Operator; - var actualDataType = sut.DataType; - var actualLogicalOperator = sut.LogicalOperator; - var actualOperand = sut.Operand; - - // Assert - actualConditionType.Should().Be(expectedConditionType); - actualOperator.Should().Be(expectedOperator); - actualOperand.Should().Be(expectedOperand); - actualLogicalOperator.Should().Be(expectedLogicalOperator); - actualDataType.Should().Be(expectedDataType); - } - } -} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Core/ConditionNodes/IntegerConditionNodeTests.cs b/tests/Rules.Framework.Tests/Core/ConditionNodes/IntegerConditionNodeTests.cs deleted file mode 100644 index 8fbe4b6e..00000000 --- a/tests/Rules.Framework.Tests/Core/ConditionNodes/IntegerConditionNodeTests.cs +++ /dev/null @@ -1,68 +0,0 @@ -namespace Rules.Framework.Tests.Core.ConditionNodes -{ - using FluentAssertions; - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; - using Rules.Framework.Tests.Stubs; - using Xunit; - - public class IntegerConditionNodeTests - { - [Fact] - public void Clone_NoConditions_ReturnsCloneInstance() - { - // Arrange - var expectedConditionType = ConditionType.IsoCountryCode; - var expectedOperator = Operators.NotEqual; - var expectedOperand = 1616; - var expectedLogicalOperator = LogicalOperators.Eval; - var expectedDataType = DataTypes.Integer; - - var sut = new ValueConditionNode(expectedDataType, expectedConditionType, expectedOperator, expectedOperand); - sut.Properties["test"] = "test"; - - // Act - var actual = sut.Clone(); - - // Assert - actual.Should() - .NotBeNull() - .And - .BeOfType>(); - var valueConditionNode = actual.As>(); - valueConditionNode.ConditionType.Should().Be(expectedConditionType); - valueConditionNode.DataType.Should().Be(expectedDataType); - valueConditionNode.LogicalOperator.Should().Be(expectedLogicalOperator); - valueConditionNode.Operator.Should().Be(expectedOperator); - valueConditionNode.Operand.Should().Be(expectedOperand); - valueConditionNode.Properties.Should().BeEquivalentTo(sut.Properties); - } - - [Fact] - public void Init_GivenSetupWithIntegerValue_ReturnsSettedValues() - { - // Arrange - var expectedConditionType = ConditionType.IsoCountryCode; - var expectedOperator = Operators.NotEqual; - var expectedOperand = 1616; - var expectedLogicalOperator = LogicalOperators.Eval; - var expectedDataType = DataTypes.Integer; - - var sut = new ValueConditionNode(expectedDataType, expectedConditionType, expectedOperator, expectedOperand); - - // Act - var actualConditionType = sut.ConditionType; - var actualOperator = sut.Operator; - var actualDataType = sut.DataType; - var actualLogicalOperator = sut.LogicalOperator; - var actualOperand = sut.Operand; - - // Assert - actualConditionType.Should().Be(expectedConditionType); - actualOperator.Should().Be(expectedOperator); - actualOperand.Should().Be(expectedOperand); - actualLogicalOperator.Should().Be(expectedLogicalOperator); - actualDataType.Should().Be(expectedDataType); - } - } -} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Core/ConditionNodes/StringConditionNodeTests.cs b/tests/Rules.Framework.Tests/Core/ConditionNodes/StringConditionNodeTests.cs deleted file mode 100644 index 55727d71..00000000 --- a/tests/Rules.Framework.Tests/Core/ConditionNodes/StringConditionNodeTests.cs +++ /dev/null @@ -1,68 +0,0 @@ -namespace Rules.Framework.Tests.Core.ConditionNodes -{ - using FluentAssertions; - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; - using Rules.Framework.Tests.Stubs; - using Xunit; - - public class StringConditionNodeTests - { - [Fact] - public void Clone_NoConditions_ReturnsCloneInstance() - { - // Arrange - var expectedConditionType = ConditionType.IsoCountryCode; - var expectedOperator = Operators.NotEqual; - var expectedOperand = "Such operand, much wow."; - var expectedLogicalOperator = LogicalOperators.Eval; - var expectedDataType = DataTypes.String; - - var sut = new ValueConditionNode(expectedDataType, expectedConditionType, expectedOperator, expectedOperand); - sut.Properties["test"] = "test"; - - // Act - var actual = sut.Clone(); - - // Assert - actual.Should() - .NotBeNull() - .And - .BeOfType>(); - var valueConditionNode = actual.As>(); - valueConditionNode.ConditionType.Should().Be(expectedConditionType); - valueConditionNode.DataType.Should().Be(expectedDataType); - valueConditionNode.LogicalOperator.Should().Be(expectedLogicalOperator); - valueConditionNode.Operator.Should().Be(expectedOperator); - valueConditionNode.Operand.Should().Be(expectedOperand); - valueConditionNode.Properties.Should().BeEquivalentTo(sut.Properties); - } - - [Fact] - public void Init_GivenSetupWithStringValue_ReturnsSettedValues() - { - // Arrange - var expectedConditionType = ConditionType.IsoCountryCode; - var expectedOperator = Operators.NotEqual; - var expectedOperand = "Such operand, much wow."; - var expectedLogicalOperator = LogicalOperators.Eval; - var expectedDataType = DataTypes.String; - - var sut = new ValueConditionNode(expectedDataType, expectedConditionType, expectedOperator, expectedOperand); - - // Act - var actualConditionType = sut.ConditionType; - var actualOperator = sut.Operator; - var actualDataType = sut.DataType; - var actualLogicalOperator = sut.LogicalOperator; - var actualOperand = sut.Operand; - - // Assert - actualConditionType.Should().Be(expectedConditionType); - actualOperator.Should().Be(expectedOperator); - actualOperand.Should().Be(expectedOperand); - actualLogicalOperator.Should().Be(expectedLogicalOperator); - actualDataType.Should().Be(expectedDataType); - } - } -} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/CompilationRulesSourceMiddlewareTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/CompilationRulesSourceMiddlewareTests.cs index 3404db54..a0356680 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/CompilationRulesSourceMiddlewareTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/CompilationRulesSourceMiddlewareTests.cs @@ -7,7 +7,8 @@ namespace Rules.Framework.Tests.Evaluation.Compiled using System.Threading.Tasks; using FluentAssertions; using Moq; - using Rules.Framework.Builder; + using Rules.Framework; + using Rules.Framework.Builder.Generic; using Rules.Framework.Core; using Rules.Framework.Evaluation.Compiled; using Rules.Framework.Source; @@ -26,39 +27,39 @@ public async Task HandleAddRuleAsync_GivenRuleWithCompiledConditionAndNextDelega // Simulate compiled rule. expectedRule.RootCondition.Properties[ConditionNodeProperties.CompilationProperties.IsCompiledKey] = true; - var addRuleArgs = new AddRuleArgs + var addRuleArgs = new AddRuleArgs { Rule = expectedRule, }; - bool nextDelegateWasInvoked = false; - var nextDelegate = new AddRuleDelegate((_) => + var nextDelegateWasInvoked = false; + var nextDelegate = new AddRuleDelegate((_) => { nextDelegateWasInvoked = true; return Task.CompletedTask; }); - Expression, bool>> expectedExpression = (_) => true; + Expression> expectedExpression = (_) => true; - var ruleConditionsExpressionBuilder = Mock.Of>(); + var ruleConditionsExpressionBuilder = Mock.Of(); Mock.Get(ruleConditionsExpressionBuilder) - .Setup(x => x.BuildExpression(It.IsAny>())) + .Setup(x => x.BuildExpression(It.IsAny())) .Returns(expectedExpression); - var rulesDataSource = Mock.Of>(); + var rulesDataSource = Mock.Of(); - var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( + var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( ruleConditionsExpressionBuilder, rulesDataSource); // Act - await compilationRulesSourceMiddleware.HandleAddRuleAsync(addRuleArgs, nextDelegate).ConfigureAwait(false); + await compilationRulesSourceMiddleware.HandleAddRuleAsync(addRuleArgs, nextDelegate); // Assert nextDelegateWasInvoked.Should().BeTrue(); Mock.Get(ruleConditionsExpressionBuilder) - .Verify(x => x.BuildExpression(It.IsAny>()), Times.Never()); + .Verify(x => x.BuildExpression(It.IsAny()), Times.Never()); } [Fact] @@ -68,39 +69,39 @@ public async Task HandleAddRuleAsync_GivenRuleWithoutConditionsAndNextDelegate_I var ruleResult = CreateTestRule(withCondition: false); var expectedRule = ruleResult.Rule; - var addRuleArgs = new AddRuleArgs + var addRuleArgs = new AddRuleArgs { Rule = expectedRule, }; - bool nextDelegateWasInvoked = false; - var nextDelegate = new AddRuleDelegate((_) => + var nextDelegateWasInvoked = false; + var nextDelegate = new AddRuleDelegate((_) => { nextDelegateWasInvoked = true; return Task.CompletedTask; }); - Expression, bool>> expectedExpression = (_) => true; + Expression> expectedExpression = (_) => true; - var ruleConditionsExpressionBuilder = Mock.Of>(); + var ruleConditionsExpressionBuilder = Mock.Of(); Mock.Get(ruleConditionsExpressionBuilder) - .Setup(x => x.BuildExpression(It.IsAny>())) + .Setup(x => x.BuildExpression(It.IsAny())) .Returns(expectedExpression); - var rulesDataSource = Mock.Of>(); + var rulesDataSource = Mock.Of(); - var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( + var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( ruleConditionsExpressionBuilder, rulesDataSource); // Act - await compilationRulesSourceMiddleware.HandleAddRuleAsync(addRuleArgs, nextDelegate).ConfigureAwait(false); + await compilationRulesSourceMiddleware.HandleAddRuleAsync(addRuleArgs, nextDelegate); // Assert nextDelegateWasInvoked.Should().BeTrue(); Mock.Get(ruleConditionsExpressionBuilder) - .Verify(x => x.BuildExpression(It.IsAny>()), Times.Never()); + .Verify(x => x.BuildExpression(It.IsAny()), Times.Never()); } [Fact] @@ -110,40 +111,40 @@ public async Task HandleAddRuleAsync_GivenRuleWithUncompiledConditionAndNextDele var ruleResult = CreateTestRule(withCondition: true); var expectedRule = ruleResult.Rule; - var addRuleArgs = new AddRuleArgs + var addRuleArgs = new AddRuleArgs { Rule = expectedRule, }; - bool nextDelegateWasInvoked = false; - var nextDelegate = new AddRuleDelegate((_) => + var nextDelegateWasInvoked = false; + var nextDelegate = new AddRuleDelegate((_) => { nextDelegateWasInvoked = true; return Task.CompletedTask; }); - Expression, bool>> expectedExpression = (_) => true; + Expression> expectedExpression = (_) => true; - var ruleConditionsExpressionBuilder = Mock.Of>(); + var ruleConditionsExpressionBuilder = Mock.Of(); Mock.Get(ruleConditionsExpressionBuilder) - .Setup(x => x.BuildExpression(It.IsAny>())) + .Setup(x => x.BuildExpression(It.IsAny())) .Returns(expectedExpression); - var rulesDataSource = Mock.Of>(); + var rulesDataSource = Mock.Of(); - var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( + var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( ruleConditionsExpressionBuilder, rulesDataSource); // Act - await compilationRulesSourceMiddleware.HandleAddRuleAsync(addRuleArgs, nextDelegate).ConfigureAwait(false); + await compilationRulesSourceMiddleware.HandleAddRuleAsync(addRuleArgs, nextDelegate); // Assert expectedRule.RootCondition.Properties.Should().HaveCount(2); expectedRule.RootCondition.Properties.Should().ContainKey(ConditionNodeProperties.CompilationProperties.IsCompiledKey) .WhoseValue.Should().Be(true); expectedRule.RootCondition.Properties.Should().ContainKey(ConditionNodeProperties.CompilationProperties.CompiledDelegateKey) - .WhoseValue.Should().BeOfType, bool>>(); + .WhoseValue.Should().BeOfType>(); nextDelegateWasInvoked.Should().BeTrue(); Mock.VerifyAll( @@ -159,48 +160,48 @@ public async Task HandleGetRulesAsync_GivenArgsFilteringToRulesWithCompiledCondi // Simulate compiled rule. expectedRule.RootCondition.Properties[ConditionNodeProperties.CompilationProperties.IsCompiledKey] = true; - var expectedRules = new[] { expectedRule }; + var expectedRules = new Rule[] { expectedRule }; - var getRulesArgs = new GetRulesArgs + var getRulesArgs = new GetRulesArgs { - ContentType = ContentType.Type1, + ContentType = RulesetNames.Type1.ToString(), DateBegin = DateTime.UtcNow.AddDays(-1), DateEnd = DateTime.UtcNow.AddDays(1), }; - bool nextDelegateWasInvoked = false; - var nextDelegate = new GetRulesDelegate((_) => + var nextDelegateWasInvoked = false; + var nextDelegate = new GetRulesDelegate((_) => { nextDelegateWasInvoked = true; - return Task.FromResult>>(expectedRules); + return Task.FromResult>(expectedRules); }); - Expression, bool>> expectedExpression = (_) => true; + Expression> expectedExpression = (_) => true; - var ruleConditionsExpressionBuilder = Mock.Of>(); + var ruleConditionsExpressionBuilder = Mock.Of(); Mock.Get(ruleConditionsExpressionBuilder) - .Setup(x => x.BuildExpression(It.IsAny>())) + .Setup(x => x.BuildExpression(It.IsAny())) .Returns(expectedExpression); - var rulesDataSource = Mock.Of>(); + var rulesDataSource = Mock.Of(); Mock.Get(rulesDataSource) - .Setup(x => x.UpdateRuleAsync(It.IsAny>())); + .Setup(x => x.UpdateRuleAsync(It.IsAny())); - var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( + var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( ruleConditionsExpressionBuilder, rulesDataSource); // Act - var actualRules = await compilationRulesSourceMiddleware.HandleGetRulesAsync(getRulesArgs, nextDelegate).ConfigureAwait(false); + var actualRules = await compilationRulesSourceMiddleware.HandleGetRulesAsync(getRulesArgs, nextDelegate); // Assert actualRules.Should().BeEquivalentTo(expectedRules); nextDelegateWasInvoked.Should().BeTrue(); Mock.Get(ruleConditionsExpressionBuilder) - .Verify(x => x.BuildExpression(It.IsAny>()), Times.Never()); + .Verify(x => x.BuildExpression(It.IsAny()), Times.Never()); Mock.Get(rulesDataSource) - .Verify(x => x.UpdateRuleAsync(It.IsAny>()), Times.Never()); + .Verify(x => x.UpdateRuleAsync(It.IsAny()), Times.Never()); } [Fact] @@ -209,48 +210,48 @@ public async Task HandleGetRulesAsync_GivenArgsFilteringToRulesWithoutConditions // Arrange var ruleResult = CreateTestRule(withCondition: false); var expectedRule = ruleResult.Rule; - var expectedRules = new[] { expectedRule }; + var expectedRules = new Rule[] { expectedRule }; - var getRulesArgs = new GetRulesArgs + var getRulesArgs = new GetRulesArgs { - ContentType = ContentType.Type1, + ContentType = RulesetNames.Type1.ToString(), DateBegin = DateTime.UtcNow.AddDays(-1), DateEnd = DateTime.UtcNow.AddDays(1), }; - bool nextDelegateWasInvoked = false; - var nextDelegate = new GetRulesDelegate((_) => + var nextDelegateWasInvoked = false; + var nextDelegate = new GetRulesDelegate((_) => { nextDelegateWasInvoked = true; - return Task.FromResult>>(expectedRules); + return Task.FromResult>(expectedRules); }); - Expression, bool>> expectedExpression = (_) => true; + Expression> expectedExpression = (_) => true; - var ruleConditionsExpressionBuilder = Mock.Of>(); + var ruleConditionsExpressionBuilder = Mock.Of(); Mock.Get(ruleConditionsExpressionBuilder) - .Setup(x => x.BuildExpression(It.IsAny>())) + .Setup(x => x.BuildExpression(It.IsAny())) .Returns(expectedExpression); - var rulesDataSource = Mock.Of>(); + var rulesDataSource = Mock.Of(); Mock.Get(rulesDataSource) - .Setup(x => x.UpdateRuleAsync(It.IsAny>())); + .Setup(x => x.UpdateRuleAsync(It.IsAny())); - var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( + var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( ruleConditionsExpressionBuilder, rulesDataSource); // Act - var actualRules = await compilationRulesSourceMiddleware.HandleGetRulesAsync(getRulesArgs, nextDelegate).ConfigureAwait(false); + var actualRules = await compilationRulesSourceMiddleware.HandleGetRulesAsync(getRulesArgs, nextDelegate); // Assert actualRules.Should().BeEquivalentTo(expectedRules); nextDelegateWasInvoked.Should().BeTrue(); Mock.Get(ruleConditionsExpressionBuilder) - .Verify(x => x.BuildExpression(It.IsAny>()), Times.Never()); + .Verify(x => x.BuildExpression(It.IsAny()), Times.Never()); Mock.Get(rulesDataSource) - .Verify(x => x.UpdateRuleAsync(It.IsAny>()), Times.Never()); + .Verify(x => x.UpdateRuleAsync(It.IsAny()), Times.Never()); } [Fact] @@ -259,39 +260,39 @@ public async Task HandleGetRulesAsync_GivenArgsFilteringToRulesWithUncompiledCon // Arrange var ruleResult = CreateTestRule(withCondition: true); var expectedRule = ruleResult.Rule; - var expectedRules = new[] { expectedRule }; + var expectedRules = new Rule[] { expectedRule }; - var getRulesArgs = new GetRulesArgs + var getRulesArgs = new GetRulesArgs { - ContentType = ContentType.Type1, + ContentType = RulesetNames.Type1.ToString(), DateBegin = DateTime.UtcNow.AddDays(-1), DateEnd = DateTime.UtcNow.AddDays(1), }; - bool nextDelegateWasInvoked = false; - var nextDelegate = new GetRulesDelegate((_) => + var nextDelegateWasInvoked = false; + var nextDelegate = new GetRulesDelegate((_) => { nextDelegateWasInvoked = true; - return Task.FromResult>>(expectedRules); + return Task.FromResult>(expectedRules); }); - Expression, bool>> expectedExpression = (_) => true; + Expression> expectedExpression = (_) => true; - var ruleConditionsExpressionBuilder = Mock.Of>(); + var ruleConditionsExpressionBuilder = Mock.Of(); Mock.Get(ruleConditionsExpressionBuilder) - .Setup(x => x.BuildExpression(It.IsAny>())) + .Setup(x => x.BuildExpression(It.IsAny())) .Returns(expectedExpression); - var rulesDataSource = Mock.Of>(); + var rulesDataSource = Mock.Of(); Mock.Get(rulesDataSource) - .Setup(x => x.UpdateRuleAsync(It.IsAny>())); + .Setup(x => x.UpdateRuleAsync(It.IsAny())); - var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( + var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( ruleConditionsExpressionBuilder, rulesDataSource); // Act - var actualRules = await compilationRulesSourceMiddleware.HandleGetRulesAsync(getRulesArgs, nextDelegate).ConfigureAwait(false); + var actualRules = await compilationRulesSourceMiddleware.HandleGetRulesAsync(getRulesArgs, nextDelegate); // Assert actualRules.Should().HaveCount(1); @@ -300,7 +301,7 @@ public async Task HandleGetRulesAsync_GivenArgsFilteringToRulesWithUncompiledCon actualRule.RootCondition.Properties.Should().ContainKey(ConditionNodeProperties.CompilationProperties.IsCompiledKey) .WhoseValue.Should().Be(true); actualRule.RootCondition.Properties.Should().ContainKey(ConditionNodeProperties.CompilationProperties.CompiledDelegateKey) - .WhoseValue.Should().BeOfType, bool>>(); + .WhoseValue.Should().BeOfType>(); nextDelegateWasInvoked.Should().BeTrue(); Mock.VerifyAll( @@ -317,46 +318,46 @@ public async Task HandleGetRulesFilteredAsync_GivenArgsFilteringToRulesWithCompi // Simulate compiled rule. expectedRule.RootCondition.Properties[ConditionNodeProperties.CompilationProperties.IsCompiledKey] = true; - var expectedRules = new[] { expectedRule }; + var expectedRules = new Rule[] { expectedRule }; - var getRulesFilteredArgs = new GetRulesFilteredArgs + var getRulesFilteredArgs = new GetRulesFilteredArgs { - ContentType = ContentType.Type1, + Ruleset = RulesetNames.Type1.ToString(), }; - bool nextDelegateWasInvoked = false; - var nextDelegate = new GetRulesFilteredDelegate((_) => + var nextDelegateWasInvoked = false; + var nextDelegate = new GetRulesFilteredDelegate((_) => { nextDelegateWasInvoked = true; - return Task.FromResult>>(expectedRules); + return Task.FromResult>(expectedRules); }); - Expression, bool>> expectedExpression = (_) => true; + Expression> expectedExpression = (_) => true; - var ruleConditionsExpressionBuilder = Mock.Of>(); + var ruleConditionsExpressionBuilder = Mock.Of(); Mock.Get(ruleConditionsExpressionBuilder) - .Setup(x => x.BuildExpression(It.IsAny>())) + .Setup(x => x.BuildExpression(It.IsAny())) .Returns(expectedExpression); - var rulesDataSource = Mock.Of>(); + var rulesDataSource = Mock.Of(); Mock.Get(rulesDataSource) - .Setup(x => x.UpdateRuleAsync(It.IsAny>())); + .Setup(x => x.UpdateRuleAsync(It.IsAny())); - var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( + var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( ruleConditionsExpressionBuilder, rulesDataSource); // Act - var actualRules = await compilationRulesSourceMiddleware.HandleGetRulesFilteredAsync(getRulesFilteredArgs, nextDelegate).ConfigureAwait(false); + var actualRules = await compilationRulesSourceMiddleware.HandleGetRulesFilteredAsync(getRulesFilteredArgs, nextDelegate); // Assert actualRules.Should().BeEquivalentTo(expectedRules); nextDelegateWasInvoked.Should().BeTrue(); Mock.Get(ruleConditionsExpressionBuilder) - .Verify(x => x.BuildExpression(It.IsAny>()), Times.Never()); + .Verify(x => x.BuildExpression(It.IsAny()), Times.Never()); Mock.Get(rulesDataSource) - .Verify(x => x.UpdateRuleAsync(It.IsAny>()), Times.Never()); + .Verify(x => x.UpdateRuleAsync(It.IsAny()), Times.Never()); } [Fact] @@ -365,46 +366,46 @@ public async Task HandleGetRulesFilteredAsync_GivenArgsFilteringToRulesWithoutCo // Arrange var ruleResult = CreateTestRule(withCondition: false); var expectedRule = ruleResult.Rule; - var expectedRules = new[] { expectedRule }; + var expectedRules = new Rule[] { expectedRule }; - var getRulesFilteredArgs = new GetRulesFilteredArgs + var getRulesFilteredArgs = new GetRulesFilteredArgs { - ContentType = ContentType.Type1, + Ruleset = RulesetNames.Type1.ToString(), }; - bool nextDelegateWasInvoked = false; - var nextDelegate = new GetRulesFilteredDelegate((_) => + var nextDelegateWasInvoked = false; + var nextDelegate = new GetRulesFilteredDelegate((_) => { nextDelegateWasInvoked = true; - return Task.FromResult>>(expectedRules); + return Task.FromResult>(expectedRules); }); - Expression, bool>> expectedExpression = (_) => true; + Expression> expectedExpression = (_) => true; - var ruleConditionsExpressionBuilder = Mock.Of>(); + var ruleConditionsExpressionBuilder = Mock.Of(); Mock.Get(ruleConditionsExpressionBuilder) - .Setup(x => x.BuildExpression(It.IsAny>())) + .Setup(x => x.BuildExpression(It.IsAny())) .Returns(expectedExpression); - var rulesDataSource = Mock.Of>(); + var rulesDataSource = Mock.Of(); Mock.Get(rulesDataSource) - .Setup(x => x.UpdateRuleAsync(It.IsAny>())); + .Setup(x => x.UpdateRuleAsync(It.IsAny())); - var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( + var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( ruleConditionsExpressionBuilder, rulesDataSource); // Act - var actualRules = await compilationRulesSourceMiddleware.HandleGetRulesFilteredAsync(getRulesFilteredArgs, nextDelegate).ConfigureAwait(false); + var actualRules = await compilationRulesSourceMiddleware.HandleGetRulesFilteredAsync(getRulesFilteredArgs, nextDelegate); // Assert actualRules.Should().BeEquivalentTo(expectedRules); nextDelegateWasInvoked.Should().BeTrue(); Mock.Get(ruleConditionsExpressionBuilder) - .Verify(x => x.BuildExpression(It.IsAny>()), Times.Never()); + .Verify(x => x.BuildExpression(It.IsAny()), Times.Never()); Mock.Get(rulesDataSource) - .Verify(x => x.UpdateRuleAsync(It.IsAny>()), Times.Never()); + .Verify(x => x.UpdateRuleAsync(It.IsAny()), Times.Never()); } [Fact] @@ -413,37 +414,37 @@ public async Task HandleGetRulesFilteredAsync_GivenArgsFilteringToRulesWithUncom // Arrange var ruleResult = CreateTestRule(withCondition: true); var expectedRule = ruleResult.Rule; - var expectedRules = new[] { expectedRule }; + var expectedRules = new Rule[] { expectedRule }; - var getRulesFilteredArgs = new GetRulesFilteredArgs + var getRulesFilteredArgs = new GetRulesFilteredArgs { - ContentType = ContentType.Type1, + Ruleset = RulesetNames.Type1.ToString(), }; - bool nextDelegateWasInvoked = false; - var nextDelegate = new GetRulesFilteredDelegate((_) => + var nextDelegateWasInvoked = false; + var nextDelegate = new GetRulesFilteredDelegate((_) => { nextDelegateWasInvoked = true; - return Task.FromResult>>(expectedRules); + return Task.FromResult>(expectedRules); }); - Expression, bool>> expectedExpression = (_) => true; + Expression> expectedExpression = (_) => true; - var ruleConditionsExpressionBuilder = Mock.Of>(); + var ruleConditionsExpressionBuilder = Mock.Of(); Mock.Get(ruleConditionsExpressionBuilder) - .Setup(x => x.BuildExpression(It.IsAny>())) + .Setup(x => x.BuildExpression(It.IsAny())) .Returns(expectedExpression); - var rulesDataSource = Mock.Of>(); + var rulesDataSource = Mock.Of(); Mock.Get(rulesDataSource) - .Setup(x => x.UpdateRuleAsync(It.IsAny>())); + .Setup(x => x.UpdateRuleAsync(It.IsAny())); - var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( + var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( ruleConditionsExpressionBuilder, rulesDataSource); // Act - var actualRules = await compilationRulesSourceMiddleware.HandleGetRulesFilteredAsync(getRulesFilteredArgs, nextDelegate).ConfigureAwait(false); + var actualRules = await compilationRulesSourceMiddleware.HandleGetRulesFilteredAsync(getRulesFilteredArgs, nextDelegate); // Assert actualRules.Should().HaveCount(1); @@ -452,7 +453,7 @@ public async Task HandleGetRulesFilteredAsync_GivenArgsFilteringToRulesWithUncom actualRule.RootCondition.Properties.Should().ContainKey(ConditionNodeProperties.CompilationProperties.IsCompiledKey) .WhoseValue.Should().Be(true); actualRule.RootCondition.Properties.Should().ContainKey(ConditionNodeProperties.CompilationProperties.CompiledDelegateKey) - .WhoseValue.Should().BeOfType, bool>>(); + .WhoseValue.Should().BeOfType>(); nextDelegateWasInvoked.Should().BeTrue(); Mock.VerifyAll( @@ -470,39 +471,39 @@ public async Task HandleUpdateRuleAsync_GivenRuleWithCompiledConditionAndNextDel // Simulate compiled rule. expectedRule.RootCondition.Properties[ConditionNodeProperties.CompilationProperties.IsCompiledKey] = true; - var updateRuleArgs = new UpdateRuleArgs + var updateRuleArgs = new UpdateRuleArgs { Rule = expectedRule, }; - bool nextDelegateWasInvoked = false; - var nextDelegate = new UpdateRuleDelegate((_) => + var nextDelegateWasInvoked = false; + var nextDelegate = new UpdateRuleDelegate((_) => { nextDelegateWasInvoked = true; return Task.CompletedTask; }); - Expression, bool>> expectedExpression = (_) => true; + Expression> expectedExpression = (_) => true; - var ruleConditionsExpressionBuilder = Mock.Of>(); + var ruleConditionsExpressionBuilder = Mock.Of(); Mock.Get(ruleConditionsExpressionBuilder) - .Setup(x => x.BuildExpression(It.IsAny>())) + .Setup(x => x.BuildExpression(It.IsAny())) .Returns(expectedExpression); - var rulesDataSource = Mock.Of>(); + var rulesDataSource = Mock.Of(); - var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( + var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( ruleConditionsExpressionBuilder, rulesDataSource); // Act - await compilationRulesSourceMiddleware.HandleUpdateRuleAsync(updateRuleArgs, nextDelegate).ConfigureAwait(false); + await compilationRulesSourceMiddleware.HandleUpdateRuleAsync(updateRuleArgs, nextDelegate); // Assert nextDelegateWasInvoked.Should().BeTrue(); Mock.Get(ruleConditionsExpressionBuilder) - .Verify(x => x.BuildExpression(It.IsAny>()), Times.Never()); + .Verify(x => x.BuildExpression(It.IsAny()), Times.Never()); } [Fact] @@ -512,39 +513,39 @@ public async Task HandleUpdateRuleAsync_GivenRuleWithoutConditionsAndNextDelegat var ruleResult = CreateTestRule(withCondition: false); var expectedRule = ruleResult.Rule; - var updateRuleArgs = new UpdateRuleArgs + var updateRuleArgs = new UpdateRuleArgs { Rule = expectedRule, }; - bool nextDelegateWasInvoked = false; - var nextDelegate = new UpdateRuleDelegate((_) => + var nextDelegateWasInvoked = false; + var nextDelegate = new UpdateRuleDelegate((_) => { nextDelegateWasInvoked = true; return Task.CompletedTask; }); - Expression, bool>> expectedExpression = (_) => true; + Expression> expectedExpression = (_) => true; - var ruleConditionsExpressionBuilder = Mock.Of>(); + var ruleConditionsExpressionBuilder = Mock.Of(); Mock.Get(ruleConditionsExpressionBuilder) - .Setup(x => x.BuildExpression(It.IsAny>())) + .Setup(x => x.BuildExpression(It.IsAny())) .Returns(expectedExpression); - var rulesDataSource = Mock.Of>(); + var rulesDataSource = Mock.Of(); - var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( + var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( ruleConditionsExpressionBuilder, rulesDataSource); // Act - await compilationRulesSourceMiddleware.HandleUpdateRuleAsync(updateRuleArgs, nextDelegate).ConfigureAwait(false); + await compilationRulesSourceMiddleware.HandleUpdateRuleAsync(updateRuleArgs, nextDelegate); // Assert nextDelegateWasInvoked.Should().BeTrue(); Mock.Get(ruleConditionsExpressionBuilder) - .Verify(x => x.BuildExpression(It.IsAny>()), Times.Never()); + .Verify(x => x.BuildExpression(It.IsAny()), Times.Never()); } [Fact] @@ -554,56 +555,56 @@ public async Task HandleUpdateRuleAsync_GivenRuleWithUncompiledConditionAndNextD var ruleResult = CreateTestRule(withCondition: true); var expectedRule = ruleResult.Rule; - var updateRuleArgs = new UpdateRuleArgs + var updateRuleArgs = new UpdateRuleArgs { Rule = expectedRule, }; - bool nextDelegateWasInvoked = false; - var nextDelegate = new UpdateRuleDelegate((_) => + var nextDelegateWasInvoked = false; + var nextDelegate = new UpdateRuleDelegate((_) => { nextDelegateWasInvoked = true; return Task.CompletedTask; }); - Expression, bool>> expectedExpression = (_) => true; + Expression> expectedExpression = (_) => true; - var ruleConditionsExpressionBuilder = Mock.Of>(); + var ruleConditionsExpressionBuilder = Mock.Of(); Mock.Get(ruleConditionsExpressionBuilder) - .Setup(x => x.BuildExpression(It.IsAny>())) + .Setup(x => x.BuildExpression(It.IsAny())) .Returns(expectedExpression); - var rulesDataSource = Mock.Of>(); + var rulesDataSource = Mock.Of(); - var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( + var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( ruleConditionsExpressionBuilder, rulesDataSource); // Act - await compilationRulesSourceMiddleware.HandleUpdateRuleAsync(updateRuleArgs, nextDelegate).ConfigureAwait(false); + await compilationRulesSourceMiddleware.HandleUpdateRuleAsync(updateRuleArgs, nextDelegate); // Assert expectedRule.RootCondition.Properties.Should().HaveCount(2); expectedRule.RootCondition.Properties.Should().ContainKey(ConditionNodeProperties.CompilationProperties.IsCompiledKey) .WhoseValue.Should().Be(true); expectedRule.RootCondition.Properties.Should().ContainKey(ConditionNodeProperties.CompilationProperties.CompiledDelegateKey) - .WhoseValue.Should().BeOfType, bool>>(); + .WhoseValue.Should().BeOfType>(); nextDelegateWasInvoked.Should().BeTrue(); Mock.VerifyAll( Mock.Get(ruleConditionsExpressionBuilder)); } - private static RuleBuilderResult CreateTestRule(bool withCondition) + private static RuleBuilderResult CreateTestRule(bool withCondition) { - var ruleBuilder = RuleBuilder.NewRule() - .WithName("Test rule") - .WithDateBegin(DateTime.UtcNow) - .WithContent(ContentType.Type1, "Test content"); + var ruleBuilder = Rule.Create("Test rule") + .InRuleset(RulesetNames.Type1) + .SetContent("Test content") + .Since(DateTime.UtcNow); if (withCondition) { - ruleBuilder.WithCondition(ConditionType.IsoCountryCode, Operators.Equal, "PT"); + ruleBuilder.ApplyWhen(ConditionNames.IsoCountryCode, Operators.Equal, "PT"); } return ruleBuilder.Build(); diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/CompiledConditionsEvalEngineTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/CompiledConditionsEvalEngineTests.cs index 37d665a5..52673f55 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/CompiledConditionsEvalEngineTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/CompiledConditionsEvalEngineTests.cs @@ -4,7 +4,8 @@ namespace Rules.Framework.Tests.Evaluation.Compiled using System.Collections.Generic; using FluentAssertions; using Moq; - using Rules.Framework.Builder; + using Rules.Framework; + using Rules.Framework.Builder.Generic; using Rules.Framework.Core; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled; @@ -18,24 +19,24 @@ public void Eval_GivenCompiledConditionNodeConditionsAndExcludeRulesWithoutSearc { // Arrange var ruleResult = CreateTestRule(); - var expectedRule = ruleResult.Rule; - Func, bool> expectedExpression = (_) => true; + var expectedRule = (Rule)ruleResult.Rule; + Func expectedExpression = (_) => true; expectedRule.RootCondition.Properties[ConditionNodeProperties.CompilationProperties.CompiledDelegateKey] = expectedExpression; - var conditions = new Dictionary(); + var conditions = new Dictionary(); var evaluationOptions = new EvaluationOptions { ExcludeRulesWithoutSearchConditions = true, MatchMode = MatchModes.Exact, }; - var conditionsTreeAnalyzer = Mock.Of>(); + var conditionsTreeAnalyzer = Mock.Of(); Mock.Get(conditionsTreeAnalyzer) - .Setup(x => x.AreAllSearchConditionsPresent(It.IsAny>(), It.IsAny>())) + .Setup(x => x.AreAllSearchConditionsPresent(It.IsAny(), It.IsAny>())) .Returns(false); var rulesEngineOptions = RulesEngineOptions.NewWithDefaults(); - var compiledConditionsEvalEngine = new CompiledConditionsEvalEngine(conditionsTreeAnalyzer, rulesEngineOptions); + var compiledConditionsEvalEngine = new CompiledConditionsEvalEngine(conditionsTreeAnalyzer, rulesEngineOptions); // Act var result = compiledConditionsEvalEngine.Eval(expectedRule.RootCondition, conditions, evaluationOptions); @@ -49,24 +50,24 @@ public void Eval_GivenCompiledConditionNodeConditionsAndExcludeRulesWithoutSearc { // Arrange var ruleResult = CreateTestRule(); - var expectedRule = ruleResult.Rule; - Func, bool> expectedExpression = (_) => true; + var expectedRule = (Rule)ruleResult.Rule; + Func expectedExpression = (_) => true; expectedRule.RootCondition.Properties[ConditionNodeProperties.CompilationProperties.CompiledDelegateKey] = expectedExpression; - var conditions = new Dictionary(); + var conditions = new Dictionary(); var evaluationOptions = new EvaluationOptions { ExcludeRulesWithoutSearchConditions = true, MatchMode = MatchModes.Exact, }; - var conditionsTreeAnalyzer = Mock.Of>(); + var conditionsTreeAnalyzer = Mock.Of(); Mock.Get(conditionsTreeAnalyzer) - .Setup(x => x.AreAllSearchConditionsPresent(It.IsAny>(), It.IsAny>())) + .Setup(x => x.AreAllSearchConditionsPresent(It.IsAny(), It.IsAny>())) .Returns(true); var rulesEngineOptions = RulesEngineOptions.NewWithDefaults(); - var compiledConditionsEvalEngine = new CompiledConditionsEvalEngine(conditionsTreeAnalyzer, rulesEngineOptions); + var compiledConditionsEvalEngine = new CompiledConditionsEvalEngine(conditionsTreeAnalyzer, rulesEngineOptions); // Act var result = compiledConditionsEvalEngine.Eval(expectedRule.RootCondition, conditions, evaluationOptions); @@ -80,21 +81,21 @@ public void Eval_GivenCompiledConditionNodeConditionsAndIncludeRulesWithoutSearc { // Arrange var ruleResult = CreateTestRule(); - var expectedRule = ruleResult.Rule; - Func, bool> expectedExpression = (_) => true; + var expectedRule = (Rule)ruleResult.Rule; + Func expectedExpression = (_) => true; expectedRule.RootCondition.Properties[ConditionNodeProperties.CompilationProperties.CompiledDelegateKey] = expectedExpression; - var conditions = new Dictionary(); + var conditions = new Dictionary(); var evaluationOptions = new EvaluationOptions { ExcludeRulesWithoutSearchConditions = false, MatchMode = MatchModes.Exact, }; - var conditionsTreeAnalyzer = Mock.Of>(); + var conditionsTreeAnalyzer = Mock.Of(); var rulesEngineOptions = RulesEngineOptions.NewWithDefaults(); - var compiledConditionsEvalEngine = new CompiledConditionsEvalEngine(conditionsTreeAnalyzer, rulesEngineOptions); + var compiledConditionsEvalEngine = new CompiledConditionsEvalEngine(conditionsTreeAnalyzer, rulesEngineOptions); // Act var result = compiledConditionsEvalEngine.Eval(expectedRule.RootCondition, conditions, evaluationOptions); @@ -108,19 +109,19 @@ public void Eval_GivenUncompiledConditionNodeConditionsAndIncludeRulesWithoutSea { // Arrange var ruleResult = CreateTestRule(); - var expectedRule = ruleResult.Rule; - var conditions = new Dictionary(); + var expectedRule = (Rule)ruleResult.Rule; + var conditions = new Dictionary(); var evaluationOptions = new EvaluationOptions { ExcludeRulesWithoutSearchConditions = false, MatchMode = MatchModes.Exact, }; - var conditionsTreeAnalyzer = Mock.Of>(); + var conditionsTreeAnalyzer = Mock.Of(); var rulesEngineOptions = RulesEngineOptions.NewWithDefaults(); - var compiledConditionsEvalEngine = new CompiledConditionsEvalEngine(conditionsTreeAnalyzer, rulesEngineOptions); + var compiledConditionsEvalEngine = new CompiledConditionsEvalEngine(conditionsTreeAnalyzer, rulesEngineOptions); // Act var action = FluentActions.Invoking(() => compiledConditionsEvalEngine.Eval(expectedRule.RootCondition, conditions, evaluationOptions)); @@ -130,11 +131,11 @@ public void Eval_GivenUncompiledConditionNodeConditionsAndIncludeRulesWithoutSea .Which.ParamName.Should().Be("conditionNode"); } - private static RuleBuilderResult CreateTestRule() => RuleBuilder.NewRule() - .WithName("Test rule") - .WithDateBegin(DateTime.UtcNow) - .WithContent(ContentType.Type1, "Test content") - .WithCondition(ConditionType.IsoCurrency, Operators.Equal, "EUR") + private static RuleBuilderResult CreateTestRule() => Rule.Create("Test rule") + .InRuleset(RulesetNames.Type1) + .SetContent("Test content") + .Since(DateTime.UtcNow) + .ApplyWhen(ConditionNames.IsoCurrency, Operators.Equal, "EUR") .Build(); } } \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilderTests.cs index 839d0a47..55d102e7 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilderTests.cs @@ -4,7 +4,7 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders using System.Linq.Expressions; using FluentAssertions; using Moq; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilderTests.cs index f34dab4e..09e2daee 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilderTests.cs @@ -4,7 +4,7 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders using System.Linq.Expressions; using FluentAssertions; using Moq; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/ConditionExpressionBuilderProviderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/ConditionExpressionBuilderProviderTests.cs index c55b877d..76a395ec 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/ConditionExpressionBuilderProviderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/ConditionExpressionBuilderProviderTests.cs @@ -4,7 +4,7 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders using System.Collections.Generic; using System.Linq; using FluentAssertions; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Xunit; diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/ContainsManyToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/ContainsManyToOneConditionExpressionBuilderTests.cs index 8c6f178e..396c2e91 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/ContainsManyToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/ContainsManyToOneConditionExpressionBuilderTests.cs @@ -5,7 +5,7 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders using System.Linq.Expressions; using FluentAssertions; using Moq; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilderTests.cs index 6ac094cf..04eb2ad4 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilderTests.cs @@ -4,7 +4,7 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders using System.Linq.Expressions; using FluentAssertions; using Moq; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilderTests.cs index 711c2726..682a9e1e 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilderTests.cs @@ -4,7 +4,7 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders using System.Linq.Expressions; using FluentAssertions; using Moq; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/EqualOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/EqualOneToOneConditionExpressionBuilderTests.cs index 3ceff59b..ed19cde9 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/EqualOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/EqualOneToOneConditionExpressionBuilderTests.cs @@ -3,7 +3,7 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders using System; using System.Linq.Expressions; using FluentAssertions; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilderTests.cs index 682973cf..50e426fa 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilderTests.cs @@ -4,7 +4,7 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders using System.Linq.Expressions; using FluentAssertions; using Moq; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilderTests.cs index aa6f5bd2..8337c74d 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilderTests.cs @@ -4,7 +4,7 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders using System.Linq.Expressions; using FluentAssertions; using Moq; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilderTests.cs index 14e7c059..fb17027d 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilderTests.cs @@ -4,7 +4,7 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders using System.Collections.Generic; using System.Linq.Expressions; using FluentAssertions; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilderTests.cs index f1b0e1c1..360fd3a2 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilderTests.cs @@ -4,7 +4,7 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders using System.Linq.Expressions; using FluentAssertions; using Moq; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilderTests.cs index 5bf384c9..579f25a2 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilderTests.cs @@ -4,7 +4,7 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders using System.Linq.Expressions; using FluentAssertions; using Moq; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilderTests.cs index dd78a75f..db72bd95 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilderTests.cs @@ -4,7 +4,7 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders using System.Linq.Expressions; using FluentAssertions; using Moq; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilderTests.cs index a0c79782..5abccab1 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilderTests.cs @@ -4,7 +4,7 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders using System.Linq.Expressions; using FluentAssertions; using Moq; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotEqualOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotEqualOneToOneConditionExpressionBuilderTests.cs index cf0849fe..9d874e55 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotEqualOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotEqualOneToOneConditionExpressionBuilderTests.cs @@ -3,7 +3,7 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders using System; using System.Linq.Expressions; using FluentAssertions; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotInOneToManyConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotInOneToManyConditionExpressionBuilderTests.cs index b29060c5..237a786b 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotInOneToManyConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotInOneToManyConditionExpressionBuilderTests.cs @@ -4,7 +4,7 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders using System.Collections.Generic; using System.Linq.Expressions; using FluentAssertions; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilderTests.cs index 44237c8e..cedd1a03 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilderTests.cs @@ -4,7 +4,7 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders using System.Linq.Expressions; using FluentAssertions; using Moq; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilderTests.cs index 309f222c..4d1f3b65 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilderTests.cs @@ -4,7 +4,7 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders using System.Linq.Expressions; using FluentAssertions; using Moq; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionsValueLookupExtensionTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionsValueLookupExtensionTests.cs index eb0f10ee..e1943576 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionsValueLookupExtensionTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionsValueLookupExtensionTests.cs @@ -13,11 +13,11 @@ public void GetValueOrDefault_GivenConditionsDictionaryAndConditionType_ReturnsN { // Arrange const string expected = "EUR"; - var conditions = new Dictionary + var conditions = new Dictionary { - { ConditionType.IsoCurrency, expected } + { ConditionNames.IsoCurrency.ToString(), expected } }; - var conditionType = ConditionType.IsoCurrency; + var conditionType = ConditionNames.IsoCurrency.ToString(); // Act var result = ConditionsValueLookupExtension.GetValueOrDefault(conditions, conditionType); @@ -30,8 +30,8 @@ public void GetValueOrDefault_GivenConditionsDictionaryAndConditionType_ReturnsN public void GetValueOrDefault_GivenEmptyConditionsDictionaryAndConditionType_ReturnsNull() { // Arrange - var conditions = new Dictionary(); - var conditionType = ConditionType.IsoCurrency; + var conditions = new Dictionary(); + var conditionType = ConditionNames.IsoCurrency.ToString(); // Act var result = ConditionsValueLookupExtension.GetValueOrDefault(conditions, conditionType); diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToManyValueConditionNodeExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToManyValueConditionNodeExpressionBuilderTests.cs index 9760f3c2..232c649d 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToManyValueConditionNodeExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToManyValueConditionNodeExpressionBuilderTests.cs @@ -9,7 +9,7 @@ namespace Rules.Framework.Tests.Evaluation.Compiled using ExpressionDebugger; using FluentAssertions; using Moq; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToOneValueConditionNodeExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToOneValueConditionNodeExpressionBuilderTests.cs index a1a246e8..b68a3c93 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToOneValueConditionNodeExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToOneValueConditionNodeExpressionBuilderTests.cs @@ -9,7 +9,7 @@ namespace Rules.Framework.Tests.Evaluation.Compiled using ExpressionDebugger; using FluentAssertions; using Moq; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToManyValueConditionNodeExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToManyValueConditionNodeExpressionBuilderTests.cs index 91166a93..54841ece 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToManyValueConditionNodeExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToManyValueConditionNodeExpressionBuilderTests.cs @@ -9,7 +9,7 @@ namespace Rules.Framework.Tests.Evaluation.Compiled using ExpressionDebugger; using FluentAssertions; using Moq; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToOneValueConditionNodeExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToOneValueConditionNodeExpressionBuilderTests.cs index d7445e07..173189ea 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToOneValueConditionNodeExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToOneValueConditionNodeExpressionBuilderTests.cs @@ -8,7 +8,7 @@ namespace Rules.Framework.Tests.Evaluation.Compiled using ExpressionDebugger; using FluentAssertions; using Moq; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/RuleConditionsExpressionBuilderTests.GoldenFile1.csx b/tests/Rules.Framework.Tests/Evaluation/Compiled/RuleConditionsExpressionBuilderTests.GoldenFile1.csx index 09d0c69f..cb4b0360 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/RuleConditionsExpressionBuilderTests.GoldenFile1.csx +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/RuleConditionsExpressionBuilderTests.GoldenFile1.csx @@ -1,7 +1,7 @@ private Func Evaluate1; -private Func, ConditionType, object> GetValueOrDefault1; +private Func, string, object> GetValueOrDefault1; -internal bool Main(EvaluationContext evaluationContext) +internal bool Main(EvaluationContext evaluationContext) { bool cnd0Result; object cnd0LeftOperand; @@ -12,7 +12,7 @@ internal bool Main(EvaluationContext evaluationContext) object cnd1RightOperand; string cnd1Multiplicity; - cnd0LeftOperand = GetValueOrDefault1.Invoke(evaluationContext.get_Conditions(), ConditionType.NumberOfSales); + cnd0LeftOperand = GetValueOrDefault1.Invoke(evaluationContext.get_Conditions(), "NumberOfSales"); cnd0RightOperand = 100; if (cnd0LeftOperand == null) @@ -41,7 +41,7 @@ internal bool Main(EvaluationContext evaluationContext) } cnd0LabelEndValueConditionNode: - cnd1LeftOperand = GetValueOrDefault1.Invoke(evaluationContext.get_Conditions(), ConditionType.IsoCountryCode); + cnd1LeftOperand = GetValueOrDefault1.Invoke(evaluationContext.get_Conditions(), "IsoCountryCode"); cnd1RightOperand = "GB"; if (cnd1LeftOperand == null) diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/RuleConditionsExpressionBuilderTests.GoldenFile2.csx b/tests/Rules.Framework.Tests/Evaluation/Compiled/RuleConditionsExpressionBuilderTests.GoldenFile2.csx index ddcb33f1..1211f317 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/RuleConditionsExpressionBuilderTests.GoldenFile2.csx +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/RuleConditionsExpressionBuilderTests.GoldenFile2.csx @@ -1,7 +1,7 @@ private Func Evaluate1; -private Func, ConditionType, object> GetValueOrDefault1; +private Func, string, object> GetValueOrDefault1; -internal bool Main(EvaluationContext evaluationContext) +internal bool Main(EvaluationContext evaluationContext) { bool cnd0Result; object cnd0LeftOperand; @@ -12,7 +12,7 @@ internal bool Main(EvaluationContext evaluationContext) object cnd1RightOperand; string cnd1Multiplicity; - cnd0LeftOperand = GetValueOrDefault1.Invoke(evaluationContext.get_Conditions(), ConditionType.NumberOfSales); + cnd0LeftOperand = GetValueOrDefault1.Invoke(evaluationContext.get_Conditions(), "NumberOfSales"); cnd0RightOperand = 100; if (cnd0LeftOperand == null) @@ -41,7 +41,7 @@ internal bool Main(EvaluationContext evaluationContext) } cnd0LabelEndValueConditionNode: - cnd1LeftOperand = GetValueOrDefault1.Invoke(evaluationContext.get_Conditions(), ConditionType.IsoCountryCode); + cnd1LeftOperand = GetValueOrDefault1.Invoke(evaluationContext.get_Conditions(), "IsoCountryCode"); cnd1RightOperand = "GB"; if (cnd1LeftOperand == null) diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/RuleConditionsExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/RuleConditionsExpressionBuilderTests.cs index f5437ee8..c1edffef 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/RuleConditionsExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/RuleConditionsExpressionBuilderTests.cs @@ -9,8 +9,8 @@ namespace Rules.Framework.Tests.Evaluation.Compiled using ExpressionDebugger; using FluentAssertions; using Moq; - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; + using Rules.Framework; + using Rules.Framework.ConditionNodes; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; @@ -19,14 +19,14 @@ namespace Rules.Framework.Tests.Evaluation.Compiled public class RuleConditionsExpressionBuilderTests { - internal static IEnumerable<(string, EvaluationContext, bool)> AndComposedConditionNodeScenarios => new[] + internal static IEnumerable<(string, EvaluationContext, bool)> AndComposedConditionNodeScenarios => new[] { ( "Scenario 1 - MissingConditionsBehavior = 'Discard', MatchMode = 'Exact', and only contains condition for 'NumberOfSales'", - new EvaluationContext( - new Dictionary + new EvaluationContext( + new Dictionary { - { ConditionType.NumberOfSales, 500 }, + { ConditionNames.NumberOfSales.ToString(), 500 }, }, MatchModes.Exact, MissingConditionBehaviors.Discard), @@ -34,11 +34,11 @@ public class RuleConditionsExpressionBuilderTests ), ( "Scenario 2 - MissingConditionsBehavior = 'Discard', MatchMode = 'Exact', and both needed conditions", - new EvaluationContext( - new Dictionary + new EvaluationContext( + new Dictionary { - { ConditionType.NumberOfSales, 500 }, - { ConditionType.IsoCountryCode, "PT" }, + { ConditionNames.NumberOfSales.ToString(), 500 }, + { ConditionNames.IsoCountryCode.ToString(), "PT" }, }, MatchModes.Exact, MissingConditionBehaviors.Discard), @@ -46,11 +46,11 @@ public class RuleConditionsExpressionBuilderTests ), ( "Scenario 3 - MissingConditionsBehavior = 'UseDataTypeDefault', MatchMode = 'Exact', and both needed conditions", - new EvaluationContext( - new Dictionary + new EvaluationContext( + new Dictionary { - { ConditionType.NumberOfSales, 500 }, - { ConditionType.IsoCountryCode, "PT" }, + { ConditionNames.NumberOfSales.ToString(), 500 }, + { ConditionNames.IsoCountryCode.ToString(), "PT" }, }, MatchModes.Exact, MissingConditionBehaviors.UseDataTypeDefault), @@ -58,11 +58,11 @@ public class RuleConditionsExpressionBuilderTests ), ( "Scenario 4 - MissingConditionsBehavior = 'UseDataTypeDefault', MatchMode = 'Search', and both needed conditions", - new EvaluationContext( - new Dictionary + new EvaluationContext( + new Dictionary { - { ConditionType.NumberOfSales, 500 }, - { ConditionType.IsoCountryCode, "PT" }, + { ConditionNames.NumberOfSales.ToString(), 500 }, + { ConditionNames.IsoCountryCode.ToString(), "PT" }, }, MatchModes.Search, MissingConditionBehaviors.UseDataTypeDefault), @@ -70,10 +70,10 @@ public class RuleConditionsExpressionBuilderTests ), ( "Scenario 5 - MissingConditionsBehavior = 'UseDataTypeDefault', MatchMode = 'Search', and only contains condition for 'NumberOfSales'", - new EvaluationContext( - new Dictionary + new EvaluationContext( + new Dictionary { - { ConditionType.NumberOfSales, 500 }, + { ConditionNames.NumberOfSales.ToString(), 500 }, }, MatchModes.Search, MissingConditionBehaviors.UseDataTypeDefault), @@ -81,14 +81,14 @@ public class RuleConditionsExpressionBuilderTests ) }; - internal static IEnumerable<(string, EvaluationContext, bool)> OrComposedConditionNodeScenarios => new[] + internal static IEnumerable<(string, EvaluationContext, bool)> OrComposedConditionNodeScenarios => new[] { ( "Scenario 1 - MissingConditionsBehavior = 'Discard', MatchMode = 'Exact', and only contains condition for 'NumberOfSales'", - new EvaluationContext( - new Dictionary + new EvaluationContext( + new Dictionary { - { ConditionType.NumberOfSales, 500 }, + { ConditionNames.NumberOfSales.ToString(), 500 }, }, MatchModes.Exact, MissingConditionBehaviors.Discard), @@ -96,11 +96,11 @@ public class RuleConditionsExpressionBuilderTests ), ( "Scenario 2 - MissingConditionsBehavior = 'Discard', MatchMode = 'Exact', and both needed conditions", - new EvaluationContext( - new Dictionary + new EvaluationContext( + new Dictionary { - { ConditionType.NumberOfSales, 500 }, - { ConditionType.IsoCountryCode, "PT" }, + { ConditionNames.NumberOfSales.ToString(), 500 }, + { ConditionNames.IsoCountryCode.ToString(), "PT" }, }, MatchModes.Exact, MissingConditionBehaviors.Discard), @@ -108,11 +108,11 @@ public class RuleConditionsExpressionBuilderTests ), ( "Scenario 3 - MissingConditionsBehavior = 'UseDataTypeDefault', MatchMode = 'Exact', and both needed conditions", - new EvaluationContext( - new Dictionary + new EvaluationContext( + new Dictionary { - { ConditionType.NumberOfSales, 500 }, - { ConditionType.IsoCountryCode, "PT" }, + { ConditionNames.NumberOfSales.ToString(), 500 }, + { ConditionNames.IsoCountryCode.ToString(), "PT" }, }, MatchModes.Exact, MissingConditionBehaviors.UseDataTypeDefault), @@ -120,11 +120,11 @@ public class RuleConditionsExpressionBuilderTests ), ( "Scenario 4 - MissingConditionsBehavior = 'UseDataTypeDefault', MatchMode = 'Search', and both needed conditions", - new EvaluationContext( - new Dictionary + new EvaluationContext( + new Dictionary { - { ConditionType.NumberOfSales, 500 }, - { ConditionType.IsoCountryCode, "PT" }, + { ConditionNames.NumberOfSales.ToString(), 500 }, + { ConditionNames.IsoCountryCode.ToString(), "PT" }, }, MatchModes.Search, MissingConditionBehaviors.UseDataTypeDefault), @@ -132,10 +132,10 @@ public class RuleConditionsExpressionBuilderTests ), ( "Scenario 5 - MissingConditionsBehavior = 'UseDataTypeDefault', MatchMode = 'Search', and only contains condition for 'NumberOfSales'", - new EvaluationContext( - new Dictionary + new EvaluationContext( + new Dictionary { - { ConditionType.NumberOfSales, 500 }, + { ConditionNames.NumberOfSales.ToString(), 500 }, }, MatchModes.Search, MissingConditionBehaviors.UseDataTypeDefault), @@ -154,12 +154,12 @@ public void BuildExpression_GivenAndComposedConditionNodeWith2ChildValueConditio expectedScript = streamReader.ReadToEnd(); } var valueConditionNode1 - = new ValueConditionNode(DataTypes.Integer, ConditionType.NumberOfSales, Operators.Equal, 100); + = new ValueConditionNode(DataTypes.Integer, ConditionNames.NumberOfSales.ToString(), Operators.Equal, 100); var valueConditionNode2 - = new ValueConditionNode(DataTypes.String, ConditionType.IsoCountryCode, Operators.Equal, "GB"); + = new ValueConditionNode(DataTypes.String, ConditionNames.IsoCountryCode.ToString(), Operators.Equal, "GB"); var composedConditionNode - = new ComposedConditionNode(LogicalOperators.And, new[] { valueConditionNode1, valueConditionNode2 }); + = new ComposedConditionNode(LogicalOperators.And, new[] { valueConditionNode1, valueConditionNode2 }); var valueConditionNodeExpressionBuilder = Mock.Of(); Mock.Get(valueConditionNodeExpressionBuilder) @@ -184,7 +184,7 @@ var composedConditionNode .Setup(x => x.GetDataTypeConfiguration(DataTypes.Integer)) .Returns(DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), 0)); - var conditionsTreeCompiler = new RuleConditionsExpressionBuilder( + var conditionsTreeCompiler = new RuleConditionsExpressionBuilder( valueConditionNodeExpressionBuilderProvider, dataTypeConfigurationProvider); @@ -197,7 +197,7 @@ var composedConditionNode var diffResult = SideBySideDiffBuilder.Diff(expectedScript, actualScript, ignoreWhiteSpace: true); diffResult.NewText.HasDifferences.Should().BeFalse(); - Func, bool> compiledLambdaExpression = null; + Func compiledLambdaExpression = null; FluentActions.Invoking(() => compiledLambdaExpression = expression.Compile()) .Should() .NotThrow("expression should be compilable"); @@ -224,12 +224,12 @@ public void BuildExpression_GivenOrComposedConditionNodeWith2ChildValueCondition expectedScript = streamReader.ReadToEnd(); } var valueConditionNode1 - = new ValueConditionNode(DataTypes.Integer, ConditionType.NumberOfSales, Operators.Equal, 100); + = new ValueConditionNode(DataTypes.Integer, ConditionNames.NumberOfSales.ToString(), Operators.Equal, 100); var valueConditionNode2 - = new ValueConditionNode(DataTypes.String, ConditionType.IsoCountryCode, Operators.Equal, "GB"); + = new ValueConditionNode(DataTypes.String, ConditionNames.IsoCountryCode.ToString(), Operators.Equal, "GB"); var composedConditionNode - = new ComposedConditionNode(LogicalOperators.Or, new[] { valueConditionNode1, valueConditionNode2 }); + = new ComposedConditionNode(LogicalOperators.Or, new[] { valueConditionNode1, valueConditionNode2 }); var valueConditionNodeExpressionBuilder = Mock.Of(); Mock.Get(valueConditionNodeExpressionBuilder) @@ -254,7 +254,7 @@ var composedConditionNode .Setup(x => x.GetDataTypeConfiguration(DataTypes.Integer)) .Returns(DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), 0)); - var conditionsTreeCompiler = new RuleConditionsExpressionBuilder( + var conditionsTreeCompiler = new RuleConditionsExpressionBuilder( valueConditionNodeExpressionBuilderProvider, dataTypeConfigurationProvider); @@ -267,7 +267,7 @@ var composedConditionNode var diffResult = SideBySideDiffBuilder.Diff(expectedScript, actualScript, ignoreWhiteSpace: true); diffResult.NewText.HasDifferences.Should().BeFalse(); - Func, bool> compiledLambdaExpression = null; + Func compiledLambdaExpression = null; FluentActions.Invoking(() => compiledLambdaExpression = expression.Compile()) .Should() .NotThrow("expression should be compilable"); @@ -287,12 +287,12 @@ var composedConditionNode public void BuildExpression_GivenUnknownConditionNode_ThrowsNotSupportedException() { // Arrange - var stubConditionNode = new StubConditionNode(); + var stubConditionNode = new StubConditionNode(); var valueConditionNodeExpressionBuilderProvider = Mock.Of(); var dataTypeConfigurationProvider = Mock.Of(); - var ruleConditionsExpressionBuilder = new RuleConditionsExpressionBuilder( + var ruleConditionsExpressionBuilder = new RuleConditionsExpressionBuilder( valueConditionNodeExpressionBuilderProvider, dataTypeConfigurationProvider); @@ -301,19 +301,19 @@ public void BuildExpression_GivenUnknownConditionNode_ThrowsNotSupportedExceptio // Assert notSupportedException.Should().NotBeNull(); - notSupportedException.Message.Should().Contain(nameof(StubConditionNode)); + notSupportedException.Message.Should().Contain(nameof(StubConditionNode)); } [Fact] public void BuildExpression_GivenUnsupportedLogicalOperatorForComposedConditionNode_ThrowsNotSupportedException() { // Arrange - var composedConditionNode = new ComposedConditionNode(LogicalOperators.Eval, Enumerable.Empty>()); + var composedConditionNode = new ComposedConditionNode(LogicalOperators.Eval, Enumerable.Empty()); var valueConditionNodeExpressionBuilderProvider = Mock.Of(); var dataTypeConfigurationProvider = Mock.Of(); - var ruleConditionsExpressionBuilder = new RuleConditionsExpressionBuilder( + var ruleConditionsExpressionBuilder = new RuleConditionsExpressionBuilder( valueConditionNodeExpressionBuilderProvider, dataTypeConfigurationProvider); diff --git a/tests/Rules.Framework.Tests/Evaluation/ConditionsTreeAnalyzerTests.cs b/tests/Rules.Framework.Tests/Evaluation/ConditionsTreeAnalyzerTests.cs index 9a97d532..6a2ae656 100644 --- a/tests/Rules.Framework.Tests/Evaluation/ConditionsTreeAnalyzerTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/ConditionsTreeAnalyzerTests.cs @@ -3,8 +3,8 @@ namespace Rules.Framework.Tests.Evaluation using System; using System.Collections.Generic; using FluentAssertions; - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; + using Rules.Framework; + using Rules.Framework.ConditionNodes; using Rules.Framework.Evaluation; using Rules.Framework.Tests.Stubs; using Xunit; @@ -15,27 +15,27 @@ public class ConditionsTreeAnalyzerTests public void AreAllSearchConditionsPresent_GivenComposedConditionNodeWithAllConditionsOnDictionary_ReturnsTrue() { // Arrange - var condition1 = new ValueConditionNode(DataTypes.String, ConditionType.IsoCountryCode, Operators.In, new[] { "US", "CA" }); - var condition2 = new ValueConditionNode(DataTypes.String, ConditionType.IsoCurrency, Operators.NotEqual, "SGD"); - var condition3 = new ValueConditionNode(DataTypes.Boolean, ConditionType.IsVip, Operators.Equal, false); + var condition1 = new ValueConditionNode(DataTypes.String, ConditionNames.IsoCountryCode.ToString(), Operators.In, new[] { "US", "CA" }); + var condition2 = new ValueConditionNode(DataTypes.String, ConditionNames.IsoCurrency.ToString(), Operators.NotEqual, "SGD"); + var condition3 = new ValueConditionNode(DataTypes.Boolean, ConditionNames.IsVip.ToString(), Operators.Equal, false); - var composedConditionNode = new ComposedConditionNode( + var composedConditionNode = new ComposedConditionNode( LogicalOperators.Or, - new IConditionNode[] { condition1, condition2, condition3 }); + new IConditionNode[] { condition1, condition2, condition3 }); - var conditions = new Dictionary + var conditions = new Dictionary { { - ConditionType.IsoCurrency, + ConditionNames.IsoCurrency.ToString(), "SGD" }, { - ConditionType.IsoCountryCode, + ConditionNames.IsoCountryCode.ToString(), "PT" } }; - var conditionsTreeAnalyzer = new ConditionsTreeAnalyzer(); + var conditionsTreeAnalyzer = new ConditionsTreeAnalyzer(); // Act var result = conditionsTreeAnalyzer.AreAllSearchConditionsPresent(composedConditionNode, conditions); @@ -48,59 +48,59 @@ public void AreAllSearchConditionsPresent_GivenComposedConditionNodeWithAllCondi public void AreAllSearchConditionsPresent_GivenComposedConditionNodeWithAndOperatorAndUnknownConditionNode_ThrowsNotSupportedException() { // Arrange - var condition1 = new StubConditionNode(); - var condition2 = new ValueConditionNode(DataTypes.String, ConditionType.IsoCurrency, Operators.NotEqual, "SGD"); + var condition1 = new StubConditionNode(); + var condition2 = new ValueConditionNode(DataTypes.String, ConditionNames.IsoCurrency.ToString(), Operators.NotEqual, "SGD"); - var composedConditionNode = new ComposedConditionNode( + var composedConditionNode = new ComposedConditionNode( LogicalOperators.Or, - new IConditionNode[] { condition1, condition2 }); + new IConditionNode[] { condition1, condition2 }); - var conditions = new Dictionary + var conditions = new Dictionary { { - ConditionType.IsoCurrency, + ConditionNames.IsoCurrency.ToString(), "SGD" }, { - ConditionType.IsoCountryCode, + ConditionNames.IsoCountryCode.ToString(), "PT" } }; - var conditionsTreeAnalyzer = new ConditionsTreeAnalyzer(); + var conditionsTreeAnalyzer = new ConditionsTreeAnalyzer(); // Act var notSupportedException = Assert.Throws(() => conditionsTreeAnalyzer.AreAllSearchConditionsPresent(composedConditionNode, conditions)); // Assert notSupportedException.Should().NotBeNull(); - notSupportedException.Message.Should().Be("Unsupported condition node: 'StubConditionNode`1'."); + notSupportedException.Message.Should().Be("Unsupported condition node: 'StubConditionNode'."); } [Fact] public void AreAllSearchConditionsPresent_GivenComposedConditionNodeWithMissingConditionOnDictionary_ReturnsFalse() { // Arrange - var condition1 = new ValueConditionNode(DataTypes.String, ConditionType.IsoCountryCode, Operators.In, new[] { "US", "CA" }); - var condition3 = new ValueConditionNode(DataTypes.Boolean, ConditionType.IsVip, Operators.Equal, false); + var condition1 = new ValueConditionNode(DataTypes.String, ConditionNames.IsoCountryCode.ToString(), Operators.In, new[] { "US", "CA" }); + var condition3 = new ValueConditionNode(DataTypes.Boolean, ConditionNames.IsVip.ToString(), Operators.Equal, false); - var composedConditionNode = new ComposedConditionNode( + var composedConditionNode = new ComposedConditionNode( LogicalOperators.Or, - new IConditionNode[] { condition1, condition3 }); + new IConditionNode[] { condition1, condition3 }); - var conditions = new Dictionary + var conditions = new Dictionary { { - ConditionType.IsoCurrency, + ConditionNames.IsoCurrency.ToString(), "SGD" }, { - ConditionType.IsoCountryCode, + ConditionNames.IsoCountryCode.ToString(), "PT" } }; - var conditionsTreeAnalyzer = new ConditionsTreeAnalyzer(); + var conditionsTreeAnalyzer = new ConditionsTreeAnalyzer(); // Act var result = conditionsTreeAnalyzer.AreAllSearchConditionsPresent(composedConditionNode, conditions); diff --git a/tests/Rules.Framework.Tests/Evaluation/DataTypeConfigurationTests.cs b/tests/Rules.Framework.Tests/Evaluation/DataTypeConfigurationTests.cs index 5918400c..c71324d7 100644 --- a/tests/Rules.Framework.Tests/Evaluation/DataTypeConfigurationTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/DataTypeConfigurationTests.cs @@ -2,7 +2,7 @@ namespace Rules.Framework.Tests.Evaluation { using System; using FluentAssertions; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation; using Xunit; diff --git a/tests/Rules.Framework.Tests/Evaluation/DataTypesConfigurationProviderTests.cs b/tests/Rules.Framework.Tests/Evaluation/DataTypesConfigurationProviderTests.cs index 3acffd43..78292b1f 100644 --- a/tests/Rules.Framework.Tests/Evaluation/DataTypesConfigurationProviderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/DataTypesConfigurationProviderTests.cs @@ -4,7 +4,7 @@ namespace Rules.Framework.Tests.Evaluation using System.Collections.Generic; using System.Linq; using FluentAssertions; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation; using Xunit; diff --git a/tests/Rules.Framework.Tests/Evaluation/Interpreted/DeferredEvalTests.cs b/tests/Rules.Framework.Tests/Evaluation/Interpreted/DeferredEvalTests.cs index cce9f715..74acacb3 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Interpreted/DeferredEvalTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Interpreted/DeferredEvalTests.cs @@ -3,8 +3,8 @@ namespace Rules.Framework.Tests.Evaluation.Interpreted using System.Collections.Generic; using FluentAssertions; using Moq; - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; + using Rules.Framework; + using Rules.Framework.ConditionNodes; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Interpreted; using Rules.Framework.Evaluation.Interpreted.ValueEvaluation.Dispatchers; @@ -17,7 +17,7 @@ public class DeferredEvalTests public void GetDeferredEvalFor_GivenBooleanConditionNode_ReturnsFuncToEvalConditionsCollection() { // Arrange - var conditionNode = new ValueConditionNode(DataTypes.Boolean, ConditionType.IsVip, Operators.NotEqual, true); + var conditionNode = new ValueConditionNode(DataTypes.Boolean, ConditionNames.IsVip.ToString(), Operators.NotEqual, true); var mockOperatorEvalStrategy = new Mock(); mockOperatorEvalStrategy.Setup(x => x.EvalDispatch(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) @@ -27,9 +27,9 @@ public void GetDeferredEvalFor_GivenBooleanConditionNode_ReturnsFuncToEvalCondit mockConditionEvalDispatchProvider.Setup(x => x.GetEvalDispatcher(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(mockOperatorEvalStrategy.Object); - var conditions = new Dictionary + var conditions = new Dictionary { - { ConditionType.IsVip, false } + { ConditionNames.IsVip.ToString(), false } }; var matchMode = MatchModes.Exact; @@ -40,7 +40,7 @@ public void GetDeferredEvalFor_GivenBooleanConditionNode_ReturnsFuncToEvalCondit // Act var actual = sut.GetDeferredEvalFor(conditionNode, matchMode); - bool actualEvalResult = actual.Invoke(conditions); + var actualEvalResult = actual.Invoke(conditions); // Assert actualEvalResult.Should().BeTrue(); @@ -53,7 +53,7 @@ public void GetDeferredEvalFor_GivenBooleanConditionNode_ReturnsFuncToEvalCondit public void GetDeferredEvalFor_GivenDecimalConditionNode_ReturnsFuncToEvalConditionsCollection() { // Arrange - var conditionNode = new ValueConditionNode(DataTypes.Decimal, ConditionType.PluviosityRate, Operators.GreaterThan, 50); + var conditionNode = new ValueConditionNode(DataTypes.Decimal, ConditionNames.PluviosityRate.ToString(), Operators.GreaterThan, 50); var mockOperatorEvalStrategy = new Mock(); mockOperatorEvalStrategy.Setup(x => x.EvalDispatch(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) @@ -63,9 +63,9 @@ public void GetDeferredEvalFor_GivenDecimalConditionNode_ReturnsFuncToEvalCondit mockConditionEvalDispatchProvider.Setup(x => x.GetEvalDispatcher(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(mockOperatorEvalStrategy.Object); - var conditions = new Dictionary + var conditions = new Dictionary { - { ConditionType.PluviosityRate, 78 } + { ConditionNames.PluviosityRate.ToString(), 78 } }; var matchMode = MatchModes.Exact; @@ -89,7 +89,7 @@ public void GetDeferredEvalFor_GivenDecimalConditionNode_ReturnsFuncToEvalCondit public void GetDeferredEvalFor_GivenIntegerConditionNode_ReturnsFuncToEvalConditionsCollection() { // Arrange - var conditionNode = new ValueConditionNode(DataTypes.Integer, ConditionType.NumberOfSales, Operators.GreaterThan, 1000); + var conditionNode = new ValueConditionNode(DataTypes.Integer, ConditionNames.NumberOfSales.ToString(), Operators.GreaterThan, 1000); var mockOperatorEvalStrategy = new Mock(); mockOperatorEvalStrategy.Setup(x => x.EvalDispatch(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) @@ -99,9 +99,9 @@ public void GetDeferredEvalFor_GivenIntegerConditionNode_ReturnsFuncToEvalCondit mockConditionEvalDispatchProvider.Setup(x => x.GetEvalDispatcher(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(mockOperatorEvalStrategy.Object); - var conditions = new Dictionary + var conditions = new Dictionary { - { ConditionType.NumberOfSales, 2300 } + { ConditionNames.NumberOfSales.ToString(), 2300 } }; var matchMode = MatchModes.Exact; @@ -125,7 +125,7 @@ public void GetDeferredEvalFor_GivenIntegerConditionNode_ReturnsFuncToEvalCondit public void GetDeferredEvalFor_GivenStringConditionNode_ReturnsFuncToEvalConditionsCollection() { // Arrange - var conditionNode = new ValueConditionNode(DataTypes.String, ConditionType.IsoCurrency, Operators.Equal, "EUR"); + var conditionNode = new ValueConditionNode(DataTypes.String, ConditionNames.IsoCurrency.ToString(), Operators.Equal, "EUR"); var mockOperatorEvalStrategy = new Mock(); mockOperatorEvalStrategy.Setup(x => x.EvalDispatch(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) @@ -135,9 +135,9 @@ public void GetDeferredEvalFor_GivenStringConditionNode_ReturnsFuncToEvalConditi mockConditionEvalDispatchProvider.Setup(x => x.GetEvalDispatcher(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(mockOperatorEvalStrategy.Object); - var conditions = new Dictionary + var conditions = new Dictionary { - { ConditionType.IsoCurrency, "EUR" } + { ConditionNames.IsoCurrency.ToString(), "EUR" } }; var matchMode = MatchModes.Exact; @@ -161,7 +161,7 @@ public void GetDeferredEvalFor_GivenStringConditionNode_ReturnsFuncToEvalConditi public void GetDeferredEvalFor_GivenStringConditionNodeWithNoConditionSuppliedAndRulesEngineConfiguredToDiscardWhenMissing_ReturnsFuncThatEvalsFalse() { // Arrange - var conditionNode = new ValueConditionNode(DataTypes.String, ConditionType.IsoCurrency, Operators.Equal, "EUR"); + var conditionNode = new ValueConditionNode(DataTypes.String, ConditionNames.IsoCurrency.ToString(), Operators.Equal, "EUR"); var mockOperatorEvalStrategy = new Mock(); mockOperatorEvalStrategy.Setup(x => x.EvalDispatch(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) @@ -171,9 +171,9 @@ public void GetDeferredEvalFor_GivenStringConditionNodeWithNoConditionSuppliedAn mockConditionEvalDispatchProvider.Setup(x => x.GetEvalDispatcher(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(mockOperatorEvalStrategy.Object); - var conditions = new Dictionary + var conditions = new Dictionary { - { ConditionType.IsoCountryCode, "PT" } + { ConditionNames.IsoCountryCode.ToString(), "PT" } }; var matchMode = MatchModes.Exact; @@ -198,7 +198,7 @@ public void GetDeferredEvalFor_GivenStringConditionNodeWithNoConditionSuppliedAn public void GetDeferredEvalFor_GivenStringConditionNodeWithNoConditionSuppliedAndRulesEngineConfiguredToUseDataTypeDefaultWhenMissing_ReturnsFuncThatEvalsFalse() { // Arrange - var conditionNode = new ValueConditionNode(DataTypes.String, ConditionType.IsoCurrency, Operators.Equal, "EUR"); + var conditionNode = new ValueConditionNode(DataTypes.String, ConditionNames.IsoCurrency.ToString(), Operators.Equal, "EUR"); var mockOperatorEvalStrategy = new Mock(); mockOperatorEvalStrategy.Setup(x => x.EvalDispatch(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) @@ -208,9 +208,9 @@ public void GetDeferredEvalFor_GivenStringConditionNodeWithNoConditionSuppliedAn mockConditionEvalDispatchProvider.Setup(x => x.GetEvalDispatcher(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(mockOperatorEvalStrategy.Object); - var conditions = new Dictionary + var conditions = new Dictionary { - { ConditionType.IsoCountryCode, "PT" } + { ConditionNames.IsoCountryCode.ToString(), "PT" } }; var matchMode = MatchModes.Exact; diff --git a/tests/Rules.Framework.Tests/Evaluation/Interpreted/InterpretedConditionsEvalEngineTests.cs b/tests/Rules.Framework.Tests/Evaluation/Interpreted/InterpretedConditionsEvalEngineTests.cs index 8fc865db..99347a01 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Interpreted/InterpretedConditionsEvalEngineTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Interpreted/InterpretedConditionsEvalEngineTests.cs @@ -4,8 +4,8 @@ namespace Rules.Framework.Tests.Evaluation.Interpreted using System.Collections.Generic; using FluentAssertions; using Moq; - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; + using Rules.Framework; + using Rules.Framework.ConditionNodes; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Interpreted; using Rules.Framework.Tests.Stubs; @@ -17,21 +17,21 @@ public class InterpretedConditionsEvalEngineTests public void Eval_GivenComposedConditionNodeWithAndOperatorAndMissingConditionWithSearchMode_EvalsAndReturnsResult() { // Arrange - var condition1 = new ValueConditionNode(DataTypes.Boolean, ConditionType.IsVip, Operators.Equal, true); - var condition2 = new ValueConditionNode(DataTypes.String, ConditionType.IsoCurrency, Operators.NotEqual, "SGD"); + var condition1 = new ValueConditionNode(DataTypes.Boolean, ConditionNames.IsVip.ToString(), Operators.Equal, true); + var condition2 = new ValueConditionNode(DataTypes.String, ConditionNames.IsoCurrency.ToString(), Operators.NotEqual, "SGD"); - var composedConditionNode = new ComposedConditionNode( + var composedConditionNode = new ComposedConditionNode( LogicalOperators.And, - new IConditionNode[] { condition1, condition2 }); + new IConditionNode[] { condition1, condition2 }); - var conditions = new Dictionary + var conditions = new Dictionary { { - ConditionType.IsoCurrency, + ConditionNames.IsoCurrency.ToString(), "SGD" }, { - ConditionType.IsVip, + ConditionNames.IsVip.ToString(), true } }; @@ -43,7 +43,7 @@ public void Eval_GivenComposedConditionNodeWithAndOperatorAndMissingConditionWit }; var mockDeferredEval = new Mock(); - mockDeferredEval.SetupSequence(x => x.GetDeferredEvalFor(It.IsAny>(), It.Is(mm => mm == MatchModes.Exact))) + mockDeferredEval.SetupSequence(x => x.GetDeferredEvalFor(It.IsAny(), It.Is(mm => mm == MatchModes.Exact))) .Returns(() => { return (_) => false; @@ -53,9 +53,9 @@ public void Eval_GivenComposedConditionNodeWithAndOperatorAndMissingConditionWit return (_) => true; }) .Throws(new NotImplementedException("Shouldn't have gotten any more deferred evals.")); - var conditionsTreeAnalyzer = Mock.Of>(); + var conditionsTreeAnalyzer = Mock.Of(); - var sut = new InterpretedConditionsEvalEngine(mockDeferredEval.Object, conditionsTreeAnalyzer); + var sut = new InterpretedConditionsEvalEngine(mockDeferredEval.Object, conditionsTreeAnalyzer); // Act var actual = sut.Eval(composedConditionNode, conditions, evaluationOptions); @@ -63,28 +63,28 @@ public void Eval_GivenComposedConditionNodeWithAndOperatorAndMissingConditionWit // Assert actual.Should().BeFalse(); - mockDeferredEval.Verify(x => x.GetDeferredEvalFor(It.IsAny>(), It.Is(mm => mm == MatchModes.Exact)), Times.Exactly(0)); + mockDeferredEval.Verify(x => x.GetDeferredEvalFor(It.IsAny(), It.Is(mm => mm == MatchModes.Exact)), Times.Exactly(0)); } [Fact] public void Eval_GivenComposedConditionNodeWithAndOperatorWithExactMatch_EvalsAndReturnsResult() { // Arrange - var condition1 = new ValueConditionNode(DataTypes.Boolean, ConditionType.IsVip, Operators.Equal, true); - var condition2 = new ValueConditionNode(DataTypes.String, ConditionType.IsoCurrency, Operators.NotEqual, "SGD"); + var condition1 = new ValueConditionNode(DataTypes.Boolean, ConditionNames.IsVip.ToString(), Operators.Equal, true); + var condition2 = new ValueConditionNode(DataTypes.String, ConditionNames.IsoCurrency.ToString(), Operators.NotEqual, "SGD"); - var composedConditionNode = new ComposedConditionNode( + var composedConditionNode = new ComposedConditionNode( LogicalOperators.Eval, - new IConditionNode[] { condition1, condition2 }); + new IConditionNode[] { condition1, condition2 }); - var conditions = new Dictionary + var conditions = new Dictionary { { - ConditionType.IsoCurrency, + ConditionNames.IsoCurrency.ToString(), "SGD" }, { - ConditionType.IsVip, + ConditionNames.IsVip.ToString(), true } }; @@ -95,9 +95,9 @@ public void Eval_GivenComposedConditionNodeWithAndOperatorWithExactMatch_EvalsAn }; var deferredEval = Mock.Of(); - var conditionsTreeAnalyzer = Mock.Of>(); + var conditionsTreeAnalyzer = Mock.Of(); - var sut = new InterpretedConditionsEvalEngine(deferredEval, conditionsTreeAnalyzer); + var sut = new InterpretedConditionsEvalEngine(deferredEval, conditionsTreeAnalyzer); // Act var notSupportedException = Assert.Throws(() => sut.Eval(composedConditionNode, conditions, evaluationOptions)); @@ -106,28 +106,28 @@ public void Eval_GivenComposedConditionNodeWithAndOperatorWithExactMatch_EvalsAn notSupportedException.Should().NotBeNull(); notSupportedException.Message.Should().Be("Unsupported logical operator: 'Eval'."); Mock.Get(deferredEval) - .Verify(x => x.GetDeferredEvalFor(It.IsAny>(), It.Is(mm => mm == MatchModes.Exact)), Times.Never()); + .Verify(x => x.GetDeferredEvalFor(It.IsAny(), It.Is(mm => mm == MatchModes.Exact)), Times.Never()); } [Fact] public void Eval_GivenComposedConditionNodeWithEvalOperator_ThrowsNotSupportedException() { // Arrange - var condition1 = new ValueConditionNode(DataTypes.Boolean, ConditionType.IsVip, Operators.Equal, true); - var condition2 = new ValueConditionNode(DataTypes.String, ConditionType.IsoCurrency, Operators.NotEqual, "SGD"); + var condition1 = new ValueConditionNode(DataTypes.Boolean, ConditionNames.IsVip.ToString(), Operators.Equal, true); + var condition2 = new ValueConditionNode(DataTypes.String, ConditionNames.IsoCurrency.ToString(), Operators.NotEqual, "SGD"); - var composedConditionNode = new ComposedConditionNode( + var composedConditionNode = new ComposedConditionNode( LogicalOperators.Eval, - new IConditionNode[] { condition1, condition2 }); + new IConditionNode[] { condition1, condition2 }); - var conditions = new Dictionary + var conditions = new Dictionary { { - ConditionType.IsoCurrency, + ConditionNames.IsoCurrency.ToString(), "SGD" }, { - ConditionType.IsVip, + ConditionNames.IsVip.ToString(), true } }; @@ -138,9 +138,9 @@ public void Eval_GivenComposedConditionNodeWithEvalOperator_ThrowsNotSupportedEx }; var deferredEval = Mock.Of(); - var conditionsTreeAnalyzer = Mock.Of>(); + var conditionsTreeAnalyzer = Mock.Of(); - var sut = new InterpretedConditionsEvalEngine(deferredEval, conditionsTreeAnalyzer); + var sut = new InterpretedConditionsEvalEngine(deferredEval, conditionsTreeAnalyzer); // Act var notSupportedException = Assert.Throws(() => sut.Eval(composedConditionNode, conditions, evaluationOptions)); @@ -149,28 +149,28 @@ public void Eval_GivenComposedConditionNodeWithEvalOperator_ThrowsNotSupportedEx notSupportedException.Should().NotBeNull(); notSupportedException.Message.Should().Be("Unsupported logical operator: 'Eval'."); Mock.Get(deferredEval) - .Verify(x => x.GetDeferredEvalFor(It.IsAny>(), It.Is(mm => mm == MatchModes.Exact)), Times.Never()); + .Verify(x => x.GetDeferredEvalFor(It.IsAny(), It.Is(mm => mm == MatchModes.Exact)), Times.Never()); } [Fact] public void Eval_GivenComposedConditionNodeWithOrOperatorWithExactMatch_EvalsAndReturnsResult() { // Arrange - var condition1 = new ValueConditionNode(DataTypes.Boolean, ConditionType.IsVip, Operators.Equal, true); - var condition2 = new ValueConditionNode(DataTypes.String, ConditionType.IsoCurrency, Operators.NotEqual, "SGD"); + var condition1 = new ValueConditionNode(DataTypes.Boolean, ConditionNames.IsVip.ToString(), Operators.Equal, true); + var condition2 = new ValueConditionNode(DataTypes.String, ConditionNames.IsoCurrency.ToString(), Operators.NotEqual, "SGD"); - var composedConditionNode = new ComposedConditionNode( + var composedConditionNode = new ComposedConditionNode( LogicalOperators.Or, - new IConditionNode[] { condition1, condition2 }); + new IConditionNode[] { condition1, condition2 }); - var conditions = new Dictionary + var conditions = new Dictionary { { - ConditionType.IsoCurrency, + ConditionNames.IsoCurrency.ToString(), "SGD" }, { - ConditionType.IsVip, + ConditionNames.IsVip.ToString(), true } }; @@ -182,7 +182,7 @@ public void Eval_GivenComposedConditionNodeWithOrOperatorWithExactMatch_EvalsAnd var deferredEval = Mock.Of(); Mock.Get(deferredEval) - .SetupSequence(x => x.GetDeferredEvalFor(It.IsAny>(), It.Is(mm => mm == MatchModes.Exact))) + .SetupSequence(x => x.GetDeferredEvalFor(It.IsAny(), It.Is(mm => mm == MatchModes.Exact))) .Returns(() => { return (_) => true; @@ -192,9 +192,9 @@ public void Eval_GivenComposedConditionNodeWithOrOperatorWithExactMatch_EvalsAnd return (_) => true; }) .Throws(new NotImplementedException("Shouldn't have gotten any more deferred evals.")); - var conditionsTreeAnalyzer = Mock.Of>(); + var conditionsTreeAnalyzer = Mock.Of(); - var sut = new InterpretedConditionsEvalEngine(deferredEval, conditionsTreeAnalyzer); + var sut = new InterpretedConditionsEvalEngine(deferredEval, conditionsTreeAnalyzer); // Act var actual = sut.Eval(composedConditionNode, conditions, evaluationOptions); @@ -203,7 +203,7 @@ public void Eval_GivenComposedConditionNodeWithOrOperatorWithExactMatch_EvalsAnd actual.Should().BeTrue(); Mock.Get(deferredEval) - .Verify(x => x.GetDeferredEvalFor(It.IsAny>(), It.Is(mm => mm == MatchModes.Exact)), Times.Exactly(2)); + .Verify(x => x.GetDeferredEvalFor(It.IsAny(), It.Is(mm => mm == MatchModes.Exact)), Times.Exactly(2)); } } } \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcherTests.cs b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcherTests.cs index 489c3be2..26d4a4e5 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcherTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcherTests.cs @@ -5,7 +5,7 @@ namespace Rules.Framework.Tests.Evaluation.ValueEvaluation.Dispatchers using System.Collections.Generic; using FluentAssertions; using Moq; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Interpreted.ValueEvaluation; using Rules.Framework.Evaluation.Interpreted.ValueEvaluation.Dispatchers; diff --git a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcherTests.cs b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcherTests.cs index 71f07e9d..aba0cbed 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcherTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcherTests.cs @@ -5,7 +5,7 @@ namespace Rules.Framework.Tests.Evaluation.ValueEvaluation.Dispatchers using System.Collections.Generic; using FluentAssertions; using Moq; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Interpreted.ValueEvaluation; using Rules.Framework.Evaluation.Interpreted.ValueEvaluation.Dispatchers; diff --git a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcherTests.cs b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcherTests.cs index 28dacd55..61e7b177 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcherTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcherTests.cs @@ -4,7 +4,7 @@ namespace Rules.Framework.Tests.Evaluation.ValueEvaluation.Dispatchers using System.Collections.Generic; using FluentAssertions; using Moq; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Interpreted.ValueEvaluation; using Rules.Framework.Evaluation.Interpreted.ValueEvaluation.Dispatchers; diff --git a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcherTests.cs b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcherTests.cs index 57f9cddd..efb3c831 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcherTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcherTests.cs @@ -3,7 +3,7 @@ namespace Rules.Framework.Tests.Evaluation.ValueEvaluation.Dispatchers using System; using FluentAssertions; using Moq; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Interpreted.ValueEvaluation; using Rules.Framework.Evaluation.Interpreted.ValueEvaluation.Dispatchers; diff --git a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/OperatorEvalStrategyFactoryTests.cs b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/OperatorEvalStrategyFactoryTests.cs index 24c6877c..f2353333 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/OperatorEvalStrategyFactoryTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/OperatorEvalStrategyFactoryTests.cs @@ -2,7 +2,7 @@ namespace Rules.Framework.Tests.Evaluation.Interpreted.ValueEvaluation { using System; using FluentAssertions; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation.Interpreted.ValueEvaluation; using Xunit; diff --git a/tests/Rules.Framework.Tests/Evaluation/MultiplicityEvaluatorTests.cs b/tests/Rules.Framework.Tests/Evaluation/MultiplicityEvaluatorTests.cs index f257bc22..94970fcc 100644 --- a/tests/Rules.Framework.Tests/Evaluation/MultiplicityEvaluatorTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/MultiplicityEvaluatorTests.cs @@ -3,7 +3,7 @@ namespace Rules.Framework.Tests.Evaluation using System; using System.Collections.Generic; using FluentAssertions; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation; using Xunit; diff --git a/tests/Rules.Framework.Tests/Evaluation/OperatorMetadataTests.cs b/tests/Rules.Framework.Tests/Evaluation/OperatorMetadataTests.cs index 75fc98d0..40857b6d 100644 --- a/tests/Rules.Framework.Tests/Evaluation/OperatorMetadataTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/OperatorMetadataTests.cs @@ -2,7 +2,7 @@ namespace Rules.Framework.Tests.Evaluation { using System.Linq; using FluentAssertions; - using Rules.Framework.Core; + using Rules.Framework; using Rules.Framework.Evaluation; using Xunit; diff --git a/tests/Rules.Framework.Tests/Evaluation/StubConditionNode.cs b/tests/Rules.Framework.Tests/Evaluation/StubConditionNode.cs index 967d6acc..4914a91e 100644 --- a/tests/Rules.Framework.Tests/Evaluation/StubConditionNode.cs +++ b/tests/Rules.Framework.Tests/Evaluation/StubConditionNode.cs @@ -2,14 +2,14 @@ namespace Rules.Framework.Tests.Evaluation { using System; using System.Collections.Generic; - using Rules.Framework.Core; + using Rules.Framework; - internal class StubConditionNode : IConditionNode + internal class StubConditionNode : IConditionNode { public LogicalOperators LogicalOperator => throw new NotImplementedException(); public IDictionary Properties => throw new NotImplementedException(); - public IConditionNode Clone() => throw new NotImplementedException(); + public IConditionNode Clone() => throw new NotImplementedException(); } } \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Extensions/GenericRuleExtensionsTests.cs b/tests/Rules.Framework.Tests/Extensions/GenericRuleExtensionsTests.cs index 90f66e09..d17d26db 100644 --- a/tests/Rules.Framework.Tests/Extensions/GenericRuleExtensionsTests.cs +++ b/tests/Rules.Framework.Tests/Extensions/GenericRuleExtensionsTests.cs @@ -3,11 +3,9 @@ namespace Rules.Framework.Tests.Extensions using System; using System.Collections.Generic; using FluentAssertions; - using Rules.Framework.Builder; - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; - using Rules.Framework.Extensions; - using Rules.Framework.Generics; + using Rules.Framework; + using Rules.Framework.Generic; + using Rules.Framework.Generic.ConditionNodes; using Rules.Framework.Tests.Stubs; using Xunit; @@ -18,33 +16,33 @@ public void GenericRuleExtensions_ToGenericRule_WithComposedCondition_Success() { var expectedRuleContent = "Type1"; - var expectedRootCondition = new GenericComposedConditionNode + var expectedRootCondition = new { - ChildConditionNodes = new List + ChildConditionNodes = new List { - new GenericValueConditionNode + new { - ConditionTypeName = ConditionType.IsVip.ToString(), + Condition = ConditionNames.IsVip, DataType = DataTypes.Boolean, LogicalOperator = LogicalOperators.Eval, Operand = true, Operator = Operators.Equal }, - new GenericComposedConditionNode + new { - ChildConditionNodes = new List + ChildConditionNodes = new List { - new GenericValueConditionNode + new { - ConditionTypeName = ConditionType.IsoCurrency.ToString(), + Condition = ConditionNames.IsoCurrency, DataType = DataTypes.String, LogicalOperator = LogicalOperators.Eval, Operand = "EUR", Operator = Operators.Equal }, - new GenericValueConditionNode + new { - ConditionTypeName = ConditionType.IsoCurrency.ToString(), + Condition = ConditionNames.IsoCurrency, DataType = DataTypes.String, LogicalOperator = LogicalOperators.Eval, Operand = "USD", @@ -57,36 +55,37 @@ public void GenericRuleExtensions_ToGenericRule_WithComposedCondition_Success() LogicalOperator = LogicalOperators.And }; - var rootComposedCondition = new RootConditionNodeBuilder() - .And(a => a - .Value(ConditionType.IsVip, Operators.Equal, true) - .Or(o => o - .Value(ConditionType.IsoCurrency, Operators.Equal, "EUR") - .Value(ConditionType.IsoCurrency, Operators.Equal, "USD") + var ruleBuilderResult = Rule.Create("Dummy Rule") + .InRuleset(RulesetNames.Type1) + .SetContent(expectedRuleContent) + .Since(DateTime.Parse("2018-01-01")) + .ApplyWhen(b => b + .And(a => a + .Value(ConditionNames.IsVip, Operators.Equal, true) + .Or(o => o + .Value(ConditionNames.IsoCurrency, Operators.Equal, "EUR") + .Value(ConditionNames.IsoCurrency, Operators.Equal, "USD") + ) ) - ); - - var ruleBuilderResult = RuleBuilder.NewRule() - .WithName("Dummy Rule") - .WithDateBegin(DateTime.Parse("2018-01-01")) - .WithContent(ContentType.Type1, expectedRuleContent) - .WithCondition(rootComposedCondition) + ) .Build(); - var rule = ruleBuilderResult.Rule; + var rule = (Rule)ruleBuilderResult.Rule; // Act - var genericRule = rule.ToGenericRule(); + var genericRule = rule.ToGenericRule(); // Assert genericRule.Should().BeEquivalentTo(rule, config => config .Excluding(r => r.ContentContainer) - .Excluding(r => r.RootCondition)); - genericRule.Content.Should().BeOfType(); - genericRule.Content.Should().Be(expectedRuleContent); - genericRule.RootCondition.Should().BeOfType(); - - var genericComposedRootCondition = genericRule.RootCondition as GenericComposedConditionNode; + .Excluding(r => r.RootCondition) + .Excluding(r => r.Ruleset)); + var content = genericRule.ContentContainer.GetContentAs(); + content.Should().BeOfType(); + content.Should().Be(expectedRuleContent); + genericRule.RootCondition.Should().BeOfType>(); + + var genericComposedRootCondition = genericRule.RootCondition as ComposedConditionNode; genericComposedRootCondition.Should().BeEquivalentTo(expectedRootCondition, config => config.IncludingAllRuntimeProperties()); } @@ -95,21 +94,24 @@ public void GenericRuleExtensions_ToGenericRule_WithoutConditions_Success() { var expectedRuleContent = "Type2"; - var ruleBuilderResult = RuleBuilder.NewRule() - .WithName("Dummy Rule") - .WithDateBegin(DateTime.Parse("2018-01-01")) - .WithContent(ContentType.Type2, expectedRuleContent) + var ruleBuilderResult = Rule.Create("Dummy Rule") + .InRuleset(RulesetNames.Type2) + .SetContent(expectedRuleContent) + .Since(DateTime.Parse("2018-01-01")) .Build(); - var rule = ruleBuilderResult.Rule; + var rule = (Rule)ruleBuilderResult.Rule; // Act - var genericRule = rule.ToGenericRule(); + var genericRule = rule.ToGenericRule(); // Assert - genericRule.Should().BeEquivalentTo(rule, config => config.Excluding(r => r.ContentContainer)); - genericRule.Content.Should().BeOfType(); - genericRule.Content.Should().Be(expectedRuleContent); + genericRule.Should().BeEquivalentTo(rule, config => config + .Excluding(r => r.ContentContainer) + .Excluding(r => r.Ruleset)); + var content = genericRule.ContentContainer.GetContentAs(); + content.Should().BeOfType(); + content.Should().Be(expectedRuleContent); genericRule.RootCondition.Should().BeNull(); } @@ -117,30 +119,39 @@ public void GenericRuleExtensions_ToGenericRule_WithoutConditions_Success() public void GenericRuleExtensions_ToGenericRule_WithValueCondition_Success() { var expectedRuleContent = "Type1"; - var expectedRootCondition = new ValueConditionNode(DataTypes.Integer, ConditionType.NumberOfSales, Operators.GreaterThan, 1000); + var expectedRootCondition = new + { + ConditionType = ConditionNames.NumberOfSales, + DataType = DataTypes.Integer, + LogicalOperator = LogicalOperators.Eval, + Operator = Operators.GreaterThan, + Operand = 1000 + }; - var ruleBuilderResult = RuleBuilder.NewRule() - .WithName("Dummy Rule") - .WithDateBegin(DateTime.Parse("2018-01-01")) - .WithContent(ContentType.Type1, expectedRuleContent) - .WithCondition(expectedRootCondition) + var ruleBuilderResult = Rule.Create("Dummy Rule") + .InRuleset(RulesetNames.Type1) + .SetContent(expectedRuleContent) + .Since(DateTime.Parse("2018-01-01")) + .ApplyWhen(ConditionNames.NumberOfSales, Operators.GreaterThan, 1000) .Build(); - var rule = ruleBuilderResult.Rule; + var rule = (Rule)ruleBuilderResult.Rule; // Act - var genericRule = rule.ToGenericRule(); + var genericRule = rule.ToGenericRule(); // Assert genericRule.Should().BeEquivalentTo(rule, config => config .Excluding(r => r.ContentContainer) - .Excluding(r => r.RootCondition)); - genericRule.Content.Should().BeOfType(); - genericRule.Content.Should().Be(expectedRuleContent); - genericRule.RootCondition.Should().BeOfType(); - - var genericValueRootCondition = genericRule.RootCondition as GenericValueConditionNode; - genericValueRootCondition.ConditionTypeName.Should().Be(expectedRootCondition.ConditionType.ToString()); + .Excluding(r => r.RootCondition) + .Excluding(r => r.Ruleset)); + var content = genericRule.ContentContainer.GetContentAs(); + content.Should().BeOfType(); + content.Should().Be(expectedRuleContent); + genericRule.RootCondition.Should().BeOfType>(); + + var genericValueRootCondition = genericRule.RootCondition as ValueConditionNode; + genericValueRootCondition.Condition.Should().Be(expectedRootCondition.ConditionType); genericValueRootCondition.DataType.Should().Be(expectedRootCondition.DataType); genericValueRootCondition.LogicalOperator.Should().Be(expectedRootCondition.LogicalOperator); genericValueRootCondition.Operand.Should().Be(expectedRootCondition.Operand); diff --git a/tests/Rules.Framework.Tests/Extensions/GenericSearchArgsExtensionsTests.cs b/tests/Rules.Framework.Tests/Extensions/GenericSearchArgsExtensionsTests.cs deleted file mode 100644 index a23f5549..00000000 --- a/tests/Rules.Framework.Tests/Extensions/GenericSearchArgsExtensionsTests.cs +++ /dev/null @@ -1,91 +0,0 @@ -namespace Rules.Framework.Tests.Extensions -{ - using System; - using System.Collections.Generic; - using FluentAssertions; - using Rules.Framework.Extensions; - using Rules.Framework.Generics; - using Rules.Framework.Tests.Stubs; - using Xunit; - - public class GenericSearchArgsExtensionsTests - { - [Fact] - public void GenericSearchArgsExtensions_ToSearchArgs_WithConditions_Success() - { - //Arrange - var contentType = ContentType.Type1; - var dateBegin = new DateTime(2018, 01, 01); - var dateEnd = new DateTime(2020, 12, 31); - var pluviosityRate = 132000.00m; - var countryCode = "USA"; - - var expectedSearchArgs = new SearchArgs(contentType, dateBegin, dateEnd) - { - Conditions = new List> - { - new Condition(ConditionType.PluviosityRate, pluviosityRate), - new Condition(ConditionType.IsoCountryCode, countryCode) - }, - ExcludeRulesWithoutSearchConditions = true - }; - - var contentTypeCode = "Type1"; - - var genericSearchArgs = new SearchArgs( - new GenericContentType { Identifier = contentTypeCode }, dateBegin, dateEnd - ) - { - Conditions = new List> - { - new Condition(new GenericConditionType { Identifier = "PluviosityRate" }, pluviosityRate), - new Condition(new GenericConditionType { Identifier = "IsoCountryCode" }, countryCode) - }, - ExcludeRulesWithoutSearchConditions = true - }; - - // Act - var convertedSearchArgs = genericSearchArgs.ToSearchArgs(); - - // Assert - convertedSearchArgs.Should().BeEquivalentTo(expectedSearchArgs); - } - - [Fact] - public void GenericSearchArgsExtensions_ToSearchArgs_WithInvalidType_ThrowsException() - { - //Arrange - var contentTypeCode = "Type1"; - var dateBegin = new DateTime(2018, 01, 01); - var dateEnd = new DateTime(2020, 12, 31); - - var genericSearchArgs = new SearchArgs( - new GenericContentType { Identifier = contentTypeCode }, dateBegin, dateEnd - ); - - // Act and Assert - Assert.Throws(() => genericSearchArgs.ToSearchArgs()); - } - - [Fact] - public void GenericSearchArgsExtensions_ToSearchArgs_WithoutConditions_Success() - { - //Arrange - var contentTypeCode = "Type1"; - var contentType = ContentType.Type1; - var dateBegin = new DateTime(2018, 01, 01); - var dateEnd = new DateTime(2020, 12, 31); - - var expectedSearchArgs = new SearchArgs(contentType, dateBegin, dateEnd, active: true); - - var genericSearchArgs = new SearchArgs( - new GenericContentType { Identifier = contentTypeCode }, dateBegin, dateEnd, active: true); - - // Act - var convertedSearchArgs = genericSearchArgs.ToSearchArgs(); - - // Assert - convertedSearchArgs.Should().BeEquivalentTo(expectedSearchArgs); - } - } -} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Extensions/RulesEngineExtensionsTests.cs b/tests/Rules.Framework.Tests/Extensions/RulesEngineExtensionsTests.cs index 051c2eb0..b37dc9fb 100644 --- a/tests/Rules.Framework.Tests/Extensions/RulesEngineExtensionsTests.cs +++ b/tests/Rules.Framework.Tests/Extensions/RulesEngineExtensionsTests.cs @@ -3,8 +3,7 @@ namespace Rules.Framework.Tests.Extensions using FluentAssertions; using Moq; using Rules.Framework.Evaluation; - using Rules.Framework.Extension; - using Rules.Framework.Generics; + using Rules.Framework.Generic; using Rules.Framework.Source; using Rules.Framework.Tests.Stubs; using Rules.Framework.Validation; @@ -13,23 +12,22 @@ namespace Rules.Framework.Tests.Extensions public class RulesEngineExtensionsTests { [Fact] - public void RulesEngineExtensions_ToGenericEngine_Success() + public void RulesEngineExtensions_MakeGeneric_ReturnsGenericRulesEngine() { // Arrange - var rulesEngine = new RulesEngine( - Mock.Of>(), - Mock.Of>(), + var rulesEngine = new RulesEngine( + Mock.Of(), + Mock.Of(), Mock.Of(), RulesEngineOptions.NewWithDefaults(), - Mock.Of>() - ); + Mock.Of()); - //Act - var genericEngine = rulesEngine.CreateGenericEngine(); + // Act + var genericEngine = rulesEngine.MakeGeneric(); - //Arrange + // Assert genericEngine.Should().NotBeNull(); - genericEngine.GetType().Should().Be(typeof(GenericRulesEngine)); + genericEngine.GetType().Should().Be(typeof(RulesEngine)); } } } \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Generic/RulesEngineTests.cs b/tests/Rules.Framework.Tests/Generic/RulesEngineTests.cs new file mode 100644 index 00000000..77ca5bce --- /dev/null +++ b/tests/Rules.Framework.Tests/Generic/RulesEngineTests.cs @@ -0,0 +1,200 @@ +namespace Rules.Framework.Tests.Generic +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using FluentAssertions; + using Moq; + using Rules.Framework; + using Rules.Framework.Generic; + using Rules.Framework.Tests.Stubs; + using Xunit; + + public class RulesEngineTests + { + private readonly IRulesEngine rulesEngineMock; + + public RulesEngineTests() + { + this.rulesEngineMock = Mock.Of(); + } + + [Fact] + public async Task GetRulesetsAsync_NoConditionsGiven_ReturnsRulesets() + { + // Arrange + var ruleset1 = new Ruleset("Type1", DateTime.UtcNow); + var ruleset2 = new Ruleset("Type2", DateTime.UtcNow); + var rulesets = new[] { ruleset1, ruleset2, }; + var expectedGenericRulesets = new[] + { + new Ruleset(ruleset1), + new Ruleset(ruleset2), + }; + Mock.Get(this.rulesEngineMock) + .Setup(x => x.GetRulesetsAsync()) + .ReturnsAsync(rulesets); + + var genericRulesEngine = new RulesEngine(this.rulesEngineMock); + + // Act + var genericRulesets = await genericRulesEngine.GetRulesetsAsync(); + + // Assert + genericRulesets.Should().BeEquivalentTo(expectedGenericRulesets); + } + + [Fact] + public async Task GetRulesetsAsync_WithEmptyRulesetsNames_ReturnsEmptyRulesetsCollection() + { + // Arrange + var mockRulesEngineEmptyContentType = new Mock(); + + var genericRulesEngine = new RulesEngine(mockRulesEngineEmptyContentType.Object); + + // Act + var genericRulesets = await genericRulesEngine.GetRulesetsAsync(); + + // Assert + genericRulesets.Should().BeEmpty(); + } + + [Fact] + public async Task GetUniqueConditions_GivenRulesetAndDatesInterval_ReturnsConditions() + { + // Arrange + Mock.Get(this.rulesEngineMock) + .Setup(x => x.GetUniqueConditionsAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(new[] { nameof(ConditionNames.NumberOfSales), nameof(ConditionNames.IsVip), }); + + var genericRulesEngine = new RulesEngine(rulesEngineMock); + + // Act + var genericConditions = await genericRulesEngine.GetUniqueConditionsAsync(RulesetNames.Type1, DateTime.MinValue, DateTime.MaxValue); + + // Assert + genericConditions.Should().NotBeNullOrEmpty() + .And.Contain(ConditionNames.NumberOfSales) + .And.Contain(ConditionNames.IsVip); + } + + [Fact] + public void Options_PropertyGet_ReturnsRulesEngineOptions() + { + // Arrange + var options = RulesEngineOptions.NewWithDefaults(); + Mock.Get(this.rulesEngineMock) + .SetupGet(x => x.Options) + .Returns(options); + + var genericRulesEngine = new RulesEngine(this.rulesEngineMock); + + // Act + var actual = genericRulesEngine.Options; + + // Assert + actual.Should().BeSameAs(options); + } + + [Fact] + public async Task SearchAsync_GivenContentTypeAndDatesIntervalAndNoConditions_ReturnsRules() + { + // Arrange + var expectedRule = Rule.Create("Test rule") + .InRuleset(RulesetNames.Type1) + .SetContent(new object()) + .Since(new DateTime(2018, 01, 01)) + .Until(new DateTime(2019, 01, 01)) + .ApplyWhen(ConditionNames.IsoCountryCode, Operators.Equal, "USA") + .Build().Rule; + expectedRule.Priority = 3; + + var dateBegin = new DateTime(2022, 01, 01); + var dateEnd = new DateTime(2022, 12, 01); + var genericContentType = RulesetNames.Type1; + + var genericSearchArgs = new SearchArgs(genericContentType, dateBegin, dateEnd); + + var testRule = Rule.Create("Test rule") + .InRuleset(RulesetNames.Type1) + .SetContent(new object()) + .Since(new DateTime(2018, 01, 01)) + .Until(new DateTime(2019, 01, 01)) + .ApplyWhen(ConditionNames.IsoCountryCode, Operators.Equal, "USA") + .Build().Rule; + testRule.Priority = 3; + var testRules = new List + { + testRule + }; + + Mock.Get(this.rulesEngineMock) + .Setup(m => m.SearchAsync(It.IsAny>())) + .ReturnsAsync(testRules); + + var genericRulesEngine = new RulesEngine(this.rulesEngineMock); + + // Act + var genericRules = await genericRulesEngine.SearchAsync(genericSearchArgs); + + // Assert + var actualRule = genericRules.First(); + actualRule.Should().BeEquivalentTo(expectedRule); + Mock.Get(this.rulesEngineMock) + .Verify(m => m.SearchAsync(It.IsAny>()), Times.Once); + } + + [Theory] + [InlineData(nameof(RulesEngine.ActivateRuleAsync), "rule", typeof(ArgumentNullException))] + [InlineData(nameof(RulesEngine.AddRuleAsync), "rule", typeof(ArgumentNullException))] + [InlineData(nameof(RulesEngine.DeactivateRuleAsync), "rule", typeof(ArgumentNullException))] + [InlineData(nameof(RulesEngine.SearchAsync), "searchArgs", typeof(ArgumentNullException))] + [InlineData(nameof(RulesEngine.UpdateRuleAsync), "rule", typeof(ArgumentNullException))] + public async Task VerifyParameters_GivenNullParameter_ThrowsArgumentNullException(string methodName, string parameterName, Type exceptionType) + { + // Arrange + var sut = new RulesEngine(this.rulesEngineMock); + + // Act + var actual = await Assert.ThrowsAsync(exceptionType, async () => + { + switch (methodName) + { + case nameof(RulesEngine.ActivateRuleAsync): + _ = await sut.ActivateRuleAsync(null); + break; + + case nameof(RulesEngine.AddRuleAsync): + _ = await sut.AddRuleAsync(null, RuleAddPriorityOption.AtTop); + break; + + case nameof(RulesEngine.DeactivateRuleAsync): + _ = await sut.DeactivateRuleAsync(null); + break; + + case nameof(RulesEngine.SearchAsync): + _ = await sut.SearchAsync(null); + break; + + case nameof(RulesEngine.UpdateRuleAsync): + _ = await sut.UpdateRuleAsync(null); + break; + + default: + Assert.Fail("Test scenario not supported, please review test implementation to support it."); + break; + } + }); + + // Assert + actual.Should().NotBeNull() + .And.BeOfType(exceptionType); + if (actual is ArgumentException argumentException) + { + argumentException.Message.Should().Contain(parameterName); + argumentException.ParamName.Should().Be(parameterName); + } + } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Generics/GenericRulesEngineTests.cs b/tests/Rules.Framework.Tests/Generics/GenericRulesEngineTests.cs deleted file mode 100644 index df107552..00000000 --- a/tests/Rules.Framework.Tests/Generics/GenericRulesEngineTests.cs +++ /dev/null @@ -1,146 +0,0 @@ -namespace Rules.Framework.Tests.Generics -{ - using System; - using System.Collections.Generic; - using System.Threading.Tasks; - using FluentAssertions; - using Moq; - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; - using Rules.Framework.Generics; - using Rules.Framework.Tests.Stubs; - using Xunit; - - public class GenericRulesEngineTests - { - private readonly Mock> mockRulesEngine; - - public GenericRulesEngineTests() - { - this.mockRulesEngine = new Mock>(); - } - - [Fact] - public void GenericRulesEngine_GetContentTypes_Success() - { - // Arrange - var expectedGenericContentTypes = new List - { - new GenericContentType { Identifier = "Type1" }, - new GenericContentType { Identifier = "Type2" }, - }; - - var genericRulesEngine = new GenericRulesEngine(this.mockRulesEngine.Object); - - // Act - var genericContentTypes = genericRulesEngine.GetContentTypes(); - - // Assert - genericContentTypes.Should().BeEquivalentTo(expectedGenericContentTypes); - } - - [Fact] - public void GenericRulesEngine_GetContentTypes_WithClassContentType_ThrowsException() - { - // Arrange - var mockRulesEngineContentTypeClass = new Mock>(); - - var genericRulesEngine = new GenericRulesEngine(mockRulesEngineContentTypeClass.Object); - - // Act and Assert - Assert.Throws(() => genericRulesEngine.GetContentTypes()); - } - - [Fact] - public void GenericRulesEngine_GetContentTypes_WithEmptyContentType_Success() - { - // Arrange - var mockRulesEngineEmptyContentType = new Mock>(); - - var genericRulesEngine = new GenericRulesEngine(mockRulesEngineEmptyContentType.Object); - - // Act - var genericContentTypes = genericRulesEngine.GetContentTypes(); - - // Assert - genericContentTypes.Should().BeEmpty(); - } - - [Fact] - public void GenericRulesEngine_GetPriorityCriterias_CallsRulesEngineMethod() - { - // Arrange - var expectedPriorityCriteria = PriorityCriterias.TopmostRuleWins; - - this.mockRulesEngine.Setup(m => m.GetPriorityCriteria()).Returns(expectedPriorityCriteria); - - var genericRulesEngine = new GenericRulesEngine(this.mockRulesEngine.Object); - - // Act - var priorityCriteria = genericRulesEngine.GetPriorityCriteria(); - - // Assert - priorityCriteria.Should().Be(expectedPriorityCriteria); - this.mockRulesEngine.Verify(m => m.GetPriorityCriteria(), Times.Once); - } - - [Fact] - public async Task GenericRulesEngine_SearchAsync_Success() - { - // Arrange - var expectedGenericRules = new List - { - new GenericRule - { - Content = new ContentContainer(ContentType.Type1, (_) => new object()).GetContentAs(), - DateBegin = new DateTime(2018, 01, 01), - DateEnd = new DateTime(2019, 01, 01), - Name = "Test rule", - Priority = 3, - Active = true, - RootCondition = new GenericValueConditionNode - { - ConditionTypeName = ConditionType.IsoCountryCode.ToString(), - DataType = DataTypes.String, - LogicalOperator = LogicalOperators.Eval, - Operator = Operators.Equal, - Operand = "USA" - } - } - }; - - var dateBegin = new DateTime(2022, 01, 01); - var dateEnd = new DateTime(2022, 12, 01); - var genericContentType = new GenericContentType { Identifier = "Type1" }; - - var genericSearchArgs = new SearchArgs(genericContentType, dateBegin, dateEnd); - - var testRules = new List> - { - new Rule - { - ContentContainer = new ContentContainer(ContentType.Type1, (_) => new object()), - DateBegin = new DateTime(2018, 01, 01), - DateEnd = new DateTime(2019, 01, 01), - Name = "Test rule", - Priority = 3, - RootCondition = new ValueConditionNode(DataTypes.String, ConditionType.IsoCountryCode, Operators.Equal, "USA") - } - }; - - this.mockRulesEngine - .Setup(m => m.SearchAsync(It.IsAny>())) - .ReturnsAsync(testRules); - - var genericRulesEngine = new GenericRulesEngine(this.mockRulesEngine.Object); - - // Act - var genericRules = await genericRulesEngine.SearchAsync(genericSearchArgs).ConfigureAwait(false); - - // Assert - genericRules.Should().BeEquivalentTo(expectedGenericRules); - this.mockRulesEngine - .Verify(m => m.SearchAsync(It.IsAny>()), Times.Once); - } - } -} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Providers/InMemory/InMemoryProviderRulesDataSourceSelectorExtensionsTests.cs b/tests/Rules.Framework.Tests/Providers/InMemory/InMemoryProviderRulesDataSourceSelectorExtensionsTests.cs index 8e912d53..1dbcad32 100644 --- a/tests/Rules.Framework.Tests/Providers/InMemory/InMemoryProviderRulesDataSourceSelectorExtensionsTests.cs +++ b/tests/Rules.Framework.Tests/Providers/InMemory/InMemoryProviderRulesDataSourceSelectorExtensionsTests.cs @@ -6,7 +6,6 @@ namespace Rules.Framework.Tests.Providers.InMemory using Moq; using Rules.Framework.Builder; using Rules.Framework.Providers.InMemory; - using Rules.Framework.Tests.Providers.InMemory.TestStubs; using Xunit; public class InMemoryProviderRulesDataSourceSelectorExtensionsTests @@ -15,7 +14,7 @@ public class InMemoryProviderRulesDataSourceSelectorExtensionsTests public void SetInMemoryDataSource_GivenNullRulesDataSourceSelector_ThrowsArgumentNullException() { // Arrange - IRulesDataSourceSelector rulesDataSourceSelector = null; + IRulesDataSourceSelector rulesDataSourceSelector = null; // Act var actual = Assert.Throws(() => rulesDataSourceSelector.SetInMemoryDataSource()); @@ -31,7 +30,7 @@ public void SetInMemoryDataSource_GivenNullServiceProvider_ThrowsArgumentNullExc // Arrange IServiceProvider serviceProvider = null; - var rulesDataSourceSelector = Mock.Of>(); + var rulesDataSourceSelector = Mock.Of(); // Act var actual = Assert.Throws(() => rulesDataSourceSelector.SetInMemoryDataSource(serviceProvider)); @@ -45,18 +44,18 @@ public void SetInMemoryDataSource_GivenNullServiceProvider_ThrowsArgumentNullExc public void SetInMemoryDataSource_GivenServiceProvider_RequestsInMemoryRulesStorageAndSetsOnSelector() { // Arrange - IInMemoryRulesStorage inMemoryRulesStorage = new InMemoryRulesStorage(); + var inMemoryRulesStorage = new InMemoryRulesStorage(); var serviceDescriptors = new ServiceCollection(); - serviceDescriptors.AddSingleton(inMemoryRulesStorage); + serviceDescriptors.AddSingleton(inMemoryRulesStorage); var serviceProvider = serviceDescriptors.BuildServiceProvider(); - var rulesDataSourceSelector = Mock.Of>(); + var rulesDataSourceSelector = Mock.Of(); - IRulesDataSource actualRulesDataSource = null; + IRulesDataSource actualRulesDataSource = null; Mock.Get(rulesDataSourceSelector) - .Setup(x => x.SetDataSource(It.IsAny>())) - .Callback>((rds) => + .Setup(x => x.SetDataSource(It.IsAny())) + .Callback((rds) => { actualRulesDataSource = rds; }); @@ -66,7 +65,7 @@ public void SetInMemoryDataSource_GivenServiceProvider_RequestsInMemoryRulesStor // Assert actualRulesDataSource.Should().NotBeNull(); - actualRulesDataSource.Should().BeOfType>(); + actualRulesDataSource.Should().BeOfType(); Mock.Get(rulesDataSourceSelector) .Verify(); } @@ -75,12 +74,12 @@ public void SetInMemoryDataSource_GivenServiceProvider_RequestsInMemoryRulesStor public void SetInMemoryDataSource_NoParametersGiven_CreatesTransientInMemoryRulesStorageAndSetsOnSelector() { // Arrange - var rulesDataSourceSelector = Mock.Of>(); + var rulesDataSourceSelector = Mock.Of(); - IRulesDataSource actualRulesDataSource = null; + IRulesDataSource actualRulesDataSource = null; Mock.Get(rulesDataSourceSelector) - .Setup(x => x.SetDataSource(It.IsAny>())) - .Callback>((rds) => + .Setup(x => x.SetDataSource(It.IsAny())) + .Callback((rds) => { actualRulesDataSource = rds; }); @@ -90,7 +89,7 @@ public void SetInMemoryDataSource_NoParametersGiven_CreatesTransientInMemoryRule // Assert actualRulesDataSource.Should().NotBeNull(); - actualRulesDataSource.Should().BeOfType>(); + actualRulesDataSource.Should().BeOfType(); Mock.Get(rulesDataSourceSelector) .Verify(); } diff --git a/tests/Rules.Framework.Tests/Providers/InMemory/InMemoryProviderRulesDataSourceTests.cs b/tests/Rules.Framework.Tests/Providers/InMemory/InMemoryProviderRulesDataSourceTests.cs index 1aea4c8e..2b970c7c 100644 --- a/tests/Rules.Framework.Tests/Providers/InMemory/InMemoryProviderRulesDataSourceTests.cs +++ b/tests/Rules.Framework.Tests/Providers/InMemory/InMemoryProviderRulesDataSourceTests.cs @@ -5,10 +5,8 @@ namespace Rules.Framework.Tests.Providers.InMemory using System.Threading.Tasks; using FluentAssertions; using Moq; - using Rules.Framework.Core; using Rules.Framework.Providers.InMemory; using Rules.Framework.Providers.InMemory.DataModel; - using Rules.Framework.Tests.Providers.InMemory.TestStubs; using Xunit; public class InMemoryProviderRulesDataSourceTests @@ -16,33 +14,32 @@ public class InMemoryProviderRulesDataSourceTests public static IEnumerable CtorArguments { get; } = new[] { new object[] { null, null }, - new object[] { Mock.Of>(), null } + new object[] { Mock.Of(), null } }; [Fact] public async Task AddRuleAsync_GivenNullRule_ThrowsArgumentNullException() { // Arrange - Rule rule = null; + Rule rule = null; - var inMemoryRulesStorage = Mock.Of>(); - var ruleFactory = Mock.Of>(); + var inMemoryRulesStorage = Mock.Of(); + var ruleFactory = Mock.Of(); Mock.Get(ruleFactory) .Setup(x => x.CreateRule(rule)) .Verifiable(); Mock.Get(inMemoryRulesStorage) - .Setup(x => x.AddRule(It.IsAny>())) + .Setup(x => x.AddRule(It.IsAny())) .Verifiable(); var inMemoryProviderRulesDataSource - = new InMemoryProviderRulesDataSource(inMemoryRulesStorage, ruleFactory); + = new InMemoryProviderRulesDataSource(inMemoryRulesStorage, ruleFactory); // Act var argumentNullException = await Assert.ThrowsAsync(async () => - await inMemoryProviderRulesDataSource.AddRuleAsync(rule).ConfigureAwait(false) - ).ConfigureAwait(false); + await inMemoryProviderRulesDataSource.AddRuleAsync(rule)); // Assert argumentNullException.Should().NotBeNull(); @@ -53,11 +50,11 @@ await inMemoryProviderRulesDataSource.AddRuleAsync(rule).ConfigureAwait(false) public async Task AddRuleAsync_GivenRule_ConvertsToRuleDataModelAndAddsToDataSource() { // Arrange - var rule = new Rule(); - var ruleDataModel = new RuleDataModel(); + var rule = new Rule(); + var ruleDataModel = new RuleDataModel(); - var inMemoryRulesStorage = Mock.Of>(); - var ruleFactory = Mock.Of>(); + var inMemoryRulesStorage = Mock.Of(); + var ruleFactory = Mock.Of(); Mock.Get(ruleFactory) .Setup(x => x.CreateRule(rule)) @@ -69,10 +66,10 @@ public async Task AddRuleAsync_GivenRule_ConvertsToRuleDataModelAndAddsToDataSou .Verifiable(); var inMemoryProviderRulesDataSource - = new InMemoryProviderRulesDataSource(inMemoryRulesStorage, ruleFactory); + = new InMemoryProviderRulesDataSource(inMemoryRulesStorage, ruleFactory); // Act - await inMemoryProviderRulesDataSource.AddRuleAsync(rule).ConfigureAwait(false); + await inMemoryProviderRulesDataSource.AddRuleAsync(rule); // Assert Mock.VerifyAll(Mock.Get(inMemoryRulesStorage), Mock.Get(ruleFactory)); @@ -83,11 +80,11 @@ var inMemoryProviderRulesDataSource public void Ctor_GivenNullParameters_ThrowsArgumentNullException(object param1, object param2) { // Arrange - var inMemoryRulesStorage = param1 as IInMemoryRulesStorage; - var ruleFactory = param2 as IRuleFactory; + var inMemoryRulesStorage = param1 as IInMemoryRulesStorage; + var ruleFactory = param2 as IRuleFactory; // Act - var argumentNullException = Assert.Throws(() => new InMemoryProviderRulesDataSource(inMemoryRulesStorage, ruleFactory)); + var argumentNullException = Assert.Throws(() => new InMemoryProviderRulesDataSource(inMemoryRulesStorage, ruleFactory)); //Assert argumentNullException.Should().NotBeNull(); @@ -98,26 +95,25 @@ public void Ctor_GivenNullParameters_ThrowsArgumentNullException(object param1, public async Task UpdateRuleAsync_GivenNullRule_ThrowsArgumentNullException() { // Arrange - Rule rule = null; + Rule rule = null; - var inMemoryRulesStorage = Mock.Of>(); - var ruleFactory = Mock.Of>(); + var inMemoryRulesStorage = Mock.Of(); + var ruleFactory = Mock.Of(); Mock.Get(ruleFactory) .Setup(x => x.CreateRule(rule)) .Verifiable(); Mock.Get(inMemoryRulesStorage) - .Setup(x => x.UpdateRule(It.IsAny>())) + .Setup(x => x.UpdateRule(It.IsAny())) .Verifiable(); var inMemoryProviderRulesDataSource - = new InMemoryProviderRulesDataSource(inMemoryRulesStorage, ruleFactory); + = new InMemoryProviderRulesDataSource(inMemoryRulesStorage, ruleFactory); // Act var argumentNullException = await Assert.ThrowsAsync(async () => - await inMemoryProviderRulesDataSource.UpdateRuleAsync(rule).ConfigureAwait(false) - ).ConfigureAwait(false); + await inMemoryProviderRulesDataSource.UpdateRuleAsync(rule)); // Assert argumentNullException.Should().NotBeNull(); @@ -128,11 +124,11 @@ await inMemoryProviderRulesDataSource.UpdateRuleAsync(rule).ConfigureAwait(false public async Task UpdateRuleAsync_GivenRule_ConvertsToRuleDataModelAndUpdatesOnDataSource() { // Arrange - var rule = new Rule(); - var ruleDataModel = new RuleDataModel(); + var rule = new Rule(); + var ruleDataModel = new RuleDataModel(); - var inMemoryRulesStorage = Mock.Of>(); - var ruleFactory = Mock.Of>(); + var inMemoryRulesStorage = Mock.Of(); + var ruleFactory = Mock.Of(); Mock.Get(ruleFactory) .Setup(x => x.CreateRule(rule)) @@ -144,10 +140,10 @@ public async Task UpdateRuleAsync_GivenRule_ConvertsToRuleDataModelAndUpdatesOnD .Verifiable(); var inMemoryProviderRulesDataSource - = new InMemoryProviderRulesDataSource(inMemoryRulesStorage, ruleFactory); + = new InMemoryProviderRulesDataSource(inMemoryRulesStorage, ruleFactory); // Act - await inMemoryProviderRulesDataSource.UpdateRuleAsync(rule).ConfigureAwait(false); + await inMemoryProviderRulesDataSource.UpdateRuleAsync(rule); // Assert Mock.VerifyAll(Mock.Get(inMemoryRulesStorage), Mock.Get(ruleFactory)); diff --git a/tests/Rules.Framework.Tests/Providers/InMemory/RuleFactoryTests.cs b/tests/Rules.Framework.Tests/Providers/InMemory/RuleFactoryTests.cs index fe48b85c..ad83f23b 100644 --- a/tests/Rules.Framework.Tests/Providers/InMemory/RuleFactoryTests.cs +++ b/tests/Rules.Framework.Tests/Providers/InMemory/RuleFactoryTests.cs @@ -4,9 +4,10 @@ namespace Rules.Framework.Tests.Providers.InMemory using System.Dynamic; using System.Linq; using FluentAssertions; + using Rules.Framework; using Rules.Framework.Builder; + using Rules.Framework.ConditionNodes; using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; using Rules.Framework.Providers.InMemory; using Rules.Framework.Providers.InMemory.DataModel; using Rules.Framework.Tests.Providers.InMemory.TestStubs; @@ -18,9 +19,9 @@ public class RuleFactoryTests public void CreateRule_GivenNullRule_ThrowsArgumentNullException() { // Arrange - Rule rule = null; + Rule rule = null; - var ruleFactory = new RuleFactory(); + var ruleFactory = new RuleFactory(); // Act var argumentNullException = Assert.Throws(() => ruleFactory.CreateRule(rule)); @@ -34,9 +35,9 @@ public void CreateRule_GivenNullRule_ThrowsArgumentNullException() public void CreateRule_GivenNullRuleDataModel_ThrowsArgumentNullException() { // Arrange - RuleDataModel ruleDataModel = null; + RuleDataModel ruleDataModel = null; - var ruleFactory = new RuleFactory(); + var ruleFactory = new RuleFactory(); // Act var argumentNullException = Assert.Throws(() => ruleFactory.CreateRule(ruleDataModel)); @@ -55,9 +56,9 @@ public void CreateRule_GivenRuleDataModelWithComposedNodeAndChildNodesOfEachData content.Prop2 = "Sample string"; content.Prop3 = 500.34m; - var integerConditionNodeDataModel = new ValueConditionNodeDataModel + var integerConditionNodeDataModel = new ValueConditionNodeDataModel { - ConditionType = ConditionType.SampleIntegerCondition, + Condition = ConditionNames.SampleIntegerCondition.ToString(), DataType = DataTypes.Integer, LogicalOperator = LogicalOperators.Eval, Operand = 20, @@ -65,9 +66,9 @@ public void CreateRule_GivenRuleDataModelWithComposedNodeAndChildNodesOfEachData Properties = new PropertiesDictionary(2), }; - var stringConditionNodeDataModel = new ValueConditionNodeDataModel + var stringConditionNodeDataModel = new ValueConditionNodeDataModel { - ConditionType = ConditionType.SampleStringCondition, + Condition = ConditionNames.SampleStringCondition.ToString(), DataType = DataTypes.String, LogicalOperator = LogicalOperators.Eval, Operand = "TEST", @@ -75,9 +76,9 @@ public void CreateRule_GivenRuleDataModelWithComposedNodeAndChildNodesOfEachData Properties = new PropertiesDictionary(2), }; - var decimalConditionNodeDataModel = new ValueConditionNodeDataModel + var decimalConditionNodeDataModel = new ValueConditionNodeDataModel { - ConditionType = ConditionType.SampleDecimalCondition, + Condition = ConditionNames.SampleDecimalCondition.ToString(), DataType = DataTypes.Decimal, LogicalOperator = LogicalOperators.Eval, Operand = 50.3m, @@ -85,9 +86,9 @@ public void CreateRule_GivenRuleDataModelWithComposedNodeAndChildNodesOfEachData Properties = new PropertiesDictionary(2), }; - var booleanConditionNodeDataModel = new ValueConditionNodeDataModel + var booleanConditionNodeDataModel = new ValueConditionNodeDataModel { - ConditionType = ConditionType.SampleBooleanCondition, + Condition = ConditionNames.SampleBooleanCondition.ToString(), DataType = DataTypes.Boolean, LogicalOperator = LogicalOperators.Eval, Operand = true, @@ -95,18 +96,18 @@ public void CreateRule_GivenRuleDataModelWithComposedNodeAndChildNodesOfEachData Properties = new PropertiesDictionary(2), }; - var ruleDataModel = new RuleDataModel + var ruleDataModel = new RuleDataModel { Content = content, - ContentType = ContentType.ContentTypeSample, + Ruleset = RulesetNames.ContentTypeSample.ToString(), DateBegin = new System.DateTime(2020, 1, 1), DateEnd = null, Name = "My rule used for testing purposes", Priority = 1, - RootCondition = new ComposedConditionNodeDataModel + RootCondition = new ComposedConditionNodeDataModel { LogicalOperator = LogicalOperators.And, - ChildConditionNodes = new ConditionNodeDataModel[] + ChildConditionNodes = new ConditionNodeDataModel[] { integerConditionNodeDataModel, stringConditionNodeDataModel, @@ -117,7 +118,7 @@ public void CreateRule_GivenRuleDataModelWithComposedNodeAndChildNodesOfEachData } }; - var ruleFactory = new RuleFactory(); + var ruleFactory = new RuleFactory(); // Act var rule = ruleFactory.CreateRule(ruleDataModel); @@ -125,22 +126,23 @@ public void CreateRule_GivenRuleDataModelWithComposedNodeAndChildNodesOfEachData // Assert rule.Should().NotBeNull(); rule.ContentContainer.Should().NotBeNull() - .And.BeOfType>(); + .And.BeOfType(); rule.DateBegin.Should().Be(ruleDataModel.DateBegin); rule.DateEnd.Should().BeNull(); rule.Name.Should().Be(ruleDataModel.Name); rule.Priority.Should().Be(ruleDataModel.Priority); - rule.RootCondition.Should().BeOfType>(); + rule.Ruleset.Should().Be(ruleDataModel.Ruleset); + rule.RootCondition.Should().BeOfType(); - var composedConditionNode = rule.RootCondition.As>(); + var composedConditionNode = rule.RootCondition.As(); composedConditionNode.LogicalOperator.Should().Be(LogicalOperators.And); composedConditionNode.ChildConditionNodes.Should().HaveCount(4); - var valueConditionNodes = composedConditionNode.ChildConditionNodes.OfType>(); + var valueConditionNodes = composedConditionNode.ChildConditionNodes.OfType(); valueConditionNodes.Should().HaveCount(4); var integerConditionNode = valueConditionNodes.First(x => x.DataType == DataTypes.Integer); integerConditionNode.Should().NotBeNull(); - integerConditionNode.ConditionType.Should().Match(x => x == integerConditionNodeDataModel.ConditionType); + integerConditionNode.Condition.Should().Match(x => x == integerConditionNodeDataModel.Condition); integerConditionNode.DataType.Should().Be(integerConditionNodeDataModel.DataType); integerConditionNode.LogicalOperator.Should().Be(integerConditionNodeDataModel.LogicalOperator); integerConditionNode.Operand.Should().Match(x => object.Equals(x, integerConditionNodeDataModel.Operand)); @@ -148,7 +150,7 @@ public void CreateRule_GivenRuleDataModelWithComposedNodeAndChildNodesOfEachData var stringConditionNode = valueConditionNodes.First(x => x.DataType == DataTypes.String); stringConditionNode.Should().NotBeNull(); - stringConditionNode.ConditionType.Should().Match(x => x == stringConditionNodeDataModel.ConditionType); + stringConditionNode.Condition.Should().Match(x => x == stringConditionNodeDataModel.Condition); stringConditionNode.DataType.Should().Be(stringConditionNodeDataModel.DataType); stringConditionNode.LogicalOperator.Should().Be(stringConditionNodeDataModel.LogicalOperator); stringConditionNode.Operand.Should().Match(x => object.Equals(x, stringConditionNodeDataModel.Operand)); @@ -156,7 +158,7 @@ public void CreateRule_GivenRuleDataModelWithComposedNodeAndChildNodesOfEachData var decimalConditionNode = valueConditionNodes.First(x => x.DataType == DataTypes.Decimal); decimalConditionNode.Should().NotBeNull(); - decimalConditionNode.ConditionType.Should().Match(x => x == decimalConditionNodeDataModel.ConditionType); + decimalConditionNode.Condition.Should().Match(x => x == decimalConditionNodeDataModel.Condition); decimalConditionNode.DataType.Should().Be(decimalConditionNodeDataModel.DataType); decimalConditionNode.LogicalOperator.Should().Be(decimalConditionNodeDataModel.LogicalOperator); decimalConditionNode.Operand.Should().Match(x => object.Equals(x, decimalConditionNodeDataModel.Operand)); @@ -164,7 +166,7 @@ public void CreateRule_GivenRuleDataModelWithComposedNodeAndChildNodesOfEachData var booleanConditionNode = valueConditionNodes.First(x => x.DataType == DataTypes.Boolean); booleanConditionNode.Should().NotBeNull(); - booleanConditionNode.ConditionType.Should().Match(x => x == booleanConditionNodeDataModel.ConditionType); + booleanConditionNode.Condition.Should().Match(x => x == booleanConditionNodeDataModel.Condition); booleanConditionNode.DataType.Should().Be(booleanConditionNodeDataModel.DataType); booleanConditionNode.LogicalOperator.Should().Be(booleanConditionNodeDataModel.LogicalOperator); booleanConditionNode.Operand.Should().Be(Convert.ToBoolean(booleanConditionNodeDataModel.Operand)); @@ -181,28 +183,29 @@ public void CreateRule_GivenRuleWithComposedNodeAndChildNodesOfEachDataType_Retu content.Prop3 = 500.34m; var booleanConditionNode = ConditionNodeFactory - .CreateValueNode(ConditionType.SampleBooleanCondition, Operators.NotEqual, true) as ValueConditionNode; + .CreateValueNode(ConditionNames.SampleBooleanCondition.ToString(), Operators.NotEqual, true) as ValueConditionNode; var decimalConditionNode = ConditionNodeFactory - .CreateValueNode(ConditionType.SampleDecimalCondition, Operators.LesserThanOrEqual, 50.3m) as ValueConditionNode; + .CreateValueNode(ConditionNames.SampleDecimalCondition.ToString(), Operators.LesserThanOrEqual, 50.3m) as ValueConditionNode; var integerConditionNode = ConditionNodeFactory - .CreateValueNode(ConditionType.SampleIntegerCondition, Operators.GreaterThan, 20) as ValueConditionNode; + .CreateValueNode(ConditionNames.SampleIntegerCondition.ToString(), Operators.GreaterThan, 20) as ValueConditionNode; var stringConditionNode = ConditionNodeFactory - .CreateValueNode(ConditionType.SampleStringCondition, Operators.Equal, "TEST") as ValueConditionNode; + .CreateValueNode(ConditionNames.SampleStringCondition.ToString(), Operators.Equal, "TEST") as ValueConditionNode; - var rule1 = RuleBuilder.NewRule() - .WithName("My rule used for testing purposes") - .WithDateBegin(new DateTime(2020, 1, 1)) - .WithContent(ContentType.ContentTypeSample, (object)content) - .WithCondition(c => c + var rule1 = Rule.Create("My rule used for testing purposes") + .InRuleset(RulesetNames.ContentTypeSample) + .SetContent((object)content) + .Since(new DateTime(2020, 1, 1)) + .ApplyWhen(c => c .And(a => a .Condition(booleanConditionNode) .Condition(decimalConditionNode) .Condition(integerConditionNode) .Condition(stringConditionNode) - )) + ) + ) .Build().Rule; - var ruleFactory = new RuleFactory(); + var ruleFactory = new RuleFactory(); // Act var rule = ruleFactory.CreateRule(rule1); @@ -216,17 +219,18 @@ public void CreateRule_GivenRuleWithComposedNodeAndChildNodesOfEachDataType_Retu rule.DateEnd.Should().BeNull(); rule.Name.Should().Be(rule.Name); rule.Priority.Should().Be(rule.Priority); - rule.RootCondition.Should().BeOfType>(); + rule.Ruleset.Should().Be(rule.Ruleset); + rule.RootCondition.Should().BeOfType(); - var composedConditionNodeDataModel = rule.RootCondition.As>(); + var composedConditionNodeDataModel = rule.RootCondition.As(); composedConditionNodeDataModel.LogicalOperator.Should().Be(LogicalOperators.And); composedConditionNodeDataModel.ChildConditionNodes.Should().HaveCount(4); - var valueConditionNodeDataModels = composedConditionNodeDataModel.ChildConditionNodes.OfType>(); + var valueConditionNodeDataModels = composedConditionNodeDataModel.ChildConditionNodes.OfType(); valueConditionNodeDataModels.Should().HaveCount(4); var integerConditionNodeDataModel = valueConditionNodeDataModels.First(v => v.DataType == DataTypes.Integer); integerConditionNodeDataModel.Should().NotBeNull(); - integerConditionNodeDataModel.ConditionType.Should().Match(x => integerConditionNode.ConditionType == x); + integerConditionNodeDataModel.Condition.Should().Match(x => integerConditionNode.Condition == x); integerConditionNodeDataModel.DataType.Should().Be(integerConditionNode.DataType); integerConditionNodeDataModel.LogicalOperator.Should().Be(integerConditionNode.LogicalOperator); integerConditionNodeDataModel.Operand.Should().Match(x => object.Equals(x, integerConditionNode.Operand)); @@ -234,7 +238,7 @@ public void CreateRule_GivenRuleWithComposedNodeAndChildNodesOfEachDataType_Retu var stringConditionNodeDataModel = valueConditionNodeDataModels.First(v => v.DataType == DataTypes.String); stringConditionNodeDataModel.Should().NotBeNull(); - stringConditionNodeDataModel.ConditionType.Should().Match(x => stringConditionNode.ConditionType == x); + stringConditionNodeDataModel.Condition.Should().Match(x => stringConditionNode.Condition == x); stringConditionNodeDataModel.DataType.Should().Be(stringConditionNode.DataType); stringConditionNodeDataModel.LogicalOperator.Should().Be(stringConditionNode.LogicalOperator); stringConditionNodeDataModel.Operand.Should().Match(x => object.Equals(x, stringConditionNode.Operand)); @@ -242,7 +246,7 @@ public void CreateRule_GivenRuleWithComposedNodeAndChildNodesOfEachDataType_Retu var decimalConditionNodeDataModel = valueConditionNodeDataModels.First(v => v.DataType == DataTypes.Decimal); decimalConditionNodeDataModel.Should().NotBeNull(); - decimalConditionNodeDataModel.ConditionType.Should().Match(x => decimalConditionNode.ConditionType == x); + decimalConditionNodeDataModel.Condition.Should().Match(x => decimalConditionNode.Condition == x); decimalConditionNodeDataModel.DataType.Should().Be(decimalConditionNode.DataType); decimalConditionNodeDataModel.LogicalOperator.Should().Be(decimalConditionNode.LogicalOperator); decimalConditionNodeDataModel.Operand.Should().Match(x => object.Equals(x, decimalConditionNode.Operand)); @@ -250,7 +254,7 @@ public void CreateRule_GivenRuleWithComposedNodeAndChildNodesOfEachDataType_Retu var booleanConditionNodeDataModel = valueConditionNodeDataModels.First(v => v.DataType == DataTypes.Boolean); booleanConditionNodeDataModel.Should().NotBeNull(); - booleanConditionNodeDataModel.ConditionType.Should().Match(x => booleanConditionNode.ConditionType == x); + booleanConditionNodeDataModel.Condition.Should().Match(x => booleanConditionNode.Condition == x); booleanConditionNodeDataModel.DataType.Should().Be(booleanConditionNode.DataType); booleanConditionNodeDataModel.LogicalOperator.Should().Be(booleanConditionNode.LogicalOperator); booleanConditionNodeDataModel.Operand.Should().Be(Convert.ToBoolean(booleanConditionNode.Operand)); diff --git a/tests/Rules.Framework.Tests/Providers/InMemory/ServiceCollectionExtensionsTests.cs b/tests/Rules.Framework.Tests/Providers/InMemory/ServiceCollectionExtensionsTests.cs index 01e093dc..16ccf276 100644 --- a/tests/Rules.Framework.Tests/Providers/InMemory/ServiceCollectionExtensionsTests.cs +++ b/tests/Rules.Framework.Tests/Providers/InMemory/ServiceCollectionExtensionsTests.cs @@ -3,7 +3,6 @@ namespace Rules.Framework.Tests.Providers.InMemory using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using Moq; - using Rules.Framework.Tests.Providers.InMemory.TestStubs; using Xunit; public class ServiceCollectionExtensionsTests @@ -24,7 +23,7 @@ public void AddInMemoryRulesDataSource_GivenSingletonLifetimeOption_AddsServiceD }); // Act - var actual = services.AddInMemoryRulesDataSource(serviceLifetime); + var actual = services.AddInMemoryRulesDataSource(serviceLifetime); // Assert actual.Should().NotBeNull(); diff --git a/tests/Rules.Framework.Tests/Providers/InMemory/TestStubs/ConditionType.cs b/tests/Rules.Framework.Tests/Providers/InMemory/TestStubs/ConditionNames.cs similarity index 87% rename from tests/Rules.Framework.Tests/Providers/InMemory/TestStubs/ConditionType.cs rename to tests/Rules.Framework.Tests/Providers/InMemory/TestStubs/ConditionNames.cs index a4629af2..a7e9b1b1 100644 --- a/tests/Rules.Framework.Tests/Providers/InMemory/TestStubs/ConditionType.cs +++ b/tests/Rules.Framework.Tests/Providers/InMemory/TestStubs/ConditionNames.cs @@ -1,6 +1,6 @@ namespace Rules.Framework.Tests.Providers.InMemory.TestStubs { - internal enum ConditionType + internal enum ConditionNames { SampleIntegerCondition = 1, diff --git a/tests/Rules.Framework.Tests/Providers/InMemory/TestStubs/ContentStub.cs b/tests/Rules.Framework.Tests/Providers/InMemory/TestStubs/ContentStub.cs index 590a740f..4c4e9576 100644 --- a/tests/Rules.Framework.Tests/Providers/InMemory/TestStubs/ContentStub.cs +++ b/tests/Rules.Framework.Tests/Providers/InMemory/TestStubs/ContentStub.cs @@ -17,7 +17,7 @@ internal class ContentStub public Guid Prop04 { get; set; } // For the purpose of testing when type must be parsed to specific enum. - public ContentType Prop05 { get; set; } + public RulesetNames Prop05 { get; set; } // For the purpose of testing when type must be parsed to int. public int Prop06 { get; set; } diff --git a/tests/Rules.Framework.Tests/Providers/InMemory/TestStubs/ContentType.cs b/tests/Rules.Framework.Tests/Providers/InMemory/TestStubs/RulesetNames.cs similarity index 77% rename from tests/Rules.Framework.Tests/Providers/InMemory/TestStubs/ContentType.cs rename to tests/Rules.Framework.Tests/Providers/InMemory/TestStubs/RulesetNames.cs index ac2c986b..d661a602 100644 --- a/tests/Rules.Framework.Tests/Providers/InMemory/TestStubs/ContentType.cs +++ b/tests/Rules.Framework.Tests/Providers/InMemory/TestStubs/RulesetNames.cs @@ -1,6 +1,6 @@ namespace Rules.Framework.Tests.Providers.InMemory.TestStubs { - internal enum ContentType + internal enum RulesetNames { ContentTypeSample = 1 } diff --git a/tests/Rules.Framework.Tests/RuleConditionsExtractorTests.cs b/tests/Rules.Framework.Tests/RuleConditionsExtractorTests.cs new file mode 100644 index 00000000..ef392a11 --- /dev/null +++ b/tests/Rules.Framework.Tests/RuleConditionsExtractorTests.cs @@ -0,0 +1,144 @@ +namespace Rules.Framework.Tests +{ + using System; + using System.Collections.Generic; + using FluentAssertions; + using Rules.Framework; + using Rules.Framework.ConditionNodes; + using Rules.Framework.Tests.Stubs; + using Xunit; + + public class RuleConditionsExtractorTests + { + [Fact] + public void GetConditionTypes_ReturnsCorrectExtraction() + { + // Arrange + + var dateBegin = new DateTime(2018, 01, 01); + var dateEnd = new DateTime(2019, 01, 01); + + var rule1 = new Rule + { + ContentContainer = new ContentContainer(_ => new object()), + DateBegin = dateBegin, + DateEnd = dateEnd, + Name = "Rule 1", + Priority = 3, + RootCondition = new ValueConditionNode(DataTypes.String, ConditionNames.IsoCountryCode.ToString(), Operators.Equal, "USA") + }; + + var rule2 = new Rule + { + ContentContainer = new ContentContainer(_ => new object()), + DateBegin = new DateTime(2020, 01, 01), + DateEnd = new DateTime(2021, 01, 01), + Name = "Rule 2", + Priority = 200, + RootCondition = new ValueConditionNode(DataTypes.String, ConditionNames.IsoCountryCode.ToString(), Operators.Equal, "USA") + }; + + var rule3 = new Rule + { + ContentContainer = new ContentContainer(_ => new object()), + DateBegin = dateBegin, + DateEnd = dateEnd, + Name = "Rule 3", + Priority = 1, + RootCondition = new ValueConditionNode(DataTypes.String, ConditionNames.IsoCurrency.ToString(), Operators.Equal, "EUR") + }; + + var rule4 = new Rule + { + ContentContainer = new ContentContainer(_ => new object()), + DateBegin = dateBegin, + DateEnd = dateEnd, + Name = "Rule 4", + Priority = 1, + RootCondition = new ComposedConditionNode( + LogicalOperators.And, + new IConditionNode[] + { + new ValueConditionNode(DataTypes.String,ConditionNames.IsVip.ToString(), Operators.Equal, "true"), + new ValueConditionNode(DataTypes.String,ConditionNames.PluviosityRate.ToString(), Operators.Equal, "15"), + new ValueConditionNode(DataTypes.String,ConditionNames.IsoCurrency.ToString(), Operators.Equal, "JPY") + } + ) + }; + + var matchRules = new[] + { + rule1, + rule2, + rule3, + rule4 + }; + + var expectedConditionTypeList = new List + { + ConditionNames.IsoCurrency.ToString(), + ConditionNames.IsoCountryCode.ToString(), + ConditionNames.IsVip.ToString(), + ConditionNames.PluviosityRate.ToString(), + }; + + var conditionTypeExtractor = new RuleConditionsExtractor(); + + // Act + var actual = conditionTypeExtractor.GetConditions(matchRules); + + // Assert + actual.Should().BeEquivalentTo(expectedConditionTypeList); + } + + [Fact] + public void GetConditionTypes_WithEmptyMatchRules_ReturnsEmptyListConditionTypes() + { + // Arrange + + var matchRules = new List(); + + var expectedConditionTypeList = new List(); + + var conditionTypeExtractor = new RuleConditionsExtractor(); + + // Act + var actual = conditionTypeExtractor.GetConditions(matchRules); + + // Assert + actual.Should().BeEquivalentTo(expectedConditionTypeList); + } + + [Fact] + public void GetConditionTypes_WithNullRootCondition_ReturnsEmptyListConditionTypes() + { + // Arrange + + var dateBegin = new DateTime(2018, 01, 01); + var dateEnd = new DateTime(2019, 01, 01); + + var matchRules = new List + { + new() + { + ContentContainer = new ContentContainer(_ => new object()), + DateBegin = dateBegin, + DateEnd = dateEnd, + Name = "Rule 3", + Priority = 1, + RootCondition = null + } + }; + + var expectedConditionTypeList = new List(); + + var conditionTypeExtractor = new RuleConditionsExtractor(); + + // Act + var actual = conditionTypeExtractor.GetConditions(matchRules); + + // Assert + actual.Should().BeEquivalentTo(expectedConditionTypeList); + } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/RuleOperationResultTests.cs b/tests/Rules.Framework.Tests/RuleOperationResultTests.cs index e1e5e159..01c56a19 100644 --- a/tests/Rules.Framework.Tests/RuleOperationResultTests.cs +++ b/tests/Rules.Framework.Tests/RuleOperationResultTests.cs @@ -8,13 +8,13 @@ namespace Rules.Framework.Tests public class RuleOperationResultTests { [Fact] - public void Error_GivenCollectionOfErrors_ReturnsRuleOperationResultWithErrorsAndNoSuccess() + public void Failure_GivenCollectionOfErrors_ReturnsRuleOperationResultWithErrorsAndNoSuccess() { // Arrange IEnumerable errors = new[] { "Error1", "Error2" }; // Act - RuleOperationResult ruleOperationResult = RuleOperationResult.Error(errors); + var ruleOperationResult = OperationResult.Failure(errors); // Assert ruleOperationResult.Should().NotBeNull(); @@ -23,13 +23,13 @@ public void Error_GivenCollectionOfErrors_ReturnsRuleOperationResultWithErrorsAn } [Fact] - public void Error_GivenNullCollectionOfErrors_ThrowsArgumentNullException() + public void Failure_GivenNullCollectionOfErrors_ThrowsArgumentNullException() { // Arrange IEnumerable errors = null; // Act - ArgumentNullException argumentNullException = Assert.Throws(() => RuleOperationResult.Error(errors)); + var argumentNullException = Assert.Throws(() => OperationResult.Failure(errors)); // Assert argumentNullException.Should().NotBeNull(); @@ -40,7 +40,7 @@ public void Error_GivenNullCollectionOfErrors_ThrowsArgumentNullException() public void Success_NoConditionGiven_ReturnsRuleOperationResultWithoutErrorsAndSuccess() { // Act - RuleOperationResult ruleOperationResult = RuleOperationResult.Success(); + var ruleOperationResult = OperationResult.Success(); // Assert ruleOperationResult.Should().NotBeNull(); diff --git a/tests/Rules.Framework.Tests/Core/RuleTests.cs b/tests/Rules.Framework.Tests/RuleTests.cs similarity index 74% rename from tests/Rules.Framework.Tests/Core/RuleTests.cs rename to tests/Rules.Framework.Tests/RuleTests.cs index 536005bc..3a8da7e0 100644 --- a/tests/Rules.Framework.Tests/Core/RuleTests.cs +++ b/tests/Rules.Framework.Tests/RuleTests.cs @@ -1,10 +1,10 @@ -namespace Rules.Framework.Tests.Core +namespace Rules.Framework.Tests { using System; using FluentAssertions; using Moq; - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; + using Rules.Framework; + using Rules.Framework.ConditionNodes; using Rules.Framework.Tests.Stubs; using Xunit; @@ -14,9 +14,9 @@ public class RuleTests public void Clone_WithRuleWithoutRootCondition_ReturnsCopy() { // Arrange - var rule = new Rule + var rule = new Rule { - ContentContainer = new ContentContainer(ContentType.Type1, _ => new object()), + ContentContainer = new ContentContainer(_ => new object()), DateBegin = DateTime.UtcNow.AddDays(-1), DateEnd = DateTime.UtcNow.AddDays(1), Priority = 1, @@ -35,14 +35,14 @@ public void Clone_WithRuleWithoutRootCondition_ReturnsCopy() public void Clone_WithRuleWithRootCondition_ReturnsCopy() { // Arrange - var rule = new Rule + var rule = new Rule { - ContentContainer = new ContentContainer(ContentType.Type1, _ => new object()), + ContentContainer = new ContentContainer(_ => new object()), DateBegin = DateTime.UtcNow.AddDays(-1), DateEnd = DateTime.UtcNow.AddDays(1), Priority = 1, Name = "Name", - RootCondition = new ValueConditionNode(DataTypes.Decimal, ConditionType.PluviosityRate, Operators.GreaterThanOrEqual, 80.0d), + RootCondition = new ValueConditionNode(DataTypes.Decimal, ConditionNames.PluviosityRate.ToString(), Operators.GreaterThanOrEqual, 80.0d), }; rule.RootCondition.Properties["key1"] = "value1"; rule.RootCondition.Properties["key2"] = new object(); @@ -58,9 +58,9 @@ public void Clone_WithRuleWithRootCondition_ReturnsCopy() public void ContentContainer_HavingSettedInstance_ReturnsProvidedInstance() { // Arrange - var expected = new ContentContainer(ContentType.Type1, (_) => null); + var expected = new ContentContainer((_) => null); - var sut = new Rule + var sut = new Rule { ContentContainer = expected }; @@ -78,7 +78,7 @@ public void DateBegin_HavingSettedValue_ReturnsProvidedValue() // Arrange var expected = new DateTime(2018, 07, 19); - var sut = new Rule + var sut = new Rule { DateBegin = expected }; @@ -96,7 +96,7 @@ public void DateEnd_HavingSettedValue_ReturnsProvidedValue() // Arrange var expected = new DateTime(2018, 07, 19); - var sut = new Rule + var sut = new Rule { DateEnd = expected }; @@ -112,7 +112,7 @@ public void DateEnd_HavingSettedValue_ReturnsProvidedValue() public void DateEnd_NotHavingSettedValue_ReturnsNull() { // Arrange - var sut = new Rule(); + var sut = new Rule(); // Act var actual = sut.DateEnd; @@ -125,9 +125,9 @@ public void DateEnd_NotHavingSettedValue_ReturnsNull() public void Name_HavingSettedValue_ReturnsProvidedValue() { // Arrange - string expected = "My awesome name"; + var expected = "My awesome name"; - var sut = new Rule + var sut = new Rule { Name = expected }; @@ -145,7 +145,7 @@ public void Priority_HavingSettedValue_ReturnsProvidedValue() // Arrange var expected = 123; - var sut = new Rule + var sut = new Rule { Priority = expected }; @@ -161,10 +161,10 @@ public void Priority_HavingSettedValue_ReturnsProvidedValue() public void RootCondition_HavingSettedInstance_ReturnsProvidedInstance() { // Arrange - var mockConditionNode = new Mock>(); + var mockConditionNode = new Mock(); var expected = mockConditionNode.Object; - var sut = new Rule + var sut = new Rule { RootCondition = expected }; diff --git a/tests/Rules.Framework.Tests/RulesEngineTests.cs b/tests/Rules.Framework.Tests/RulesEngineTests.cs index fe302417..af9c4500 100644 --- a/tests/Rules.Framework.Tests/RulesEngineTests.cs +++ b/tests/Rules.Framework.Tests/RulesEngineTests.cs @@ -2,6 +2,7 @@ namespace Rules.Framework.Tests { using System; using System.Collections.Generic; + using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -9,8 +10,8 @@ namespace Rules.Framework.Tests using FluentValidation; using FluentValidation.Results; using Moq; - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; + using Rules.Framework; + using Rules.Framework.ConditionNodes; using Rules.Framework.Evaluation; using Rules.Framework.Source; using Rules.Framework.Tests.Stubs; @@ -19,90 +20,162 @@ namespace Rules.Framework.Tests public class RulesEngineTests { - private readonly Mock> mockConditionsEvalEngine; - private readonly Mock> mockCondtionTypeExtractor; - private readonly Mock> mockRulesSource; + private readonly IConditionsEvalEngine conditionsEvalEngineMock; + private readonly IRuleConditionsExtractor conditionTypeExtractorMock; + private readonly IRulesSource rulesSourceMock; + private readonly IValidatorProvider validatorProviderMock; public RulesEngineTests() { - this.mockRulesSource = new Mock>(); - this.mockCondtionTypeExtractor = new Mock>(); - this.mockConditionsEvalEngine = new Mock>(); + this.rulesSourceMock = Mock.Of(); + this.conditionTypeExtractorMock = Mock.Of(); + this.conditionsEvalEngineMock = Mock.Of(); + this.validatorProviderMock = Mock.Of(); } [Fact] public async Task ActivateRuleAsync_GivenEmptyRuleDataSource_ActivatesRuleSuccessfully() { // Arrange - var contentType = ContentType.Type1; + var ruleset = nameof(RulesetNames.Type1); - var testRule = new Rule + var testRule = new Rule { - ContentContainer = new ContentContainer(contentType, (t) => new object()), + ContentContainer = new ContentContainer(_ => new object()), DateBegin = new DateTime(2018, 01, 01), DateEnd = new DateTime(2019, 01, 01), Name = "Update test rule", Priority = 3, Active = false, - RootCondition = new ValueConditionNode(DataTypes.String, ConditionType.IsoCountryCode, Operators.Equal, "USA") + RootCondition = new ValueConditionNode(DataTypes.String, ConditionNames.IsoCountryCode.ToString(), Operators.Equal, "USA"), + Ruleset = ruleset, }; - var evaluationOptions = new EvaluationOptions + Mock.Get(rulesSourceMock) + .Setup(s => s.GetRulesFilteredAsync(It.IsAny())) + .ReturnsAsync(new List { testRule }); + + var validatorProvider = Mock.Of(); + var rulesEngineOptions = RulesEngineOptions.NewWithDefaults(); + + var sut = new RulesEngine(conditionsEvalEngineMock, rulesSourceMock, validatorProvider, rulesEngineOptions, conditionTypeExtractorMock); + + // Act + var actual = await sut.ActivateRuleAsync(testRule); + + // Assert + actual.IsSuccess.Should().BeTrue(); + actual.Errors.Should().BeEmpty(); + + Mock.Get(rulesSourceMock).Verify(x => x.GetRulesFilteredAsync(It.IsAny()), Times.Once()); + Mock.Get(conditionsEvalEngineMock).VerifyNoOtherCalls(); + } + + [Fact] + public async Task AddRuleAsync_GivenEmptyRuleDataSourceAndExistentRuleset_AddsRuleSuccessfully() + { + // Arrange + var ruleset = new Ruleset(RulesetNames.Type1.ToString(), DateTime.UtcNow); + + var testRule = new Rule { - MatchMode = MatchModes.Exact + Ruleset = ruleset.Name, + ContentContainer = new ContentContainer(_ => new object()), + DateBegin = new DateTime(2018, 01, 01), + DateEnd = new DateTime(2019, 01, 01), + Name = "Test rule", + Priority = 3, + RootCondition = new ValueConditionNode(DataTypes.String, ConditionNames.IsoCountryCode.ToString(), Operators.Equal, "USA") }; - mockRulesSource.Setup(s => s.GetRulesFilteredAsync(It.IsAny>())) - .ReturnsAsync(new List> { testRule }); + Mock.Get(rulesSourceMock) + .Setup(x => x.GetRulesetsAsync(It.IsAny())) + .ReturnsAsync(new[] { ruleset }); - var validatorProvider = Mock.Of(); var rulesEngineOptions = RulesEngineOptions.NewWithDefaults(); - var sut = new RulesEngine(mockConditionsEvalEngine.Object, mockRulesSource.Object, validatorProvider, rulesEngineOptions, mockCondtionTypeExtractor.Object); + rulesEngineOptions.PriorityCriteria = PriorityCriterias.BottommostRuleWins; + + var sut = new RulesEngine(conditionsEvalEngineMock, rulesSourceMock, validatorProviderMock, rulesEngineOptions, conditionTypeExtractorMock); // Act - var actual = await sut.ActivateRuleAsync(testRule).ConfigureAwait(false); + var actual = await sut.AddRuleAsync(testRule, RuleAddPriorityOption.AtBottom); // Assert actual.IsSuccess.Should().BeTrue(); actual.Errors.Should().BeEmpty(); - mockRulesSource.Verify(x => x.GetRulesFilteredAsync(It.IsAny>()), Times.Once()); - mockConditionsEvalEngine.Verify(x => x.Eval( - It.IsAny>(), - It.IsAny>(), - It.Is(eo => eo == evaluationOptions)), Times.Never()); + Mock.Get(rulesSourceMock).Verify(x => x.GetRulesFilteredAsync(It.IsAny()), Times.Once()); + Mock.Get(conditionsEvalEngineMock).VerifyNoOtherCalls(); } [Fact] - public async Task AddRuleAsync_GivenEmptyRuleDataSource_AddsRuleSuccesfully() + public async Task AddRuleAsync_GivenEmptyRuleDataSourceAndNonExistentRulesetAndAutoCreateRulesetDisabled_DoesNotAddRuleAndReportsError() { // Arrange - var contentType = ContentType.Type1; + var ruleset = new Ruleset(RulesetNames.Type1.ToString(), DateTime.UtcNow); - var testRule = new Rule + var testRule = new Rule { - ContentContainer = new ContentContainer(contentType, (t) => new object()), + Ruleset = ruleset.Name, + ContentContainer = new ContentContainer(_ => new object()), DateBegin = new DateTime(2018, 01, 01), DateEnd = new DateTime(2019, 01, 01), Name = "Test rule", Priority = 3, - RootCondition = new ValueConditionNode(DataTypes.String, ConditionType.IsoCountryCode, Operators.Equal, "USA") + RootCondition = new ValueConditionNode(DataTypes.String, ConditionNames.IsoCountryCode.ToString(), Operators.Equal, "USA") }; - EvaluationOptions evaluationOptions = new() + Mock.Get(rulesSourceMock) + .Setup(x => x.GetRulesetsAsync(It.IsAny())) + .ReturnsAsync(Array.Empty()); + + var rulesEngineOptions = RulesEngineOptions.NewWithDefaults(); + + rulesEngineOptions.PriorityCriteria = PriorityCriterias.BottommostRuleWins; + + var sut = new RulesEngine(conditionsEvalEngineMock, rulesSourceMock, validatorProviderMock, rulesEngineOptions, conditionTypeExtractorMock); + + // Act + var actual = await sut.AddRuleAsync(testRule, RuleAddPriorityOption.AtBottom); + + // Assert + actual.IsSuccess.Should().BeFalse(); + actual.Errors.Should().HaveCount(1); + + Mock.Get(rulesSourceMock).Verify(x => x.GetRulesFilteredAsync(It.IsAny()), Times.Never()); + Mock.Get(conditionsEvalEngineMock).VerifyNoOtherCalls(); + } + + [Fact] + public async Task AddRuleAsync_GivenEmptyRuleDataSourceAndNonExistentRulesetAndAutoCreateRulesetEnabled_CreatesRulesetAndAddsRuleSuccessfully() + { + // Arrange + var ruleset = new Ruleset(RulesetNames.Type1.ToString(), DateTime.UtcNow); + + var testRule = new Rule { - MatchMode = MatchModes.Exact + Ruleset = ruleset.Name, + ContentContainer = new ContentContainer(_ => new object()), + DateBegin = new DateTime(2018, 01, 01), + DateEnd = new DateTime(2019, 01, 01), + Name = "Test rule", + Priority = 3, + RootCondition = new ValueConditionNode(DataTypes.String, ConditionNames.IsoCountryCode.ToString(), Operators.Equal, "USA") }; - this.SetupMockForConditionsEvalEngine(true, evaluationOptions); + Mock.Get(this.rulesSourceMock) + .Setup(x => x.GetRulesetsAsync(It.IsAny())) + .ReturnsAsync(Array.Empty()); + Mock.Get(this.rulesSourceMock) + .Setup(x => x.CreateRulesetAsync(It.IsAny())) + .Returns(Task.CompletedTask); - var validatorProvider = Mock.Of(); var rulesEngineOptions = RulesEngineOptions.NewWithDefaults(); - rulesEngineOptions.PriorityCriteria = PriorityCriterias.BottommostRuleWins; + rulesEngineOptions.AutoCreateRulesets = true; - var sut = new RulesEngine(mockConditionsEvalEngine.Object, mockRulesSource.Object, validatorProvider, rulesEngineOptions, mockCondtionTypeExtractor.Object); + var sut = new RulesEngine(conditionsEvalEngineMock, rulesSourceMock, validatorProviderMock, rulesEngineOptions, conditionTypeExtractorMock); // Act var actual = await sut.AddRuleAsync(testRule, RuleAddPriorityOption.AtBottom); @@ -111,27 +184,90 @@ public async Task AddRuleAsync_GivenEmptyRuleDataSource_AddsRuleSuccesfully() actual.IsSuccess.Should().BeTrue(); actual.Errors.Should().BeEmpty(); - mockRulesSource.Verify(x => x.GetRulesAsync(It.IsAny>()), Times.Never()); - mockConditionsEvalEngine.Verify(x => x.Eval( - It.IsAny>(), - It.IsAny>(), - It.Is(eo => eo == evaluationOptions)), Times.Never()); + Mock.Get(rulesSourceMock).Verify(x => x.GetRulesetsAsync(It.IsAny()), Times.Once()); + Mock.Get(rulesSourceMock).Verify(x => x.CreateRulesetAsync(It.IsAny()), Times.Once()); + Mock.Get(rulesSourceMock).Verify(x => x.GetRulesFilteredAsync(It.IsAny()), Times.Once()); + Mock.Get(conditionsEvalEngineMock).VerifyNoOtherCalls(); + } + + [Fact] + public async Task CreateRulesetAsync_GivenExistentRulesetName_DoesNotAddRulesetToRulesSource() + { + // Arrange + var contentType = RulesetNames.Type1.ToString(); + + Mock.Get(this.rulesSourceMock) + .Setup(x => x.GetRulesetsAsync(It.IsAny())) + .ReturnsAsync(new[] { new Ruleset(nameof(RulesetNames.Type1), DateTime.UtcNow), }); + + var rulesEngineOptions = RulesEngineOptions.NewWithDefaults(); + + rulesEngineOptions.PriorityCriteria = PriorityCriterias.BottommostRuleWins; + + var sut = new RulesEngine(conditionsEvalEngineMock, rulesSourceMock, validatorProviderMock, rulesEngineOptions, conditionTypeExtractorMock); + + // Act + var operationResult = await sut.CreateRulesetAsync(contentType); + + // Assert + operationResult.Should().NotBeNull(); + operationResult.IsSuccess.Should().BeFalse(); + operationResult.Errors.Should().NotBeNull() + .And.HaveCount(1); + + Mock.Get(rulesSourceMock).Verify(x => x.GetRulesetsAsync(It.IsAny()), Times.Once()); + Mock.Get(rulesSourceMock).Verify(x => x.CreateRulesetAsync(It.Is(x => string.Equals(x.Name, contentType))), Times.Never()); + Mock.Get(conditionsEvalEngineMock).VerifyNoOtherCalls(); + } + + [Fact] + public async Task CreateRulesetAsync_GivenNonExistentRulesetName_AddsRulesetToRulesSource() + { + // Arrange + var contentType = RulesetNames.Type1.ToString(); + + Mock.Get(this.rulesSourceMock) + .Setup(x => x.GetRulesetsAsync(It.IsAny())) + .ReturnsAsync(Array.Empty()); + Mock.Get(rulesSourceMock) + .Setup(x => x.CreateRulesetAsync(It.Is(x => string.Equals(x.Name, contentType)))) + .Returns(Task.CompletedTask); + + var rulesEngineOptions = RulesEngineOptions.NewWithDefaults(); + + rulesEngineOptions.PriorityCriteria = PriorityCriterias.BottommostRuleWins; + + var sut = new RulesEngine(conditionsEvalEngineMock, rulesSourceMock, validatorProviderMock, rulesEngineOptions, conditionTypeExtractorMock); + + // Act + var operationResult = await sut.CreateRulesetAsync(contentType); + + // Assert + operationResult.Should().NotBeNull(); + operationResult.IsSuccess.Should().BeTrue(); + operationResult.Errors.Should().NotBeNull() + .And.BeEmpty(); + + Mock.Get(rulesSourceMock).Verify(x => x.GetRulesetsAsync(It.IsAny()), Times.Once()); + Mock.Get(rulesSourceMock).Verify(x => x.CreateRulesetAsync(It.Is(x => string.Equals(x.Name, contentType))), Times.Once()); + Mock.Get(conditionsEvalEngineMock).VerifyNoOtherCalls(); } [Fact] public async Task DeactivateRuleAsync_GivenEmptyRuleDataSource_DeactivatesRuleSuccessfully() { // Arrange - var contentType = ContentType.Type1; + var ruleset = nameof(RulesetNames.Type1); - var testRule = new Rule + var testRule = new Rule { - ContentContainer = new ContentContainer(contentType, (t) => new object()), + ContentContainer = new ContentContainer(_ => new object()), DateBegin = new DateTime(2018, 01, 01), DateEnd = new DateTime(2019, 01, 01), Name = "Update test rule", Priority = 3, - RootCondition = new ValueConditionNode(DataTypes.String, ConditionType.IsoCountryCode, Operators.Equal, "USA") + RootCondition = new ValueConditionNode(DataTypes.String, ConditionNames.IsoCountryCode.ToString(), Operators.Equal, "USA"), + Ruleset = ruleset, }; var evaluationOptions = new EvaluationOptions @@ -139,49 +275,56 @@ public async Task DeactivateRuleAsync_GivenEmptyRuleDataSource_DeactivatesRuleSu MatchMode = MatchModes.Exact }; - mockRulesSource.Setup(s => s.GetRulesFilteredAsync(It.IsAny>())) - .ReturnsAsync(new List> { testRule }); + Mock.Get(rulesSourceMock).Setup(s => s.GetRulesFilteredAsync(It.IsAny())) + .ReturnsAsync(new List { testRule }); var validatorProvider = Mock.Of(); var rulesEngineOptions = RulesEngineOptions.NewWithDefaults(); - var sut = new RulesEngine(mockConditionsEvalEngine.Object, mockRulesSource.Object, validatorProvider, rulesEngineOptions, mockCondtionTypeExtractor.Object); + var sut = new RulesEngine(conditionsEvalEngineMock, rulesSourceMock, validatorProvider, rulesEngineOptions, conditionTypeExtractorMock); // Act - var actual = await sut.DeactivateRuleAsync(testRule).ConfigureAwait(false); + var actual = await sut.DeactivateRuleAsync(testRule); // Assert actual.IsSuccess.Should().BeTrue(); actual.Errors.Should().BeEmpty(); - mockRulesSource.Verify(x => x.GetRulesFilteredAsync(It.IsAny>()), Times.Once()); - mockConditionsEvalEngine.Verify(x => x.Eval( - It.IsAny>(), - It.IsAny>(), + Mock.Get(rulesSourceMock).Verify(x => x.GetRulesFilteredAsync(It.IsAny()), Times.Once()); + Mock.Get(conditionsEvalEngineMock).Verify(x => x.Eval( + It.IsAny(), + It.IsAny>(), It.Is(eo => eo == evaluationOptions)), Times.Never()); } [Fact] - public void GetPriorityCriterias_GivenRulesEngineOptionsNewWithDefaults_ReturnsTopMostRuleWins() + public async Task GetRulesetsAsync_NoConditionsGiven_ReturnsRulesetsFromRulesSource() { // Arrange - var rulesEngine = new RulesEngine( - Mock.Of>(), - Mock.Of>(), - Mock.Of(), - RulesEngineOptions.NewWithDefaults(), - Mock.Of>() - ); - - //Act - var priorityCriterias = rulesEngine.GetPriorityCriteria(); - - //Arrange - priorityCriterias.Should().Be(PriorityCriterias.TopmostRuleWins); + Mock.Get(this.rulesSourceMock) + .Setup(x => x.GetRulesetsAsync(It.IsAny())) + .ReturnsAsync(new[] { new Ruleset(nameof(RulesetNames.Type1), DateTime.UtcNow), new Ruleset(nameof(RulesetNames.Type2), DateTime.UtcNow), }); + var rulesEngineOptions = RulesEngineOptions.NewWithDefaults(); + + rulesEngineOptions.PriorityCriteria = PriorityCriterias.BottommostRuleWins; + + var sut = new RulesEngine(conditionsEvalEngineMock, rulesSourceMock, validatorProviderMock, rulesEngineOptions, conditionTypeExtractorMock); + + // Act + var contentTypes = await sut.GetRulesetsAsync(); + + // Assert + contentTypes.Should().NotBeNull() + .And.HaveCount(2) + .And.Contain(r => string.Equals(r.Name, nameof(RulesetNames.Type1), StringComparison.Ordinal)) + .And.Contain(r => string.Equals(r.Name, nameof(RulesetNames.Type2), StringComparison.Ordinal)); + + Mock.Get(rulesSourceMock).Verify(x => x.GetRulesetsAsync(It.IsAny()), Times.Once()); + Mock.Get(conditionsEvalEngineMock).VerifyNoOtherCalls(); } [Fact] - public async Task GetUniqueConditionTypesAsync_GivenThereAreRulesInDataSource_ReturnsAllRequiredConditionTypes() + public async Task GetUniqueConditionsAsync_GivenThereAreRulesInDataSource_ReturnsAllRequiredConditions() { // Arrange @@ -193,78 +336,71 @@ public async Task GetUniqueConditionTypesAsync_GivenThereAreRulesInDataSource_Re MatchMode = MatchModes.Exact }; - var expectedCondtionTypes = new List { ConditionType.IsoCountryCode }; + var expectedConditions = new List { ConditionNames.IsoCountryCode.ToString() }; - mockCondtionTypeExtractor.Setup(x => x.GetConditionTypes(It.IsAny>>())) - .Returns(expectedCondtionTypes); + Mock.Get(conditionTypeExtractorMock) + .Setup(x => x.GetConditions(It.IsAny>())) + .Returns(expectedConditions); - this.SetupMockForConditionsEvalEngine((rootConditionNode, _, _) => - { - switch (rootConditionNode) - { - case ValueConditionNode stringConditionNode: - return stringConditionNode.Operand.ToString() == "USA"; - - default: - return false; - } - }, evaluationOptions); + this.SetupMockForConditionsEvalEngine( + (rootConditionNode, _, _) => rootConditionNode is ValueConditionNode stringConditionNode && stringConditionNode.Operand.ToString() == "USA", + evaluationOptions); var validatorProvider = Mock.Of(); var rulesEngineOptions = RulesEngineOptions.NewWithDefaults(); - var sut = new RulesEngine(mockConditionsEvalEngine.Object, mockRulesSource.Object, validatorProvider, rulesEngineOptions, mockCondtionTypeExtractor.Object); + var sut = new RulesEngine(conditionsEvalEngineMock, rulesSourceMock, validatorProvider, rulesEngineOptions, conditionTypeExtractorMock); // Act - var actual = await sut.GetUniqueConditionTypesAsync(ContentType.Type1, dateBegin, dateEnd); + var actual = await sut.GetUniqueConditionsAsync(RulesetNames.Type1.ToString(), dateBegin, dateEnd); // Assert actual.Should().NotBeNull(); actual.ToList().Count.Should().Be(1); - actual.Should().BeEquivalentTo(expectedCondtionTypes); + actual.Should().BeEquivalentTo(expectedConditions); } [Fact] - public async Task MatchManyAsync_GivenContentTypeDateAndConditions_FetchesRulesForDayEvalsAndReturnsAllMatches() + public async Task MatchManyAsync_GivenRulesetDateAndConditions_FetchesRulesForDayEvalsAndReturnsAllMatches() { // Arrange var matchDateTime = new DateTime(2018, 07, 01, 18, 19, 30); - var contentType = ContentType.Type1; - var conditions = new[] + var ruleset = RulesetNames.Type1.ToString(); + var conditions = new Dictionary { - new Condition(ConditionType.IsoCountryCode, "USA"), - new Condition(ConditionType.IsoCurrency, "USD") + { ConditionNames.IsoCountryCode.ToString(), "USA" }, + { ConditionNames.IsoCurrency.ToString(), "USD" }, }; - var expected1 = new Rule + var expected1 = new Rule { - ContentContainer = new ContentContainer(contentType, (t) => new object()), + ContentContainer = new ContentContainer(_ => new object()), DateBegin = new DateTime(2018, 01, 01), DateEnd = new DateTime(2019, 01, 01), Name = "Expected rule 1", Priority = 3, - RootCondition = new ValueConditionNode(DataTypes.String, ConditionType.IsoCountryCode, Operators.Equal, "USA") + RootCondition = new ValueConditionNode(DataTypes.String, ConditionNames.IsoCountryCode.ToString(), Operators.Equal, "USA") }; - var expected2 = new Rule + var expected2 = new Rule { - ContentContainer = new ContentContainer(contentType, (t) => new object()), + ContentContainer = new ContentContainer(_ => new object()), DateBegin = new DateTime(2010, 01, 01), DateEnd = new DateTime(2021, 01, 01), Name = "Expected rule 2", Priority = 200, - RootCondition = new ValueConditionNode(DataTypes.String, ConditionType.IsoCountryCode, Operators.Equal, "USA") + RootCondition = new ValueConditionNode(DataTypes.String, ConditionNames.IsoCountryCode.ToString(), Operators.Equal, "USA") }; - var notExpected = new Rule + var notExpected = new Rule { - ContentContainer = new ContentContainer(contentType, (t) => new object()), + ContentContainer = new ContentContainer(_ => new object()), DateBegin = new DateTime(2018, 01, 01), DateEnd = new DateTime(2019, 01, 01), Name = "Not expected rule", Priority = 1, // Topmost rule, should be the one that wins if options are set to topmost wins. - RootCondition = new ValueConditionNode(DataTypes.String, ConditionType.IsoCountryCode, Operators.Equal, "CHE") + RootCondition = new ValueConditionNode(DataTypes.String, ConditionNames.IsoCountryCode.ToString(), Operators.Equal, "CHE") }; var rules = new[] @@ -282,59 +418,59 @@ public async Task MatchManyAsync_GivenContentTypeDateAndConditions_FetchesRulesF this.SetupMockForConditionsEvalEngine((rootConditionNode, _, _) => { - return rootConditionNode is ValueConditionNode stringConditionNode && stringConditionNode.Operand.ToString() == "USA"; + return rootConditionNode is ValueConditionNode stringConditionNode && stringConditionNode.Operand.ToString() == "USA"; }, evaluationOptions); var validatorProvider = Mock.Of(); var rulesEngineOptions = RulesEngineOptions.NewWithDefaults(); - var sut = new RulesEngine(mockConditionsEvalEngine.Object, mockRulesSource.Object, validatorProvider, rulesEngineOptions, mockCondtionTypeExtractor.Object); + var sut = new RulesEngine(conditionsEvalEngineMock, rulesSourceMock, validatorProvider, rulesEngineOptions, conditionTypeExtractorMock); // Act - var actual = await sut.MatchManyAsync(contentType, matchDateTime, conditions).ConfigureAwait(false); + var actual = await sut.MatchManyAsync(ruleset, matchDateTime, conditions); // Assert actual.Should().Contain(expected1) .And.Contain(expected2) .And.NotContain(notExpected); - mockRulesSource.Verify(x => x.GetRulesAsync(It.IsAny>()), Times.Once()); - mockConditionsEvalEngine.Verify(x => x.Eval( - It.IsAny>(), - It.IsAny>(), + Mock.Get(rulesSourceMock).Verify(x => x.GetRulesAsync(It.IsAny()), Times.Once()); + Mock.Get(conditionsEvalEngineMock).Verify(x => x.Eval( + It.IsAny(), + It.IsAny>(), It.Is(eo => eo == evaluationOptions)), Times.AtLeastOnce()); } [Fact] - public async Task MatchOneAsync_GivenContentTypeDateAndConditions_FetchesRulesForDayEvalsAndReturnsTheBottommostPriorityOne() + public async Task MatchOneAsync_GivenRulesetDateAndConditions_FetchesRulesForDayEvalsAndReturnsTheBottommostPriorityOne() { // Arrange var matchDateTime = new DateTime(2018, 07, 01, 18, 19, 30); - var contentType = ContentType.Type1; - var conditions = new[] + var ruleset = RulesetNames.Type1.ToString(); + var conditions = new Dictionary { - new Condition(ConditionType.IsoCountryCode, "USA"), - new Condition(ConditionType.IsoCurrency, "USD") + { ConditionNames.IsoCountryCode.ToString(), "USA" }, + { ConditionNames.IsoCurrency.ToString(), "USD" }, }; - var other = new Rule + var other = new Rule { - ContentContainer = new ContentContainer(contentType, (t) => new object()), + ContentContainer = new ContentContainer(_ => new object()), DateBegin = new DateTime(2018, 01, 01), DateEnd = new DateTime(2019, 01, 01), Name = "Expected rule", Priority = 3, - RootCondition = new ValueConditionNode(DataTypes.String, ConditionType.IsoCountryCode, Operators.Equal, "USA") + RootCondition = new ValueConditionNode(DataTypes.String, ConditionNames.IsoCountryCode.ToString(), Operators.Equal, "USA") }; - var expected = new Rule + var expected = new Rule { - ContentContainer = new ContentContainer(contentType, (t) => new object()), + ContentContainer = new ContentContainer(_ => new object()), DateBegin = new DateTime(2010, 01, 01), DateEnd = new DateTime(2021, 01, 01), Name = "Expected rule", Priority = 200, - RootCondition = new ValueConditionNode(DataTypes.String, ConditionType.IsoCountryCode, Operators.Equal, "USA") + RootCondition = new ValueConditionNode(DataTypes.String, ConditionNames.IsoCountryCode.ToString(), Operators.Equal, "USA") }; var rules = new[] @@ -357,50 +493,50 @@ public async Task MatchOneAsync_GivenContentTypeDateAndConditions_FetchesRulesFo rulesEngineOptions.PriorityCriteria = PriorityCriterias.BottommostRuleWins; - var sut = new RulesEngine(mockConditionsEvalEngine.Object, mockRulesSource.Object, validatorProvider, rulesEngineOptions, mockCondtionTypeExtractor.Object); + var sut = new RulesEngine(conditionsEvalEngineMock, rulesSourceMock, validatorProvider, rulesEngineOptions, conditionTypeExtractorMock); // Act - var actual = await sut.MatchOneAsync(contentType, matchDateTime, conditions); + var actual = await sut.MatchOneAsync(ruleset, matchDateTime, conditions); // Assert actual.Should().BeSameAs(expected); - mockRulesSource.Verify(x => x.GetRulesAsync(It.IsAny>()), Times.Once()); - mockConditionsEvalEngine.Verify(x => x.Eval( - It.IsAny>(), - It.IsAny>(), + Mock.Get(rulesSourceMock).Verify(x => x.GetRulesAsync(It.IsAny()), Times.Once()); + Mock.Get(conditionsEvalEngineMock).Verify(x => x.Eval( + It.IsAny(), + It.IsAny>(), It.Is(eo => eo == evaluationOptions)), Times.AtLeastOnce()); } [Fact] - public async Task MatchOneAsync_GivenContentTypeDateAndConditions_FetchesRulesForDayEvalsAndReturnsTheTopmostPriorityOne() + public async Task MatchOneAsync_GivenRulesetDateAndConditions_FetchesRulesForDayEvalsAndReturnsTheTopmostPriorityOne() { // Arrange var matchDateTime = new DateTime(2018, 07, 01, 18, 19, 30); - var contentType = ContentType.Type1; - var conditions = new[] + var ruleset = RulesetNames.Type1.ToString(); + var conditions = new Dictionary { - new Condition(ConditionType.IsoCountryCode, "USA"), - new Condition(ConditionType.IsoCurrency, "USD") + { ConditionNames.IsoCountryCode.ToString(), "USA" }, + { ConditionNames.IsoCurrency.ToString(), "USD" }, }; - var expected = new Rule + var expected = new Rule { - ContentContainer = new ContentContainer(contentType, (t) => new object()), + ContentContainer = new ContentContainer(_ => new object()), DateBegin = new DateTime(2018, 01, 01), DateEnd = new DateTime(2019, 01, 01), Name = "Expected rule", Priority = 3, - RootCondition = new ValueConditionNode(DataTypes.String, ConditionType.IsoCountryCode, Operators.Equal, "USA") + RootCondition = new ValueConditionNode(DataTypes.String, ConditionNames.IsoCountryCode.ToString(), Operators.Equal, "USA") }; - var other = new Rule + var other = new Rule { - ContentContainer = new ContentContainer(contentType, (t) => new object()), + ContentContainer = new ContentContainer(_ => new object()), DateBegin = new DateTime(2010, 01, 01), DateEnd = new DateTime(2021, 01, 01), Name = "Expected rule", Priority = 200, - RootCondition = new ValueConditionNode(DataTypes.String, ConditionType.IsoCountryCode, Operators.Equal, "USA") + RootCondition = new ValueConditionNode(DataTypes.String, ConditionNames.IsoCountryCode.ToString(), Operators.Equal, "USA") }; var rules = new[] @@ -421,51 +557,51 @@ public async Task MatchOneAsync_GivenContentTypeDateAndConditions_FetchesRulesFo var validatorProvider = Mock.Of(); var rulesEngineOptions = RulesEngineOptions.NewWithDefaults(); - var sut = new RulesEngine(mockConditionsEvalEngine.Object, mockRulesSource.Object, validatorProvider, rulesEngineOptions, mockCondtionTypeExtractor.Object); + var sut = new RulesEngine(conditionsEvalEngineMock, rulesSourceMock, validatorProvider, rulesEngineOptions, conditionTypeExtractorMock); // Act - var actual = await sut.MatchOneAsync(contentType, matchDateTime, conditions); + var actual = await sut.MatchOneAsync(ruleset, matchDateTime, conditions); // Assert actual.Should().BeSameAs(expected); - mockRulesSource.Verify(x => x.GetRulesAsync(It.IsAny>()), Times.Once()); - mockConditionsEvalEngine.Verify(x => x.Eval( - It.IsAny>(), - It.IsAny>(), + Mock.Get(rulesSourceMock).Verify(x => x.GetRulesAsync(It.IsAny()), Times.Once()); + Mock.Get(conditionsEvalEngineMock).Verify(x => x.Eval( + It.IsAny(), + It.IsAny>(), It.Is(eo => eo == evaluationOptions)), Times.AtLeastOnce()); } [Fact] - public async Task MatchOneAsync_GivenContentTypeDateAndConditions_FetchesRulesForDayFailsEvalsAndReturnsNull() + public async Task MatchOneAsync_GivenRulesetDateAndConditions_FetchesRulesForDayFailsEvalsAndReturnsNull() { // Arrange var matchDateTime = new DateTime(2018, 07, 01, 18, 19, 30); - var contentType = ContentType.Type1; - var conditions = new[] + var ruleset = RulesetNames.Type1.ToString(); + var conditions = new Dictionary { - new Condition(ConditionType.IsoCountryCode, "USA"), - new Condition(ConditionType.IsoCurrency, "USD") + { ConditionNames.IsoCountryCode.ToString(), "USA" }, + { ConditionNames.IsoCurrency.ToString(), "USD" }, }; var rules = new[] { - new Rule + new Rule { - ContentContainer = new ContentContainer(contentType, (t) => new object()), + ContentContainer = new ContentContainer(_ => new object()), DateBegin = new DateTime(2018, 01, 01), DateEnd = new DateTime(2019, 01, 01), Name = "Expected rule", Priority = 3, - RootCondition = new ValueConditionNode(DataTypes.String,ConditionType.IsoCountryCode, Operators.Equal, "USA") + RootCondition = new ValueConditionNode(DataTypes.String,ConditionNames.IsoCountryCode.ToString(), Operators.Equal, "USA") }, - new Rule + new Rule { - ContentContainer = new ContentContainer(contentType, (t) => new object()), + ContentContainer = new ContentContainer(_ => new object()), DateBegin = new DateTime(2010, 01, 01), DateEnd = new DateTime(2021, 01, 01), Name = "Expected rule", Priority = 200, - RootCondition = new ValueConditionNode(DataTypes.String,ConditionType.IsoCountryCode, Operators.Equal, "USA") + RootCondition = new ValueConditionNode(DataTypes.String,ConditionNames.IsoCountryCode.ToString(), Operators.Equal, "USA") } }; @@ -481,48 +617,35 @@ public async Task MatchOneAsync_GivenContentTypeDateAndConditions_FetchesRulesFo var validatorProvider = Mock.Of(); var rulesEngineOptions = RulesEngineOptions.NewWithDefaults(); - var sut = new RulesEngine(mockConditionsEvalEngine.Object, mockRulesSource.Object, validatorProvider, rulesEngineOptions, mockCondtionTypeExtractor.Object); + var sut = new RulesEngine(conditionsEvalEngineMock, rulesSourceMock, validatorProvider, rulesEngineOptions, conditionTypeExtractorMock); // Act - var actual = await sut.MatchOneAsync(contentType, matchDateTime, conditions); + var actual = await sut.MatchOneAsync(ruleset, matchDateTime, conditions); // Assert actual.Should().BeNull(); - mockRulesSource.Verify(x => x.GetRulesAsync(It.IsAny>()), Times.Once()); - mockConditionsEvalEngine.Verify(x => x.Eval( - It.IsAny>(), - It.IsAny>(), + Mock.Get(rulesSourceMock).Verify(x => x.GetRulesAsync(It.IsAny()), Times.Once()); + Mock.Get(conditionsEvalEngineMock).Verify(x => x.Eval( + It.IsAny(), + It.IsAny>(), It.Is(eo => eo == evaluationOptions)), Times.AtLeastOnce()); } [Fact] - public async Task SearchAsync_GivenInvalidSearchArgs_ThrowsArgumentException() + public async Task UpdateRuleAsync_GivenEmptyRuleDataSource_UpdatesRuleSuccesfully() { // Arrange - var contentType = ContentType.Type1; - var matchDateTime = new DateTime(2018, 07, 01, 18, 19, 30); - var searchArgs = new SearchArgs(contentType, matchDateTime, matchDateTime); + var ruleset = nameof(RulesetNames.Type1); - var rules = new[] + var testRule = new Rule { - new Rule - { - ContentContainer = new ContentContainer(contentType, (t) => new object()), - DateBegin = new DateTime(2018, 01, 01), - DateEnd = new DateTime(2019, 01, 01), - Name = "Expected rule", - Priority = 3, - RootCondition = new ValueConditionNode(DataTypes.String,ConditionType.IsoCountryCode, Operators.Equal, "USA") - }, - new Rule - { - ContentContainer = new ContentContainer(contentType, (t) => new object()), - DateBegin = new DateTime(2010, 01, 01), - DateEnd = new DateTime(2021, 01, 01), - Name = "Expected rule", - Priority = 200, - RootCondition = new ValueConditionNode(DataTypes.String,ConditionType.IsoCountryCode, Operators.Equal, "USA") - } + ContentContainer = new ContentContainer(_ => new object()), + DateBegin = new DateTime(2018, 01, 01), + DateEnd = new DateTime(2019, 01, 01), + Name = "Update test rule", + Priority = 3, + RootCondition = new ValueConditionNode(DataTypes.String, ConditionNames.IsoCountryCode.ToString(), Operators.Equal, "USA"), + Ruleset = ruleset, }; var evaluationOptions = new EvaluationOptions @@ -530,96 +653,43 @@ public async Task SearchAsync_GivenInvalidSearchArgs_ThrowsArgumentException() MatchMode = MatchModes.Exact }; - this.SetupMockForRulesDataSource(rules); - - this.SetupMockForConditionsEvalEngine(false, evaluationOptions); - - var validator = Mock.Of>>(); - Mock.Get(validator) - .Setup(x => x.ValidateAsync(It.IsAny>(), It.IsAny())) - .ReturnsAsync(new ValidationResult(new[] { new ValidationFailure("Prop1", "Sample error message") })); + Mock.Get(rulesSourceMock).Setup(s => s.GetRulesFilteredAsync(It.IsAny())) + .ReturnsAsync(new List { testRule }); var validatorProvider = Mock.Of(); - Mock.Get(validatorProvider) - .Setup(x => x.GetValidatorFor>()) - .Returns(validator); var rulesEngineOptions = RulesEngineOptions.NewWithDefaults(); - var sut = new RulesEngine(mockConditionsEvalEngine.Object, mockRulesSource.Object, validatorProvider, rulesEngineOptions, mockCondtionTypeExtractor.Object); - - // Act - var argumentException = await Assert.ThrowsAsync(() => sut.SearchAsync(searchArgs)).ConfigureAwait(false); - - // Assert - argumentException.Should().NotBeNull(); - argumentException.ParamName.Should().Be(nameof(searchArgs)); - argumentException.Message.Should().StartWith($"Specified '{nameof(searchArgs)}' with invalid search values:"); - } - - [Fact] - public async Task SearchAsync_GivenNullSearchArgs_ThrowsArgumentNullException() - { - // Arrange - SearchArgs searchArgs = null; - var contentType = ContentType.Type1; - - var rules = new[] - { - new Rule - { - ContentContainer = new ContentContainer(contentType, (t) => new object()), - DateBegin = new DateTime(2018, 01, 01), - DateEnd = new DateTime(2019, 01, 01), - Name = "Expected rule", - Priority = 3, - RootCondition = new ValueConditionNode(DataTypes.String,ConditionType.IsoCountryCode, Operators.Equal, "USA") - }, - new Rule - { - ContentContainer = new ContentContainer(contentType, (t) => new object()), - DateBegin = new DateTime(2010, 01, 01), - DateEnd = new DateTime(2021, 01, 01), - Name = "Expected rule", - Priority = 200, - RootCondition = new ValueConditionNode(DataTypes.String,ConditionType.IsoCountryCode, Operators.Equal, "USA") - } - }; - - var evaluationOptions = new EvaluationOptions - { - MatchMode = MatchModes.Exact - }; - - this.SetupMockForRulesDataSource(rules); + var sut = new RulesEngine(conditionsEvalEngineMock, rulesSourceMock, validatorProvider, rulesEngineOptions, conditionTypeExtractorMock); - this.SetupMockForConditionsEvalEngine(false, evaluationOptions); - var validatorProvider = Mock.Of(); - var rulesEngineOptions = RulesEngineOptions.NewWithDefaults(); - - var sut = new RulesEngine(mockConditionsEvalEngine.Object, mockRulesSource.Object, validatorProvider, rulesEngineOptions, mockCondtionTypeExtractor.Object); + testRule.DateEnd = new DateTime(2019, 01, 02); + testRule.Priority = 1; // Act - var argumentNullException = await Assert.ThrowsAsync(() => sut.SearchAsync(searchArgs)).ConfigureAwait(false); + var actual = await sut.UpdateRuleAsync(testRule); // Assert - argumentNullException.Should().NotBeNull(); - argumentNullException.ParamName.Should().Be(nameof(searchArgs)); + actual.IsSuccess.Should().BeTrue(); + actual.Errors.Should().BeEmpty(); + + Mock.Get(rulesSourceMock).Verify(x => x.GetRulesFilteredAsync(It.IsAny()), Times.Once()); + Mock.Get(conditionsEvalEngineMock).Verify(x => x.Eval( + It.IsAny(), + It.IsAny>(), + It.Is(eo => eo == evaluationOptions)), Times.Never()); } [Fact] - public async Task UpdateRuleAsync_GivenEmptyRuleDataSource_UpdatesRuleSuccesfully() + public async Task UpdateRuleAsync_GivenRuleWithInvalidDateEnd_UpdatesRuleFailure() { // Arrange - var contentType = ContentType.Type1; - - var testRule = new Rule + var testRule = new Rule { - ContentContainer = new ContentContainer(contentType, (t) => new object()), + ContentContainer = new ContentContainer(_ => new object()), DateBegin = new DateTime(2018, 01, 01), DateEnd = new DateTime(2019, 01, 01), Name = "Update test rule", Priority = 3, - RootCondition = new ValueConditionNode(DataTypes.String, ConditionType.IsoCountryCode, Operators.Equal, "USA") + RootCondition = new ValueConditionNode(DataTypes.String, ConditionNames.IsoCountryCode.ToString(), Operators.Equal, "USA") }; var evaluationOptions = new EvaluationOptions @@ -627,98 +697,178 @@ public async Task UpdateRuleAsync_GivenEmptyRuleDataSource_UpdatesRuleSuccesfull MatchMode = MatchModes.Exact }; - mockRulesSource.Setup(s => s.GetRulesFilteredAsync(It.IsAny>())) - .ReturnsAsync(new List> { testRule }); + Mock.Get(rulesSourceMock).Setup(s => s.GetRulesFilteredAsync(It.IsAny())) + .ReturnsAsync(new List { testRule }); var validatorProvider = Mock.Of(); var rulesEngineOptions = RulesEngineOptions.NewWithDefaults(); - var sut = new RulesEngine(mockConditionsEvalEngine.Object, mockRulesSource.Object, validatorProvider, rulesEngineOptions, mockCondtionTypeExtractor.Object); + var sut = new RulesEngine(conditionsEvalEngineMock, rulesSourceMock, validatorProvider, rulesEngineOptions, conditionTypeExtractorMock); - testRule.DateEnd = new DateTime(2019, 01, 02); + testRule.DateEnd = testRule.DateBegin.AddYears(-2); testRule.Priority = 1; // Act - var actual = await sut.UpdateRuleAsync(testRule).ConfigureAwait(false); + var actual = await sut.UpdateRuleAsync(testRule); // Assert - actual.IsSuccess.Should().BeTrue(); - actual.Errors.Should().BeEmpty(); + actual.IsSuccess.Should().BeFalse(); + actual.Errors.Should().NotBeEmpty(); - mockRulesSource.Verify(x => x.GetRulesFilteredAsync(It.IsAny>()), Times.Once()); - mockConditionsEvalEngine.Verify(x => x.Eval( - It.IsAny>(), - It.IsAny>(), + Mock.Get(rulesSourceMock).Verify(x => x.GetRulesFilteredAsync(It.IsAny()), Times.Once()); + Mock.Get(conditionsEvalEngineMock).Verify(x => x.Eval( + It.IsAny(), + It.IsAny>(), It.Is(eo => eo == evaluationOptions)), Times.Never()); } - [Fact] - public async Task UpdateRuleAsync_GivenRuleWithInvalidDateEnd_UpdatesRuleFailure() + [Theory] + [InlineData(nameof(RulesEngine.ActivateRuleAsync), "rule", typeof(ArgumentNullException))] + [InlineData(nameof(RulesEngine.AddRuleAsync), "rule", typeof(ArgumentNullException))] + [InlineData(nameof(RulesEngine.AddRuleAsync), "ruleAddPriorityOption", typeof(ArgumentNullException))] + [InlineData(nameof(RulesEngine.CreateRulesetAsync), "ruleset", typeof(ArgumentNullException))] + [InlineData(nameof(RulesEngine.DeactivateRuleAsync), "rule", typeof(ArgumentNullException))] + [InlineData(nameof(RulesEngine.GetUniqueConditionsAsync), "ruleset", typeof(ArgumentNullException))] + [InlineData(nameof(RulesEngine.MatchManyAsync), "ruleset", typeof(ArgumentNullException))] + [InlineData(nameof(RulesEngine.MatchOneAsync), "ruleset", typeof(ArgumentNullException))] + [InlineData(nameof(RulesEngine.SearchAsync), "searchArgs", typeof(ArgumentNullException))] + [InlineData(nameof(RulesEngine.SearchAsync), "searchArgs", typeof(ArgumentException))] + [InlineData(nameof(RulesEngine.UpdateRuleAsync), "rule", typeof(ArgumentNullException))] + public async Task VerifyParameters_GivenNullParameter_ThrowsArgumentNullException(string methodName, string parameterName, Type exceptionType) { // Arrange - var contentType = ContentType.Type1; - - var testRule = new Rule - { - ContentContainer = new ContentContainer(contentType, (t) => new object()), - DateBegin = new DateTime(2018, 01, 01), - DateEnd = new DateTime(2019, 01, 01), - Name = "Update test rule", - Priority = 3, - RootCondition = new ValueConditionNode(DataTypes.String, ConditionType.IsoCountryCode, Operators.Equal, "USA") - }; - var evaluationOptions = new EvaluationOptions { MatchMode = MatchModes.Exact }; - mockRulesSource.Setup(s => s.GetRulesFilteredAsync(It.IsAny>())) - .ReturnsAsync(new List> { testRule }); - + var validator = Mock.Of>>(); + Mock.Get(validator) + .Setup(x => x.ValidateAsync(It.IsAny>(), It.IsAny())) + .ReturnsAsync(new ValidationResult(new[] { new ValidationFailure("Prop1", "Sample error message") })); var validatorProvider = Mock.Of(); + Mock.Get(validatorProvider) + .Setup(x => x.GetValidatorFor>()) + .Returns(validator); var rulesEngineOptions = RulesEngineOptions.NewWithDefaults(); - var sut = new RulesEngine(mockConditionsEvalEngine.Object, mockRulesSource.Object, validatorProvider, rulesEngineOptions, mockCondtionTypeExtractor.Object); - - testRule.DateEnd = testRule.DateBegin.AddYears(-2); - testRule.Priority = 1; + var sut = new RulesEngine(conditionsEvalEngineMock, rulesSourceMock, validatorProvider, rulesEngineOptions, conditionTypeExtractorMock); // Act - var actual = await sut.UpdateRuleAsync(testRule).ConfigureAwait(false); + var actual = await Assert.ThrowsAsync(exceptionType, async () => + { + switch (methodName) + { + case nameof(RulesEngine.ActivateRuleAsync): + _ = await sut.ActivateRuleAsync(null); + break; + + case nameof(RulesEngine.AddRuleAsync): + switch (parameterName) + { + case "rule": + _ = await sut.AddRuleAsync(null, RuleAddPriorityOption.AtTop); + break; + + case "ruleAddPriorityOption": + _ = await sut.AddRuleAsync(CreateTestStubRule(), null); + break; + + default: + Assert.Fail("Test scenario not supported, please review test implementation to support it."); + break; + } + break; + + case nameof(RulesEngine.CreateRulesetAsync): + await sut.CreateRulesetAsync(null); + break; + + case nameof(RulesEngine.DeactivateRuleAsync): + _ = await sut.DeactivateRuleAsync(null); + break; + + case nameof(RulesEngine.GetUniqueConditionsAsync): + _ = await sut.GetUniqueConditionsAsync(null, DateTime.MinValue, DateTime.MaxValue); + break; + + case nameof(RulesEngine.MatchManyAsync): + _ = await sut.MatchManyAsync(null, DateTime.UtcNow, new Dictionary()); + break; + + case nameof(RulesEngine.MatchOneAsync): + _ = await sut.MatchOneAsync(null, DateTime.UtcNow, new Dictionary()); + break; + + case nameof(RulesEngine.SearchAsync): + switch (exceptionType.Name) + { + case nameof(ArgumentNullException): + _ = await sut.SearchAsync(null); + break; + + case nameof(ArgumentException): + _ = await sut.SearchAsync(new SearchArgs("test", DateTime.MinValue, DateTime.MaxValue)); + break; + + default: + Assert.Fail("Test scenario not supported, please review test implementation to support it."); + break; + } + break; + + case nameof(RulesEngine.UpdateRuleAsync): + _ = await sut.UpdateRuleAsync(null); + break; - // Assert - actual.IsSuccess.Should().BeFalse(); - actual.Errors.Should().NotBeEmpty(); + default: + Assert.Fail("Test scenario not supported, please review test implementation to support it."); + break; + } + }); - mockRulesSource.Verify(x => x.GetRulesFilteredAsync(It.IsAny>()), Times.Once()); - mockConditionsEvalEngine.Verify(x => x.Eval( - It.IsAny>(), - It.IsAny>(), - It.Is(eo => eo == evaluationOptions)), Times.Never()); + // Assert + actual.Should().NotBeNull() + .And.BeOfType(exceptionType); + if (actual is ArgumentException argumentException) + { + argumentException.Message.Should().Contain(parameterName); + argumentException.ParamName.Should().Be(parameterName); + } } - private void SetupMockForConditionsEvalEngine(Func, IDictionary, EvaluationOptions, bool> evalFunc, EvaluationOptions evaluationOptions) + private static Rule CreateTestStubRule() + => Rule.Create("Test stub") + .InRuleset("Test content type") + .SetContent(new object()) + .Since(DateTime.Parse("2024-08-17", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal)) + .Build() + .Rule; + + private void SetupMockForConditionsEvalEngine(Func, EvaluationOptions, bool> evalFunc, EvaluationOptions evaluationOptions) { - this.mockConditionsEvalEngine.Setup(x => x.Eval( - It.IsAny>(), - It.IsAny>(), + Mock.Get(this.conditionsEvalEngineMock) + .Setup(x => x.Eval( + It.IsAny(), + It.IsAny>(), It.Is(eo => eo == evaluationOptions))) .Returns(evalFunc); } private void SetupMockForConditionsEvalEngine(bool result, EvaluationOptions evaluationOptions) { - this.mockConditionsEvalEngine.Setup(x => x.Eval( - It.IsAny>(), - It.IsAny>(), + Mock.Get(this.conditionsEvalEngineMock) + .Setup(x => x.Eval( + It.IsAny(), + It.IsAny>(), It.Is(eo => eo == evaluationOptions))) .Returns(result); } - private void SetupMockForRulesDataSource(IEnumerable> rules) + private void SetupMockForRulesDataSource(IEnumerable rules) { - this.mockRulesSource.Setup(x => x.GetRulesAsync(It.IsAny>())) + Mock.Get(this.rulesSourceMock) + .Setup(x => x.GetRulesAsync(It.IsAny())) .ReturnsAsync(rules); } } diff --git a/tests/Rules.Framework.Tests/Serialization/SerializedContentContainerTests.cs b/tests/Rules.Framework.Tests/Serialization/SerializedContentContainerTests.cs index 2873b11a..0e805d96 100644 --- a/tests/Rules.Framework.Tests/Serialization/SerializedContentContainerTests.cs +++ b/tests/Rules.Framework.Tests/Serialization/SerializedContentContainerTests.cs @@ -13,22 +13,22 @@ public class SerializedContentContainerTests public void Init_GivenSerializedContent_DeserializesAndReturnsWhenFetchingContent() { // Arrange - ContentType expectedContentType = ContentType.Type1; - object serializedContent = new object(); + var expectedContentType = RulesetNames.Type1.ToString(); + var serializedContent = new object(); object expected = 19m; - Mock mockContentSerializer = new Mock(); + var mockContentSerializer = new Mock(); mockContentSerializer.Setup(x => x.Deserialize(It.IsAny(), It.IsAny())) .Returns(expected); - Mock> mockContentSerializationProvider = new Mock>(); - mockContentSerializationProvider.Setup(x => x.GetContentSerializer(It.Is(y => y == expectedContentType))) + var mockContentSerializationProvider = new Mock(); + mockContentSerializationProvider.Setup(x => x.GetContentSerializer(It.Is(y => y == expectedContentType))) .Returns(mockContentSerializer.Object); - SerializedContentContainer sut = new SerializedContentContainer(expectedContentType, serializedContent, mockContentSerializationProvider.Object); + var sut = new SerializedContentContainer(expectedContentType, serializedContent, mockContentSerializationProvider.Object); // Act - decimal actual = sut.GetContentAs(); + var actual = sut.GetContentAs(); // Assert actual.Should().Be(expected.As()); diff --git a/tests/Rules.Framework.Tests/Source/RulesSourceTests.cs b/tests/Rules.Framework.Tests/Source/RulesSourceTests.cs index 6e9b9c4e..23a5b75d 100644 --- a/tests/Rules.Framework.Tests/Source/RulesSourceTests.cs +++ b/tests/Rules.Framework.Tests/Source/RulesSourceTests.cs @@ -6,7 +6,6 @@ namespace Rules.Framework.Tests.Source using System.Threading.Tasks; using FluentAssertions; using Moq; - using Rules.Framework.Core; using Rules.Framework.Source; using Rules.Framework.Tests.Stubs; using Xunit; @@ -17,118 +16,213 @@ public class RulesSourceTests public async Task AddRuleAsync_NoMiddlewares_CallsRulesDataSource() { // Arrange - Rule rule = CreateRule(); + var rule = CreateRule(); - AddRuleArgs addRuleArgs = new() + AddRuleArgs addRuleArgs = new() { Rule = rule, }; - IEnumerable> rulesSourceMiddlewares - = Enumerable.Empty>(); + var rulesSourceMiddlewares + = Enumerable.Empty(); - IRulesDataSource rulesDataSource = Mock.Of>(); + var rulesDataSource = Mock.Of(); Mock.Get(rulesDataSource) - .Setup(x => x.AddRuleAsync(It.Is>((val, _) => object.ReferenceEquals(val, rule)))) + .Setup(x => x.AddRuleAsync(It.Is((val, _) => object.ReferenceEquals(val, rule)))) .Returns(Task.CompletedTask); - RulesSource rulesSource = new(rulesDataSource, rulesSourceMiddlewares); + RulesSource rulesSource = new(rulesDataSource, rulesSourceMiddlewares); // Act - await rulesSource.AddRuleAsync(addRuleArgs).ConfigureAwait(false); + await rulesSource.AddRuleAsync(addRuleArgs); // Assert Mock.Get(rulesDataSource) - .Verify(x => x.AddRuleAsync(It.Is>((val, _) => object.ReferenceEquals(val, rule))), Times.Once()); + .Verify(x => x.AddRuleAsync(It.Is((val, _) => object.ReferenceEquals(val, rule))), Times.Once()); } [Fact] public async Task AddRuleAsync_OneMiddleware_CallsMiddlewareAndRulesDataSourceAfter() { // Arrange - Rule rule = CreateRule(); + var rule = CreateRule(); - AddRuleArgs addRuleArgs = new() + AddRuleArgs addRuleArgs = new() { Rule = rule, }; - StubRulesSourceMiddleware middleware1 = new(nameof(middleware1), new List()); + StubRulesSourceMiddleware middleware1 = new(nameof(middleware1), new List()); - IEnumerable> rulesSourceMiddlewares = new[] { middleware1 }; + IEnumerable rulesSourceMiddlewares = new[] { middleware1 }; - IRulesDataSource rulesDataSource = Mock.Of>(); + var rulesDataSource = Mock.Of(); Mock.Get(rulesDataSource) - .Setup(x => x.AddRuleAsync(It.Is>((val, _) => object.ReferenceEquals(val, rule)))) + .Setup(x => x.AddRuleAsync(It.Is((val, _) => object.ReferenceEquals(val, rule)))) .Returns(Task.CompletedTask); - RulesSource rulesSource = new(rulesDataSource, rulesSourceMiddlewares); + RulesSource rulesSource = new(rulesDataSource, rulesSourceMiddlewares); // Act - await rulesSource.AddRuleAsync(addRuleArgs).ConfigureAwait(false); + await rulesSource.AddRuleAsync(addRuleArgs); // Assert middleware1.AddRuleCalls.Should().Be(1); Mock.Get(rulesDataSource) - .Verify(x => x.AddRuleAsync(It.Is>((val, _) => object.ReferenceEquals(val, rule))), Times.Once()); + .Verify(x => x.AddRuleAsync(It.Is((val, _) => object.ReferenceEquals(val, rule))), Times.Once()); } [Fact] public async Task AddRuleAsync_TwoMiddlewares_CallsFirstMiddlewareSecondMiddlewareAfterAndRulesDataSourceByLast() { // Arrange - Rule rule = CreateRule(); + var rule = CreateRule(); - AddRuleArgs addRuleArgs = new() + AddRuleArgs addRuleArgs = new() { Rule = rule, }; List middlewareMessages = new(); - StubRulesSourceMiddleware middleware1 = new(nameof(middleware1), middlewareMessages); - StubRulesSourceMiddleware middleware2 = new(nameof(middleware2), middlewareMessages); + StubRulesSourceMiddleware middleware1 = new(nameof(middleware1), middlewareMessages); + StubRulesSourceMiddleware middleware2 = new(nameof(middleware2), middlewareMessages); - IEnumerable> rulesSourceMiddlewares = new[] { middleware1, middleware2 }; + IEnumerable rulesSourceMiddlewares = new[] { middleware1, middleware2 }; - IRulesDataSource rulesDataSource = Mock.Of>(); + var rulesDataSource = Mock.Of(); Mock.Get(rulesDataSource) - .Setup(x => x.AddRuleAsync(It.Is>((val, _) => object.ReferenceEquals(val, rule)))) + .Setup(x => x.AddRuleAsync(It.Is((val, _) => object.ReferenceEquals(val, rule)))) .Returns(Task.CompletedTask); - RulesSource rulesSource = new(rulesDataSource, rulesSourceMiddlewares); + RulesSource rulesSource = new(rulesDataSource, rulesSourceMiddlewares); // Act - await rulesSource.AddRuleAsync(addRuleArgs).ConfigureAwait(false); + await rulesSource.AddRuleAsync(addRuleArgs); // Assert middleware1.AddRuleCalls.Should().Be(1); middleware2.AddRuleCalls.Should().Be(1); middlewareMessages.Should().ContainInOrder("Enter middleware1.", "Enter middleware2.", "Exit middleware2.", "Exit middleware1."); Mock.Get(rulesDataSource) - .Verify(x => x.AddRuleAsync(It.Is>((val, _) => object.ReferenceEquals(val, rule))), Times.Once()); + .Verify(x => x.AddRuleAsync(It.Is((val, _) => object.ReferenceEquals(val, rule))), Times.Once()); + } + + [Fact] + public async Task CreateRulesetAsync_NoMiddlewares_CallsRulesDataSource() + { + // Arrange + var ruleset = "TestRuleset1"; + + CreateRulesetArgs createRulesetArgs = new() + { + Name = ruleset, + }; + + var rulesSourceMiddlewares + = Enumerable.Empty(); + + var rulesDataSource = Mock.Of(); + Mock.Get(rulesDataSource) + .Setup(x => x.CreateRulesetAsync(It.Is((val, _) => object.Equals(val, ruleset)))) + .Returns(Task.CompletedTask); + + RulesSource rulesSource = new(rulesDataSource, rulesSourceMiddlewares); + + // Act + await rulesSource.CreateRulesetAsync(createRulesetArgs); + + // Assert + Mock.Get(rulesDataSource) + .Verify(x => x.CreateRulesetAsync(It.Is((val, _) => object.Equals(val, ruleset))), Times.Once()); + } + + [Fact] + public async Task CreateRulesetAsync_OneMiddleware_CallsMiddlewareAndRulesDataSourceAfter() + { + // Arrange + var ruleset = "TestRuleset1"; + + CreateRulesetArgs createRulesetArgs = new() + { + Name = ruleset, + }; + + StubRulesSourceMiddleware middleware1 = new(nameof(middleware1), new List()); + + IEnumerable rulesSourceMiddlewares = new[] { middleware1 }; + + var rulesDataSource = Mock.Of(); + Mock.Get(rulesDataSource) + .Setup(x => x.CreateRulesetAsync(It.Is((val, _) => object.Equals(val, ruleset)))) + .Returns(Task.CompletedTask); + + RulesSource rulesSource = new(rulesDataSource, rulesSourceMiddlewares); + + // Act + await rulesSource.CreateRulesetAsync(createRulesetArgs); + + // Assert + middleware1.CreateRulesetCalls.Should().Be(1); + Mock.Get(rulesDataSource) + .Verify(x => x.CreateRulesetAsync(It.Is((val, _) => object.Equals(val, ruleset))), Times.Once()); + } + + [Fact] + public async Task CreateRulesetAsync_TwoMiddlewares_CallsFirstMiddlewareSecondMiddlewareAfterAndRulesDataSourceByLast() + { + // Arrange + var ruleset = "TestRuleset1"; + + CreateRulesetArgs createRulesetArgs = new() + { + Name = ruleset, + }; + + List middlewareMessages = new(); + StubRulesSourceMiddleware middleware1 = new(nameof(middleware1), middlewareMessages); + StubRulesSourceMiddleware middleware2 = new(nameof(middleware2), middlewareMessages); + + IEnumerable rulesSourceMiddlewares = new[] { middleware1, middleware2 }; + + var rulesDataSource = Mock.Of(); + Mock.Get(rulesDataSource) + .Setup(x => x.CreateRulesetAsync(It.Is((val, _) => object.Equals(val, ruleset)))) + .Returns(Task.CompletedTask); + + RulesSource rulesSource = new(rulesDataSource, rulesSourceMiddlewares); + + // Act + await rulesSource.CreateRulesetAsync(createRulesetArgs); + + // Assert + middleware1.CreateRulesetCalls.Should().Be(1); + middleware2.CreateRulesetCalls.Should().Be(1); + middlewareMessages.Should().ContainInOrder("Enter middleware1.", "Enter middleware2.", "Exit middleware2.", "Exit middleware1."); + Mock.Get(rulesDataSource) + .Verify(x => x.CreateRulesetAsync(It.Is((val, _) => object.Equals(val, ruleset))), Times.Once()); } [Fact] public async Task GetRulesAsync_NoMiddlewares_CallsRulesDataSource() { // Arrange - Rule rule = CreateRule(); + var rule = CreateRule(); var expected = new[] { rule }; - GetRulesArgs getRulesArgs = CreateGetRulesArgs(); + var getRulesArgs = CreateGetRulesArgs(); - IEnumerable> rulesSourceMiddlewares - = Enumerable.Empty>(); + var rulesSourceMiddlewares + = Enumerable.Empty(); - IRulesDataSource rulesDataSource = Mock.Of>(); + var rulesDataSource = Mock.Of(); Mock.Get(rulesDataSource) .Setup(x => x.GetRulesAsync(It.IsIn(getRulesArgs.ContentType), It.IsIn(getRulesArgs.DateBegin), It.IsIn(getRulesArgs.DateEnd))) .ReturnsAsync(expected); - RulesSource rulesSource = new(rulesDataSource, rulesSourceMiddlewares); + RulesSource rulesSource = new(rulesDataSource, rulesSourceMiddlewares); // Act - var actual = await rulesSource.GetRulesAsync(getRulesArgs).ConfigureAwait(false); + var actual = await rulesSource.GetRulesAsync(getRulesArgs); // Assert actual.Should().NotBeNullOrEmpty() @@ -141,24 +235,24 @@ IEnumerable> rulesSourceMiddl public async Task GetRulesAsync_OneMiddleware_CallsMiddlewareAndRulesDataSourceAfter() { // Arrange - Rule rule = CreateRule(); + var rule = CreateRule(); var expected = new[] { rule }; - GetRulesArgs getRulesArgs = CreateGetRulesArgs(); + var getRulesArgs = CreateGetRulesArgs(); - StubRulesSourceMiddleware middleware1 = new(nameof(middleware1), new List()); + StubRulesSourceMiddleware middleware1 = new(nameof(middleware1), new List()); - IEnumerable> rulesSourceMiddlewares = new[] { middleware1 }; + IEnumerable rulesSourceMiddlewares = new[] { middleware1 }; - IRulesDataSource rulesDataSource = Mock.Of>(); + var rulesDataSource = Mock.Of(); Mock.Get(rulesDataSource) .Setup(x => x.GetRulesAsync(It.IsIn(getRulesArgs.ContentType), It.IsIn(getRulesArgs.DateBegin), It.IsIn(getRulesArgs.DateEnd))) .ReturnsAsync(expected); - RulesSource rulesSource = new(rulesDataSource, rulesSourceMiddlewares); + RulesSource rulesSource = new(rulesDataSource, rulesSourceMiddlewares); // Act - var actual = await rulesSource.GetRulesAsync(getRulesArgs).ConfigureAwait(false); + var actual = await rulesSource.GetRulesAsync(getRulesArgs); // Assert actual.Should().NotBeNullOrEmpty() @@ -172,26 +266,26 @@ public async Task GetRulesAsync_OneMiddleware_CallsMiddlewareAndRulesDataSourceA public async Task GetRulesAsync_TwoMiddlewares_CallsFirstMiddlewareSecondMiddlewareAfterAndRulesDataSourceByLast() { // Arrange - Rule rule = CreateRule(); + var rule = CreateRule(); var expected = new[] { rule }; - GetRulesArgs getRulesArgs = CreateGetRulesArgs(); + var getRulesArgs = CreateGetRulesArgs(); List middlewareMessages = new(); - StubRulesSourceMiddleware middleware1 = new(nameof(middleware1), middlewareMessages); - StubRulesSourceMiddleware middleware2 = new(nameof(middleware2), middlewareMessages); + StubRulesSourceMiddleware middleware1 = new(nameof(middleware1), middlewareMessages); + StubRulesSourceMiddleware middleware2 = new(nameof(middleware2), middlewareMessages); - IEnumerable> rulesSourceMiddlewares = new[] { middleware1, middleware2 }; + IEnumerable rulesSourceMiddlewares = new[] { middleware1, middleware2 }; - IRulesDataSource rulesDataSource = Mock.Of>(); + var rulesDataSource = Mock.Of(); Mock.Get(rulesDataSource) .Setup(x => x.GetRulesAsync(It.IsIn(getRulesArgs.ContentType), It.IsIn(getRulesArgs.DateBegin), It.IsIn(getRulesArgs.DateEnd))) .ReturnsAsync(expected); - RulesSource rulesSource = new(rulesDataSource, rulesSourceMiddlewares); + RulesSource rulesSource = new(rulesDataSource, rulesSourceMiddlewares); // Act - var actual = await rulesSource.GetRulesAsync(getRulesArgs).ConfigureAwait(false); + var actual = await rulesSource.GetRulesAsync(getRulesArgs); // Assert actual.Should().NotBeNullOrEmpty() @@ -203,95 +297,199 @@ public async Task GetRulesAsync_TwoMiddlewares_CallsFirstMiddlewareSecondMiddlew .Verify(x => x.GetRulesAsync(It.IsIn(getRulesArgs.ContentType), It.IsIn(getRulesArgs.DateBegin), It.IsIn(getRulesArgs.DateEnd)), Times.Once()); } + [Fact] + public async Task GetRulesetsAsync_NoMiddlewares_CallsRulesDataSource() + { + // Arrange + var expected = new[] + { + new Ruleset("Content Type 1", DateTime.UtcNow), + new Ruleset("Content Type 2", DateTime.UtcNow), + }; + + var getRulesetsArgs = new GetRulesetsArgs(); + + var rulesSourceMiddlewares + = Enumerable.Empty(); + + var rulesDataSource = Mock.Of(); + Mock.Get(rulesDataSource) + .Setup(x => x.GetRulesetsAsync()) + .ReturnsAsync(expected); + + RulesSource rulesSource = new(rulesDataSource, rulesSourceMiddlewares); + + // Act + var actual = await rulesSource.GetRulesetsAsync(getRulesetsArgs); + + // Assert + actual.Should().NotBeNullOrEmpty() + .And.BeEquivalentTo(expected); + Mock.Get(rulesDataSource) + .Verify(x => x.GetRulesetsAsync(), Times.Once()); + } + + [Fact] + public async Task GetRulesetsAsync_OneMiddleware_CallsMiddlewareAndRulesDataSourceAfter() + { + // Arrange + var expected = new[] + { + new Ruleset("Content Type 1", DateTime.UtcNow), + new Ruleset("Content Type 2", DateTime.UtcNow), + }; + + var getRulesetsArgs = new GetRulesetsArgs(); + + StubRulesSourceMiddleware middleware1 = new(nameof(middleware1), new List()); + + IEnumerable rulesSourceMiddlewares = new[] { middleware1 }; + + var rulesDataSource = Mock.Of(); + Mock.Get(rulesDataSource) + .Setup(x => x.GetRulesetsAsync()) + .ReturnsAsync(expected); + + RulesSource rulesSource = new(rulesDataSource, rulesSourceMiddlewares); + + // Act + var actual = await rulesSource.GetRulesetsAsync(getRulesetsArgs); + + // Assert + actual.Should().NotBeNullOrEmpty() + .And.Contain(expected); + middleware1.GetRulesetsCalls.Should().Be(1); + Mock.Get(rulesDataSource) + .Verify(x => x.GetRulesetsAsync(), Times.Once()); + } + + [Fact] + public async Task GetRulesetsAsync_TwoMiddlewares_CallsFirstMiddlewareSecondMiddlewareAfterAndRulesDataSourceByLast() + { + // Arrange + var expected = new[] + { + new Ruleset("Content Type 1", DateTime.UtcNow), + new Ruleset("Content Type 2", DateTime.UtcNow), + }; + + var getRulesetsArgs = new GetRulesetsArgs(); + + List middlewareMessages = new(); + StubRulesSourceMiddleware middleware1 = new(nameof(middleware1), middlewareMessages); + StubRulesSourceMiddleware middleware2 = new(nameof(middleware2), middlewareMessages); + + IEnumerable rulesSourceMiddlewares = new[] { middleware1, middleware2 }; + + var rulesDataSource = Mock.Of(); + Mock.Get(rulesDataSource) + .Setup(x => x.GetRulesetsAsync()) + .ReturnsAsync(expected); + + RulesSource rulesSource = new(rulesDataSource, rulesSourceMiddlewares); + + // Act + var actual = await rulesSource.GetRulesetsAsync(getRulesetsArgs); + + // Assert + actual.Should().NotBeNullOrEmpty() + .And.Contain(expected); + middleware1.GetRulesetsCalls.Should().Be(1); + middleware2.GetRulesetsCalls.Should().Be(1); + middlewareMessages.Should().ContainInOrder("Enter middleware1.", "Enter middleware2.", "Exit middleware2.", "Exit middleware1."); + Mock.Get(rulesDataSource) + .Verify(x => x.GetRulesetsAsync(), Times.Once()); + } + [Fact] public async Task GetRulesFilteredAsync_NoMiddlewares_CallsRulesDataSource() { // Arrange - Rule rule = CreateRule(); + var rule = CreateRule(); var expected = new[] { rule }; - GetRulesFilteredArgs getRulesFilteredArgs = CreateGetRulesFilteredArgs(); + var getRulesFilteredArgs = CreateGetRulesFilteredArgs(); - IEnumerable> rulesSourceMiddlewares - = Enumerable.Empty>(); + var rulesSourceMiddlewares + = Enumerable.Empty(); - IRulesDataSource rulesDataSource = Mock.Of>(); + var rulesDataSource = Mock.Of(); Mock.Get(rulesDataSource) - .Setup(x => x.GetRulesByAsync(It.Is>( - (val) => string.Equals(val.Name, getRulesFilteredArgs.Name) && val.ContentType == getRulesFilteredArgs.ContentType && val.Priority == getRulesFilteredArgs.Priority))) + .Setup(x => x.GetRulesByAsync(It.Is( + (val) => string.Equals(val.Name, getRulesFilteredArgs.Name) && val.Ruleset == getRulesFilteredArgs.Ruleset && val.Priority == getRulesFilteredArgs.Priority))) .ReturnsAsync(expected); - RulesSource rulesSource = new(rulesDataSource, rulesSourceMiddlewares); + RulesSource rulesSource = new(rulesDataSource, rulesSourceMiddlewares); // Act - var actual = await rulesSource.GetRulesFilteredAsync(getRulesFilteredArgs).ConfigureAwait(false); + var actual = await rulesSource.GetRulesFilteredAsync(getRulesFilteredArgs); // Assert actual.Should().NotBeNullOrEmpty() .And.BeEquivalentTo(expected); Mock.Get(rulesDataSource) - .Verify(x => x.GetRulesByAsync(It.Is>( - (val) => string.Equals(val.Name, getRulesFilteredArgs.Name) && val.ContentType == getRulesFilteredArgs.ContentType && val.Priority == getRulesFilteredArgs.Priority)), Times.Once()); + .Verify(x => x.GetRulesByAsync(It.Is( + (val) => string.Equals(val.Name, getRulesFilteredArgs.Name) && val.Ruleset == getRulesFilteredArgs.Ruleset && val.Priority == getRulesFilteredArgs.Priority)), Times.Once()); } [Fact] public async Task GetRulesFilteredAsync_OneMiddleware_CallsMiddlewareAndRulesDataSourceAfter() { // Arrange - Rule rule = CreateRule(); + var rule = CreateRule(); var expected = new[] { rule }; - GetRulesFilteredArgs getRulesFilteredArgs = CreateGetRulesFilteredArgs(); + var getRulesFilteredArgs = CreateGetRulesFilteredArgs(); - StubRulesSourceMiddleware middleware1 = new(nameof(middleware1), new List()); + StubRulesSourceMiddleware middleware1 = new(nameof(middleware1), new List()); - IEnumerable> rulesSourceMiddlewares = new[] { middleware1 }; + IEnumerable rulesSourceMiddlewares = new[] { middleware1 }; - IRulesDataSource rulesDataSource = Mock.Of>(); + var rulesDataSource = Mock.Of(); Mock.Get(rulesDataSource) - .Setup(x => x.GetRulesByAsync(It.Is>( - (val) => string.Equals(val.Name, getRulesFilteredArgs.Name) && val.ContentType == getRulesFilteredArgs.ContentType && val.Priority == getRulesFilteredArgs.Priority))) + .Setup(x => x.GetRulesByAsync(It.Is( + (val) => string.Equals(val.Name, getRulesFilteredArgs.Name) && val.Ruleset == getRulesFilteredArgs.Ruleset && val.Priority == getRulesFilteredArgs.Priority))) .ReturnsAsync(expected); - RulesSource rulesSource = new(rulesDataSource, rulesSourceMiddlewares); + RulesSource rulesSource = new(rulesDataSource, rulesSourceMiddlewares); // Act - var actual = await rulesSource.GetRulesFilteredAsync(getRulesFilteredArgs).ConfigureAwait(false); + var actual = await rulesSource.GetRulesFilteredAsync(getRulesFilteredArgs); // Assert actual.Should().NotBeNullOrEmpty() .And.Contain(expected); middleware1.GetRulesFilteredCalls.Should().Be(1); Mock.Get(rulesDataSource) - .Verify(x => x.GetRulesByAsync(It.Is>( - (val) => string.Equals(val.Name, getRulesFilteredArgs.Name) && val.ContentType == getRulesFilteredArgs.ContentType && val.Priority == getRulesFilteredArgs.Priority)), Times.Once()); + .Verify(x => x.GetRulesByAsync(It.Is( + (val) => string.Equals(val.Name, getRulesFilteredArgs.Name) && val.Ruleset == getRulesFilteredArgs.Ruleset && val.Priority == getRulesFilteredArgs.Priority)), Times.Once()); } [Fact] public async Task GetRulesFilteredAsync_TwoMiddlewares_CallsFirstMiddlewareSecondMiddlewareAfterAndRulesDataSourceByLast() { // Arrange - Rule rule = CreateRule(); + var rule = CreateRule(); var expected = new[] { rule }; - GetRulesFilteredArgs getRulesFilteredArgs = CreateGetRulesFilteredArgs(); + var getRulesFilteredArgs = CreateGetRulesFilteredArgs(); List middlewareMessages = new(); - StubRulesSourceMiddleware middleware1 = new(nameof(middleware1), middlewareMessages); - StubRulesSourceMiddleware middleware2 = new(nameof(middleware2), middlewareMessages); + StubRulesSourceMiddleware middleware1 = new(nameof(middleware1), middlewareMessages); + StubRulesSourceMiddleware middleware2 = new(nameof(middleware2), middlewareMessages); - IEnumerable> rulesSourceMiddlewares = new[] { middleware1, middleware2 }; + IEnumerable rulesSourceMiddlewares = new[] { middleware1, middleware2 }; - IRulesDataSource rulesDataSource = Mock.Of>(); + var rulesDataSource = Mock.Of(); Mock.Get(rulesDataSource) - .Setup(x => x.GetRulesByAsync(It.Is>( - (val) => string.Equals(val.Name, getRulesFilteredArgs.Name) && val.ContentType == getRulesFilteredArgs.ContentType && val.Priority == getRulesFilteredArgs.Priority))) + .Setup(x => x.GetRulesByAsync(It.Is( + (val) => string.Equals(val.Name, getRulesFilteredArgs.Name) && val.Ruleset == getRulesFilteredArgs.Ruleset && val.Priority == getRulesFilteredArgs.Priority))) .ReturnsAsync(expected); - RulesSource rulesSource = new(rulesDataSource, rulesSourceMiddlewares); + RulesSource rulesSource = new(rulesDataSource, rulesSourceMiddlewares); // Act - var actual = await rulesSource.GetRulesFilteredAsync(getRulesFilteredArgs).ConfigureAwait(false); + var actual = await rulesSource.GetRulesFilteredAsync(getRulesFilteredArgs); // Assert actual.Should().NotBeNullOrEmpty() @@ -300,124 +498,124 @@ public async Task GetRulesFilteredAsync_TwoMiddlewares_CallsFirstMiddlewareSecon middleware2.GetRulesFilteredCalls.Should().Be(1); middlewareMessages.Should().ContainInOrder("Enter middleware1.", "Enter middleware2.", "Exit middleware2.", "Exit middleware1."); Mock.Get(rulesDataSource) - .Verify(x => x.GetRulesByAsync(It.Is>( - (val) => string.Equals(val.Name, getRulesFilteredArgs.Name) && val.ContentType == getRulesFilteredArgs.ContentType && val.Priority == getRulesFilteredArgs.Priority)), Times.Once()); + .Verify(x => x.GetRulesByAsync(It.Is( + (val) => string.Equals(val.Name, getRulesFilteredArgs.Name) && val.Ruleset == getRulesFilteredArgs.Ruleset && val.Priority == getRulesFilteredArgs.Priority)), Times.Once()); } [Fact] public async Task UpdateRuleAsync_NoMiddlewares_CallsRulesDataSource() { // Arrange - Rule rule = CreateRule(); + var rule = CreateRule(); - UpdateRuleArgs updateRuleArgs = new() + UpdateRuleArgs updateRuleArgs = new() { Rule = rule, }; - IEnumerable> rulesSourceMiddlewares - = Enumerable.Empty>(); + var rulesSourceMiddlewares + = Enumerable.Empty(); - IRulesDataSource rulesDataSource = Mock.Of>(); + var rulesDataSource = Mock.Of(); Mock.Get(rulesDataSource) - .Setup(x => x.UpdateRuleAsync(It.Is>((val, _) => object.ReferenceEquals(val, rule)))) + .Setup(x => x.UpdateRuleAsync(It.Is((val, _) => object.ReferenceEquals(val, rule)))) .Returns(Task.CompletedTask); - RulesSource rulesSource = new(rulesDataSource, rulesSourceMiddlewares); + RulesSource rulesSource = new(rulesDataSource, rulesSourceMiddlewares); // Act - await rulesSource.UpdateRuleAsync(updateRuleArgs).ConfigureAwait(false); + await rulesSource.UpdateRuleAsync(updateRuleArgs); // Assert Mock.Get(rulesDataSource) - .Verify(x => x.UpdateRuleAsync(It.Is>((val, _) => object.ReferenceEquals(val, rule))), Times.Once()); + .Verify(x => x.UpdateRuleAsync(It.Is((val, _) => object.ReferenceEquals(val, rule))), Times.Once()); } [Fact] public async Task UpdateRuleAsync_OneMiddleware_CallsMiddlewareAndRulesDataSourceAfter() { // Arrange - Rule rule = CreateRule(); + var rule = CreateRule(); - UpdateRuleArgs updateRuleArgs = new() + UpdateRuleArgs updateRuleArgs = new() { Rule = rule, }; - StubRulesSourceMiddleware middleware1 = new(nameof(middleware1), new List()); + StubRulesSourceMiddleware middleware1 = new(nameof(middleware1), new List()); - IEnumerable> rulesSourceMiddlewares = new[] { middleware1 }; + IEnumerable rulesSourceMiddlewares = new[] { middleware1 }; - IRulesDataSource rulesDataSource = Mock.Of>(); + var rulesDataSource = Mock.Of(); Mock.Get(rulesDataSource) - .Setup(x => x.UpdateRuleAsync(It.Is>((val, _) => object.ReferenceEquals(val, rule)))) + .Setup(x => x.UpdateRuleAsync(It.Is((val, _) => object.ReferenceEquals(val, rule)))) .Returns(Task.CompletedTask); - RulesSource rulesSource = new(rulesDataSource, rulesSourceMiddlewares); + RulesSource rulesSource = new(rulesDataSource, rulesSourceMiddlewares); // Act - await rulesSource.UpdateRuleAsync(updateRuleArgs).ConfigureAwait(false); + await rulesSource.UpdateRuleAsync(updateRuleArgs); // Assert middleware1.UpdateRulesCalls.Should().Be(1); Mock.Get(rulesDataSource) - .Verify(x => x.UpdateRuleAsync(It.Is>((val, _) => object.ReferenceEquals(val, rule))), Times.Once()); + .Verify(x => x.UpdateRuleAsync(It.Is((val, _) => object.ReferenceEquals(val, rule))), Times.Once()); } [Fact] public async Task UpdateRuleAsync_TwoMiddlewares_CallsFirstMiddlewareSecondMiddlewareAfterAndRulesDataSourceByLast() { // Arrange - Rule rule = CreateRule(); + var rule = CreateRule(); - UpdateRuleArgs updateRuleArgs = new() + UpdateRuleArgs updateRuleArgs = new() { Rule = rule, }; List middlewareMessages = new(); - StubRulesSourceMiddleware middleware1 = new(nameof(middleware1), middlewareMessages); - StubRulesSourceMiddleware middleware2 = new(nameof(middleware2), middlewareMessages); + StubRulesSourceMiddleware middleware1 = new(nameof(middleware1), middlewareMessages); + StubRulesSourceMiddleware middleware2 = new(nameof(middleware2), middlewareMessages); - IEnumerable> rulesSourceMiddlewares = new[] { middleware1, middleware2 }; + IEnumerable rulesSourceMiddlewares = new[] { middleware1, middleware2 }; - IRulesDataSource rulesDataSource = Mock.Of>(); + var rulesDataSource = Mock.Of(); Mock.Get(rulesDataSource) - .Setup(x => x.UpdateRuleAsync(It.Is>((val, _) => object.ReferenceEquals(val, rule)))) + .Setup(x => x.UpdateRuleAsync(It.Is((val, _) => object.ReferenceEquals(val, rule)))) .Returns(Task.CompletedTask); - RulesSource rulesSource = new(rulesDataSource, rulesSourceMiddlewares); + RulesSource rulesSource = new(rulesDataSource, rulesSourceMiddlewares); // Act - await rulesSource.UpdateRuleAsync(updateRuleArgs).ConfigureAwait(false); + await rulesSource.UpdateRuleAsync(updateRuleArgs); // Assert middleware1.UpdateRulesCalls.Should().Be(1); middleware2.UpdateRulesCalls.Should().Be(1); middlewareMessages.Should().ContainInOrder("Enter middleware1.", "Enter middleware2.", "Exit middleware2.", "Exit middleware1."); Mock.Get(rulesDataSource) - .Verify(x => x.UpdateRuleAsync(It.Is>((val, _) => object.ReferenceEquals(val, rule))), Times.Once()); + .Verify(x => x.UpdateRuleAsync(It.Is((val, _) => object.ReferenceEquals(val, rule))), Times.Once()); } - private static GetRulesArgs CreateGetRulesArgs() => new() + private static GetRulesArgs CreateGetRulesArgs() => new() { - ContentType = ContentType.Type1, + ContentType = RulesetNames.Type1.ToString(), DateBegin = DateTime.Parse("2022-01-01Z"), DateEnd = DateTime.Parse("2023-01-01Z"), }; - private static GetRulesFilteredArgs CreateGetRulesFilteredArgs() => new() + private static GetRulesFilteredArgs CreateGetRulesFilteredArgs() => new() { - ContentType = ContentType.Type1, + Ruleset = RulesetNames.Type1.ToString(), Name = "test", Priority = 1, }; - private static Rule CreateRule() => - RuleBuilder.NewRule() - .WithName("Test rule") - .WithDateBegin(DateTime.Parse("2022-11-27Z")) - .WithContent(ContentType.Type1, "test") + private static Rule CreateRule() => + Rule.Create("Test rule") + .InRuleset(RulesetNames.Type1.ToString()) + .SetContent("test") + .Since(DateTime.Parse("2022-11-27Z")) .Build() .Rule; } diff --git a/tests/Rules.Framework.Tests/Source/StubRulesSourceMiddleware.cs b/tests/Rules.Framework.Tests/Source/StubRulesSourceMiddleware.cs index d2e7037c..9e58c1fd 100644 --- a/tests/Rules.Framework.Tests/Source/StubRulesSourceMiddleware.cs +++ b/tests/Rules.Framework.Tests/Source/StubRulesSourceMiddleware.cs @@ -1,32 +1,30 @@ namespace Rules.Framework.Tests.Source { - using Rules.Framework.Core; - using Rules.Framework.Source; - using System; using System.Collections.Generic; - using System.Linq; - using System.Text; using System.Threading.Tasks; + using Rules.Framework.Source; - internal class StubRulesSourceMiddleware : IRulesSourceMiddleware + internal class StubRulesSourceMiddleware : IRulesSourceMiddleware { private readonly List middlewareMessages; - public int AddRuleCalls { get; private set; } - public int GetRulesCalls { get; private set; } - public int GetRulesFilteredCalls { get; private set; } - public int UpdateRulesCalls { get; private set; } - public string Name { get; } - public StubRulesSourceMiddleware(string name, List middlewareMessages) { this.Name = name; this.middlewareMessages = middlewareMessages; } + public int AddRuleCalls { get; private set; } + public int CreateRulesetCalls { get; private set; } + public int GetRulesCalls { get; private set; } + public int GetRulesetsCalls { get; private set; } + public int GetRulesFilteredCalls { get; private set; } + public string Name { get; } + public int UpdateRulesCalls { get; private set; } + public async Task HandleAddRuleAsync( - AddRuleArgs args, - AddRuleDelegate next) + AddRuleArgs args, + AddRuleDelegate next) { this.AddRuleCalls++; this.middlewareMessages.Add($"Enter {this.Name}."); @@ -34,9 +32,19 @@ public async Task HandleAddRuleAsync( this.middlewareMessages.Add($"Exit {this.Name}."); } - public async Task>> HandleGetRulesAsync( - GetRulesArgs args, - GetRulesDelegate next) + public async Task HandleCreateRulesetAsync( + CreateRulesetArgs args, + CreateRulesetDelegate next) + { + this.CreateRulesetCalls++; + this.middlewareMessages.Add($"Enter {this.Name}."); + await next.Invoke(args).ConfigureAwait(false); + this.middlewareMessages.Add($"Exit {this.Name}."); + } + + public async Task> HandleGetRulesAsync( + GetRulesArgs args, + GetRulesDelegate next) { this.GetRulesCalls++; this.middlewareMessages.Add($"Enter {this.Name}."); @@ -45,9 +53,18 @@ public async Task>> HandleGetRule return rules; } - public async Task>> HandleGetRulesFilteredAsync( - GetRulesFilteredArgs args, - GetRulesFilteredDelegate next) + public async Task> HandleGetRulesetsAsync(GetRulesetsArgs args, GetRulesetsDelegate next) + { + this.GetRulesetsCalls++; + this.middlewareMessages.Add($"Enter {this.Name}."); + var contentTypes = await next.Invoke(args).ConfigureAwait(false); + this.middlewareMessages.Add($"Exit {this.Name}."); + return contentTypes; + } + + public async Task> HandleGetRulesFilteredAsync( + GetRulesFilteredArgs args, + GetRulesFilteredDelegate next) { this.GetRulesFilteredCalls++; this.middlewareMessages.Add($"Enter {this.Name}."); @@ -57,8 +74,8 @@ public async Task>> HandleGetRule } public async Task HandleUpdateRuleAsync( - UpdateRuleArgs args, - UpdateRuleDelegate next) + UpdateRuleArgs args, + UpdateRuleDelegate next) { this.UpdateRulesCalls++; this.middlewareMessages.Add($"Enter {this.Name}."); @@ -66,4 +83,4 @@ public async Task HandleUpdateRuleAsync( this.middlewareMessages.Add($"Exit {this.Name}."); } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/TestStubs/ContentTypeClass.cs b/tests/Rules.Framework.Tests/TestStubs/ConditionClass.cs similarity index 78% rename from tests/Rules.Framework.Tests/TestStubs/ContentTypeClass.cs rename to tests/Rules.Framework.Tests/TestStubs/ConditionClass.cs index b3545c93..a95fb206 100644 --- a/tests/Rules.Framework.Tests/TestStubs/ContentTypeClass.cs +++ b/tests/Rules.Framework.Tests/TestStubs/ConditionClass.cs @@ -1,6 +1,6 @@ namespace Rules.Framework.Tests.Stubs { - internal class ContentTypeClass + internal class ConditionClass { public int Id { get; set; } diff --git a/tests/Rules.Framework.Tests/TestStubs/ConditionType.cs b/tests/Rules.Framework.Tests/TestStubs/ConditionNames.cs similarity index 84% rename from tests/Rules.Framework.Tests/TestStubs/ConditionType.cs rename to tests/Rules.Framework.Tests/TestStubs/ConditionNames.cs index 7d4cbcb7..eab0a6b4 100644 --- a/tests/Rules.Framework.Tests/TestStubs/ConditionType.cs +++ b/tests/Rules.Framework.Tests/TestStubs/ConditionNames.cs @@ -1,6 +1,6 @@ namespace Rules.Framework.Tests.Stubs { - internal enum ConditionType + internal enum ConditionNames { IsoCountryCode = 1, diff --git a/tests/Rules.Framework.Tests/TestStubs/EmptyContentType.cs b/tests/Rules.Framework.Tests/TestStubs/EmptyRulesetNames.cs similarity index 59% rename from tests/Rules.Framework.Tests/TestStubs/EmptyContentType.cs rename to tests/Rules.Framework.Tests/TestStubs/EmptyRulesetNames.cs index 680b9ee7..5ffc6db5 100644 --- a/tests/Rules.Framework.Tests/TestStubs/EmptyContentType.cs +++ b/tests/Rules.Framework.Tests/TestStubs/EmptyRulesetNames.cs @@ -1,6 +1,6 @@ namespace Rules.Framework.Tests.Stubs { - internal enum EmptyContentType + internal enum EmptyRulesetNames { } } \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/TestStubs/ConditionTypeClass.cs b/tests/Rules.Framework.Tests/TestStubs/RulesetClass.cs similarity index 77% rename from tests/Rules.Framework.Tests/TestStubs/ConditionTypeClass.cs rename to tests/Rules.Framework.Tests/TestStubs/RulesetClass.cs index ca1047fe..6a97e090 100644 --- a/tests/Rules.Framework.Tests/TestStubs/ConditionTypeClass.cs +++ b/tests/Rules.Framework.Tests/TestStubs/RulesetClass.cs @@ -1,6 +1,6 @@ namespace Rules.Framework.Tests.Stubs { - internal class ConditionTypeClass + internal class RulesetClass { public int Id { get; set; } diff --git a/tests/Rules.Framework.Tests/TestStubs/ContentType.cs b/tests/Rules.Framework.Tests/TestStubs/RulesetNames.cs similarity index 74% rename from tests/Rules.Framework.Tests/TestStubs/ContentType.cs rename to tests/Rules.Framework.Tests/TestStubs/RulesetNames.cs index 53a00b03..ee4a8481 100644 --- a/tests/Rules.Framework.Tests/TestStubs/ContentType.cs +++ b/tests/Rules.Framework.Tests/TestStubs/RulesetNames.cs @@ -1,6 +1,6 @@ namespace Rules.Framework.Tests.Stubs { - internal enum ContentType + internal enum RulesetNames { Type1 = 1, diff --git a/tests/Rules.Framework.Tests/Validation/SearchArgsValidatorTests.cs b/tests/Rules.Framework.Tests/Validation/SearchArgsValidatorTests.cs index 44d74244..45c1d572 100644 --- a/tests/Rules.Framework.Tests/Validation/SearchArgsValidatorTests.cs +++ b/tests/Rules.Framework.Tests/Validation/SearchArgsValidatorTests.cs @@ -1,9 +1,9 @@ namespace Rules.Framework.Tests.Validation { using System; + using System.Collections.Generic; using System.Linq; using FluentAssertions; - using FluentValidation.Results; using Rules.Framework.Tests.Stubs; using Rules.Framework.Validation; using Xunit; @@ -14,86 +14,63 @@ public class SearchArgsValidatorTests public void Validate_GivenConditionWithTypeAsAsEnumTypeAndUndefinedValue_ReturnsFailedValidation() { // Arrange - SearchArgs searchArgs = new SearchArgs(ContentType.Type1, DateTime.MinValue, DateTime.MaxValue) + var searchArgs = new SearchArgs(RulesetNames.Type1, DateTime.MinValue, DateTime.MaxValue) { - Conditions = new[] + Conditions = new Dictionary { - new Condition(0, 1) - } + { 0, 1 }, + }, }; - SearchArgsValidator validator = new SearchArgsValidator(); + var validator = new SearchArgsValidator(); // Act - ValidationResult validationResult = validator.Validate(searchArgs); + var validationResult = validator.Validate(searchArgs); // Assert validationResult.IsValid.Should().BeFalse(); validationResult.Errors.Should().HaveCount(1); - validationResult.Errors.Should().Match(c => c.Any(vf => vf.PropertyName == $"{nameof(searchArgs.Conditions)}[0].Type")); + validationResult.Errors.Should().Match(c => c.Any(vf => vf.PropertyName == $"{nameof(searchArgs.Conditions)}[0].Key")); } [Fact] public void Validate_GivenConditionWithTypeAsClassTypeAndNotNullValue_ReturnsSuccessValidation() { // Arrange - SearchArgs searchArgs = new SearchArgs(ContentType.Type1, DateTime.MinValue, DateTime.MaxValue) + var searchArgs = new SearchArgs(RulesetNames.Type1, DateTime.MinValue, DateTime.MaxValue) { - Conditions = new[] + Conditions = new Dictionary { - new Condition(new ConditionTypeClass{Id = 1, Name = "Sample Condition Type" }, 1) - } + { new ConditionClass { Id = 1, Name = "Sample Condition Type" }, 1 }, + }, }; - SearchArgsValidator validator = new SearchArgsValidator(); + var validator = new SearchArgsValidator(); // Act - ValidationResult validationResult = validator.Validate(searchArgs); + var validationResult = validator.Validate(searchArgs); // Assert validationResult.IsValid.Should().BeTrue(); validationResult.Errors.Should().BeEmpty(); } - [Fact] - public void Validate_GivenConditionWithTypeAsClassTypeAndNullValue_ReturnsFailedValidation() - { - // Arrange - SearchArgs searchArgs = new SearchArgs(ContentType.Type1, DateTime.MinValue, DateTime.MaxValue) - { - Conditions = new[] - { - new Condition(null, 1) - } - }; - - SearchArgsValidator validator = new SearchArgsValidator(); - - // Act - ValidationResult validationResult = validator.Validate(searchArgs); - - // Assert - validationResult.IsValid.Should().BeFalse(); - validationResult.Errors.Should().HaveCount(1); - validationResult.Errors.Should().Match(c => c.Any(vf => vf.PropertyName == $"{nameof(searchArgs.Conditions)}[0].Type")); - } - [Fact] public void Validate_GivenConditionWithTypeAsEnumTypeAndDefinedValue_ReturnsSuccessValidation() { // Arrange - SearchArgs searchArgs = new SearchArgs(ContentType.Type1, DateTime.MinValue, DateTime.MaxValue) + var searchArgs = new SearchArgs(RulesetNames.Type1, DateTime.MinValue, DateTime.MaxValue) { - Conditions = new[] + Conditions = new Dictionary { - new Condition(ConditionType.IsoCountryCode,"PT") - } + { ConditionNames.IsoCountryCode, "PT" }, + }, }; - SearchArgsValidator validator = new SearchArgsValidator(); + var validator = new SearchArgsValidator(); // Act - ValidationResult validationResult = validator.Validate(searchArgs); + var validationResult = validator.Validate(searchArgs); // Assert validationResult.IsValid.Should().BeTrue(); @@ -104,17 +81,17 @@ public void Validate_GivenConditionWithTypeAsEnumTypeAndDefinedValue_ReturnsSucc public void Validate_GivenContentTypeAsClassTypeAndNotNullValue_ReturnsSuccessValidation() { // Arrange - ContentTypeClass contentType = new ContentTypeClass + var contentType = new RulesetClass { Id = 1, Name = "Sample" }; - SearchArgs searchArgs = new SearchArgs(contentType, DateTime.MinValue, DateTime.MaxValue); + var searchArgs = new SearchArgs(contentType, DateTime.MinValue, DateTime.MaxValue); - SearchArgsValidator validator = new SearchArgsValidator(); + var validator = new SearchArgsValidator(); // Act - ValidationResult validationResult = validator.Validate(searchArgs); + var validationResult = validator.Validate(searchArgs); // Assert validationResult.IsValid.Should().BeTrue(); @@ -125,29 +102,29 @@ public void Validate_GivenContentTypeAsClassTypeAndNotNullValue_ReturnsSuccessVa public void Validate_GivenContentTypeAsClassTypeAndNullValue_ReturnsFailedValidation() { // Arrange - SearchArgs searchArgs = new SearchArgs(null, DateTime.MinValue, DateTime.MaxValue); + var searchArgs = new SearchArgs(null, DateTime.MinValue, DateTime.MaxValue); - SearchArgsValidator validator = new SearchArgsValidator(); + var validator = new SearchArgsValidator(); // Act - ValidationResult validationResult = validator.Validate(searchArgs); + var validationResult = validator.Validate(searchArgs); // Assert validationResult.IsValid.Should().BeFalse(); validationResult.Errors.Should().HaveCount(1); - validationResult.Errors.Should().Match(c => c.Any(vf => vf.PropertyName == nameof(searchArgs.ContentType))); + validationResult.Errors.Should().Match(c => c.Any(vf => vf.PropertyName == nameof(searchArgs.Ruleset))); } [Fact] public void Validate_GivenContentTypeAsEnumTypeAndDefinedValue_ReturnsSuccessValidation() { // Arrange - SearchArgs searchArgs = new SearchArgs(ContentType.Type1, DateTime.MinValue, DateTime.MaxValue); + var searchArgs = new SearchArgs(RulesetNames.Type1, DateTime.MinValue, DateTime.MaxValue); - SearchArgsValidator validator = new SearchArgsValidator(); + var validator = new SearchArgsValidator(); // Act - ValidationResult validationResult = validator.Validate(searchArgs); + var validationResult = validator.Validate(searchArgs); // Assert validationResult.IsValid.Should().BeTrue(); @@ -158,29 +135,29 @@ public void Validate_GivenContentTypeAsEnumTypeAndDefinedValue_ReturnsSuccessVal public void Validate_GivenContentTypeAsEnumTypeAndUndefinedValue_ReturnsFailedValidation() { // Arrange - SearchArgs searchArgs = new SearchArgs(0, DateTime.MinValue, DateTime.MaxValue); + var searchArgs = new SearchArgs(0, DateTime.MinValue, DateTime.MaxValue); - SearchArgsValidator validator = new SearchArgsValidator(); + var validator = new SearchArgsValidator(); // Act - ValidationResult validationResult = validator.Validate(searchArgs); + var validationResult = validator.Validate(searchArgs); // Assert validationResult.IsValid.Should().BeFalse(); validationResult.Errors.Should().HaveCount(1); - validationResult.Errors.Should().Match(c => c.Any(vf => vf.PropertyName == nameof(searchArgs.ContentType))); + validationResult.Errors.Should().Match(c => c.Any(vf => vf.PropertyName == nameof(searchArgs.Ruleset))); } [Fact] public void Validate_GivenDateEndLesserThanDateEnd_ReturnsFailedValidation() { // Arrange - SearchArgs searchArgs = new SearchArgs(ContentType.Type1, DateTime.Parse("2021-03-01Z"), DateTime.Parse("2021-02-01Z")); + var searchArgs = new SearchArgs(RulesetNames.Type1, DateTime.Parse("2021-03-01Z"), DateTime.Parse("2021-02-01Z")); - SearchArgsValidator validator = new SearchArgsValidator(); + var validator = new SearchArgsValidator(); // Act - ValidationResult validationResult = validator.Validate(searchArgs); + var validationResult = validator.Validate(searchArgs); // Assert validationResult.IsValid.Should().BeFalse(); diff --git a/tests/Rules.Framework.WebUI.Tests/Extensions/RuleDtoExtensionsTests.cs b/tests/Rules.Framework.WebUI.Tests/Extensions/RuleDtoExtensionsTests.cs deleted file mode 100644 index ef9c4343..00000000 --- a/tests/Rules.Framework.WebUI.Tests/Extensions/RuleDtoExtensionsTests.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace Rules.Framework.WebUI.Tests.Extensions -{ - using FluentAssertions; - using Rules.Framework.Generics; - using Rules.Framework.WebUI.Dto; - using Rules.Framework.WebUI.Extensions; - using Xunit; - - public class RuleDtoExtensionsTests - { - private readonly IRuleStatusDtoAnalyzer ruleStatusDtoAnalyzer; - - public RuleDtoExtensionsTests() - { - this.ruleStatusDtoAnalyzer = new RuleStatusDtoAnalyzer(); - } - - [Fact] - public void RuleDtoExtensions_ToRuleDto_Success() - { - // Arrange - var genericRule = new GenericRule(); - var contentType = "contentType"; - // Act - var ruleDto = genericRule.ToRuleDto(contentType, this.ruleStatusDtoAnalyzer); - - // Assert - ruleDto.Should().NotBeNull(); - ruleDto.Should().BeOfType(); - } - } -} diff --git a/tests/Rules.Framework.WebUI.Tests/Handlers/GetConfigurationsHandlerTests.cs b/tests/Rules.Framework.WebUI.Tests/Handlers/GetConfigurationsHandlerTests.cs deleted file mode 100644 index 9d6a9772..00000000 --- a/tests/Rules.Framework.WebUI.Tests/Handlers/GetConfigurationsHandlerTests.cs +++ /dev/null @@ -1,64 +0,0 @@ -namespace Rules.Framework.WebUI.Tests.Handlers -{ - using System.IO; - using System.Net; - using System.Threading.Tasks; - using FluentAssertions; - using Microsoft.AspNetCore.Http; - using Moq; - using Rules.Framework.Generics; - using Rules.Framework.WebUI.Handlers; - using Rules.Framework.WebUI.Tests.Utilities; - using Xunit; - - public class GetConfigurationsHandlerTests - { - private readonly GetConfigurationsHandler handler; - private readonly Mock rulesEngine; - - public GetConfigurationsHandlerTests() - { - this.rulesEngine = new Mock(); - this.handler = new GetConfigurationsHandler(rulesEngine.Object, new WebUIOptions()); - } - - [Theory] - [InlineData("POST", "/rules/api/v1/configurations", false)] - [InlineData("GET", "/rules/api/v1/contentTypes", false)] - [InlineData("GET", "/rules/api/v1/configurations", true)] - public async Task HandleRequestAsync_Validation(string httpMethod, string resourcePath, - bool expectedResult) - { - //Arrange - var httpContext = HttpContextHelper.CreateHttpContext(httpMethod, resourcePath); - RequestDelegate next = (HttpContext _) => Task.CompletedTask; - - //Act - var result = await this.handler - .HandleAsync(httpContext.Request, httpContext.Response, next) - .ConfigureAwait(false); - - //Assert - result.Should().Be(expectedResult); - if (expectedResult) - { - httpContext.Response.Should().NotBeNull(); - httpContext.Response.StatusCode.Should().Be((int)HttpStatusCode.OK); - httpContext.Response.ContentType.Should().Be("application/json"); - string body = string.Empty; - using (var reader = new StreamReader(httpContext.Response.Body)) - { - httpContext.Response.Body.Seek(0, SeekOrigin.Begin); - body = await reader.ReadToEndAsync().ConfigureAwait(false); - } - body.Should().NotBeNullOrWhiteSpace(); - httpContext.Response.ContentLength.Should().Be(body.Length); - this.rulesEngine.Verify(s => s.GetPriorityCriteria(), Times.Once); - } - else - { - this.rulesEngine.Verify(s => s.GetPriorityCriteria(), Times.Never); - } - } - } -} \ No newline at end of file diff --git a/tests/Rules.Framework.WebUI.Tests/Handlers/GetContentTypeHandlerTests.cs b/tests/Rules.Framework.WebUI.Tests/Handlers/GetContentTypeHandlerTests.cs deleted file mode 100644 index 4b2a6fe0..00000000 --- a/tests/Rules.Framework.WebUI.Tests/Handlers/GetContentTypeHandlerTests.cs +++ /dev/null @@ -1,65 +0,0 @@ -namespace Rules.Framework.WebUI.Tests.Handlers -{ - using System.IO; - using System.Net; - using System.Threading.Tasks; - using FluentAssertions; - using Microsoft.AspNetCore.Http; - using Moq; - using Rules.Framework.Generics; - using Rules.Framework.WebUI.Dto; - using Rules.Framework.WebUI.Handlers; - using Rules.Framework.WebUI.Tests.Utilities; - using Xunit; - - public class GetContentTypeHandlerTests - { - private readonly GetContentTypeHandler handler; - private readonly Mock rulesEngine; - - public GetContentTypeHandlerTests() - { - var ruleStatusDtoAnalyzer = new RuleStatusDtoAnalyzer(); - this.rulesEngine = new Mock(); - this.handler = new GetContentTypeHandler(rulesEngine.Object, ruleStatusDtoAnalyzer, new WebUIOptions()); - } - - [Theory] - [InlineData("POST", "/rules/api/v1/contentTypes", false)] - [InlineData("GET", "/rules/api/v1/rules", false)] - [InlineData("GET", "/rules/api/v1/contentTypes", true)] - public async Task HandleRequestAsync_Validation(string httpMethod, string resourcePath, - bool expectedResult) - { - //Arrange - var httpContext = HttpContextHelper.CreateHttpContext(httpMethod, resourcePath); - RequestDelegate next = (HttpContext _) => Task.CompletedTask; - //Act - var result = await this.handler - .HandleAsync(httpContext.Request, httpContext.Response, next) - .ConfigureAwait(false); - - //Assert - result.Should().Be(expectedResult); - if (expectedResult) - { - httpContext.Response.Should().NotBeNull(); - httpContext.Response.StatusCode.Should().Be((int)HttpStatusCode.OK); - httpContext.Response.ContentType.Should().Be("application/json"); - string body = string.Empty; - using (var reader = new StreamReader(httpContext.Response.Body)) - { - httpContext.Response.Body.Seek(0, SeekOrigin.Begin); - body = await reader.ReadToEndAsync(); - } - body.Should().NotBeNullOrWhiteSpace(); - httpContext.Response.ContentLength.Should().Be(body.Length); - this.rulesEngine.Verify(s => s.GetContentTypes(), Times.Once); - } - else - { - this.rulesEngine.Verify(s => s.GetContentTypes(), Times.Never); - } - } - } -} \ No newline at end of file diff --git a/tests/Rules.Framework.WebUI.Tests/Handlers/GetIndexPageHandlerTests.cs b/tests/Rules.Framework.WebUI.Tests/Handlers/GetIndexPageHandlerTests.cs deleted file mode 100644 index 749b1a62..00000000 --- a/tests/Rules.Framework.WebUI.Tests/Handlers/GetIndexPageHandlerTests.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace Rules.Framework.WebUI.Tests.Handlers -{ - using System.Threading.Tasks; - using FluentAssertions; - using Microsoft.AspNetCore.Http; - using Rules.Framework.WebUI.Handlers; - using Rules.Framework.WebUI.Tests.Utilities; - using Xunit; - - public class GetIndexPageHandlerTests - { - private readonly GetIndexPageHandler handler; - - public GetIndexPageHandlerTests() - { - this.handler = new GetIndexPageHandler(new WebUIOptions()); - } - - [Theory] - [InlineData("POST", "/rules/index.html", false)] - [InlineData("GET", "/rules/Rule/List", false)] - [InlineData("GET", "/rules/index.html", true)] - [InlineData("GET", "/rules", true)] - public async Task HandleRequestAsync_Validation(string httpMethod, string resourcePath, - bool expectedResult) - { - //Arrange - var httpContext = HttpContextHelper.CreateHttpContext(httpMethod, resourcePath); - RequestDelegate next = (HttpContext _) => Task.CompletedTask; - - //Act - var result = await this.handler - .HandleAsync(httpContext.Request, httpContext.Response, next) - .ConfigureAwait(false); - - //Assert - result.Should().Be(expectedResult); - } - } -} \ No newline at end of file diff --git a/tests/Rules.Framework.WebUI.Tests/Handlers/GetRulesHandlerTests.cs b/tests/Rules.Framework.WebUI.Tests/Handlers/GetRulesHandlerTests.cs deleted file mode 100644 index 973c1f2b..00000000 --- a/tests/Rules.Framework.WebUI.Tests/Handlers/GetRulesHandlerTests.cs +++ /dev/null @@ -1,100 +0,0 @@ -namespace Rules.Framework.WebUI.Tests.Handlers -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Net; - using System.Threading.Tasks; - using FluentAssertions; - using Microsoft.AspNetCore.Http; - using Moq; - using Rules.Framework.Generics; - using Rules.Framework.WebUI.Dto; - using Rules.Framework.WebUI.Handlers; - using Rules.Framework.WebUI.Tests.Utilities; - using Xunit; - - public class GetRulesHandlerTests - { - private readonly GetRulesHandler handler; - private readonly Mock rulesEngine; - - public GetRulesHandlerTests() - { - this.rulesEngine = new Mock(); - var ruleStatusDtoAnalyzer = new RuleStatusDtoAnalyzer(); - this.handler = new GetRulesHandler(rulesEngine.Object, ruleStatusDtoAnalyzer, new WebUIOptions()); - } - - [Theory] - [InlineData("POST", "/rules/api/v1/rules", false, null)] - [InlineData("GET", "/rules/api/v1/contentTypes", false, null)] - [InlineData("GET", "/rules/api/v1/rules", true, HttpStatusCode.OK)] - [InlineData("GET", "/rules/api/v1/rules", true, HttpStatusCode.InternalServerError)] - public async Task HandleRequestAsync_Validation(string httpMethod, string resourcePath, - bool expectedResult, HttpStatusCode? statusCode) - { - //Arrange - var httpContext = HttpContextHelper.CreateHttpContext(httpMethod, resourcePath); - var genericRule = new List(); - var verifySearchAsync = false; - - if (statusCode == HttpStatusCode.OK || statusCode == HttpStatusCode.InternalServerError) - { - verifySearchAsync = true; - - httpContext.Request.QueryString = new QueryString("?contentType=1"); - - if (statusCode == HttpStatusCode.OK) - { - this.rulesEngine.Setup(d => d.SearchAsync(It.IsAny>())) - .ReturnsAsync(genericRule); - } - - if (statusCode == HttpStatusCode.InternalServerError) - { - this.rulesEngine.Setup(d => d.SearchAsync(It.IsAny>())) - .Throws(new Exception("message", new Exception("inner"))); - } - } - else - { - httpContext.Request.QueryString = new QueryString(); - } - RequestDelegate next = (HttpContext _) => Task.CompletedTask; - - //Act - var result = await this.handler - .HandleAsync(httpContext.Request, httpContext.Response, next) - .ConfigureAwait(false); - - //Assert - result.Should().Be(expectedResult); - if (expectedResult) - { - httpContext.Response.Should().NotBeNull(); - httpContext.Response.StatusCode.Should().Be((int)statusCode); - httpContext.Response.ContentType.Should().Be("application/json"); - string body = string.Empty; - using (var reader = new StreamReader(httpContext.Response.Body)) - { - httpContext.Response.Body.Seek(0, SeekOrigin.Begin); - body = await reader.ReadToEndAsync(); - } - body.Should().NotBeNullOrWhiteSpace(); - httpContext.Response.ContentLength.Should().Be(body.Length); - } - - if (verifySearchAsync) - { - this.rulesEngine - .Verify(s => s.SearchAsync(It.IsAny>()), Times.Once); - } - else - { - this.rulesEngine - .Verify(s => s.SearchAsync(It.IsAny>()), Times.Never); - } - } - } -} diff --git a/tests/Rules.Framework.WebUI.Tests/Rules.Framework.WebUI.Tests.csproj b/tests/Rules.Framework.WebUI.Tests/Rules.Framework.WebUI.Tests.csproj index 407f146e..5b0657fb 100644 --- a/tests/Rules.Framework.WebUI.Tests/Rules.Framework.WebUI.Tests.csproj +++ b/tests/Rules.Framework.WebUI.Tests/Rules.Framework.WebUI.Tests.csproj @@ -1,19 +1,19 @@ - - net8.0 - 9.0 - Full - false - + + net8.0 + 9.0 + Full + false + - - 10.0 - + + 10.0 + - - 10.0 - + + 10.0 + @@ -31,7 +31,7 @@ - - - + + + \ No newline at end of file diff --git a/tests/Rules.Framework.WebUI.Tests/Services/GuidGeneratorTests.cs b/tests/Rules.Framework.WebUI.Tests/Services/GuidGeneratorTests.cs new file mode 100644 index 00000000..16f24223 --- /dev/null +++ b/tests/Rules.Framework.WebUI.Tests/Services/GuidGeneratorTests.cs @@ -0,0 +1,49 @@ +namespace Rules.Framework.WebUI.Tests.Services +{ + using System; + using FluentAssertions; + using Rules.Framework.WebUI.Services; + using Xunit; + + public class GuidGeneratorTests + { + [Theory] + [InlineData("")] + [InlineData(" ")] + public void GenerateFromString_GivenEmptyOrWhiteSpaceSourceString_ThrowsException(string source) + { + // Act + var actual = Assert.Throws(() => GuidGenerator.GenerateFromString(source)); + + // Assert + actual.Should().BeOfType(); + actual.ParamName.Should().Be("source"); + } + + [Fact] + public void GenerateFromString_GivenNullSourceString_ThrowsException() + { + // Act + var actual = Assert.Throws(() => GuidGenerator.GenerateFromString(null)); + + // Assert + actual.Should().BeOfType(); + actual.ParamName.Should().Be("source"); + } + + [Theory] + [InlineData("Test string", "849de4a3-f13d-2e3c-2a77-86f6ecd7e0d1")] + [InlineData("Yet another test string", "1ea74e0c-ac94-db72-ad40-e19920a0fa00")] + public void GenerateFromString_GivenSourceString_ReturnsGuid(string source, string expectedGuid) + { + // Arrange + var expected = Guid.Parse(expectedGuid); + + // Act + var actual = GuidGenerator.GenerateFromString(source); + + // Assert + actual.Should().Be(expected); + } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.WebUI.Tests/Services/RulesEngineInstanceProviderTests.cs b/tests/Rules.Framework.WebUI.Tests/Services/RulesEngineInstanceProviderTests.cs new file mode 100644 index 00000000..38ec4218 --- /dev/null +++ b/tests/Rules.Framework.WebUI.Tests/Services/RulesEngineInstanceProviderTests.cs @@ -0,0 +1,100 @@ +namespace Rules.Framework.WebUI.Tests.Services +{ + using System; + using FluentAssertions; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Rules.Framework.WebUI.Services; + using Xunit; + + public class RulesEngineInstanceProviderTests + { + [Fact] + public void RegisterOneInstanceTest_Success() + { + // Arrange + var name = "Sample engine"; + var instanceId = Guid.Parse("aeccf6a8-7851-cda0-4e2b-802a97707225"); + var rulesEngine = Mock.Of(); + + var serviceProvider = Mock.Of(); + Mock.Get(serviceProvider) + .Setup(x => x.GetService(typeof(IRulesEngine))) + .Returns(rulesEngine); + var rulesEngineInstanceProvider = new RulesEngineInstanceProvider(); + + // Act + rulesEngineInstanceProvider.AddInstance(name, (sp, _) => sp.GetService()); + rulesEngineInstanceProvider.EnumerateInstances(serviceProvider); + var instance = rulesEngineInstanceProvider.GetInstance(instanceId); + var instances = rulesEngineInstanceProvider.GetAllInstances(); + + // Assert + instance.Should().NotBeNull(); + instance.Id.Should().Be(instanceId); + instance.Name.Should().Be(name); + instance.RulesEngine.Should().BeSameAs(rulesEngine); + instances.Should().NotBeNull() + .And.HaveCount(1) + .And.Contain(instance); + } + + [Fact] + public void RegisterTwoInstanceSameNameTest_Failure() + { + // Arrange + var name = "Sample engine"; + + var rulesEngineInstanceProvider = new RulesEngineInstanceProvider(); + + // Act + rulesEngineInstanceProvider.AddInstance(name, (sp, _) => sp.GetService()); + var exception = Assert.Throws( + () => rulesEngineInstanceProvider.AddInstance(name, (sp, _) => sp.GetService())); + + // Assert + exception.Message.Should().Contain(name); + } + + [Fact] + public void RegisterTwoInstanceTest_Success() + { + // Arrange + var name1 = "Sample engine"; + var instanceId1 = Guid.Parse("aeccf6a8-7851-cda0-4e2b-802a97707225"); + var rulesEngine1 = Mock.Of(); + var name2 = "Another sample engine"; + var instanceId2 = Guid.Parse("1c45bfc8-7dfb-f399-adbf-0976e00d3e3e"); + var rulesEngine2 = Mock.Of(); + + var serviceProvider = Mock.Of(); + Mock.Get(serviceProvider) + .SetupSequence(x => x.GetService(typeof(IRulesEngine))) + .Returns(rulesEngine1) + .Returns(rulesEngine2); + var rulesEngineInstanceProvider = new RulesEngineInstanceProvider(); + + // Act + rulesEngineInstanceProvider.AddInstance(name1, (sp, _) => sp.GetService()); + rulesEngineInstanceProvider.AddInstance(name2, (sp, _) => sp.GetService()); + rulesEngineInstanceProvider.EnumerateInstances(serviceProvider); + var instance1 = rulesEngineInstanceProvider.GetInstance(instanceId1); + var instance2 = rulesEngineInstanceProvider.GetInstance(instanceId2); + var instances = rulesEngineInstanceProvider.GetAllInstances(); + + // Assert + instance1.Should().NotBeNull(); + instance1.Id.Should().Be(instanceId1); + instance1.Name.Should().Be(name1); + instance1.RulesEngine.Should().BeSameAs(rulesEngine1); + instance2.Should().NotBeNull(); + instance2.Id.Should().Be(instanceId2); + instance2.Name.Should().Be(name2); + instance2.RulesEngine.Should().BeSameAs(rulesEngine2); + instances.Should().NotBeNull() + .And.HaveCount(2) + .And.Contain(instance1) + .And.Contain(instance2); + } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.WebUI.Tests/Utilities/HttpContextHelper.cs b/tests/Rules.Framework.WebUI.Tests/Utilities/HttpContextHelper.cs deleted file mode 100644 index 9e061426..00000000 --- a/tests/Rules.Framework.WebUI.Tests/Utilities/HttpContextHelper.cs +++ /dev/null @@ -1,69 +0,0 @@ -namespace Rules.Framework.WebUI.Tests.Utilities -{ - using System.IO; - using System.Text; - using System.Threading.Tasks; - using Microsoft.AspNetCore.Http; - using Newtonsoft.Json; - - internal static class HttpContextHelper - { - public static async Task CreateContext(string path, string method, object requestBody = null) - { - var context = new DefaultHttpContext(); - context.Request.Path = path; - context.Request.Method = method; - context.Request.ContentType = "application/json"; - - if (requestBody is not null) - { - var body = JsonConvert.SerializeObject(requestBody, - new JsonSerializerSettings { DateTimeZoneHandling = DateTimeZoneHandling.Utc }); - - using (var writer = new StreamWriter(context.Request.Body, Encoding.UTF8)) - { - await writer.WriteAsync(body).ConfigureAwait(false); - } - } - - context.Response.Body = new MemoryStream(); - - return context; - } - - public static HttpContext CreateDefaultContext() - { - return new DefaultHttpContext(); - } - - public static HttpContext CreateHttpContext(string httpMethod, string resourcePath) - { - var context = new DefaultHttpContext(); - - context.Request.Path = resourcePath; - context.Request.Method = httpMethod; - - context.Response.Body = new MemoryStream(); - - return context; - } - - public static async Task ReadResponse(HttpResponse response) - { - //Rewind the stream - response.Body.Seek(0, SeekOrigin.Begin); - - T responseDto; - - using (var reader = new StreamReader(response.Body, Encoding.UTF8)) - { - var requestMessage = await reader.ReadToEndAsync().ConfigureAwait(false); - - responseDto = JsonConvert.DeserializeObject(requestMessage, - new JsonSerializerSettings { DateTimeZoneHandling = DateTimeZoneHandling.Utc }); - } - - return responseDto; - } - } -} \ No newline at end of file diff --git a/tests/Rules.Framework.WebUI.Tests/Utilities/WebUIMiddlewareFactory.cs b/tests/Rules.Framework.WebUI.Tests/Utilities/WebUIMiddlewareFactory.cs deleted file mode 100644 index bc865304..00000000 --- a/tests/Rules.Framework.WebUI.Tests/Utilities/WebUIMiddlewareFactory.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Rules.Framework.WebUI.Tests.Utilities -{ - using System.Collections.Generic; - using System.Threading.Tasks; - using Microsoft.AspNetCore.Hosting; - using Microsoft.Extensions.Logging; - - public static class WebUIMiddlewareFactory - { - internal static WebUIMiddleware Create( - IWebHostEnvironment hostingEnv, - ILoggerFactory loggerFactory, - IEnumerable handlers) - { - return new WebUIMiddleware(loggerFactory: loggerFactory, hostingEnv: hostingEnv, - next: (_) => - { - return Task.CompletedTask; - }, - httpRequestHandlers: handlers, - options: new WebUIOptions()); - } - } -} \ No newline at end of file diff --git a/tests/Rules.Framework.WebUI.Tests/ViewModels/RuleViewModelExtensionsTests.cs b/tests/Rules.Framework.WebUI.Tests/ViewModels/RuleViewModelExtensionsTests.cs new file mode 100644 index 00000000..e12426a0 --- /dev/null +++ b/tests/Rules.Framework.WebUI.Tests/ViewModels/RuleViewModelExtensionsTests.cs @@ -0,0 +1,125 @@ +namespace Rules.Framework.WebUI.Tests.ViewModels +{ + using System; + using FluentAssertions; + using Rules.Framework.WebUI.ViewModels; + using Xunit; + + public class RuleViewModelExtensionsTests + { + [Fact] + public void ToExportRulesModel_GivenRuleViewModel_ReturnsExportRulesModel() + { + // Arrange + var ruleViewModel = new RuleViewModel + { + Active = true, + Content = new object(), + DateBegin = DateTime.Parse("2024-10-01Z"), + DateEnd = DateTime.Parse("2024-10-31Z"), + Name = "Sample name", + Priority = 1, + RootCondition = new ComposedConditionNodeViewModel + { + ChildConditionNodes = new[] + { + new ValueConditionNodeViewModel + { + Condition = "Condition1", + DataType = "String", + LogicalOperator = "Eval", + Operand = "xyz", + Operator = "Equal", + }, + new ValueConditionNodeViewModel + { + Condition = "Condition2", + DataType = "Integer", + LogicalOperator = "Eval", + Operand = "123", + Operator = "Equal", + }, + }, + LogicalOperator = "Or", + }, + Ruleset = "Ruleset1", + }; + + // Act + var actual = ruleViewModel.ToExportRulesModel(); + + // Assert + actual.Should().NotBeNull(); + actual.Active.Should().BeTrue(); + actual.Content.Should().NotBeNull() + .And.BeSameAs(ruleViewModel.Content); + actual.DateBegin.Should().Be(ruleViewModel.DateBegin); + actual.DateEnd.Should().Be(ruleViewModel.DateEnd); + actual.Name.Should().Be(ruleViewModel.Name); + actual.Priority.Should().Be(ruleViewModel.Priority); + actual.RootCondition.Should().BeEquivalentTo(ruleViewModel.RootCondition); + actual.Ruleset.Should().Be(ruleViewModel.Ruleset); + } + + [Fact] + public void ToViewModel_GivenRule_ReturnsRuleViewModel() + { + // Arrange + var rule = Rule.Create("Sample name") + .InRuleset("Ruleset1") + .SetContent(new object()) + .Since(DateTime.Parse("2024-10-01Z")) + .Until(DateTime.Parse("2024-10-31Z")) + .ApplyWhen(b => b + .Or(or => or + .Value("Condition1", Operators.Equal, "xyz") + .Value("Condition2", Operators.Equal, 123) + ) + ) + .Build() + .Rule; + rule.Priority = 1; + var expected = new RuleViewModel + { + Active = true, + Content = new object(), + DateBegin = DateTime.Parse("2024-10-01Z"), + DateEnd = DateTime.Parse("2024-10-31Z"), + Id = Guid.Parse("7247859b-3519-f813-cd7b-2c23723673ae"), + Name = "Sample name", + Priority = 1, + RootCondition = new ComposedConditionNodeViewModel + { + ChildConditionNodes = new[] + { + new ValueConditionNodeViewModel + { + Condition = "Condition1", + DataType = "String", + LogicalOperator = "Eval", + Operand = "xyz", + Operator = "Equal", + }, + new ValueConditionNodeViewModel + { + Condition = "Condition2", + DataType = "Integer", + LogicalOperator = "Eval", + Operand = "123", + Operator = "Equal", + }, + }, + LogicalOperator = "Or", + }, + Ruleset = "Ruleset1", + }; + + // Act + var actual = rule.ToViewModel(); + + // Assert + actual.Should().NotBeNull() + .And.BeEquivalentTo(expected); + } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.WebUI.Tests/WebUIMiddlewareTests.cs b/tests/Rules.Framework.WebUI.Tests/WebUIMiddlewareTests.cs deleted file mode 100644 index cee48598..00000000 --- a/tests/Rules.Framework.WebUI.Tests/WebUIMiddlewareTests.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace Rules.Framework.WebUI.Tests -{ - using System.Collections.Generic; - using System.Threading.Tasks; - using Microsoft.AspNetCore.Hosting; - using Microsoft.AspNetCore.Http; - using Microsoft.Extensions.Logging; - using Moq; - using Rules.Framework.WebUI.Tests.Utilities; - using Xunit; - - public class WebUIMiddlewareTests - { - [Fact] - public async Task InvokeAsync_CallsHttpRequestHandler() - { - var mockHttpRequestHandler = new Mock(); - var mockLoggerFactory = new Mock(); - var mockLogger = new Mock(); - var mockWebHostEnvironment = new Mock(); - - mockLoggerFactory.Setup(d => d.CreateLogger(It.IsAny())).Returns(mockLogger.Object); - var middleware = WebUIMiddlewareFactory.Create(mockWebHostEnvironment.Object, - mockLoggerFactory.Object, - new List { mockHttpRequestHandler.Object }); - - var context = new DefaultHttpContext(); - - // act - await middleware.InvokeAsync(context); - - // assert - mockHttpRequestHandler.Verify(mock => mock.HandleAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once()); - } - } -} \ No newline at end of file diff --git a/tests/Rules.Framework.WebUI.Tests/WebUIOptionsRegistryTests.cs b/tests/Rules.Framework.WebUI.Tests/WebUIOptionsRegistryTests.cs new file mode 100644 index 00000000..f39f6e2f --- /dev/null +++ b/tests/Rules.Framework.WebUI.Tests/WebUIOptionsRegistryTests.cs @@ -0,0 +1,41 @@ +namespace Rules.Framework.WebUI.Tests +{ + using System; + using FluentAssertions; + using Xunit; + + public class WebUIOptionsRegistryTests + { + [Fact] + public void Register_GivenNullOptions_ThrowsArgumentNullException() + { + // Arrange + var webUIOptionsRegistry = new WebUIOptionsRegistry(); + + // Act + var exception = Assert.Throws(() => webUIOptionsRegistry.Register(null)); + + // Assert + exception.ParamName.Should().Be("webUIOptions"); + webUIOptionsRegistry.RegisteredOptions.Should().BeNull(); + } + + [Fact] + public void Register_GivenValidOptions_RegistersOptions() + { + // Arrange + var webUIOptions = new WebUIOptions + { + DocumentTitle = "Title", + }; + var webUIOptionsRegistry = new WebUIOptionsRegistry(); + + // Act + webUIOptionsRegistry.Register(webUIOptions); + + // Assert + webUIOptionsRegistry.RegisteredOptions.Should().NotBeNull() + .And.BeSameAs(webUIOptions); + } + } +} \ No newline at end of file