From e1f539ea8ae80fd606edb92aff54e978ae17d16c Mon Sep 17 00:00:00 2001 From: Erdi Rowlands Date: Mon, 17 Jun 2024 16:08:50 +0100 Subject: [PATCH] FFM-11548 Add new `jsonVariationToken` method and deprecate `jsonVariation` (#132) --- client/api/CfClient.cs | 5 +++ client/api/Evaluator.cs | 47 ++++++++++++++++++++++- client/api/InnerClient.cs | 40 +++++++++++++++++++ ff-netF48-server-sdk.csproj | 4 +- tests/ff-server-sdk-test/EvaluatorTest.cs | 3 ++ 5 files changed, 96 insertions(+), 3 deletions(-) diff --git a/client/api/CfClient.cs b/client/api/CfClient.cs index 7ffcf8b..6ab9e8d 100644 --- a/client/api/CfClient.cs +++ b/client/api/CfClient.cs @@ -21,8 +21,10 @@ public interface ICfClient bool boolVariation(string key, dto.Target target, bool defaultValue); string stringVariation(string key, dto.Target target, string defaultValue); double numberVariation(string key, dto.Target target, double defaultValue); + JToken jsonVariationToken(string key, dto.Target target, JToken defaultValue); JObject jsonVariation(string key, dto.Target target, JObject defaultValue); + event EventHandler InitializationCompleted; event EventHandler EvaluationChanged; @@ -210,8 +212,11 @@ public async Task Initialize(IConnector connector, Config config) public bool boolVariation(string key, dto.Target target, bool defaultValue) { return client.BoolVariation(key, target, defaultValue); } public string stringVariation(string key, dto.Target target, string defaultValue) { return client.StringVariation(key, target, defaultValue); } public double numberVariation(string key, dto.Target target, double defaultValue) { return client.NumberVariation(key, target, defaultValue); } + public JToken jsonVariationToken(string key, dto.Target target, JToken defaultValue) { return client.JsonVariationToken(key, target, defaultValue); } + [Obsolete("This method only supports JSON objects. If a JSON array variation is returned it will result in a warning being logged and the default variation being returned. Use jsonVariationToken(string key, Target target, JToken defaultValue) since version 1.7.0 for support of both JSON objects and arrays.")] public JObject jsonVariation(string key, dto.Target target, JObject defaultValue) { return client.JsonVariation(key, target, defaultValue); } + // force message public void Update(Message msg) { client.Update(msg, true); } diff --git a/client/api/Evaluator.cs b/client/api/Evaluator.cs index 660c8ca..7833881 100644 --- a/client/api/Evaluator.cs +++ b/client/api/Evaluator.cs @@ -9,6 +9,7 @@ using Newtonsoft.Json.Linq; using Target = io.harness.cfsdk.client.dto.Target; using System.Collections.Generic; +using Newtonsoft.Json; [assembly: InternalsVisibleToAttribute("ff-server-sdk-test")] @@ -24,7 +25,9 @@ internal interface IEvaluator bool BoolVariation(string key, Target target, bool defaultValue); string StringVariation(string key, Target target, string defaultValue); double NumberVariation(string key, Target target, double defaultValue); + JToken JsonVariationToken(string key, Target target, JToken defaultValue); JObject JsonVariation(string key, Target target, JObject defaultValue); + } internal class Evaluator : IEvaluator @@ -60,10 +63,52 @@ public bool BoolVariation(string key, Target target, bool defaultValue) return defaultValue; } + public JToken JsonVariationToken(string key, Target target, JToken defaultValue) + { + var variation = EvaluateVariation(key, target, FeatureConfigKind.Json); + if (variation == null) + { + LogEvaluationFailureError(FeatureConfigKind.Json, key, target, defaultValue.ToString()); + return defaultValue; + } + + try + { + return JToken.Parse(variation.Value); + } + catch (JsonReaderException ex) + { + if (!logger.IsEnabled(LogLevel.Warning)) return defaultValue; + + LogEvaluationFailureError(FeatureConfigKind.Json, key, target, defaultValue.ToString()); + return defaultValue; + } + + } + public JObject JsonVariation(string key, Target target, JObject defaultValue) { var variation = EvaluateVariation(key, target, FeatureConfigKind.Json); - if (variation != null) return JObject.Parse(variation.Value); + if (variation != null) + { + try + { + var token = JToken.Parse(variation.Value); + if (token.Type == JTokenType.Object) return (JObject)token; + + if (!logger.IsEnabled(LogLevel.Warning)) return defaultValue; + + logger.LogWarning("JSON variation is not an object. Returning default value. Use JsonVariationToken(string key, Target target, JToken defaultValue) which is available since version 1.7.0"); + LogEvaluationFailureError(FeatureConfigKind.Json, key, target, defaultValue.ToString()); + return defaultValue; + } + catch (JsonReaderException ex) + { + // Log the error if parsing fails + LogEvaluationFailureError(FeatureConfigKind.Json, key, target, ex.Message); + return defaultValue; + } + } LogEvaluationFailureError(FeatureConfigKind.Json, key, target, defaultValue.ToString()); return defaultValue; diff --git a/client/api/InnerClient.cs b/client/api/InnerClient.cs index 896f942..bfd1ae4 100644 --- a/client/api/InnerClient.cs +++ b/client/api/InnerClient.cs @@ -337,6 +337,45 @@ public double NumberVariation(string key, Target target, double defaultValue) } } + public JToken JsonVariationToken(string key, Target target, JToken defaultValue) + { + try + { + return evaluator.JsonVariationToken(key, target, defaultValue); + } + catch (InvalidCacheStateException ex) + { + if (logger.IsEnabled(LogLevel.Warning)) + logger.LogWarning(ex, + "Invalid cache state detected when evaluating json variation for flag {Key}, refreshing cache and retrying evaluation", + key); + var result = polling.RefreshFlagsAndSegments(TimeSpan.FromSeconds(config.CacheRecoveryTimeoutInMs)); + if (result != RefreshOutcome.Success) + { + if (logger.IsEnabled(LogLevel.Error)) + logger.LogError( + "Refreshing cache for json variation for flag {Key} failed, returning default variation", + key); + LogEvaluationFailureError(FeatureConfigKind.Json, key, target, defaultValue.ToString()); + return defaultValue; + } + + try + { + return evaluator.JsonVariationToken(key, target, defaultValue); + } + catch (InvalidCacheStateException) + { + if (logger.IsEnabled(LogLevel.Warning)) + logger.LogWarning( + "Attempted re-evaluation of json variation for flag {Key} after refreshing cache failed due to invalid cache state, returning default variation", + key); + LogEvaluationFailureError(FeatureConfigKind.Json, key, target, defaultValue.ToString()); + return defaultValue; + } + } + } + public JObject JsonVariation(string key, Target target, JObject defaultValue) { @@ -378,6 +417,7 @@ public JObject JsonVariation(string key, Target target, JObject defaultValue) } + public void Close() { this.connector?.Close(); diff --git a/ff-netF48-server-sdk.csproj b/ff-netF48-server-sdk.csproj index ade9169..d4599f2 100644 --- a/ff-netF48-server-sdk.csproj +++ b/ff-netF48-server-sdk.csproj @@ -8,9 +8,9 @@ ff-dotnet-server-sdk io.harness.cfsdk false - 1.7.0-rc3 + 1.7.0 true - 1.7.0-rc3 + 1.7.0 1.7.0 support@harness.io Copyright © 2024 diff --git a/tests/ff-server-sdk-test/EvaluatorTest.cs b/tests/ff-server-sdk-test/EvaluatorTest.cs index bf67ac8..adb876a 100644 --- a/tests/ff-server-sdk-test/EvaluatorTest.cs +++ b/tests/ff-server-sdk-test/EvaluatorTest.cs @@ -161,6 +161,7 @@ public void ExecuteTestCases(string testName, string targetIdentifier, object ex } object got = null; + object gotJsonTokenMethod = null; switch (kind) { @@ -175,6 +176,7 @@ public void ExecuteTestCases(string testName, string targetIdentifier, object ex break; case FeatureConfigKind.Json: got = evaluator.JsonVariation(featureFlag, target, JObject.Parse("{val: 'default value'}")); + gotJsonTokenMethod = evaluator.JsonVariationToken(featureFlag, target, JObject.Parse("{val: 'default value'}")); break; } @@ -188,6 +190,7 @@ public void ExecuteTestCases(string testName, string targetIdentifier, object ex { var expectedJson = JObject.Parse((string)expected); Assert.AreEqual(expectedJson, got, $"Expected result for {featureFlag} was {expected}"); + Assert.AreEqual(expectedJson, gotJsonTokenMethod, $"Expected result for {featureFlag} was {expected}"); } else {