From 728286c3ad8677cb92ef378ae714cb1b5f2cfea4 Mon Sep 17 00:00:00 2001 From: Florian Bacher Date: Wed, 21 Feb 2024 15:14:22 +0100 Subject: [PATCH] feat: add custom jsonLogic string evaluators (#158) Signed-off-by: Florian Bacher Co-authored-by: Todd Baert --- .../CustomEvaluators/StringEvaluator.cs | 19 +++ .../Resolver/InProcess/JsonEvaluator.cs | 6 + .../StringEvaluatorTest.cs | 132 ++++++++++++++++++ 3 files changed, 157 insertions(+) create mode 100644 src/OpenFeature.Contrib.Providers.Flagd/Resolver/InProcess/CustomEvaluators/StringEvaluator.cs create mode 100644 test/OpenFeature.Contrib.Providers.Flagd.Test/StringEvaluatorTest.cs diff --git a/src/OpenFeature.Contrib.Providers.Flagd/Resolver/InProcess/CustomEvaluators/StringEvaluator.cs b/src/OpenFeature.Contrib.Providers.Flagd/Resolver/InProcess/CustomEvaluators/StringEvaluator.cs new file mode 100644 index 00000000..01c9490d --- /dev/null +++ b/src/OpenFeature.Contrib.Providers.Flagd/Resolver/InProcess/CustomEvaluators/StringEvaluator.cs @@ -0,0 +1,19 @@ +using System; +using JsonLogic.Net; +using Newtonsoft.Json.Linq; + +namespace OpenFeature.Contrib.Providers.Flagd.Resolver.InProcess.CustomEvaluators +{ + internal class StringEvaluator + { + internal object StartsWith(IProcessJsonLogic p, JToken[] args, object data) + { + return p.Apply(args[0], data).ToString().StartsWith(p.Apply(args[1], data).ToString()); + } + + internal object EndsWith(IProcessJsonLogic p, JToken[] args, object data) + { + return p.Apply(args[0], data).ToString().EndsWith(p.Apply(args[1], data).ToString()); + } + } +} \ No newline at end of file diff --git a/src/OpenFeature.Contrib.Providers.Flagd/Resolver/InProcess/JsonEvaluator.cs b/src/OpenFeature.Contrib.Providers.Flagd/Resolver/InProcess/JsonEvaluator.cs index 21f6e155..fe84baec 100644 --- a/src/OpenFeature.Contrib.Providers.Flagd/Resolver/InProcess/JsonEvaluator.cs +++ b/src/OpenFeature.Contrib.Providers.Flagd/Resolver/InProcess/JsonEvaluator.cs @@ -7,6 +7,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OpenFeature.Constant; +using OpenFeature.Contrib.Providers.Flagd.Resolver.InProcess.CustomEvaluators; using OpenFeature.Error; using OpenFeature.Model; @@ -58,6 +59,11 @@ internal class JsonEvaluator internal JsonEvaluator(string selector) { _selector = selector; + + var stringEvaluator = new StringEvaluator(); + + EvaluateOperators.Default.AddOperator("starts_with", stringEvaluator.StartsWith); + EvaluateOperators.Default.AddOperator("ends_with", stringEvaluator.EndsWith); } internal void Sync(FlagConfigurationUpdateType updateType, string flagConfigurations) diff --git a/test/OpenFeature.Contrib.Providers.Flagd.Test/StringEvaluatorTest.cs b/test/OpenFeature.Contrib.Providers.Flagd.Test/StringEvaluatorTest.cs new file mode 100644 index 00000000..24892baa --- /dev/null +++ b/test/OpenFeature.Contrib.Providers.Flagd.Test/StringEvaluatorTest.cs @@ -0,0 +1,132 @@ +using System.Collections.Generic; +using JsonLogic.Net; +using Newtonsoft.Json.Linq; +using OpenFeature.Contrib.Providers.Flagd.Resolver.InProcess.CustomEvaluators; +using Xunit; + +namespace OpenFeature.Contrib.Providers.Flagd.Test +{ + public class StringEvaluatorTest + { + + [Fact] + public void StartsWith() + { + // Arrange + var evaluator = new JsonLogicEvaluator(EvaluateOperators.Default); + var stringEvaluator = new StringEvaluator(); + EvaluateOperators.Default.AddOperator("starts_with", stringEvaluator.StartsWith); + + var targetingString = @"{""starts_with"": [ + { + ""var"": [ + ""color"" + ] + }, + ""yellow"" + ]}"; + + // Parse json into hierarchical structure + var rule = JObject.Parse(targetingString); + + var data = new Dictionary { { "color", "yellowcolor" } }; + + // Act & Assert + var result = evaluator.Apply(rule, data); + Assert.True(result.IsTruthy()); + + data.Clear(); + data.Add("color", "blue"); + + result = evaluator.Apply(rule, data); + Assert.False(result.IsTruthy()); + } + + [Fact] + public void EndsWith() + { + // Arrange + var evaluator = new JsonLogicEvaluator(EvaluateOperators.Default); + var stringEvaluator = new StringEvaluator(); + EvaluateOperators.Default.AddOperator("ends_with", stringEvaluator.EndsWith); + + var targetingString = @"{""ends_with"": [ + { + ""var"": [ + ""color"" + ] + }, + ""purple"" + ]}"; + + // Parse json into hierarchical structure + var rule = JObject.Parse(targetingString); + + var data = new Dictionary { { "color", "deep-purple" } }; + + // Act & Assert + var result = evaluator.Apply(rule, data); + Assert.True(result.IsTruthy()); + + data.Clear(); + data.Add("color", "purple-nightmare"); + + result = evaluator.Apply(rule, data); + Assert.False(result.IsTruthy()); + } + + [Fact] + public void NonStringTypeInRule() + { + // Arrange + var evaluator = new JsonLogicEvaluator(EvaluateOperators.Default); + var stringEvaluator = new StringEvaluator(); + EvaluateOperators.Default.AddOperator("ends_with", stringEvaluator.EndsWith); + + var targetingString = @"{""ends_with"": [ + { + ""var"": [ + ""color"" + ] + }, + 1 + ]}"; + + // Parse json into hierarchical structure + var rule = JObject.Parse(targetingString); + + var data = new Dictionary { { "color", "deep-purple" } }; + + // Act & Assert + var result = evaluator.Apply(rule, data); + Assert.False(result.IsTruthy()); + } + + [Fact] + public void NonStringTypeInData() + { + // Arrange + var evaluator = new JsonLogicEvaluator(EvaluateOperators.Default); + var stringEvaluator = new StringEvaluator(); + EvaluateOperators.Default.AddOperator("ends_with", stringEvaluator.EndsWith); + + var targetingString = @"{""ends_with"": [ + { + ""var"": [ + ""color"" + ] + }, + ""green"" + ]}"; + + // Parse json into hierarchical structure + var rule = JObject.Parse(targetingString); + + var data = new Dictionary { { "color", 5 } }; + + // Act & Assert + var result = evaluator.Apply(rule, data); + Assert.False(result.IsTruthy()); + } + } +} \ No newline at end of file