From df8beca877f47d2a1f9b48cafeb98c7932da5a76 Mon Sep 17 00:00:00 2001 From: Ty Potter Date: Thu, 23 May 2024 12:59:02 -0600 Subject: [PATCH 01/11] Support the operator --- dot-net-sdk/dto/OperatorType.cs | 3 +- dot-net-sdk/validators/RuleValidator.cs | 10 ++- eppo-sdk-test/validators/RuleValidatorTest.cs | 76 +++++++++++++++++++ 3 files changed, 86 insertions(+), 3 deletions(-) diff --git a/dot-net-sdk/dto/OperatorType.cs b/dot-net-sdk/dto/OperatorType.cs index 6cf3e43..f7628aa 100644 --- a/dot-net-sdk/dto/OperatorType.cs +++ b/dot-net-sdk/dto/OperatorType.cs @@ -12,5 +12,6 @@ public enum OperatorType LTE, LT, ONE_OF, - NOT_ONE_OF + NOT_ONE_OF, + IS_NULL } diff --git a/dot-net-sdk/validators/RuleValidator.cs b/dot-net-sdk/validators/RuleValidator.cs index 02b6938..7da240b 100644 --- a/dot-net-sdk/validators/RuleValidator.cs +++ b/dot-net-sdk/validators/RuleValidator.cs @@ -2,6 +2,8 @@ using eppo_sdk.dto; using static eppo_sdk.dto.OperatorType; using NuGet.Versioning; +using NLog.LayoutRenderers; +using System.Reflection.Metadata; namespace eppo_sdk.validators; @@ -28,8 +30,12 @@ private static bool EvaluateCondition(SubjectAttributes subjectAttributes, Condi { try { - if (subjectAttributes.ContainsKey(condition.attribute) && - subjectAttributes.TryGetValue(condition.attribute, out EppoValue outVal)) + // Operators other than `IS_NULL` need to assume non-null + if (condition.operatorType == OperatorType.IS_NULL) { + bool isNull = !subjectAttributes.TryGetValue(condition.attribute, out EppoValue outVal) || outVal.isNull(); + return condition.value.BoolValue() == isNull; + } + else if (subjectAttributes.TryGetValue(condition.attribute, out EppoValue outVal)) { var value = outVal!; // Assuming non-null for simplicity, handle nulls as necessary diff --git a/eppo-sdk-test/validators/RuleValidatorTest.cs b/eppo-sdk-test/validators/RuleValidatorTest.cs index 4103e21..8c3686a 100644 --- a/eppo-sdk-test/validators/RuleValidatorTest.cs +++ b/eppo-sdk-test/validators/RuleValidatorTest.cs @@ -1,5 +1,6 @@ using eppo_sdk.dto; using eppo_sdk.validators; +using Microsoft.AspNetCore.Builder; namespace eppo_sdk_test.validators; @@ -150,6 +151,81 @@ public void ShouldNotMatchAnyRuleWithNotOneOfRuleNotPassed() Assert.That(RuleValidator.FindMatchingRule(subjectAttributes, rules), Is.Null); } + [Test] + public void ShouldMatchRuleIsNullTrue() + { + var rules = new List(); + var rule = CreateRule(new List()); + AddIsNullCondition(rule, true); + rules.Add(rule); + + var subjectAttributes = new SubjectAttributes { { "isnull", new EppoValue() } }; + + Assert.That(rule, Is.EqualTo(RuleValidator.FindMatchingRule(subjectAttributes, rules))); + } + + + [Test] + public void ShouldMatchRuleIsNullNoAttribute() + { + var rules = new List(); + var rule = CreateRule(new List()); + AddIsNullCondition(rule, true); + rules.Add(rule); + + var subjectAttributes = new SubjectAttributes { }; + + Assert.That(rule, Is.EqualTo(RuleValidator.FindMatchingRule(subjectAttributes, rules))); + } + + [Test] + public void ShouldMatchRuleIsNullFalse() + { + var rules = new List(); + var rule = CreateRule(new List()); + AddIsNullCondition(rule, false); + rules.Add(rule); + + var subjectAttributes = new SubjectAttributes { { "isnull", new EppoValue("not null", EppoValueType.STRING) } }; + + Assert.That(rule, Is.EqualTo(RuleValidator.FindMatchingRule(subjectAttributes, rules))); + } + + [Test] + public void ShouldNotMatchRuleIsNullTrue() + { + var rules = new List(); + var rule = CreateRule(new List()); + AddIsNullCondition(rule, true); + rules.Add(rule); + + var subjectAttributes = new SubjectAttributes { { "isnull", new EppoValue("not null", EppoValueType.STRING) } }; + + Assert.That(RuleValidator.FindMatchingRule(subjectAttributes, rules), Is.Null); + } + + [Test] + public void ShouldNotMatchRuleIsNullFalse() + { + var rules = new List(); + var rule = CreateRule(new List()); + AddIsNullCondition(rule, false); + rules.Add(rule); + + var subjectAttributes = new SubjectAttributes { { "isnull", new EppoValue() } }; + + Assert.That(RuleValidator.FindMatchingRule(subjectAttributes, rules), Is.Null); + } + private static void AddIsNullCondition(Rule rule, Boolean value) + { + rule.conditions.Add(new Condition + { + value = new EppoValue(value ? "true" : "false", EppoValueType.BOOLEAN), + attribute = "isnull", + operatorType = OperatorType.IS_NULL + }); + } + private static void AddOneOfCondition(Rule rule) { rule.conditions.Add(new Condition From 00b5d75f94cce2e3acc20f42c0c0abf39a297dda Mon Sep 17 00:00:00 2001 From: Ty Potter Date: Thu, 23 May 2024 16:34:00 -0600 Subject: [PATCH 02/11] enum is in context now --- dot-net-sdk/validators/RuleValidator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dot-net-sdk/validators/RuleValidator.cs b/dot-net-sdk/validators/RuleValidator.cs index 7da240b..07119b4 100644 --- a/dot-net-sdk/validators/RuleValidator.cs +++ b/dot-net-sdk/validators/RuleValidator.cs @@ -31,7 +31,7 @@ private static bool EvaluateCondition(SubjectAttributes subjectAttributes, Condi try { // Operators other than `IS_NULL` need to assume non-null - if (condition.operatorType == OperatorType.IS_NULL) { + if (condition.operatorType == IS_NULL) { bool isNull = !subjectAttributes.TryGetValue(condition.attribute, out EppoValue outVal) || outVal.isNull(); return condition.value.BoolValue() == isNull; } From 17e70aa693ac9717acdb7da0c5e6dd999fac0d3e Mon Sep 17 00:00:00 2001 From: Ty Potter Date: Thu, 23 May 2024 16:37:12 -0600 Subject: [PATCH 03/11] unused import artifacts? --- dot-net-sdk/validators/RuleValidator.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/dot-net-sdk/validators/RuleValidator.cs b/dot-net-sdk/validators/RuleValidator.cs index 07119b4..3e88a1b 100644 --- a/dot-net-sdk/validators/RuleValidator.cs +++ b/dot-net-sdk/validators/RuleValidator.cs @@ -2,8 +2,6 @@ using eppo_sdk.dto; using static eppo_sdk.dto.OperatorType; using NuGet.Versioning; -using NLog.LayoutRenderers; -using System.Reflection.Metadata; namespace eppo_sdk.validators; From 88964b2b643032372573f3873dc33b8877e5da69 Mon Sep 17 00:00:00 2001 From: Ty Potter Date: Thu, 23 May 2024 16:38:42 -0600 Subject: [PATCH 04/11] unused import artifacts? --- eppo-sdk-test/validators/RuleValidatorTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/eppo-sdk-test/validators/RuleValidatorTest.cs b/eppo-sdk-test/validators/RuleValidatorTest.cs index 8c3686a..85ae6f4 100644 --- a/eppo-sdk-test/validators/RuleValidatorTest.cs +++ b/eppo-sdk-test/validators/RuleValidatorTest.cs @@ -1,6 +1,5 @@ using eppo_sdk.dto; using eppo_sdk.validators; -using Microsoft.AspNetCore.Builder; namespace eppo_sdk_test.validators; From fa745360c55409be94423fd385969ba826eb439a Mon Sep 17 00:00:00 2001 From: Ty Potter Date: Mon, 27 May 2024 13:12:10 -0600 Subject: [PATCH 05/11] Unused value --- dot-net-sdk/http/ExperimentConfigurationRequester.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/dot-net-sdk/http/ExperimentConfigurationRequester.cs b/dot-net-sdk/http/ExperimentConfigurationRequester.cs index 0f76f06..3713e59 100644 --- a/dot-net-sdk/http/ExperimentConfigurationRequester.cs +++ b/dot-net-sdk/http/ExperimentConfigurationRequester.cs @@ -15,20 +15,15 @@ public ExperimentConfigurationRequester(EppoHttpClient eppoHttpClient) { public ExperimentConfigurationResponse? FetchExperimentConfiguration() { - ExperimentConfigurationResponse? config = null; try { return this.eppoHttpClient.Get(Constants.RAC_ENDPOINT); } - catch (UnauthorizedAccessException e) - { - throw e; - } catch (Exception e) { logger.Warn($"Unable to Fetch Experiment Configuration: {e.Message}"); } - return config; + return null; } } \ No newline at end of file From 01f68649739f8e67bad728310049ff727eb172e9 Mon Sep 17 00:00:00 2001 From: Ty Potter Date: Mon, 27 May 2024 13:32:33 -0600 Subject: [PATCH 06/11] park --- dot-net-sdk/dto/EppoValue.cs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/dot-net-sdk/dto/EppoValue.cs b/dot-net-sdk/dto/EppoValue.cs index 746c628..c7f65d6 100644 --- a/dot-net-sdk/dto/EppoValue.cs +++ b/dot-net-sdk/dto/EppoValue.cs @@ -6,16 +6,34 @@ namespace eppo_sdk.dto; [JsonConverter(typeof(EppoValueDeserializer))] public class EppoValue { - public string value { get; set; } + public string? value { get; set; } public EppoValueType type { get; set; } = EppoValueType.NULL; - public List array { get; set; } + public List? array { get; set; } public EppoValue() { } + public static EppoValue Bool(string value) { + return new EppoValue(value, EppoValueType.BOOLEAN); + } + public static EppoValue Bool(bool value) { + return new EppoValue(value.ToString(), EppoValueType.BOOLEAN); + } + public static EppoValue Number(string value) { + return new EppoValue(value, EppoValueType.NUMBER); + } + public static EppoValue String(string value) { + return new EppoValue(value, EppoValueType.STRING); + } + public static EppoValue Integer(string value) { + return new EppoValue(value, EppoValueType.INTEGER); + } + public static EppoValue Null() { + return new EppoValue(); + } public EppoValue(string value, EppoValueType type) { this.value = value; From 9fb36a72d775a9f1ca277189413667113ffe308d Mon Sep 17 00:00:00 2001 From: Ty Potter Date: Mon, 27 May 2024 13:40:05 -0600 Subject: [PATCH 07/11] catch null value on the isNull condition --- dot-net-sdk/validators/RuleValidator.cs | 2 +- eppo-sdk-test/validators/RuleValidatorTest.cs | 53 ++++++++++++------- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/dot-net-sdk/validators/RuleValidator.cs b/dot-net-sdk/validators/RuleValidator.cs index 3e88a1b..28f2765 100644 --- a/dot-net-sdk/validators/RuleValidator.cs +++ b/dot-net-sdk/validators/RuleValidator.cs @@ -30,7 +30,7 @@ private static bool EvaluateCondition(SubjectAttributes subjectAttributes, Condi { // Operators other than `IS_NULL` need to assume non-null if (condition.operatorType == IS_NULL) { - bool isNull = !subjectAttributes.TryGetValue(condition.attribute, out EppoValue outVal) || outVal.isNull(); + bool isNull = !subjectAttributes.TryGetValue(condition.attribute, out EppoValue outVal) || outVal.isNull() || outVal.value == null; return condition.value.BoolValue() == isNull; } else if (subjectAttributes.TryGetValue(condition.attribute, out EppoValue outVal)) diff --git a/eppo-sdk-test/validators/RuleValidatorTest.cs b/eppo-sdk-test/validators/RuleValidatorTest.cs index 85ae6f4..c3c5341 100644 --- a/eppo-sdk-test/validators/RuleValidatorTest.cs +++ b/eppo-sdk-test/validators/RuleValidatorTest.cs @@ -52,8 +52,8 @@ public void ShouldMatchAnyRuleWhenRuleMatches() var subjectAttributes = new SubjectAttributes { - { "price", new EppoValue("15", EppoValueType.NUMBER) }, - { "appVersion", new EppoValue("1.15.0", EppoValueType.STRING) } + { "price", EppoValue.Number("15") }, + { "appVersion", EppoValue.String("1.15.0") } }; Assert.That(rule, Is.EqualTo(RuleValidator.FindMatchingRule(subjectAttributes, rules))); @@ -67,7 +67,7 @@ public void ShouldNotMatchAnyRuleWhenThrowInvalidSubjectAttribute() AddNumericConditionToRule(rule); rules.Add(rule); - var subjectAttributes = new SubjectAttributes { { "price", new EppoValue("abcd", EppoValueType.STRING) } }; + var subjectAttributes = new SubjectAttributes { { "price", EppoValue.String("abcd") } }; Assert.That(RuleValidator.FindMatchingRule(subjectAttributes, rules), Is.Null); } @@ -80,7 +80,7 @@ public void ShouldMatchAnyRuleWithRegexCondition() AddRegexConditionToRule(rule); rules.Add(rule); - var subjectAttributes = new SubjectAttributes { { "match", new EppoValue("abcd", EppoValueType.STRING) } }; + var subjectAttributes = new SubjectAttributes { { "match", EppoValue.String("abcd")} }; Assert.That(rule, Is.EqualTo(RuleValidator.FindMatchingRule(subjectAttributes, rules))); } @@ -93,7 +93,7 @@ public void ShouldNotMatchAnyRuleWithRegexConditionIsUnmatched() AddRegexConditionToRule(rule); rules.Add(rule); - var subjectAttributes = new SubjectAttributes { { "match", new EppoValue("123", EppoValueType.STRING) } }; + var subjectAttributes = new SubjectAttributes { { "match", EppoValue.String("123") } }; Assert.That(RuleValidator.FindMatchingRule(subjectAttributes, rules), Is.Null); } @@ -106,7 +106,7 @@ public void ShouldMatchAnyRuleWithOneOfRule() AddOneOfCondition(rule); rules.Add(rule); - var subjectAttributes = new SubjectAttributes { { "oneOf", new EppoValue("value2", EppoValueType.STRING) } }; + var subjectAttributes = new SubjectAttributes { { "oneOf", EppoValue.String("value2") } }; Assert.That(rule, Is.EqualTo(RuleValidator.FindMatchingRule(subjectAttributes, rules))); } @@ -119,7 +119,7 @@ public void ShouldNotMatchAnyRuleWithOneOfRule() AddOneOfCondition(rule); rules.Add(rule); - var subjectAttributes = new SubjectAttributes { { "oneOf", new EppoValue("value3", EppoValueType.STRING) } }; + var subjectAttributes = new SubjectAttributes { { "oneOf", EppoValue.String("value3")} }; Assert.That(RuleValidator.FindMatchingRule(subjectAttributes, rules), Is.Null); } @@ -132,7 +132,7 @@ public void ShouldMatchAnyRuleWithNotOneOfRule() AddNotOneOfCondition(rule); rules.Add(rule); - var subjectAttributes = new SubjectAttributes { { "oneOf", new EppoValue("value3", EppoValueType.STRING) } }; + var subjectAttributes = new SubjectAttributes { { "oneOf", EppoValue.String("value3") } }; Assert.That(rule, Is.EqualTo(RuleValidator.FindMatchingRule(subjectAttributes, rules))); } @@ -145,13 +145,13 @@ public void ShouldNotMatchAnyRuleWithNotOneOfRuleNotPassed() AddNotOneOfCondition(rule); rules.Add(rule); - var subjectAttributes = new SubjectAttributes { { "oneOf", new EppoValue("value1", EppoValueType.STRING) } }; + var subjectAttributes = new SubjectAttributes { { "oneOf", EppoValue.String("value1") } }; Assert.That(RuleValidator.FindMatchingRule(subjectAttributes, rules), Is.Null); } [Test] - public void ShouldMatchRuleIsNullTrue() + public void ShouldMatchRuleIsNullTrueNullType() { var rules = new List(); var rule = CreateRule(new List()); @@ -163,6 +163,19 @@ public void ShouldMatchRuleIsNullTrue() Assert.That(rule, Is.EqualTo(RuleValidator.FindMatchingRule(subjectAttributes, rules))); } + [Test] + public void ShouldMatchRuleIsNullTrue() + { + var rules = new List(); + var rule = CreateRule(new List()); + AddIsNullCondition(rule, true); + rules.Add(rule); + + var subjectAttributes = new SubjectAttributes { { "isnull", EppoValue.String(null) } }; + + Assert.That(rule, Is.EqualTo(RuleValidator.FindMatchingRule(subjectAttributes, rules))); + } + [Test] public void ShouldMatchRuleIsNullNoAttribute() @@ -185,7 +198,7 @@ public void ShouldMatchRuleIsNullFalse() AddIsNullCondition(rule, false); rules.Add(rule); - var subjectAttributes = new SubjectAttributes { { "isnull", new EppoValue("not null", EppoValueType.STRING) } }; + var subjectAttributes = new SubjectAttributes { { "isnull", EppoValue.String("not null") } }; Assert.That(rule, Is.EqualTo(RuleValidator.FindMatchingRule(subjectAttributes, rules))); } @@ -198,7 +211,7 @@ public void ShouldNotMatchRuleIsNullTrue() AddIsNullCondition(rule, true); rules.Add(rule); - var subjectAttributes = new SubjectAttributes { { "isnull", new EppoValue("not null", EppoValueType.STRING) } }; + var subjectAttributes = new SubjectAttributes { { "isnull", EppoValue.String("not null") } }; Assert.That(RuleValidator.FindMatchingRule(subjectAttributes, rules), Is.Null); } @@ -219,7 +232,7 @@ private static void AddIsNullCondition(Rule rule, Boolean value) { rule.conditions.Add(new Condition { - value = new EppoValue(value ? "true" : "false", EppoValueType.BOOLEAN), + value = EppoValue.Bool(value), attribute = "isnull", operatorType = OperatorType.IS_NULL }); @@ -257,7 +270,7 @@ private static void AddRegexConditionToRule(Rule rule) { var condition = new Condition { - value = new EppoValue("[a-z]+", EppoValueType.STRING), + value = EppoValue.String("[a-z]+"), attribute = "match", operatorType = OperatorType.MATCHES }; @@ -266,21 +279,21 @@ private static void AddRegexConditionToRule(Rule rule) private static void AddPriceToSubjectAttribute(SubjectAttributes subjectAttributes) { - subjectAttributes.Add("price", new EppoValue("30", EppoValueType.STRING)); + subjectAttributes.Add("price", EppoValue.String("30")); } private static void AddNumericConditionToRule(Rule rule) { rule.conditions.Add(new Condition { - value = new EppoValue("10", EppoValueType.NUMBER), + value = EppoValue.Number("10"), attribute = "price", operatorType = OperatorType.GTE }); rule.conditions.Add(new Condition { - value = new EppoValue("20", EppoValueType.NUMBER), + value = EppoValue.Number("20"), attribute = "price", operatorType = OperatorType.LTE }); @@ -290,14 +303,14 @@ private static void AddSemVerConditionToRule(Rule rule) { rule.conditions.Add(new Condition { - value = new EppoValue("1.2.3", EppoValueType.STRING), + value = EppoValue.String("1.2.3"), attribute = "appVersion", operatorType = OperatorType.GTE }); rule.conditions.Add(new Condition { - value = new EppoValue("2.2.0", EppoValueType.STRING), + value = EppoValue.String("2.2.0"), attribute = "appVersion", operatorType = OperatorType.LTE }); @@ -305,7 +318,7 @@ private static void AddSemVerConditionToRule(Rule rule) private static void AddNameToSubjectAttribute(SubjectAttributes subjectAttributes) { - subjectAttributes.Add("name", new EppoValue("test", EppoValueType.STRING)); + subjectAttributes.Add("name", EppoValue.String("test")); } private static Rule CreateRule(List conditions) From 07a6ce107eb00c37333af56401495a686a1476fe Mon Sep 17 00:00:00 2001 From: Ty Potter Date: Mon, 27 May 2024 13:40:28 -0600 Subject: [PATCH 08/11] Handy shortcut constructors for EppoValue --- dot-net-sdk/dto/EppoValue.cs | 6 +++--- dot-net-sdk/dto/EppoValueDeserializer.cs | 11 ++++++----- dot-net-sdk/dto/EppoValueType.cs | 1 + 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/dot-net-sdk/dto/EppoValue.cs b/dot-net-sdk/dto/EppoValue.cs index c7f65d6..d72c01b 100644 --- a/dot-net-sdk/dto/EppoValue.cs +++ b/dot-net-sdk/dto/EppoValue.cs @@ -16,7 +16,7 @@ public EppoValue() { } - public static EppoValue Bool(string value) { + public static EppoValue Bool(string? value) { return new EppoValue(value, EppoValueType.BOOLEAN); } public static EppoValue Bool(bool value) { @@ -25,7 +25,7 @@ public static EppoValue Bool(bool value) { public static EppoValue Number(string value) { return new EppoValue(value, EppoValueType.NUMBER); } - public static EppoValue String(string value) { + public static EppoValue String(string? value) { return new EppoValue(value, EppoValueType.STRING); } public static EppoValue Integer(string value) { @@ -34,7 +34,7 @@ public static EppoValue Integer(string value) { public static EppoValue Null() { return new EppoValue(); } - public EppoValue(string value, EppoValueType type) + public EppoValue(string? value, EppoValueType type) { this.value = value; this.type = type; diff --git a/dot-net-sdk/dto/EppoValueDeserializer.cs b/dot-net-sdk/dto/EppoValueDeserializer.cs index 7f7e28d..b5d6e3e 100644 --- a/dot-net-sdk/dto/EppoValueDeserializer.cs +++ b/dot-net-sdk/dto/EppoValueDeserializer.cs @@ -18,14 +18,15 @@ public override void WriteJson(JsonWriter writer, EppoValue? value, JsonSerializ switch (reader.TokenType) { case JsonToken.String: - return new EppoValue(value.ToString(), EppoValueType.STRING); + return EppoValue.String(value.ToString()); case JsonToken.Integer: + return EppoValue.Integer(value.ToString()); case JsonToken.Float: - return new EppoValue(value.ToString(), EppoValueType.NUMBER); + return EppoValue.Number(value.ToString()); case JsonToken.Boolean: - return new EppoValue(value.ToString(), EppoValueType.BOOLEAN); + return EppoValue.Bool(value.ToString()); case JsonToken.Null: - return new EppoValue(EppoValueType.NULL); + return EppoValue.Null(); case JsonToken.StartArray: var val = new List(); reader.Read(); @@ -36,7 +37,7 @@ public override void WriteJson(JsonWriter writer, EppoValue? value, JsonSerializ } return new EppoValue(val); default: - throw new UnsupportedEppoValueException("Unsupported Eppo Values"); + throw new UnsupportedEppoValueException("Unsupported Eppo Value Type: " + reader.TokenType.ToString()); } } } \ No newline at end of file diff --git a/dot-net-sdk/dto/EppoValueType.cs b/dot-net-sdk/dto/EppoValueType.cs index 726ce6c..c20b4bc 100644 --- a/dot-net-sdk/dto/EppoValueType.cs +++ b/dot-net-sdk/dto/EppoValueType.cs @@ -3,6 +3,7 @@ namespace eppo_sdk.dto; public enum EppoValueType { NUMBER, + INTEGER, STRING, BOOLEAN, NULL, From f709a095a8e4a0e799eb2e8385e88304e64e0394 Mon Sep 17 00:00:00 2001 From: Ty Potter Date: Mon, 27 May 2024 13:49:21 -0600 Subject: [PATCH 09/11] plumb through Integer value type --- dot-net-sdk/EppoClient.cs | 6 ++++++ dot-net-sdk/dto/EppoValue.cs | 5 +++++ eppo-sdk-test/EppoClientTest.cs | 18 ++++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/dot-net-sdk/EppoClient.cs b/dot-net-sdk/EppoClient.cs index 6ec518f..8d0f9d4 100644 --- a/dot-net-sdk/EppoClient.cs +++ b/dot-net-sdk/EppoClient.cs @@ -40,6 +40,12 @@ private EppoClient(ConfigurationStore configurationStore, EppoClientConfig eppoC } + public double? GetIntegerAssignment(string subjectKey, string flagKey, SubjectAttributes? subjectAttributes = null) + { + return GetAssignment(subjectKey, flagKey, subjectAttributes ?? new SubjectAttributes())?.IntegerValue(); + } + + public string? GetStringAssignment(string subjectKey, string flagKey, SubjectAttributes? subjectAttributes = null) { return GetAssignment(subjectKey, flagKey, subjectAttributes ?? new SubjectAttributes())?.StringValue(); diff --git a/dot-net-sdk/dto/EppoValue.cs b/dot-net-sdk/dto/EppoValue.cs index d72c01b..4098a5f 100644 --- a/dot-net-sdk/dto/EppoValue.cs +++ b/dot-net-sdk/dto/EppoValue.cs @@ -61,6 +61,11 @@ public double DoubleValue() return double.Parse(value, NumberStyles.Number); } + public double IntegerValue() + { + return int.Parse(value, NumberStyles.Number); + } + public bool isNumeric() { return double.TryParse(value, out _); diff --git a/eppo-sdk-test/EppoClientTest.cs b/eppo-sdk-test/EppoClientTest.cs index a6ce2b4..4a5affc 100644 --- a/eppo-sdk-test/EppoClientTest.cs +++ b/eppo-sdk-test/EppoClientTest.cs @@ -64,6 +64,11 @@ public void ShouldValidateAssignments(AssignmentTestCase assignmentTestCase) var numericExpectations = assignmentTestCase.expectedAssignments.ConvertAll(x => x.DoubleValue()); Assert.That(GetNumericAssignments(assignmentTestCase), Is.EqualTo(numericExpectations)); + break; + case "integer": + var intExpectations = assignmentTestCase.expectedAssignments.ConvertAll(x => x.IntegerValue()); + Assert.That(GetIntegerAssignments(assignmentTestCase), Is.EqualTo(intExpectations)); + break; case "string": var stringExpectations = assignmentTestCase.expectedAssignments.ConvertAll(x => x.StringValue()); @@ -99,6 +104,19 @@ public void ShouldValidateAssignments(AssignmentTestCase assignmentTestCase) client.GetNumericAssignment(subject, assignmentTestCase.experiment)); } + private static List GetIntegerAssignments(AssignmentTestCase assignmentTestCase) + { + var client = EppoClient.GetInstance(); + if (assignmentTestCase.subjectsWithAttributes != null) + { + return assignmentTestCase.subjectsWithAttributes.ConvertAll(subject => client.GetIntegerAssignment(subject.subjectKey, assignmentTestCase.experiment, + subject.subjectAttributes)); + } + + return assignmentTestCase.subjects.ConvertAll(subject => + client.GetNumericAssignment(subject, assignmentTestCase.experiment)); + } + private static List GetStringAssignments(AssignmentTestCase assignmentTestCase) { var client = EppoClient.GetInstance(); From 34d39c61172e6fcf2fe9fcaf034b4abeee440454 Mon Sep 17 00:00:00 2001 From: Ty Potter Date: Mon, 27 May 2024 22:10:37 -0600 Subject: [PATCH 10/11] more refactorings --- dot-net-sdk/EppoClient.cs | 2 +- dot-net-sdk/dto/EppoValue.cs | 66 ++++++------------------- dot-net-sdk/validators/RuleValidator.cs | 10 ++-- 3 files changed, 22 insertions(+), 56 deletions(-) diff --git a/dot-net-sdk/EppoClient.cs b/dot-net-sdk/EppoClient.cs index 8d0f9d4..7c28ce3 100644 --- a/dot-net-sdk/EppoClient.cs +++ b/dot-net-sdk/EppoClient.cs @@ -65,7 +65,7 @@ private EppoClient(ConfigurationStore configurationStore, EppoClientConfig eppoC } var subjectVariationOverride = this.GetSubjectVariationOverride(subjectKey, configuration); - if (!subjectVariationOverride.isNull()) + if (!subjectVariationOverride.IsNull()) { return subjectVariationOverride; } diff --git a/dot-net-sdk/dto/EppoValue.cs b/dot-net-sdk/dto/EppoValue.cs index 4098a5f..e47f626 100644 --- a/dot-net-sdk/dto/EppoValue.cs +++ b/dot-net-sdk/dto/EppoValue.cs @@ -12,28 +12,18 @@ public class EppoValue public List? array { get; set; } + + public static EppoValue Bool(string? value) => new(value, EppoValueType.BOOLEAN); + public static EppoValue Bool(bool value) => new(value.ToString(), EppoValueType.BOOLEAN); + public static EppoValue Number(string value) => new(value, EppoValueType.NUMBER); + public static EppoValue String(string? value) => new(value, EppoValueType.STRING); + public static EppoValue Integer(string value) => new(value, EppoValueType.INTEGER); + public static EppoValue Null() => new(); + public EppoValue() { } - public static EppoValue Bool(string? value) { - return new EppoValue(value, EppoValueType.BOOLEAN); - } - public static EppoValue Bool(bool value) { - return new EppoValue(value.ToString(), EppoValueType.BOOLEAN); - } - public static EppoValue Number(string value) { - return new EppoValue(value, EppoValueType.NUMBER); - } - public static EppoValue String(string? value) { - return new EppoValue(value, EppoValueType.STRING); - } - public static EppoValue Integer(string value) { - return new EppoValue(value, EppoValueType.INTEGER); - } - public static EppoValue Null() { - return new EppoValue(); - } public EppoValue(string? value, EppoValueType type) { this.value = value; @@ -46,43 +36,19 @@ public EppoValue(List array) this.type = EppoValueType.ARRAY_OF_STRING; } - public EppoValue(EppoValueType type) - { - this.type = type; - } + public EppoValue(EppoValueType type) => this.type = type; - public bool BoolValue() - { - return bool.Parse(value); - } + public bool BoolValue() => bool.Parse(value); - public double DoubleValue() - { - return double.Parse(value, NumberStyles.Number); - } + public double DoubleValue() => double.Parse(value, NumberStyles.Number); - public double IntegerValue() - { - return int.Parse(value, NumberStyles.Number); - } + public double IntegerValue() => int.Parse(value, NumberStyles.Number); - public bool isNumeric() - { - return double.TryParse(value, out _); - } + public bool IsNumeric() => double.TryParse(value, out _); - public string StringValue() - { - return value; - } + public string StringValue() => value; - public List ArrayValue() - { - return array; - } + public List ArrayValue() => array; - public bool isNull() - { - return EppoValueType.NULL.Equals(type); - } + public bool IsNull() => EppoValueType.NULL.Equals(type); } \ No newline at end of file diff --git a/dot-net-sdk/validators/RuleValidator.cs b/dot-net-sdk/validators/RuleValidator.cs index 28f2765..5384609 100644 --- a/dot-net-sdk/validators/RuleValidator.cs +++ b/dot-net-sdk/validators/RuleValidator.cs @@ -30,7 +30,7 @@ private static bool EvaluateCondition(SubjectAttributes subjectAttributes, Condi { // Operators other than `IS_NULL` need to assume non-null if (condition.operatorType == IS_NULL) { - bool isNull = !subjectAttributes.TryGetValue(condition.attribute, out EppoValue outVal) || outVal.isNull() || outVal.value == null; + bool isNull = !subjectAttributes.TryGetValue(condition.attribute, out EppoValue outVal) || outVal.IsNull() || outVal.value == null; return condition.value.BoolValue() == isNull; } else if (subjectAttributes.TryGetValue(condition.attribute, out EppoValue outVal)) @@ -39,7 +39,7 @@ private static bool EvaluateCondition(SubjectAttributes subjectAttributes, Condi if (condition.operatorType == GTE) { - if (value.isNumeric() && condition.value.isNumeric()) + if (value.IsNumeric() && condition.value.IsNumeric()) { return value.DoubleValue() >= condition.value.DoubleValue(); } @@ -54,7 +54,7 @@ private static bool EvaluateCondition(SubjectAttributes subjectAttributes, Condi } else if (condition.operatorType == GT) { - if (value.isNumeric() && condition.value.isNumeric()) + if (value.IsNumeric() && condition.value.IsNumeric()) { return value.DoubleValue() > condition.value.DoubleValue(); } @@ -69,7 +69,7 @@ private static bool EvaluateCondition(SubjectAttributes subjectAttributes, Condi } else if (condition.operatorType == LTE) { - if (value.isNumeric() && condition.value.isNumeric()) + if (value.IsNumeric() && condition.value.IsNumeric()) { return value.DoubleValue() <= condition.value.DoubleValue(); } @@ -84,7 +84,7 @@ private static bool EvaluateCondition(SubjectAttributes subjectAttributes, Condi } else if (condition.operatorType == LT) { - if (value.isNumeric() && condition.value.isNumeric()) + if (value.IsNumeric() && condition.value.IsNumeric()) { return value.DoubleValue() < condition.value.DoubleValue(); } From 163d4bd63063235ca96d831143c4572d7c2fb133 Mon Sep 17 00:00:00 2001 From: Ty Potter Date: Tue, 28 May 2024 14:34:30 -0600 Subject: [PATCH 11/11] bugs --- dot-net-sdk/EppoClient.cs | 2 +- dot-net-sdk/dto/EppoValue.cs | 10 ++++++++-- dot-net-sdk/validators/RuleValidator.cs | 4 ++-- eppo-sdk-test/EppoClientTest.cs | 4 ++-- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/dot-net-sdk/EppoClient.cs b/dot-net-sdk/EppoClient.cs index 7c28ce3..6f900c8 100644 --- a/dot-net-sdk/EppoClient.cs +++ b/dot-net-sdk/EppoClient.cs @@ -40,7 +40,7 @@ private EppoClient(ConfigurationStore configurationStore, EppoClientConfig eppoC } - public double? GetIntegerAssignment(string subjectKey, string flagKey, SubjectAttributes? subjectAttributes = null) + public int? GetIntegerAssignment(string subjectKey, string flagKey, SubjectAttributes? subjectAttributes = null) { return GetAssignment(subjectKey, flagKey, subjectAttributes ?? new SubjectAttributes())?.IntegerValue(); } diff --git a/dot-net-sdk/dto/EppoValue.cs b/dot-net-sdk/dto/EppoValue.cs index e47f626..ef09ad0 100644 --- a/dot-net-sdk/dto/EppoValue.cs +++ b/dot-net-sdk/dto/EppoValue.cs @@ -42,7 +42,7 @@ public EppoValue(List array) public double DoubleValue() => double.Parse(value, NumberStyles.Number); - public double IntegerValue() => int.Parse(value, NumberStyles.Number); + public int IntegerValue() => int.Parse(value, NumberStyles.Number); public bool IsNumeric() => double.TryParse(value, out _); @@ -51,4 +51,10 @@ public EppoValue(List array) public List ArrayValue() => array; public bool IsNull() => EppoValueType.NULL.Equals(type); -} \ No newline at end of file + + public static bool IsNullValue(EppoValue? value) => + value == null /* null pointer */ || + value.IsNull() /* parsed as a null JSON token type */ || + value.value == null; /* Value type is set but value is null */ + +} diff --git a/dot-net-sdk/validators/RuleValidator.cs b/dot-net-sdk/validators/RuleValidator.cs index 5384609..54c3170 100644 --- a/dot-net-sdk/validators/RuleValidator.cs +++ b/dot-net-sdk/validators/RuleValidator.cs @@ -30,10 +30,10 @@ private static bool EvaluateCondition(SubjectAttributes subjectAttributes, Condi { // Operators other than `IS_NULL` need to assume non-null if (condition.operatorType == IS_NULL) { - bool isNull = !subjectAttributes.TryGetValue(condition.attribute, out EppoValue outVal) || outVal.IsNull() || outVal.value == null; + bool isNull = !subjectAttributes.TryGetValue(condition.attribute, out EppoValue? outVal) || EppoValue.IsNullValue(outVal); return condition.value.BoolValue() == isNull; } - else if (subjectAttributes.TryGetValue(condition.attribute, out EppoValue outVal)) + else if (subjectAttributes.TryGetValue(condition.attribute, out EppoValue? outVal)) { var value = outVal!; // Assuming non-null for simplicity, handle nulls as necessary diff --git a/eppo-sdk-test/EppoClientTest.cs b/eppo-sdk-test/EppoClientTest.cs index 4a5affc..50153a1 100644 --- a/eppo-sdk-test/EppoClientTest.cs +++ b/eppo-sdk-test/EppoClientTest.cs @@ -104,7 +104,7 @@ public void ShouldValidateAssignments(AssignmentTestCase assignmentTestCase) client.GetNumericAssignment(subject, assignmentTestCase.experiment)); } - private static List GetIntegerAssignments(AssignmentTestCase assignmentTestCase) + private static List GetIntegerAssignments(AssignmentTestCase assignmentTestCase) { var client = EppoClient.GetInstance(); if (assignmentTestCase.subjectsWithAttributes != null) @@ -114,7 +114,7 @@ public void ShouldValidateAssignments(AssignmentTestCase assignmentTestCase) } return assignmentTestCase.subjects.ConvertAll(subject => - client.GetNumericAssignment(subject, assignmentTestCase.experiment)); + client.GetIntegerAssignment(subject, assignmentTestCase.experiment)); } private static List GetStringAssignments(AssignmentTestCase assignmentTestCase)