Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ jobs:
- name: Test
run: dotnet test -c Release --no-build --logger GitHubActions

- name: aot-publish test
run: |
dotnet publish ./samples/AspNetCore/Samples.AspNetCore.csproj
Comment thread
toddbaert marked this conversation as resolved.

packaging:
needs: build

Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/dotnet-format.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,6 @@ jobs:
global-json-file: global.json

- name: dotnet format
run: dotnet format --verify-no-changes OpenFeature.slnx
run: |
# Exclude diagnostics to work around dotnet-format issue, see https://github.com/dotnet/sdk/issues/50012
dotnet format --verify-no-changes OpenFeature.slnx --exclude-diagnostics IL2026 --exclude-diagnostics IL3050
38 changes: 36 additions & 2 deletions samples/AspNetCore/Program.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Mvc;
using OpenFeature;
using OpenFeature.DependencyInjection.Providers.Memory;
using OpenFeature.Hooks;
using OpenFeature.Model;
using OpenFeature.Providers.Memory;
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

var builder = WebApplication.CreateBuilder(args);
var builder = WebApplication.CreateSlimBuilder(args);

// Add services to the container.
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
});

builder.Services.AddProblemDetails();

// Configure OpenTelemetry
Expand Down Expand Up @@ -40,6 +47,14 @@
{
"welcome-message", new Flag<bool>(
new Dictionary<string, bool> { { "show", true }, { "hide", false } }, "show")
},
{
"test-config", new Flag<Value>(new Dictionary<string, Value>()
{
{ "enable", new Value(Structure.Builder().Set(nameof(TestConfig.Threshold), 100).Build()) },
{ "half", new Value(Structure.Builder().Set(nameof(TestConfig.Threshold), 50).Build()) },
{ "disable", new Value(Structure.Builder().Set(nameof(TestConfig.Threshold), 0).Build()) }
}, "disable")
}
});
});
Expand All @@ -60,5 +75,24 @@

return TypedResults.Ok("Hello world!");
});
app.MapGet("/test-config", async ([FromServices] IFeatureClient featureClient) =>
{
var testConfigValue = await featureClient.GetObjectValueAsync("test-config",
new Value(Structure.Builder().Set("Threshold", 50).Build())
);
var json = JsonSerializer.Serialize(testConfigValue, AppJsonSerializerContext.Default.Value);
var config = JsonSerializer.Deserialize(json, AppJsonSerializerContext.Default.TestConfig);
return Results.Ok(config);
});

app.Run();


public class TestConfig
{
public int Threshold { get; set; } = 10;
}

[JsonSerializable(typeof(TestConfig))]
[JsonSerializable(typeof(Value))]
public partial class AppJsonSerializerContext : JsonSerializerContext;
2 changes: 2 additions & 0 deletions samples/AspNetCore/Samples.AspNetCore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

<PropertyGroup>
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
<PublishAot>true</PublishAot>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>

