Skip to content

Commit e03aeba

Browse files
authored
fix: expose ValueJsonConverter for generator support and add JsonSourceGenerator test cases (#537)
* feat: expose ValueJsonConverter and add JsonSourceGenerator test cases Signed-off-by: Weihan Li <[email protected]> * style: apply dotnet-format Signed-off-by: Weihan Li <[email protected]> * feat: let the sample aot safe Signed-off-by: Weihan Li <[email protected]> * feat: enable aot analyzer and add necessary annotation Signed-off-by: Weihan Li <[email protected]> * feat: update aot support for sample project Signed-off-by: Weihan Li <[email protected]> * build: fix aot publish error Signed-off-by: Weihan Li <[email protected]> * build: simplify the PublishAot error workaround Signed-off-by: Weihan Li <[email protected]> * build: fix format action error Signed-off-by: Weihan Li <[email protected]> * sample: update sample usage Signed-off-by: Weihan Li <[email protected]> --------- Signed-off-by: Weihan Li <[email protected]>
1 parent c0eb12a commit e03aeba

File tree

8 files changed

+102
-8
lines changed

8 files changed

+102
-8
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ jobs:
5353
- name: Test
5454
run: dotnet test -c Release --no-build --logger GitHubActions
5555

56+
- name: aot-publish test
57+
run: |
58+
dotnet publish ./samples/AspNetCore/Samples.AspNetCore.csproj
59+
5660
packaging:
5761
needs: build
5862

.github/workflows/dotnet-format.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,6 @@ jobs:
2323
global-json-file: global.json
2424

2525
- name: dotnet format
26-
run: dotnet format --verify-no-changes OpenFeature.slnx
26+
run: |
27+
# Exclude diagnostics to work around dotnet-format issue, see https://github.com/dotnet/sdk/issues/50012
28+
dotnet format --verify-no-changes OpenFeature.slnx --exclude-diagnostics IL2026 --exclude-diagnostics IL3050

samples/AspNetCore/Program.cs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
1+
using System.Text.Json;
2+
using System.Text.Json.Serialization;
13
using Microsoft.AspNetCore.Mvc;
24
using OpenFeature;
35
using OpenFeature.DependencyInjection.Providers.Memory;
46
using OpenFeature.Hooks;
7+
using OpenFeature.Model;
58
using OpenFeature.Providers.Memory;
6-
using OpenTelemetry.Logs;
79
using OpenTelemetry.Metrics;
810
using OpenTelemetry.Resources;
911
using OpenTelemetry.Trace;
1012

11-
var builder = WebApplication.CreateBuilder(args);
13+
var builder = WebApplication.CreateSlimBuilder(args);
1214

1315
// Add services to the container.
16+
builder.Services.ConfigureHttpJsonOptions(options =>
17+
{
18+
options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
19+
});
20+
1421
builder.Services.AddProblemDetails();
1522

1623
// Configure OpenTelemetry
@@ -40,6 +47,14 @@
4047
{
4148
"welcome-message", new Flag<bool>(
4249
new Dictionary<string, bool> { { "show", true }, { "hide", false } }, "show")
50+
},
51+
{
52+
"test-config", new Flag<Value>(new Dictionary<string, Value>()
53+
{
54+
{ "enable", new Value(Structure.Builder().Set(nameof(TestConfig.Threshold), 100).Build()) },
55+
{ "half", new Value(Structure.Builder().Set(nameof(TestConfig.Threshold), 50).Build()) },
56+
{ "disable", new Value(Structure.Builder().Set(nameof(TestConfig.Threshold), 0).Build()) }
57+
}, "disable")
4358
}
4459
});
4560
});
@@ -60,5 +75,24 @@
6075

6176
return TypedResults.Ok("Hello world!");
6277
});
78+
app.MapGet("/test-config", async ([FromServices] IFeatureClient featureClient) =>
79+
{
80+
var testConfigValue = await featureClient.GetObjectValueAsync("test-config",
81+
new Value(Structure.Builder().Set("Threshold", 50).Build())
82+
);
83+
var json = JsonSerializer.Serialize(testConfigValue, AppJsonSerializerContext.Default.Value);
84+
var config = JsonSerializer.Deserialize(json, AppJsonSerializerContext.Default.TestConfig);
85+
return Results.Ok(config);
86+
});
6387

