Skip to content

Commit

Permalink
feat(gofeatureflag): Support flag metadata
Browse files Browse the repository at this point in the history
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>
  • Loading branch information
thomaspoignant committed Feb 5, 2025
1 parent e603c08 commit 4f47a73
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Threading.Tasks;
using OpenFeature.Constant;
using OpenFeature.Contrib.Providers.GOFeatureFlag.exception;
using OpenFeature.Contrib.Providers.GOFeatureFlag.helpers;
using OpenFeature.Model;

namespace OpenFeature.Contrib.Providers.GOFeatureFlag
Expand Down Expand Up @@ -97,7 +98,8 @@ public override async Task<ResolutionDetails<bool>> ResolveBooleanValueAsync(str
{
var resp = await CallApi(flagKey, defaultValue, context);
return new ResolutionDetails<bool>(flagKey, bool.Parse(resp.value.ToString()), ErrorType.None,
resp.reason, resp.variationType);
resp.reason, resp.variationType, null,
resp.metadata == null ? null : new ImmutableMetadata(resp.metadata));
}
catch (FormatException e)
{
Expand All @@ -121,7 +123,8 @@ public override async Task<ResolutionDetails<bool>> ResolveBooleanValueAsync(str
/// <exception cref="FlagNotFoundError">If the flag does not exists</exception>
/// <exception cref="GeneralError">If an unknown error happen</exception>
/// <exception cref="FlagDisabled">If the flag is disabled</exception>
public override async Task<ResolutionDetails<string>> ResolveStringValueAsync(string flagKey, string defaultValue,
public override async Task<ResolutionDetails<string>> ResolveStringValueAsync(string flagKey,
string defaultValue,
EvaluationContext context = null, CancellationToken cancellationToken = default)
{
try
Expand All @@ -130,7 +133,7 @@ public override async Task<ResolutionDetails<string>> ResolveStringValueAsync(st
if (!(resp.value is JsonElement element && element.ValueKind == JsonValueKind.String))
throw new TypeMismatchError($"flag value {flagKey} had unexpected type");
return new ResolutionDetails<string>(flagKey, resp.value.ToString(), ErrorType.None, resp.reason,
resp.variationType);
resp.variationType, null, resp.metadata == null ? null : new ImmutableMetadata(resp.metadata));
}
catch (FormatException e)
{
Expand Down Expand Up @@ -161,7 +164,8 @@ public override async Task<ResolutionDetails<int>> ResolveIntegerValueAsync(stri
{
var resp = await CallApi(flagKey, defaultValue, context);
return new ResolutionDetails<int>(flagKey, int.Parse(resp.value.ToString()), ErrorType.None,
resp.reason, resp.variationType);
resp.reason, resp.variationType, null,
resp.metadata == null ? null : new ImmutableMetadata(resp.metadata));
}
catch (FormatException e)
{
Expand All @@ -185,15 +189,17 @@ public override async Task<ResolutionDetails<int>> ResolveIntegerValueAsync(stri
/// <exception cref="FlagNotFoundError">If the flag does not exists</exception>
/// <exception cref="GeneralError">If an unknown error happen</exception>
/// <exception cref="FlagDisabled">If the flag is disabled</exception>
public override async Task<ResolutionDetails<double>> ResolveDoubleValueAsync(string flagKey, double defaultValue,
public override async Task<ResolutionDetails<double>> ResolveDoubleValueAsync(string flagKey,
double defaultValue,
EvaluationContext context = null, CancellationToken cancellationToken = default)
{
try
{
var resp = await CallApi(flagKey, defaultValue, context);
return new ResolutionDetails<double>(flagKey,
double.Parse(resp.value.ToString(), CultureInfo.InvariantCulture), ErrorType.None,
resp.reason, resp.variationType);
resp.reason, resp.variationType, null,
resp.metadata == null ? null : new ImmutableMetadata(resp.metadata));
}
catch (FormatException e)
{
Expand All @@ -217,7 +223,8 @@ public override async Task<ResolutionDetails<double>> ResolveDoubleValueAsync(st
/// <exception cref="FlagNotFoundError">If the flag does not exists</exception>
/// <exception cref="GeneralError">If an unknown error happen</exception>
/// <exception cref="FlagDisabled">If the flag is disabled</exception>
public override async Task<ResolutionDetails<Value>> ResolveStructureValueAsync(string flagKey, Value defaultValue,
public override async Task<ResolutionDetails<Value>> ResolveStructureValueAsync(string flagKey,
Value defaultValue,
EvaluationContext context = null, CancellationToken cancellationToken = default)
{
try
Expand All @@ -227,7 +234,7 @@ public override async Task<ResolutionDetails<Value>> ResolveStructureValueAsync(
{
var value = ConvertValue((JsonElement)resp.value);
return new ResolutionDetails<Value>(flagKey, value, ErrorType.None, resp.reason,
resp.variationType);
resp.variationType, null, resp.metadata == null ? null : new ImmutableMetadata(resp.metadata));
}

throw new TypeMismatchError($"flag value {flagKey} had unexpected type");
Expand Down Expand Up @@ -285,6 +292,7 @@ private async Task<GoFeatureFlagResponse> CallApi<T>(string flagKey, T defaultVa
if ("FLAG_NOT_FOUND".Equals(goffResp.errorCode))
throw new FlagNotFoundError($"flag {flagKey} was not found in your configuration");

if (goffResp.metadata != null) goffResp.metadata = DictionaryConverter.ConvertDictionary(goffResp.metadata);
return goffResp;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Collections.Generic;

namespace OpenFeature.Contrib.Providers.GOFeatureFlag
{
/// <summary>
Expand Down Expand Up @@ -39,5 +41,10 @@ public class GoFeatureFlagResponse
/// value contains the result of the flag.
/// </summary>
public object value { get; set; }

/// <summary>
/// metadata contains the metadata of the flag.
/// </summary>
public Dictionary<string, object> metadata { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;

namespace OpenFeature.Contrib.Providers.GOFeatureFlag.helpers
{
/// <summary>
/// DictionaryConverter is converting a json Dictionary to a Dictionary<string, object>

Check warning on line 8 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / e2e

XML comment has badly formed XML -- 'The character(s) ',' cannot be used at this location.'

Check warning on line 8 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / e2e

XML comment has badly formed XML -- 'The character(s) ',' cannot be used at this location.'

Check warning on line 8 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

XML comment has badly formed XML -- 'The character(s) ',' cannot be used at this location.'

Check warning on line 8 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

XML comment has badly formed XML -- 'The character(s) ',' cannot be used at this location.'

Check warning on line 8 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

XML comment has badly formed XML -- 'The character(s) ',' cannot be used at this location.'

Check warning on line 8 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

XML comment has badly formed XML -- 'The character(s) ',' cannot be used at this location.'

Check failure on line 8 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / packaging

XML comment has badly formed XML -- 'The character(s) ',' cannot be used at this location.'

Check failure on line 8 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / packaging

XML comment has badly formed XML -- 'The character(s) ',' cannot be used at this location.'
/// </summary>

Check warning on line 9 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / e2e

XML comment has badly formed XML -- 'End tag 'summary' does not match the start tag 'string'.'

Check warning on line 9 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / e2e

XML comment has badly formed XML -- 'End tag 'summary' does not match the start tag 'string'.'

Check warning on line 9 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

XML comment has badly formed XML -- 'End tag 'summary' does not match the start tag 'string'.'

Check warning on line 9 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

XML comment has badly formed XML -- 'End tag 'summary' does not match the start tag 'string'.'

Check warning on line 9 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

XML comment has badly formed XML -- 'End tag 'summary' does not match the start tag 'string'.'

Check warning on line 9 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

XML comment has badly formed XML -- 'End tag 'summary' does not match the start tag 'string'.'

Check failure on line 9 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / packaging

XML comment has badly formed XML -- 'End tag 'summary' does not match the start tag 'string'.'

Check failure on line 9 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / packaging

XML comment has badly formed XML -- 'End tag 'summary' does not match the start tag 'string'.'
public static class DictionaryConverter

Check warning on line 10 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / e2e

XML comment has badly formed XML -- 'Expected an end tag for element 'summary'.'

Check warning on line 10 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / e2e

XML comment has badly formed XML -- 'Expected an end tag for element 'summary'.'

Check warning on line 10 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

XML comment has badly formed XML -- 'Expected an end tag for element 'summary'.'

Check warning on line 10 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

XML comment has badly formed XML -- 'Expected an end tag for element 'summary'.'

Check warning on line 10 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

XML comment has badly formed XML -- 'Expected an end tag for element 'summary'.'

Check warning on line 10 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

XML comment has badly formed XML -- 'Expected an end tag for element 'summary'.'

Check failure on line 10 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / packaging

XML comment has badly formed XML -- 'Expected an end tag for element 'summary'.'

Check failure on line 10 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / packaging

XML comment has badly formed XML -- 'Expected an end tag for element 'summary'.'
{
/// <summary>
/// Function that convert the dictionary to a Dictionary<string, object>

Check warning on line 13 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / e2e

XML comment has badly formed XML -- 'The character(s) ',' cannot be used at this location.'

Check warning on line 13 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / e2e

XML comment has badly formed XML -- 'The character(s) ',' cannot be used at this location.'

Check warning on line 13 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

XML comment has badly formed XML -- 'The character(s) ',' cannot be used at this location.'

Check warning on line 13 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

XML comment has badly formed XML -- 'The character(s) ',' cannot be used at this location.'

Check warning on line 13 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

XML comment has badly formed XML -- 'The character(s) ',' cannot be used at this location.'

Check warning on line 13 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

XML comment has badly formed XML -- 'The character(s) ',' cannot be used at this location.'

Check failure on line 13 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / packaging

XML comment has badly formed XML -- 'The character(s) ',' cannot be used at this location.'

Check failure on line 13 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / packaging

XML comment has badly formed XML -- 'The character(s) ',' cannot be used at this location.'
/// </summary>

Check warning on line 14 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / e2e

XML comment has badly formed XML -- 'End tag 'summary' does not match the start tag 'string'.'

Check warning on line 14 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

XML comment has badly formed XML -- 'End tag 'summary' does not match the start tag 'string'.'

Check warning on line 14 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

XML comment has badly formed XML -- 'End tag 'summary' does not match the start tag 'string'.'

Check failure on line 14 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / packaging

XML comment has badly formed XML -- 'End tag 'summary' does not match the start tag 'string'.'
/// <param name="inputDictionary"></param>
/// <returns></returns>
public static Dictionary<string, object> ConvertDictionary(Dictionary<string, object> inputDictionary)

Check warning on line 17 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / e2e

XML comment has badly formed XML -- 'Expected an end tag for element 'summary'.'

Check warning on line 17 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

XML comment has badly formed XML -- 'Expected an end tag for element 'summary'.'

Check warning on line 17 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

XML comment has badly formed XML -- 'Expected an end tag for element 'summary'.'

Check failure on line 17 in src/OpenFeature.Contrib.Providers.GOFeatureFlag/helpers/DictionaryConverter.cs

View workflow job for this annotation

GitHub Actions / packaging

XML comment has badly formed XML -- 'Expected an end tag for element 'summary'.'
{
return inputDictionary.ToDictionary(
kvp => kvp.Key,
kvp => ConvertValue(kvp.Value)
);
}

/// <summary>
/// Function that convert a value to a object
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static object ConvertValue(object value)
{
if (value is JsonElement jsonElement)
switch (jsonElement.ValueKind)
{
case JsonValueKind.String:
return jsonElement.GetString();
case JsonValueKind.Number:
if (jsonElement.TryGetInt32(out var intValue)) return intValue;

if (jsonElement.TryGetDouble(out var doubleValue)) return doubleValue;
return jsonElement.GetRawText(); // Fallback to string if not int or double
case JsonValueKind.True:
return true;
case JsonValueKind.False:
return false;
case JsonValueKind.Null:
return null;
case JsonValueKind.Object:
return ConvertDictionary(
JsonSerializer
.Deserialize<Dictionary<string, object>>(jsonElement
.GetRawText())); //Recursive for nested objects
case JsonValueKind.Array:
var array = new List<object>();
foreach (var element in jsonElement.EnumerateArray()) array.Add(ConvertValue(element));

return array;
default:
return jsonElement.GetRawText(); // Handle other types as needed
}

return value; // Return original value if not a JsonElement
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ private static HttpMessageHandler InitMock()
"{\"trackEvents\":true,\"variationType\":\"True\",\"failed\":false,\"version\":\"\",\"reason\":\"CUSTOM_REASON\",\"errorCode\":\"\",\"value\":true}");
mockHttp.When($"{prefixEval}does_not_exists{suffixEval}").Respond(mediaType,
"{\"trackEvents\":true,\"variationType\":\"defaultSdk\",\"failed\":true,\"version\":\"\",\"reason\":\"ERROR\",\"errorCode\":\"FLAG_NOT_FOUND\",\"value\":\"\"}");
mockHttp.When($"{prefixEval}integer_with_metadata{suffixEval}").Respond(mediaType,
"{\"trackEvents\":true,\"variationType\":\"True\",\"failed\":false,\"version\":\"\",\"reason\":\"TARGETING_MATCH\",\"errorCode\":\"\",\"value\":100, \"metadata\": {\"key1\": \"key1\", \"key2\":1, \"key3\":1.345, \"key4\":true}}");
return mockHttp;
}

Expand Down Expand Up @@ -560,4 +562,28 @@ public async Task should_use_object_default_value_if_flag_not_found()
Assert.Equal(ErrorType.FlagNotFound, res.Result.ErrorType);
Assert.Equal("flag does_not_exists was not found in your configuration", res.Result.ErrorMessage);
}
}

[Fact]
public async Task should_resolve_a_flag_with_metadata()
{
var g = new GoFeatureFlagProvider(new GoFeatureFlagProviderOptions
{
Endpoint = baseUrl,
HttpMessageHandler = _mockHttp,
Timeout = new TimeSpan(1000 * TimeSpan.TicksPerMillisecond)
});
await Api.Instance.SetProviderAsync(g);
var client = Api.Instance.GetClient("test-client");
var res = client.GetIntegerDetailsAsync("integer_with_metadata", 1200, _defaultEvaluationCtx);
Assert.NotNull(res.Result);
Assert.Equal(100, res.Result.Value);
Assert.Equal(ErrorType.None, res.Result.ErrorType);
Assert.Equal(Reason.TargetingMatch, res.Result.Reason);
Assert.Equal("True", res.Result.Variant);
Assert.NotNull(res.Result.FlagMetadata);
Assert.Equal("key1", res.Result.FlagMetadata.GetString("key1"));
Assert.Equal(1, res.Result.FlagMetadata.GetInt("key2"));
Assert.Equal(1.345, res.Result.FlagMetadata.GetDouble("key3"));
Assert.True(res.Result.FlagMetadata.GetBool("key4"));
}
}

0 comments on commit 4f47a73

Please sign in to comment.