From 61bb915378d2ce0e6495cf44778fa5d3b30797b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <2493377+askpt@users.noreply.github.com> Date: Tue, 9 Dec 2025 18:28:56 +0000 Subject: [PATCH 1/4] fix: Add MetadataExtensions for converting metadata to primitive types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: André Silva <2493377+askpt@users.noreply.github.com> --- .../Extensions/MetadataExtensions.cs | 46 +++++++++++++++++++ .../OfrepProvider.cs | 5 +- 2 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 src/OpenFeature.Providers.Ofrep/Extensions/MetadataExtensions.cs diff --git a/src/OpenFeature.Providers.Ofrep/Extensions/MetadataExtensions.cs b/src/OpenFeature.Providers.Ofrep/Extensions/MetadataExtensions.cs new file mode 100644 index 00000000..08fbcc75 --- /dev/null +++ b/src/OpenFeature.Providers.Ofrep/Extensions/MetadataExtensions.cs @@ -0,0 +1,46 @@ +using System.Text.Json; + +namespace OpenFeature.Providers.Ofrep.Extensions; + +/// +/// Extension methods for handling metadata conversion. +/// +internal static class MetadataExtensions +{ + /// + /// Converts a dictionary with JsonElement values to a dictionary with primitive types. + /// + /// The metadata dictionary with potential JsonElement values. + /// A new dictionary with primitive type values (string, double, bool). + internal static Dictionary ToPrimitiveTypes(this Dictionary metadata) + { + return metadata.ToDictionary( + kvp => kvp.Key, + kvp => ExtractPrimitiveValue(kvp.Key, kvp.Value) + ); + } + + /// + /// Extracts a primitive value from an object that may be a JsonElement. + /// + /// The key for error reporting purposes. + /// The value to extract. + /// The extracted primitive value. + private static object ExtractPrimitiveValue(string key, object value) + { + if (value is JsonElement jsonElement) + { + return jsonElement.ValueKind switch + { + JsonValueKind.String => jsonElement.GetString() ?? string.Empty, + JsonValueKind.Number => jsonElement.GetDouble(), + JsonValueKind.True => true, + JsonValueKind.False => false, + _ => value.ToString() ?? string.Empty + }; + } + + // If it's already a primitive type, return as-is + return value; + } +} diff --git a/src/OpenFeature.Providers.Ofrep/OfrepProvider.cs b/src/OpenFeature.Providers.Ofrep/OfrepProvider.cs index a1054b50..eaf54018 100644 --- a/src/OpenFeature.Providers.Ofrep/OfrepProvider.cs +++ b/src/OpenFeature.Providers.Ofrep/OfrepProvider.cs @@ -3,6 +3,7 @@ using OpenFeature.Providers.Ofrep.Client; using OpenFeature.Providers.Ofrep.Client.Constants; using OpenFeature.Providers.Ofrep.Configuration; +using OpenFeature.Providers.Ofrep.Extensions; namespace OpenFeature.Providers.Ofrep; @@ -129,7 +130,7 @@ await this._client.EvaluateFlag(flagKey, defaultValue.AsObject, reason: response.Reason, variant: response.Variant, errorMessage: response.ErrorMessage, - flagMetadata: response.Metadata != null ? new ImmutableMetadata(response.Metadata) : null); + flagMetadata: response.Metadata != null ? new ImmutableMetadata(response.Metadata.ToPrimitiveTypes()) : null); } /// @@ -163,7 +164,7 @@ private async Task> ResolveFlag( reason: response.Reason, variant: response.Variant, errorMessage: response.ErrorMessage, - flagMetadata: response.Metadata != null ? new ImmutableMetadata(response.Metadata) : null); + flagMetadata: response.Metadata != null ? new ImmutableMetadata(response.Metadata.ToPrimitiveTypes()) : null); } /// From c1bcb6f760d8551044c66c0f27a78aeb96042f64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <2493377+askpt@users.noreply.github.com> Date: Tue, 9 Dec 2025 18:30:45 +0000 Subject: [PATCH 2/4] test: Add unit tests for ToPrimitiveTypes method in MetadataExtensions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: André Silva <2493377+askpt@users.noreply.github.com> --- .../Extensions/MetadataExtensionsTest.cs | 317 ++++++++++++++++++ 1 file changed, 317 insertions(+) create mode 100644 test/OpenFeature.Providers.Ofrep.Test/Extensions/MetadataExtensionsTest.cs diff --git a/test/OpenFeature.Providers.Ofrep.Test/Extensions/MetadataExtensionsTest.cs b/test/OpenFeature.Providers.Ofrep.Test/Extensions/MetadataExtensionsTest.cs new file mode 100644 index 00000000..8b4e044f --- /dev/null +++ b/test/OpenFeature.Providers.Ofrep.Test/Extensions/MetadataExtensionsTest.cs @@ -0,0 +1,317 @@ +using System.Text.Json; +using OpenFeature.Providers.Ofrep.Extensions; +using Xunit; + +namespace OpenFeature.Providers.Ofrep.Test.Extensions; + +public class MetadataExtensionsTest +{ + [Fact] + public void ToPrimitiveTypes_ShouldConvertJsonElementString_ToString() + { + // Arrange + var json = JsonSerializer.Deserialize("\"hello world\""); + var metadata = new Dictionary + { + { "stringKey", json } + }; + + // Act + var result = metadata.ToPrimitiveTypes(); + + // Assert + Assert.IsType(result["stringKey"]); + Assert.Equal("hello world", result["stringKey"]); + } + + [Fact] + public void ToPrimitiveTypes_ShouldConvertJsonElementNumber_ToDouble() + { + // Arrange + var json = JsonSerializer.Deserialize("42.5"); + var metadata = new Dictionary + { + { "numberKey", json } + }; + + // Act + var result = metadata.ToPrimitiveTypes(); + + // Assert + Assert.IsType(result["numberKey"]); + Assert.Equal(42.5, result["numberKey"]); + } + + [Fact] + public void ToPrimitiveTypes_ShouldConvertJsonElementInteger_ToDouble() + { + // Arrange + var json = JsonSerializer.Deserialize("100"); + var metadata = new Dictionary + { + { "intKey", json } + }; + + // Act + var result = metadata.ToPrimitiveTypes(); + + // Assert + Assert.IsType(result["intKey"]); + Assert.Equal(100.0, result["intKey"]); + } + + [Fact] + public void ToPrimitiveTypes_ShouldConvertJsonElementTrue_ToBoolTrue() + { + // Arrange + var json = JsonSerializer.Deserialize("true"); + var metadata = new Dictionary + { + { "boolKey", json } + }; + + // Act + var result = metadata.ToPrimitiveTypes(); + + // Assert + Assert.IsType(result["boolKey"]); + Assert.True((bool)result["boolKey"]); + } + + [Fact] + public void ToPrimitiveTypes_ShouldConvertJsonElementFalse_ToBoolFalse() + { + // Arrange + var json = JsonSerializer.Deserialize("false"); + var metadata = new Dictionary + { + { "boolKey", json } + }; + + // Act + var result = metadata.ToPrimitiveTypes(); + + // Assert + Assert.IsType(result["boolKey"]); + Assert.False((bool)result["boolKey"]); + } + + [Fact] + public void ToPrimitiveTypes_ShouldHandleNullJsonElement_AsEmptyString() + { + // Arrange + var json = JsonSerializer.Deserialize("null"); + var metadata = new Dictionary + { + { "nullKey", json } + }; + + // Act + var result = metadata.ToPrimitiveTypes(); + + // Assert + Assert.IsType(result["nullKey"]); + Assert.Equal(string.Empty, result["nullKey"]); + } + + [Fact] + public void ToPrimitiveTypes_ShouldHandleJsonElementObject_AsString() + { + // Arrange + var json = JsonSerializer.Deserialize("{\"nested\": \"value\"}"); + var metadata = new Dictionary + { + { "objectKey", json } + }; + + // Act + var result = metadata.ToPrimitiveTypes(); + + // Assert + Assert.IsType(result["objectKey"]); + Assert.Contains("nested", (string)result["objectKey"]); + } + + [Fact] + public void ToPrimitiveTypes_ShouldHandleJsonElementArray_AsString() + { + // Arrange + var json = JsonSerializer.Deserialize("[1, 2, 3]"); + var metadata = new Dictionary + { + { "arrayKey", json } + }; + + // Act + var result = metadata.ToPrimitiveTypes(); + + // Assert + Assert.IsType(result["arrayKey"]); + Assert.Contains("1", (string)result["arrayKey"]); + } + + [Fact] + public void ToPrimitiveTypes_ShouldPreservePrimitiveString() + { + // Arrange + var metadata = new Dictionary + { + { "stringKey", "already a string" } + }; + + // Act + var result = metadata.ToPrimitiveTypes(); + + // Assert + Assert.IsType(result["stringKey"]); + Assert.Equal("already a string", result["stringKey"]); + } + + [Fact] + public void ToPrimitiveTypes_ShouldPreservePrimitiveDouble() + { + // Arrange + var metadata = new Dictionary + { + { "doubleKey", 3.14 } + }; + + // Act + var result = metadata.ToPrimitiveTypes(); + + // Assert + Assert.IsType(result["doubleKey"]); + Assert.Equal(3.14, result["doubleKey"]); + } + + [Fact] + public void ToPrimitiveTypes_ShouldPreservePrimitiveInt() + { + // Arrange + var metadata = new Dictionary + { + { "intKey", 42 } + }; + + // Act + var result = metadata.ToPrimitiveTypes(); + + // Assert + Assert.IsType(result["intKey"]); + Assert.Equal(42, result["intKey"]); + } + + [Fact] + public void ToPrimitiveTypes_ShouldPreservePrimitiveBool() + { + // Arrange + var metadata = new Dictionary + { + { "boolKey", true } + }; + + // Act + var result = metadata.ToPrimitiveTypes(); + + // Assert + Assert.IsType(result["boolKey"]); + Assert.True((bool)result["boolKey"]); + } + + [Fact] + public void ToPrimitiveTypes_ShouldHandleMixedTypes() + { + // Arrange + var jsonString = JsonSerializer.Deserialize("\"json string\""); + var jsonNumber = JsonSerializer.Deserialize("99.9"); + var jsonBool = JsonSerializer.Deserialize("true"); + + var metadata = new Dictionary + { + { "jsonString", jsonString }, + { "jsonNumber", jsonNumber }, + { "jsonBool", jsonBool }, + { "primitiveString", "native string" }, + { "primitiveInt", 123 }, + { "primitiveBool", false } + }; + + // Act + var result = metadata.ToPrimitiveTypes(); + + // Assert + Assert.Equal("json string", result["jsonString"]); + Assert.Equal(99.9, result["jsonNumber"]); + Assert.True((bool)result["jsonBool"]); + Assert.Equal("native string", result["primitiveString"]); + Assert.Equal(123, result["primitiveInt"]); + Assert.False((bool)result["primitiveBool"]); + } + + [Fact] + public void ToPrimitiveTypes_ShouldReturnEmptyDictionary_WhenInputIsEmpty() + { + // Arrange + var metadata = new Dictionary(); + + // Act + var result = metadata.ToPrimitiveTypes(); + + // Assert + Assert.Empty(result); + } + + [Fact] + public void ToPrimitiveTypes_ShouldHandleEmptyString() + { + // Arrange + var json = JsonSerializer.Deserialize("\"\""); + var metadata = new Dictionary + { + { "emptyString", json } + }; + + // Act + var result = metadata.ToPrimitiveTypes(); + + // Assert + Assert.IsType(result["emptyString"]); + Assert.Equal(string.Empty, result["emptyString"]); + } + + [Fact] + public void ToPrimitiveTypes_ShouldHandleNegativeNumbers() + { + // Arrange + var json = JsonSerializer.Deserialize("-42.5"); + var metadata = new Dictionary + { + { "negativeNumber", json } + }; + + // Act + var result = metadata.ToPrimitiveTypes(); + + // Assert + Assert.IsType(result["negativeNumber"]); + Assert.Equal(-42.5, result["negativeNumber"]); + } + + [Fact] + public void ToPrimitiveTypes_ShouldHandleZero() + { + // Arrange + var json = JsonSerializer.Deserialize("0"); + var metadata = new Dictionary + { + { "zero", json } + }; + + // Act + var result = metadata.ToPrimitiveTypes(); + + // Assert + Assert.IsType(result["zero"]); + Assert.Equal(0.0, result["zero"]); + } +} From 618ee919985500fcdf969470f88fb42b9fd2c667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <2493377+askpt@users.noreply.github.com> Date: Tue, 9 Dec 2025 18:40:54 +0000 Subject: [PATCH 3/4] fix: Simplify ExtractPrimitiveValue method by removing unused key parameter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: André Silva <2493377+askpt@users.noreply.github.com> --- .../Extensions/MetadataExtensions.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/OpenFeature.Providers.Ofrep/Extensions/MetadataExtensions.cs b/src/OpenFeature.Providers.Ofrep/Extensions/MetadataExtensions.cs index 08fbcc75..5a7bc668 100644 --- a/src/OpenFeature.Providers.Ofrep/Extensions/MetadataExtensions.cs +++ b/src/OpenFeature.Providers.Ofrep/Extensions/MetadataExtensions.cs @@ -16,17 +16,16 @@ internal static Dictionary ToPrimitiveTypes(this Dictionary kvp.Key, - kvp => ExtractPrimitiveValue(kvp.Key, kvp.Value) + kvp => ExtractPrimitiveValue(kvp.Value) ); } /// /// Extracts a primitive value from an object that may be a JsonElement. /// - /// The key for error reporting purposes. /// The value to extract. /// The extracted primitive value. - private static object ExtractPrimitiveValue(string key, object value) + private static object ExtractPrimitiveValue(object value) { if (value is JsonElement jsonElement) { From c6bcf4f0442d7c7e9d81694ac818666d15b65545 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <2493377+askpt@users.noreply.github.com> Date: Tue, 9 Dec 2025 18:56:25 +0000 Subject: [PATCH 4/4] feat: Enhance ToPrimitiveTypes method to handle JSON objects and arrays MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: André Silva <2493377+askpt@users.noreply.github.com> --- .../Extensions/MetadataExtensions.cs | 35 +++++- .../Extensions/MetadataExtensionsTest.cs | 113 ++++++++++++++++-- 2 files changed, 140 insertions(+), 8 deletions(-) diff --git a/src/OpenFeature.Providers.Ofrep/Extensions/MetadataExtensions.cs b/src/OpenFeature.Providers.Ofrep/Extensions/MetadataExtensions.cs index 5a7bc668..01a01bf0 100644 --- a/src/OpenFeature.Providers.Ofrep/Extensions/MetadataExtensions.cs +++ b/src/OpenFeature.Providers.Ofrep/Extensions/MetadataExtensions.cs @@ -35,11 +35,44 @@ private static object ExtractPrimitiveValue(object value) JsonValueKind.Number => jsonElement.GetDouble(), JsonValueKind.True => true, JsonValueKind.False => false, - _ => value.ToString() ?? string.Empty + JsonValueKind.Object => ConvertJsonObject(jsonElement), + JsonValueKind.Array => ConvertJsonArray(jsonElement), + JsonValueKind.Null => string.Empty, + _ => jsonElement.GetRawText() }; } // If it's already a primitive type, return as-is return value; } + + /// + /// Converts a JsonElement object to a Dictionary with primitive values. + /// + /// The JSON element containing the object. + /// A dictionary with string keys and primitive values. + private static Dictionary ConvertJsonObject(JsonElement jsonElement) + { + var result = new Dictionary(); + foreach (var property in jsonElement.EnumerateObject()) + { + result[property.Name] = ExtractPrimitiveValue(property.Value); + } + return result; + } + + /// + /// Converts a JsonElement array to a List with primitive values. + /// + /// The JSON element containing the array. + /// A list with primitive values. + private static List ConvertJsonArray(JsonElement jsonElement) + { + var result = new List(); + foreach (var element in jsonElement.EnumerateArray()) + { + result.Add(ExtractPrimitiveValue(element)); + } + return result; + } } diff --git a/test/OpenFeature.Providers.Ofrep.Test/Extensions/MetadataExtensionsTest.cs b/test/OpenFeature.Providers.Ofrep.Test/Extensions/MetadataExtensionsTest.cs index 8b4e044f..e1f66c53 100644 --- a/test/OpenFeature.Providers.Ofrep.Test/Extensions/MetadataExtensionsTest.cs +++ b/test/OpenFeature.Providers.Ofrep.Test/Extensions/MetadataExtensionsTest.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using OpenFeature.Model; using OpenFeature.Providers.Ofrep.Extensions; using Xunit; @@ -115,10 +116,10 @@ public void ToPrimitiveTypes_ShouldHandleNullJsonElement_AsEmptyString() } [Fact] - public void ToPrimitiveTypes_ShouldHandleJsonElementObject_AsString() + public void ToPrimitiveTypes_ShouldHandleJsonElementObject_AsDictionary() { // Arrange - var json = JsonSerializer.Deserialize("{\"nested\": \"value\"}"); + var json = JsonSerializer.Deserialize("{\"nested\": \"value\", \"number\": 42}"); var metadata = new Dictionary { { "objectKey", json } @@ -128,12 +129,36 @@ public void ToPrimitiveTypes_ShouldHandleJsonElementObject_AsString() var result = metadata.ToPrimitiveTypes(); // Assert - Assert.IsType(result["objectKey"]); - Assert.Contains("nested", (string)result["objectKey"]); + Assert.IsType>(result["objectKey"]); + var dict = (Dictionary)result["objectKey"]; + Assert.Equal(2, dict.Count); + Assert.Equal("value", dict["nested"]); + Assert.Equal(42.0, dict["number"]); } [Fact] - public void ToPrimitiveTypes_ShouldHandleJsonElementArray_AsString() + public void ToPrimitiveTypes_ShouldHandleNestedJsonObject() + { + // Arrange + var json = JsonSerializer.Deserialize("{\"outer\": {\"inner\": \"value\"}}"); + var metadata = new Dictionary + { + { "objectKey", json } + }; + + // Act + var result = metadata.ToPrimitiveTypes(); + + // Assert + Assert.IsType>(result["objectKey"]); + var outerDict = (Dictionary)result["objectKey"]; + Assert.IsType>(outerDict["outer"]); + var innerDict = (Dictionary)outerDict["outer"]; + Assert.Equal("value", innerDict["inner"]); + } + + [Fact] + public void ToPrimitiveTypes_ShouldHandleJsonElementArray_AsList() { // Arrange var json = JsonSerializer.Deserialize("[1, 2, 3]"); @@ -146,8 +171,82 @@ public void ToPrimitiveTypes_ShouldHandleJsonElementArray_AsString() var result = metadata.ToPrimitiveTypes(); // Assert - Assert.IsType(result["arrayKey"]); - Assert.Contains("1", (string)result["arrayKey"]); + Assert.IsType>(result["arrayKey"]); + var list = (List)result["arrayKey"]; + Assert.Equal(3, list.Count); + Assert.Equal(1.0, list[0]); + Assert.Equal(2.0, list[1]); + Assert.Equal(3.0, list[2]); + } + + [Fact] + public void ToPrimitiveTypes_ShouldHandleMixedTypeArray() + { + // Arrange + var json = JsonSerializer.Deserialize("[\"hello\", 42, true, null]"); + var metadata = new Dictionary + { + { "arrayKey", json } + }; + + // Act + var result = metadata.ToPrimitiveTypes(); + + // Assert + Assert.IsType>(result["arrayKey"]); + var list = (List)result["arrayKey"]; + Assert.Equal(4, list.Count); + Assert.Equal("hello", list[0]); + Assert.Equal(42.0, list[1]); + Assert.Equal(true, list[2]); + Assert.Equal(string.Empty, list[3]); // null converts to empty string + } + + [Fact] + public void ToPrimitiveTypes_ShouldHandleNestedArrays() + { + // Arrange + var json = JsonSerializer.Deserialize("[[1, 2], [3, 4]]"); + var metadata = new Dictionary + { + { "arrayKey", json } + }; + + // Act + var result = metadata.ToPrimitiveTypes(); + + // Assert + Assert.IsType>(result["arrayKey"]); + var outerList = (List)result["arrayKey"]; + Assert.Equal(2, outerList.Count); + Assert.IsType>(outerList[0]); + var innerList = (List)outerList[0]; + Assert.Equal(1.0, innerList[0]); + Assert.Equal(2.0, innerList[1]); + } + + [Fact] + public void ToPrimitiveTypes_ShouldHandleArrayOfObjects() + { + // Arrange + var json = JsonSerializer.Deserialize("[{\"name\": \"Alice\"}, {\"name\": \"Bob\"}]"); + var metadata = new Dictionary + { + { "arrayKey", json } + }; + + // Act + var result = metadata.ToPrimitiveTypes(); + + // Assert + Assert.IsType>(result["arrayKey"]); + var list = (List)result["arrayKey"]; + Assert.Equal(2, list.Count); + Assert.IsType>(list[0]); + var firstItem = (Dictionary)list[0]; + Assert.Equal("Alice", firstItem["name"]); + var secondItem = (Dictionary)list[1]; + Assert.Equal("Bob", secondItem["name"]); } [Fact]