<ItemGroup>
Expand Down
4 changes: 4 additions & 0 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
<Project>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), 'OpenFeature.slnx'))\build\Common.prod.props" />
<PropertyGroup>
<!-- Enable native AOT related analyzers https://learn.microsoft.com/dotnet/core/deploying/native-aot/#aot-compatibility-analyzers -->
<IsAotCompatible>$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))</IsAotCompatible>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,11 @@ public static OpenFeatureBuilder AddPolicyName(this OpenFeatureBuilder builder,
/// <param name="builder">The <see cref="OpenFeatureBuilder"/> instance.</param>
/// <param name="implementationFactory">Optional factory for controlling how <typeparamref name="THook"/> will be created in the DI container.</param>
/// <returns>The <see cref="OpenFeatureBuilder"/> instance.</returns>
public static OpenFeatureBuilder AddHook<THook>(this OpenFeatureBuilder builder, Func<IServiceProvider, THook>? implementationFactory = null)
public static OpenFeatureBuilder AddHook<
#if NET
[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
#endif
THook>(this OpenFeatureBuilder builder, Func<IServiceProvider, THook>? implementationFactory = null)
where THook : Hook
{
return builder.AddHook(typeof(THook).Name, implementationFactory);
Expand All @@ -285,7 +289,11 @@ public static OpenFeatureBuilder AddHook<THook>(this OpenFeatureBuilder builder,
/// <param name="builder">The <see cref="OpenFeatureBuilder"/> instance.</param>
/// <param name="hook">Instance of Hook to inject into the OpenFeature context.</param>
/// <returns>The <see cref="OpenFeatureBuilder"/> instance.</returns>
public static OpenFeatureBuilder AddHook<THook>(this OpenFeatureBuilder builder, THook hook)
public static OpenFeatureBuilder AddHook<
#if NET
[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
#endif
THook>(this OpenFeatureBuilder builder, THook hook)
where THook : Hook
{
return builder.AddHook(typeof(THook).Name, hook);
Expand All @@ -299,7 +307,11 @@ public static OpenFeatureBuilder AddHook<THook>(this OpenFeatureBuilder builder,
/// <param name="hookName">The name of the <see cref="Hook"/> that is being added.</param>
/// <param name="hook">Instance of Hook to inject into the OpenFeature context.</param>
/// <returns>The <see cref="OpenFeatureBuilder"/> instance.</returns>
public static OpenFeatureBuilder AddHook<THook>(this OpenFeatureBuilder builder, string hookName, THook hook)
public static OpenFeatureBuilder AddHook<
#if NET
[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
#endif
THook>(this OpenFeatureBuilder builder, string hookName, THook hook)
where THook : Hook
{
return builder.AddHook(hookName, _ => hook);
Expand All @@ -313,7 +325,12 @@ public static OpenFeatureBuilder AddHook<THook>(this OpenFeatureBuilder builder,
/// <param name="hookName">The name of the <see cref="Hook"/> that is being added.</param>
/// <param name="implementationFactory">Optional factory for controlling how <typeparamref name="THook"/> will be created in the DI container.</param>
/// <returns>The <see cref="OpenFeatureBuilder"/> instance.</returns>
public static OpenFeatureBuilder AddHook<THook>(this OpenFeatureBuilder builder, string hookName, Func<IServiceProvider, THook>? implementationFactory = null)
public static OpenFeatureBuilder AddHook<
#if NET
[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
#endif
THook>
(this OpenFeatureBuilder builder, string hookName, Func<IServiceProvider, THook>? implementationFactory = null)
where THook : Hook
{
builder.Services.PostConfigure<OpenFeatureOptions>(options => options.AddHookName(hookName));
Expand Down
7 changes: 6 additions & 1 deletion src/OpenFeature/Model/ValueJsonConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@

namespace OpenFeature.Model;

internal sealed class ValueJsonConverter : JsonConverter<Value>
/// <summary>
/// A <see cref="JsonConverter{T}"/> for <see cref="Value"/> for Json serialization
/// </summary>
public sealed class ValueJsonConverter : JsonConverter<Value>
{
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, Value value, JsonSerializerOptions options) =>
WriteJsonValue(value, writer);

/// <inheritdoc />
public override Value Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
ReadJsonValue(ref reader);

Expand Down
26 changes: 26 additions & 0 deletions test/OpenFeature.Tests/StructureTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Collections.Immutable;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using OpenFeature.Model;

namespace OpenFeature.Tests;
Expand Down Expand Up @@ -125,6 +126,15 @@ public void JsonSerializeTest(Value value, string expectedJson)
Assert.True(JsonNode.DeepEquals(expectJsonNode, serializedJsonNode));
}

[Theory]
[MemberData(nameof(JsonSerializeTestData))]
public void JsonSerializeWithGeneratorTest(Value value, string expectedJson)
{
var serializedJsonNode = JsonSerializer.SerializeToNode(value, ValueJsonSerializerContext.Default.Value);
var expectJsonNode = JsonNode.Parse(expectedJson);
Assert.True(JsonNode.DeepEquals(expectJsonNode, serializedJsonNode));
}

[Theory]
[MemberData(nameof(JsonSerializeTestData))]
public void JsonDeserializeTest(Value value, string expectedJson)
Expand All @@ -135,6 +145,17 @@ public void JsonDeserializeTest(Value value, string expectedJson)
Assert.True(JsonNode.DeepEquals(expectJsonNode, serializedJsonNode));
}

[Theory]
[MemberData(nameof(JsonSerializeTestData))]
public void JsonDeserializeWithGeneratorTest(Value value, string expectedJson)
{
var serializedJsonNode = JsonSerializer.SerializeToNode(value, ValueJsonSerializerContext.Default.Value);
var expectValue = JsonSerializer.Deserialize(expectedJson, ValueJsonSerializerContext.Default.Value);
Assert.NotNull(expectValue);
var expectJsonNode = JsonSerializer.SerializeToNode(expectValue, ValueJsonSerializerContext.Default.Value);
Assert.True(JsonNode.DeepEquals(expectJsonNode, serializedJsonNode));
}

public static IEnumerable<object[]> JsonSerializeTestData()
{
yield return [new Value("test"), "\"test\""];
Expand Down Expand Up @@ -178,3 +199,8 @@ public static IEnumerable<object[]> JsonSerializeTestData()
];
}
}

[JsonSerializable(typeof(Value))]
public partial class ValueJsonSerializerContext : JsonSerializerContext
{
}