6488
app.Run();
89+
90+
91+
public class TestConfig
92+
{
93+
public int Threshold { get; set; } = 10;
94+
}
95+
96+
[JsonSerializable(typeof(TestConfig))]
97+
[JsonSerializable(typeof(Value))]
98+
public partial class AppJsonSerializerContext : JsonSerializerContext;

samples/AspNetCore/Samples.AspNetCore.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
<PropertyGroup>
44
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
5+
<PublishAot>true</PublishAot>
6+
<InvariantGlobalization>true</InvariantGlobalization>
57
</PropertyGroup>
68

79
<ItemGroup>

src/Directory.Build.props

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
<Project>
22
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), 'OpenFeature.slnx'))\build\Common.prod.props" />
3+
<PropertyGroup>
4+
<!-- Enable native AOT related analyzers https://learn.microsoft.com/dotnet/core/deploying/native-aot/#aot-compatibility-analyzers -->
5+
<IsAotCompatible>$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))</IsAotCompatible>
6+
</PropertyGroup>
37
</Project>

src/OpenFeature.DependencyInjection/OpenFeatureBuilderExtensions.cs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,11 @@ public static OpenFeatureBuilder AddPolicyName(this OpenFeatureBuilder builder,
272272
/// <param name="builder">The <see cref="OpenFeatureBuilder"/> instance.</param>
273273
/// <param name="implementationFactory">Optional factory for controlling how <typeparamref name="THook"/> will be created in the DI container.</param>
274274
/// <returns>The <see cref="OpenFeatureBuilder"/> instance.</returns>
275-
public static OpenFeatureBuilder AddHook<THook>(this OpenFeatureBuilder builder, Func<IServiceProvider, THook>? implementationFactory = null)
275+
public static OpenFeatureBuilder AddHook<
276+
#if NET
277+
[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
278+
#endif
279+
THook>(this OpenFeatureBuilder builder, Func<IServiceProvider, THook>? implementationFactory = null)
276280
where THook : Hook
277281
{
278282
return builder.AddHook(typeof(THook).Name, implementationFactory);
@@ -285,7 +289,11 @@ public static OpenFeatureBuilder AddHook<THook>(this OpenFeatureBuilder builder,
285289
/// <param name="builder">The <see cref="OpenFeatureBuilder"/> instance.</param>
286290
/// <param name="hook">Instance of Hook to inject into the OpenFeature context.</param>
287291
/// <returns>The <see cref="OpenFeatureBuilder"/> instance.</returns>
288-
public static OpenFeatureBuilder AddHook<THook>(this OpenFeatureBuilder builder, THook hook)
292+
public static OpenFeatureBuilder AddHook<
293+
#if NET
294+
[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
295+
#endif
296+
THook>(this OpenFeatureBuilder builder, THook hook)
289297
where THook : Hook
290298
{
291299
return builder.AddHook(typeof(THook).Name, hook);
@@ -299,7 +307,11 @@ public static OpenFeatureBuilder AddHook<THook>(this OpenFeatureBuilder builder,
299307
/// <param name="hookName">The name of the <see cref="Hook"/> that is being added.</param>
300308
/// <param name="hook">Instance of Hook to inject into the OpenFeature context.</param>
301309
/// <returns>The <see cref="OpenFeatureBuilder"/> instance.</returns>
302-
public static OpenFeatureBuilder AddHook<THook>(this OpenFeatureBuilder builder, string hookName, THook hook)
310+
public static OpenFeatureBuilder AddHook<
311+
#if NET
312+
[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
313+
#endif
314+
THook>(this OpenFeatureBuilder builder, string hookName, THook hook)
303315
where THook : Hook
304316
{
305317
return builder.AddHook(hookName, _ => hook);
@@ -313,7 +325,12 @@ public static OpenFeatureBuilder AddHook<THook>(this OpenFeatureBuilder builder,
313325
/// <param name="hookName">The name of the <see cref="Hook"/> that is being added.</param>
314326
/// <param name="implementationFactory">Optional factory for controlling how <typeparamref name="THook"/> will be created in the DI container.</param>
315327
/// <returns>The <see cref="OpenFeatureBuilder"/> instance.</returns>
316-
public static OpenFeatureBuilder AddHook<THook>(this OpenFeatureBuilder builder, string hookName, Func<IServiceProvider, THook>? implementationFactory = null)
328+
public static OpenFeatureBuilder AddHook<
329+
#if NET
330+
[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
331+
#endif
332+
THook>
333+
(this OpenFeatureBuilder builder, string hookName, Func<IServiceProvider, THook>? implementationFactory = null)
317334
where THook : Hook
318335
{
319336
builder.Services.PostConfigure<OpenFeatureOptions>(options => options.AddHookName(hookName));

src/OpenFeature/Model/ValueJsonConverter.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,16 @@
44

55
namespace OpenFeature.Model;
66

7-
internal sealed class ValueJsonConverter : JsonConverter<Value>
7+
/// <summary>
8+
/// A <see cref="JsonConverter{T}"/> for <see cref="Value"/> for Json serialization
9+
/// </summary>
10+
public sealed class ValueJsonConverter : JsonConverter<Value>
811
{
12+
/// <inheritdoc />
913
public override void Write(Utf8JsonWriter writer, Value value, JsonSerializerOptions options) =>
1014
WriteJsonValue(value, writer);
1115

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

test/OpenFeature.Tests/StructureTests.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Collections.Immutable;
22
using System.Text.Json;
33
using System.Text.Json.Nodes;
4+
using System.Text.Json.Serialization;
45
using OpenFeature.Model;
56

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

129+
[Theory]
130+
[MemberData(nameof(JsonSerializeTestData))]
131+
public void JsonSerializeWithGeneratorTest(Value value, string expectedJson)
132+
{
133+
var serializedJsonNode = JsonSerializer.SerializeToNode(value, ValueJsonSerializerContext.Default.Value);
134+
var expectJsonNode = JsonNode.Parse(expectedJson);
135+
Assert.True(JsonNode.DeepEquals(expectJsonNode, serializedJsonNode));
136+
}
137+
128138
[Theory]
129139
[MemberData(nameof(JsonSerializeTestData))]
130140
public void JsonDeserializeTest(Value value, string expectedJson)
@@ -135,6 +145,17 @@ public void JsonDeserializeTest(Value value, string expectedJson)
135145
Assert.True(JsonNode.DeepEquals(expectJsonNode, serializedJsonNode));
136146
}
137147

148+
[Theory]
149+
[MemberData(nameof(JsonSerializeTestData))]
150+
public void JsonDeserializeWithGeneratorTest(Value value, string expectedJson)
151+
{
152+
var serializedJsonNode = JsonSerializer.SerializeToNode(value, ValueJsonSerializerContext.Default.Value);
153+
var expectValue = JsonSerializer.Deserialize(expectedJson, ValueJsonSerializerContext.Default.Value);
154+
Assert.NotNull(expectValue);
155+
var expectJsonNode = JsonSerializer.SerializeToNode(expectValue, ValueJsonSerializerContext.Default.Value);
156+
Assert.True(JsonNode.DeepEquals(expectJsonNode, serializedJsonNode));
157+
}
158+
138159
public static IEnumerable<object[]> JsonSerializeTestData()
139160
{
140161
yield return [new Value("test"), "\"test\""];
@@ -178,3 +199,8 @@ public static IEnumerable<object[]> JsonSerializeTestData()
178199
];
179200
}
180201
}
202+
203+
[JsonSerializable(typeof(Value))]
204+
public partial class ValueJsonSerializerContext : JsonSerializerContext
205+
{
206+
}

0 commit comments

Comments
 (0)