From ed0a54df8c3e1660a7cc6b500585863b70664bf4 Mon Sep 17 00:00:00 2001 From: adams85 <31276480+adams85@users.noreply.github.com> Date: Thu, 21 Mar 2024 11:10:02 +0100 Subject: [PATCH] Evaluation log improvements (#89) * Align config json error handling of EvaluateLogHelper with error reporting of RolloutEvaluator * Improve naming * Improve User Object tests * Allow leading/trailing whitespace in length prefix for (NOT) STARTS/ENDS WITH ANY OF comparators to align behavior with other SDKs * Correct grammar mistake * Make naming of UserComparator members consistent * Add tests for some number parsing edge cases * Adjust terminology to docs (eliminate the usage of term 'match' in the context of conditions) * Bump version --- appveyor.yml | 2 +- .../ConfigV2EvaluationTests.cs | 14 +++- src/ConfigCat.Client.Tests/UserTests.cs | 24 ++++-- .../Evaluation/EvaluateLogHelper.cs | 63 ++++++++------ .../Evaluation/RolloutEvaluator.cs | 47 ++++++----- .../Models/PrerequisiteFlagComparator.cs | 4 +- .../Models/SegmentComparator.cs | 4 +- src/ConfigCatClient/Models/UserComparator.cs | 84 +++++++++---------- 8 files changed, 134 insertions(+), 108 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 06115f61..ee3cde05 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,5 @@ environment: - build_version: 9.0.2 + build_version: 9.1.0 version: $(build_version)-{build} image: Visual Studio 2022 configuration: Release diff --git a/src/ConfigCat.Client.Tests/ConfigV2EvaluationTests.cs b/src/ConfigCat.Client.Tests/ConfigV2EvaluationTests.cs index ba5b68c9..272c1d34 100644 --- a/src/ConfigCat.Client.Tests/ConfigV2EvaluationTests.cs +++ b/src/ConfigCat.Client.Tests/ConfigV2EvaluationTests.cs @@ -504,6 +504,7 @@ public void UserObjectAttributeValueConversion_TextComparisons_Test() [DataRow("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "decimal:5", ">=5")] [DataRow("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "decimal:79228162514264337593543950335", ">5")] [DataRow("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "-Infinity", "<2.1")] + [DataRow("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", " -Infinity ", "<2.1")] [DataRow("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "-1", "<2.1")] [DataRow("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "2", "<2.1")] [DataRow("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "2.1", "<=2,1")] @@ -511,7 +512,9 @@ public void UserObjectAttributeValueConversion_TextComparisons_Test() [DataRow("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "3", "<>4.2")] [DataRow("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "5", ">=5")] [DataRow("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "Infinity", ">5")] + [DataRow("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", " Infinity ", ">5")] [DataRow("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "NaN", "<>4.2")] + [DataRow("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", " NaN ", "<>4.2")] [DataRow("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "NaNa", "80%")] // Date time-based comparisons [DataRow("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", "datetime:2023-03-31T23:59:59.9990000Z", false)] @@ -542,12 +545,15 @@ public void UserObjectAttributeValueConversion_TextComparisons_Test() [DataRow("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", 1682899199, true)] [DataRow("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", 1682899201, false)] [DataRow("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", "-Infinity", false)] + [DataRow("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", " -Infinity ", false)] [DataRow("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", "1680307199.999", false)] [DataRow("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", "1680307200.001", true)] [DataRow("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", "1682899199.999", true)] [DataRow("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", "1682899200.001", false)] [DataRow("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", "+Infinity", false)] + [DataRow("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", " +Infinity ", false)] [DataRow("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", "NaN", false)] + [DataRow("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", " NaN ", false)] // String array-based comparisons [DataRow("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "stringArrayContainsAnyOfDogDefaultCat", "12345", "Custom1", new string[] { "x", "read" }, "Dog")] [DataRow("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "stringArrayContainsAnyOfDogDefaultCat", "12345", "Custom1", new string[] { "x", "Read" }, "Cat")] @@ -736,10 +742,10 @@ public void ComparisonAttributeTrimming_Test(string key, string expectedReturnVa [DataRow("notendswithanyof", "no trim")] [DataRow("arraycontainsanyof", "no trim")] [DataRow("arraynotcontainsanyof", "no trim")] - [DataRow("startwithanyofhashed", "default")] - [DataRow("notstartwithanyofhashed", "default")] - [DataRow("endswithanyofhashed", "default")] - [DataRow("notendswithanyofhashed", "default")] + [DataRow("startwithanyofhashed", "no trim")] + [DataRow("notstartwithanyofhashed", "no trim")] + [DataRow("endswithanyofhashed", "no trim")] + [DataRow("notendswithanyofhashed", "no trim")] //semver comparator values trimmed because of backward compatibility [DataRow("semverisoneof", "4 trim")] [DataRow("semverisnotoneof", "5 trim")] diff --git a/src/ConfigCat.Client.Tests/UserTests.cs b/src/ConfigCat.Client.Tests/UserTests.cs index dbae79db..f6e48dde 100644 --- a/src/ConfigCat.Client.Tests/UserTests.cs +++ b/src/ConfigCat.Client.Tests/UserTests.cs @@ -13,7 +13,6 @@ public void CreateUser_WithIdAndEmailAndCountry_AllAttributesShouldContainsPasse var user = new User("id") { Email = "id@example.com", - Country = "US" }; @@ -25,12 +24,17 @@ public void CreateUser_WithIdAndEmailAndCountry_AllAttributesShouldContainsPasse Assert.IsTrue(actualAttributes.TryGetValue(nameof(User.Email), out var s)); Assert.AreEqual("id@example.com", s); + Assert.AreEqual("id@example.com", user.GetAttribute(nameof(User.Email))); Assert.IsTrue(actualAttributes.TryGetValue(nameof(User.Country), out s)); Assert.AreEqual("US", s); + Assert.AreEqual("US", user.GetAttribute(nameof(User.Country))); Assert.IsTrue(actualAttributes.TryGetValue(nameof(User.Identifier), out s)); Assert.AreEqual("id", s); + Assert.AreEqual("id", user.GetAttribute(nameof(User.Identifier))); + + Assert.AreEqual(3, actualAttributes.Count); } [TestMethod] @@ -41,9 +45,7 @@ public void UseWellKnownAttributesAsCustomProperties_ShouldNotAppendAllAttribute var user = new User("id") { Email = "id@example.com", - Country = "US", - Custom = { { "myCustomAttribute", "myCustomAttributeValue"}, @@ -59,13 +61,17 @@ public void UseWellKnownAttributesAsCustomProperties_ShouldNotAppendAllAttribute // Assert - Assert.IsTrue(actualAttributes.TryGetValue(nameof(User.Identifier), out var s)); - Assert.AreEqual("id", s); - Assert.AreNotEqual("myIdentifier", s); + Assert.IsTrue(actualAttributes.TryGetValue(nameof(User.Email), out var s)); + Assert.AreEqual("id@example.com", s); + Assert.AreEqual("id@example.com", user.GetAttribute(nameof(User.Email))); Assert.IsTrue(actualAttributes.TryGetValue(nameof(User.Country), out s)); Assert.AreEqual("US", s); - Assert.AreNotEqual("United States", s); + Assert.AreEqual("US", user.GetAttribute(nameof(User.Country))); + + Assert.IsTrue(actualAttributes.TryGetValue(nameof(User.Identifier), out s)); + Assert.AreEqual("id", s); + Assert.AreEqual("id", user.GetAttribute(nameof(User.Identifier))); Assert.IsTrue(actualAttributes.TryGetValue(nameof(User.Email), out s)); Assert.AreEqual("id@example.com", s); @@ -89,9 +95,7 @@ public void UseWellKnownAttributesAsCustomPropertiesWithDifferentNames_ShouldApp var user = new User("id") { Email = "id@example.com", - Country = "US", - Custom = { { attributeName, attributeValue} @@ -108,6 +112,7 @@ public void UseWellKnownAttributesAsCustomPropertiesWithDifferentNames_ShouldApp Assert.IsTrue(actualAttributes.TryGetValue(attributeName, out var s)); Assert.AreEqual(attributeValue, s); + Assert.AreEqual(attributeValue, user.GetAttribute(attributeName)); } [DataTestMethod()] @@ -122,5 +127,6 @@ public void CreateUser_ShouldSetIdentifier(string identifier, string expectedVal Assert.AreEqual(expectedValue, user.Identifier); Assert.AreEqual(expectedValue, user.GetAllAttributes()[nameof(User.Identifier)]); + Assert.AreEqual(expectedValue, user.GetAttribute(nameof(User.Identifier))); } } diff --git a/src/ConfigCatClient/Evaluation/EvaluateLogHelper.cs b/src/ConfigCatClient/Evaluation/EvaluateLogHelper.cs index d9fc69a2..a26c5077 100644 --- a/src/ConfigCatClient/Evaluation/EvaluateLogHelper.cs +++ b/src/ConfigCatClient/Evaluation/EvaluateLogHelper.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Globalization; using ConfigCat.Client.Utils; @@ -13,11 +14,6 @@ internal static class EvaluateLogHelper internal const int StringListMaxLength = 10; - public static IndentedTextBuilder AppendEvaluationResult(this IndentedTextBuilder builder, bool result) - { - return builder.Append(result ? "true" : "false"); - } - private static IndentedTextBuilder AppendUserCondition(this IndentedTextBuilder builder, string? comparisonAttribute, UserComparator comparator, object? comparisonValue) { return builder.Append($"User.{comparisonAttribute} {comparator.ToDisplayText()} '{comparisonValue ?? InvalidValuePlaceholder}'"); @@ -64,12 +60,14 @@ private static IndentedTextBuilder AppendUserCondition(this IndentedTextBuilder public static IndentedTextBuilder AppendUserCondition(this IndentedTextBuilder builder, UserCondition condition) { + var comparisonAttribute = condition.ComparisonAttribute ?? InvalidNamePlaceholder; + return condition.Comparator switch { - UserComparator.IsOneOf or - UserComparator.IsNotOneOf or - UserComparator.ContainsAnyOf or - UserComparator.NotContainsAnyOf or + UserComparator.TextIsOneOf or + UserComparator.TextIsNotOneOf or + UserComparator.TextContainsAnyOf or + UserComparator.TextNotContainsAnyOf or UserComparator.SemVerIsOneOf or UserComparator.SemVerIsNotOneOf or UserComparator.TextStartsWithAnyOf or @@ -78,7 +76,7 @@ UserComparator.TextEndsWithAnyOf or UserComparator.TextNotEndsWithAnyOf or UserComparator.ArrayContainsAnyOf or UserComparator.ArrayNotContainsAnyOf => - builder.AppendUserCondition(condition.ComparisonAttribute, condition.Comparator, condition.StringListValue, isSensitive: false), + builder.AppendUserCondition(comparisonAttribute, condition.Comparator, condition.StringListValue, isSensitive: false), UserComparator.SemVerLess or UserComparator.SemVerLessOrEquals or @@ -86,7 +84,7 @@ UserComparator.SemVerGreater or UserComparator.SemVerGreaterOrEquals or UserComparator.TextEquals or UserComparator.TextNotEquals => - builder.AppendUserCondition(condition.ComparisonAttribute, condition.Comparator, condition.StringValue, isSensitive: false), + builder.AppendUserCondition(comparisonAttribute, condition.Comparator, condition.StringValue, isSensitive: false), UserComparator.NumberEquals or UserComparator.NumberNotEquals or @@ -94,34 +92,38 @@ UserComparator.NumberLess or UserComparator.NumberLessOrEquals or UserComparator.NumberGreater or UserComparator.NumberGreaterOrEquals => - builder.AppendUserCondition(condition.ComparisonAttribute, condition.Comparator, condition.DoubleValue), + builder.AppendUserCondition(comparisonAttribute, condition.Comparator, condition.DoubleValue), - UserComparator.SensitiveIsOneOf or - UserComparator.SensitiveIsNotOneOf or + UserComparator.SensitiveTextIsOneOf or + UserComparator.SensitiveTextIsNotOneOf or UserComparator.SensitiveTextStartsWithAnyOf or UserComparator.SensitiveTextNotStartsWithAnyOf or UserComparator.SensitiveTextEndsWithAnyOf or UserComparator.SensitiveTextNotEndsWithAnyOf or UserComparator.SensitiveArrayContainsAnyOf or UserComparator.SensitiveArrayNotContainsAnyOf => - builder.AppendUserCondition(condition.ComparisonAttribute, condition.Comparator, condition.StringListValue, isSensitive: true), + builder.AppendUserCondition(comparisonAttribute, condition.Comparator, condition.StringListValue, isSensitive: true), UserComparator.DateTimeBefore or UserComparator.DateTimeAfter => - builder.AppendUserCondition(condition.ComparisonAttribute, condition.Comparator, condition.DoubleValue, isDateTime: true), + builder.AppendUserCondition(comparisonAttribute, condition.Comparator, condition.DoubleValue, isDateTime: true), UserComparator.SensitiveTextEquals or UserComparator.SensitiveTextNotEquals => - builder.AppendUserCondition(condition.ComparisonAttribute, condition.Comparator, condition.StringValue, isSensitive: true), + builder.AppendUserCondition(comparisonAttribute, condition.Comparator, condition.StringValue, isSensitive: true), _ => - builder.AppendUserCondition(condition.ComparisonAttribute, condition.Comparator, condition.GetComparisonValue(throwIfInvalid: false)), + builder.AppendUserCondition(comparisonAttribute, condition.Comparator, condition.GetComparisonValue(throwIfInvalid: false)), }; } - public static IndentedTextBuilder AppendPrerequisiteFlagCondition(this IndentedTextBuilder builder, PrerequisiteFlagCondition condition) + public static IndentedTextBuilder AppendPrerequisiteFlagCondition(this IndentedTextBuilder builder, PrerequisiteFlagCondition condition, IReadOnlyDictionary? settings = null) { - var prerequisiteFlagKey = condition.PrerequisiteFlagKey ?? InvalidReferencePlaceholder; + var prerequisiteFlagKey = + condition.PrerequisiteFlagKey is null ? InvalidNamePlaceholder : + settings is not null && !settings.ContainsKey(condition.PrerequisiteFlagKey) ? InvalidReferencePlaceholder : + condition.PrerequisiteFlagKey; + var comparator = condition.Comparator; var comparisonValue = condition.ComparisonValue.GetValue(throwIfInvalid: false); @@ -133,15 +135,22 @@ public static IndentedTextBuilder AppendSegmentCondition(this IndentedTextBuilde var segment = condition.Segment; var comparator = condition.Comparator; - var segmentName = segment?.Name ?? - (segment is null ? InvalidReferencePlaceholder : InvalidNamePlaceholder); + var segmentName = + segment is null ? InvalidReferencePlaceholder : + segment.Name is not { Length: > 0 } ? InvalidNamePlaceholder : + segment.Name; return builder.Append($"User {comparator.ToDisplayText()} '{segmentName}'"); } + public static IndentedTextBuilder AppendConditionResult(this IndentedTextBuilder builder, bool result) + { + return builder.Append(result ? "true" : "false"); + } + public static IndentedTextBuilder AppendConditionConsequence(this IndentedTextBuilder builder, bool result) { - builder.Append(" => ").AppendEvaluationResult(result); + builder.Append(" => ").AppendConditionResult(result); return result ? builder : builder.Append(", skipping the remaining AND conditions"); } @@ -303,10 +312,10 @@ public static string ToDisplayText(this UserComparator comparator) { return comparator switch { - UserComparator.IsOneOf or UserComparator.SensitiveIsOneOf or UserComparator.SemVerIsOneOf => "IS ONE OF", - UserComparator.IsNotOneOf or UserComparator.SensitiveIsNotOneOf or UserComparator.SemVerIsNotOneOf => "IS NOT ONE OF", - UserComparator.ContainsAnyOf => "CONTAINS ANY OF", - UserComparator.NotContainsAnyOf => "NOT CONTAINS ANY OF", + UserComparator.TextIsOneOf or UserComparator.SensitiveTextIsOneOf or UserComparator.SemVerIsOneOf => "IS ONE OF", + UserComparator.TextIsNotOneOf or UserComparator.SensitiveTextIsNotOneOf or UserComparator.SemVerIsNotOneOf => "IS NOT ONE OF", + UserComparator.TextContainsAnyOf => "CONTAINS ANY OF", + UserComparator.TextNotContainsAnyOf => "NOT CONTAINS ANY OF", UserComparator.SemVerLess or UserComparator.NumberLess => "<", UserComparator.SemVerLessOrEquals or UserComparator.NumberLessOrEquals => "<=", UserComparator.SemVerGreater or UserComparator.NumberGreater => ">", diff --git a/src/ConfigCatClient/Evaluation/RolloutEvaluator.cs b/src/ConfigCatClient/Evaluation/RolloutEvaluator.cs index 9a80ba7b..121c2f56 100644 --- a/src/ConfigCatClient/Evaluation/RolloutEvaluator.cs +++ b/src/ConfigCatClient/Evaluation/RolloutEvaluator.cs @@ -254,7 +254,7 @@ private bool TryEvaluatePercentageOptions(PercentageOption[] percentageOptions, return true; } - throw new InvalidOperationException("Sum of percentage option percentages are less than 100."); + throw new InvalidOperationException("Sum of percentage option percentages is less than 100."); } private bool EvaluateConditions(TCondition[] conditions, TargetingRule? targetingRule, string contextSalt, ref EvaluateContext context, out string? error) @@ -383,16 +383,16 @@ private bool EvaluateUserCondition(UserCondition condition, string contextSalt, return EvaluateSensitiveTextEquals(text, condition.StringValue, EnsureConfigJsonSalt(context.Setting.ConfigJsonSalt), contextSalt, negate: comparator == UserComparator.SensitiveTextNotEquals); - case UserComparator.IsOneOf: - case UserComparator.IsNotOneOf: + case UserComparator.TextIsOneOf: + case UserComparator.TextIsNotOneOf: text = GetUserAttributeValueAsText(userAttributeName, userAttributeValue, condition, context.Key); - return EvaluateIsOneOf(text, condition.StringListValue, negate: comparator == UserComparator.IsNotOneOf); + return EvaluateTextIsOneOf(text, condition.StringListValue, negate: comparator == UserComparator.TextIsNotOneOf); - case UserComparator.SensitiveIsOneOf: - case UserComparator.SensitiveIsNotOneOf: + case UserComparator.SensitiveTextIsOneOf: + case UserComparator.SensitiveTextIsNotOneOf: text = GetUserAttributeValueAsText(userAttributeName, userAttributeValue, condition, context.Key); - return EvaluateSensitiveIsOneOf(text, condition.StringListValue, - EnsureConfigJsonSalt(context.Setting.ConfigJsonSalt), contextSalt, negate: comparator == UserComparator.SensitiveIsNotOneOf); + return EvaluateSensitiveTextIsOneOf(text, condition.StringListValue, + EnsureConfigJsonSalt(context.Setting.ConfigJsonSalt), contextSalt, negate: comparator == UserComparator.SensitiveTextIsNotOneOf); case UserComparator.TextStartsWithAnyOf: case UserComparator.TextNotStartsWithAnyOf: @@ -416,10 +416,10 @@ private bool EvaluateUserCondition(UserCondition condition, string contextSalt, return EvaluateSensitiveTextSliceEqualsAnyOf(text, condition.StringListValue, EnsureConfigJsonSalt(context.Setting.ConfigJsonSalt), contextSalt, startsWith: false, negate: comparator == UserComparator.SensitiveTextNotEndsWithAnyOf); - case UserComparator.ContainsAnyOf: - case UserComparator.NotContainsAnyOf: + case UserComparator.TextContainsAnyOf: + case UserComparator.TextNotContainsAnyOf: text = GetUserAttributeValueAsText(userAttributeName, userAttributeValue, condition, context.Key); - return EvaluateContainsAnyOf(text, condition.StringListValue, negate: comparator == UserComparator.NotContainsAnyOf); + return EvaluateTextContainsAnyOf(text, condition.StringListValue, negate: comparator == UserComparator.TextNotContainsAnyOf); case UserComparator.SemVerIsOneOf: case UserComparator.SemVerIsNotOneOf: @@ -479,7 +479,7 @@ private static bool EvaluateSensitiveTextEquals(string text, string? comparisonV return hash.Equals(hexString: comparisonValue.AsSpan()) ^ negate; } - private static bool EvaluateIsOneOf(string text, string[]? comparisonValues, bool negate) + private static bool EvaluateTextIsOneOf(string text, string[]? comparisonValues, bool negate) { EnsureComparisonValue(comparisonValues); @@ -494,7 +494,7 @@ private static bool EvaluateIsOneOf(string text, string[]? comparisonValues, boo return negate; } - private static bool EvaluateSensitiveIsOneOf(string text, string[]? comparisonValues, string configJsonSalt, string contextSalt, bool negate) + private static bool EvaluateSensitiveTextIsOneOf(string text, string[]? comparisonValues, string configJsonSalt, string contextSalt, bool negate) { EnsureComparisonValue(comparisonValues); @@ -549,7 +549,7 @@ private static bool EvaluateSensitiveTextSliceEqualsAnyOf(string text, string[]? var index = item.IndexOf('_'); if (index < 0 - || !int.TryParse(item.AsSpan(0, index).ToParsable(), NumberStyles.None, CultureInfo.InvariantCulture, out var sliceLength) + || !int.TryParse(item.AsSpan(0, index).ToParsable(), NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite, CultureInfo.InvariantCulture, out var sliceLength) || (hash2 = item.AsSpan(index + 1)).IsEmpty) { EnsureComparisonValue(null); @@ -573,7 +573,7 @@ private static bool EvaluateSensitiveTextSliceEqualsAnyOf(string text, string[]? return negate; } - private static bool EvaluateContainsAnyOf(string text, string[]? comparisonValues, bool negate) + private static bool EvaluateTextContainsAnyOf(string text, string[]? comparisonValues, bool negate) { EnsureComparisonValue(comparisonValues); @@ -711,12 +711,17 @@ private static bool EvaluateSensitiveArrayContainsAnyOf(string[] array, string[] private bool EvaluatePrerequisiteFlagCondition(PrerequisiteFlagCondition condition, ref EvaluateContext context) { var logBuilder = context.LogBuilder; - logBuilder?.AppendPrerequisiteFlagCondition(condition); + logBuilder?.AppendPrerequisiteFlagCondition(condition, context.Settings); + Setting? prerequisiteFlag; var prerequisiteFlagKey = condition.PrerequisiteFlagKey; - if (prerequisiteFlagKey is null || !context.Settings.TryGetValue(prerequisiteFlagKey, out var prerequisiteFlag)) + if (prerequisiteFlagKey is null) { - throw new InvalidOperationException("Prerequisite flag key is missing or invalid."); + throw new InvalidOperationException("Prerequisite flag key is missing."); + } + else if (!context.Settings.TryGetValue(prerequisiteFlagKey, out prerequisiteFlag)) + { + throw new InvalidOperationException("Prerequisite flag is missing."); } var comparisonValue = EnsureComparisonValue(condition.ComparisonValue.GetValue(throwIfInvalid: false)); @@ -759,8 +764,8 @@ private bool EvaluatePrerequisiteFlagCondition(PrerequisiteFlagCondition conditi logBuilder? .NewLine().Append($"Prerequisite flag evaluation result: '{prerequisiteFlagValue}'.") .NewLine("Condition (") - .AppendPrerequisiteFlagCondition(condition) - .Append(") evaluates to ").AppendEvaluationResult(result).Append(".") + .AppendPrerequisiteFlagCondition(condition, context.Settings) + .Append(") evaluates to ").AppendConditionResult(result).Append(".") .DecreaseIndent() .NewLine(")"); @@ -818,7 +823,7 @@ private bool EvaluateSegmentCondition(SegmentCondition condition, ref EvaluateCo logBuilder.NewLine("Condition (").AppendSegmentCondition(condition).Append(")"); (error is null - ? logBuilder.Append(" evaluates to ").AppendEvaluationResult(result) + ? logBuilder.Append(" evaluates to ").AppendConditionResult(result) : logBuilder.Append(" failed to evaluate")) .Append("."); diff --git a/src/ConfigCatClient/Models/PrerequisiteFlagComparator.cs b/src/ConfigCatClient/Models/PrerequisiteFlagComparator.cs index 2414e97d..f957cd1d 100644 --- a/src/ConfigCatClient/Models/PrerequisiteFlagComparator.cs +++ b/src/ConfigCatClient/Models/PrerequisiteFlagComparator.cs @@ -6,12 +6,12 @@ namespace ConfigCat.Client; public enum PrerequisiteFlagComparator : byte { /// - /// EQUALS - It matches when the evaluated value of the specified prerequisite flag is equal to the comparison value. + /// EQUALS - Checks whether the evaluated value of the specified prerequisite flag is equal to the comparison value. /// Equals = 0, /// - /// NOT EQUALS - It matches when the evaluated value of the specified prerequisite flag is not equal to the comparison value. + /// NOT EQUALS - Checks whether the evaluated value of the specified prerequisite flag is not equal to the comparison value. /// NotEquals = 1 } diff --git a/src/ConfigCatClient/Models/SegmentComparator.cs b/src/ConfigCatClient/Models/SegmentComparator.cs index 2a411cf8..35cbfbad 100644 --- a/src/ConfigCatClient/Models/SegmentComparator.cs +++ b/src/ConfigCatClient/Models/SegmentComparator.cs @@ -6,12 +6,12 @@ namespace ConfigCat.Client; public enum SegmentComparator : byte { /// - /// IS IN SEGMENT - It matches when the conditions of the specified segment are evaluated to true. + /// IS IN SEGMENT - Checks whether the conditions of the specified segment are evaluated to true. /// IsIn = 0, /// - /// IS NOT IN SEGMENT - It matches when the conditions of the specified segment are evaluated to false. + /// IS NOT IN SEGMENT - Checks whether the conditions of the specified segment are evaluated to false. /// IsNotIn = 1, } diff --git a/src/ConfigCatClient/Models/UserComparator.cs b/src/ConfigCatClient/Models/UserComparator.cs index 0ea2273a..4941f593 100644 --- a/src/ConfigCatClient/Models/UserComparator.cs +++ b/src/ConfigCatClient/Models/UserComparator.cs @@ -6,182 +6,182 @@ namespace ConfigCat.Client; public enum UserComparator : byte { /// - /// IS ONE OF (cleartext) - It matches when the comparison attribute is equal to any of the comparison values. + /// IS ONE OF (cleartext) - Checks whether the comparison attribute is equal to any of the comparison values. /// - IsOneOf = 0, + TextIsOneOf = 0, /// - /// IS NOT ONE OF (cleartext) - It matches when the comparison attribute is not equal to any of the comparison values. + /// IS NOT ONE OF (cleartext) - Checks whether the comparison attribute is not equal to any of the comparison values. /// - IsNotOneOf = 1, + TextIsNotOneOf = 1, /// - /// CONTAINS ANY OF (cleartext) - It matches when the comparison attribute contains any comparison values as a substring. + /// CONTAINS ANY OF (cleartext) - Checks whether the comparison attribute contains any comparison values as a substring. /// - ContainsAnyOf = 2, + TextContainsAnyOf = 2, /// - /// NOT CONTAINS ANY OF (cleartext) - It matches when the comparison attribute does not contain any comparison values as a substring. + /// NOT CONTAINS ANY OF (cleartext) - Checks whether the comparison attribute does not contain any comparison values as a substring. /// - NotContainsAnyOf = 3, + TextNotContainsAnyOf = 3, /// - /// IS ONE OF (semver) - It matches when the comparison attribute interpreted as a semantic version is equal to any of the comparison values. + /// IS ONE OF (semver) - Checks whether the comparison attribute interpreted as a semantic version is equal to any of the comparison values. /// SemVerIsOneOf = 4, /// - /// IS NOT ONE OF (semver) - It matches when the comparison attribute interpreted as a semantic version is not equal to any of the comparison values. + /// IS NOT ONE OF (semver) - Checks whether the comparison attribute interpreted as a semantic version is not equal to any of the comparison values. /// SemVerIsNotOneOf = 5, /// - /// < (semver) - It matches when the comparison attribute interpreted as a semantic version is less than the comparison value. + /// < (semver) - Checks whether the comparison attribute interpreted as a semantic version is less than the comparison value. /// SemVerLess = 6, /// - /// <= (semver) - It matches when the comparison attribute interpreted as a semantic version is less than or equal to the comparison value. + /// <= (semver) - Checks whether the comparison attribute interpreted as a semantic version is less than or equal to the comparison value. /// SemVerLessOrEquals = 7, /// - /// > (semver) - It matches when the comparison attribute interpreted as a semantic version is greater than the comparison value. + /// > (semver) - Checks whether the comparison attribute interpreted as a semantic version is greater than the comparison value. /// SemVerGreater = 8, /// - /// >= (semver) - It matches when the comparison attribute interpreted as a semantic version is greater than or equal to the comparison value. + /// >= (semver) - Checks whether the comparison attribute interpreted as a semantic version is greater than or equal to the comparison value. /// SemVerGreaterOrEquals = 9, /// - /// = (number) - It matches when the comparison attribute interpreted as a decimal number is equal to the comparison value. + /// = (number) - Checks whether the comparison attribute interpreted as a decimal number is equal to the comparison value. /// NumberEquals = 10, /// - /// != (number) - It matches when the comparison attribute interpreted as a decimal number is not equal to the comparison value. + /// != (number) - Checks whether the comparison attribute interpreted as a decimal number is not equal to the comparison value. /// NumberNotEquals = 11, /// - /// < (number) - It matches when the comparison attribute interpreted as a decimal number is less than the comparison value. + /// < (number) - Checks whether the comparison attribute interpreted as a decimal number is less than the comparison value. /// NumberLess = 12, /// - /// <= (number) - It matches when the comparison attribute interpreted as a decimal number is less than or equal to the comparison value. + /// <= (number) - Checks whether the comparison attribute interpreted as a decimal number is less than or equal to the comparison value. /// NumberLessOrEquals = 13, /// - /// > (number) - It matches when the comparison attribute interpreted as a decimal number is greater than the comparison value. + /// > (number) - Checks whether the comparison attribute interpreted as a decimal number is greater than the comparison value. /// NumberGreater = 14, /// - /// >= (number) - It matches when the comparison attribute interpreted as a decimal number is greater than or equal to the comparison value. + /// >= (number) - Checks whether the comparison attribute interpreted as a decimal number is greater than or equal to the comparison value. /// NumberGreaterOrEquals = 15, /// - /// IS ONE OF (hashed) - It matches when the comparison attribute is equal to any of the comparison values (where the comparison is performed using the salted SHA256 hashes of the values). + /// IS ONE OF (hashed) - Checks whether the comparison attribute is equal to any of the comparison values (where the comparison is performed using the salted SHA256 hashes of the values). /// - SensitiveIsOneOf = 16, + SensitiveTextIsOneOf = 16, /// - /// IS NOT ONE OF (hashed) - It matches when the comparison attribute is not equal to any of the comparison values (where the comparison is performed using the salted SHA256 hashes of the values). + /// IS NOT ONE OF (hashed) - Checks whether the comparison attribute is not equal to any of the comparison values (where the comparison is performed using the salted SHA256 hashes of the values). /// - SensitiveIsNotOneOf = 17, + SensitiveTextIsNotOneOf = 17, /// - /// BEFORE (UTC datetime) - It matches when the comparison attribute interpreted as the seconds elapsed since Unix Epoch is less than the comparison value. + /// BEFORE (UTC datetime) - Checks whether the comparison attribute interpreted as the seconds elapsed since Unix Epoch is less than the comparison value. /// DateTimeBefore = 18, /// - /// AFTER (UTC datetime) - It matches when the comparison attribute interpreted as the seconds elapsed since Unix Epoch is greater than the comparison value. + /// AFTER (UTC datetime) - Checks whether the comparison attribute interpreted as the seconds elapsed since Unix Epoch is greater than the comparison value. /// DateTimeAfter = 19, /// - /// EQUALS (hashed) - It matches when the comparison attribute is equal to the comparison value (where the comparison is performed using the salted SHA256 hashes of the values). + /// EQUALS (hashed) - Checks whether the comparison attribute is equal to the comparison value (where the comparison is performed using the salted SHA256 hashes of the values). /// SensitiveTextEquals = 20, /// - /// NOT EQUALS (hashed) - It matches when the comparison attribute is not equal to the comparison value (where the comparison is performed using the salted SHA256 hashes of the values). + /// NOT EQUALS (hashed) - Checks whether the comparison attribute is not equal to the comparison value (where the comparison is performed using the salted SHA256 hashes of the values). /// SensitiveTextNotEquals = 21, /// - /// STARTS WITH ANY OF (hashed) - It matches when the comparison attribute starts with any of the comparison values (where the comparison is performed using the salted SHA256 hashes of the values). + /// STARTS WITH ANY OF (hashed) - Checks whether the comparison attribute starts with any of the comparison values (where the comparison is performed using the salted SHA256 hashes of the values). /// SensitiveTextStartsWithAnyOf = 22, /// - /// NOT STARTS WITH ANY OF (hashed) - It matches when the comparison attribute does not start with any of the comparison values (where the comparison is performed using the salted SHA256 hashes of the values). + /// NOT STARTS WITH ANY OF (hashed) - Checks whether the comparison attribute does not start with any of the comparison values (where the comparison is performed using the salted SHA256 hashes of the values). /// SensitiveTextNotStartsWithAnyOf = 23, /// - /// ENDS WITH ANY OF (hashed) - It matches when the comparison attribute ends with any of the comparison values (where the comparison is performed using the salted SHA256 hashes of the values). + /// ENDS WITH ANY OF (hashed) - Checks whether the comparison attribute ends with any of the comparison values (where the comparison is performed using the salted SHA256 hashes of the values). /// SensitiveTextEndsWithAnyOf = 24, /// - /// NOT ENDS WITH ANY OF (hashed) - It matches when the comparison attribute does not end with any of the comparison values (where the comparison is performed using the salted SHA256 hashes of the values). + /// NOT ENDS WITH ANY OF (hashed) - Checks whether the comparison attribute does not end with any of the comparison values (where the comparison is performed using the salted SHA256 hashes of the values). /// SensitiveTextNotEndsWithAnyOf = 25, /// - /// ARRAY CONTAINS ANY OF (hashed) - It matches when the comparison attribute interpreted as a comma-separated list contains any of the comparison values (where the comparison is performed using the salted SHA256 hashes of the values). + /// ARRAY CONTAINS ANY OF (hashed) - Checks whether the comparison attribute interpreted as a comma-separated list contains any of the comparison values (where the comparison is performed using the salted SHA256 hashes of the values). /// SensitiveArrayContainsAnyOf = 26, /// - /// ARRAY NOT CONTAINS ANY OF (hashed) - It matches when the comparison attribute interpreted as a comma-separated list does not contain any of the comparison values (where the comparison is performed using the salted SHA256 hashes of the values). + /// ARRAY NOT CONTAINS ANY OF (hashed) - Checks whether the comparison attribute interpreted as a comma-separated list does not contain any of the comparison values (where the comparison is performed using the salted SHA256 hashes of the values). /// SensitiveArrayNotContainsAnyOf = 27, /// - /// EQUALS (cleartext) - It matches when the comparison attribute is equal to the comparison value. + /// EQUALS (cleartext) - Checks whether the comparison attribute is equal to the comparison value. /// TextEquals = 28, /// - /// NOT EQUALS (cleartext) - It matches when the comparison attribute is not equal to the comparison value. + /// NOT EQUALS (cleartext) - Checks whether the comparison attribute is not equal to the comparison value. /// TextNotEquals = 29, /// - /// STARTS WITH ANY OF (cleartext) - It matches when the comparison attribute starts with any of the comparison values. + /// STARTS WITH ANY OF (cleartext) - Checks whether the comparison attribute starts with any of the comparison values. /// TextStartsWithAnyOf = 30, /// - /// NOT STARTS WITH ANY OF (cleartext) - It matches when the comparison attribute does not start with any of the comparison values. + /// NOT STARTS WITH ANY OF (cleartext) - Checks whether the comparison attribute does not start with any of the comparison values. /// TextNotStartsWithAnyOf = 31, /// - /// ENDS WITH ANY OF (cleartext) - It matches when the comparison attribute ends with any of the comparison values. + /// ENDS WITH ANY OF (cleartext) - Checks whether the comparison attribute ends with any of the comparison values. /// TextEndsWithAnyOf = 32, /// - /// NOT ENDS WITH ANY OF (cleartext) - It matches when the comparison attribute does not end with any of the comparison values. + /// NOT ENDS WITH ANY OF (cleartext) - Checks whether the comparison attribute does not end with any of the comparison values. /// TextNotEndsWithAnyOf = 33, /// - /// ARRAY CONTAINS ANY OF (cleartext) - It matches when the comparison attribute interpreted as a comma-separated list contains any of the comparison values. + /// ARRAY CONTAINS ANY OF (cleartext) - Checks whether the comparison attribute interpreted as a comma-separated list contains any of the comparison values. /// ArrayContainsAnyOf = 34, /// - /// ARRAY NOT CONTAINS ANY OF (cleartext) - It matches when the comparison attribute interpreted as a comma-separated list does not contain any of the comparison values. + /// ARRAY NOT CONTAINS ANY OF (cleartext) - Checks whether the comparison attribute interpreted as a comma-separated list does not contain any of the comparison values. /// ArrayNotContainsAnyOf = 35, }