Skip to content

Commit

Permalink
fix: Support empty string enum serialization and deserialization (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
HofmeisterAn authored Nov 30, 2024
1 parent e036666 commit c8d5bd4
Show file tree
Hide file tree
Showing 10 changed files with 90 additions and 24 deletions.
11 changes: 8 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,20 @@ on:
jobs:
build:
runs-on: ubuntu-22.04
strategy:
matrix:
framework:
- net8.0
- net9.0
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup .NET Core
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.x
dotnet-version: 9.x
- name: Build
run: dotnet build -c Release
run: dotnet build -c Release --framework ${{ matrix.framework }}
- name: Test
run: dotnet test -c Release --no-build
run: dotnet test -c Release --framework ${{ matrix.framework }} --no-build
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.x
dotnet-version: 9.x
- name: Install NBGV tool
run: dotnet tool install --tool-path . nbgv
- name: Set Version
Expand Down
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<PropertyGroup>
<IsPackable>true</IsPackable>
<TargetFrameworks>net6.0;net8.0;netstandard2.0;netstandard2.1</TargetFrameworks>
<TargetFrameworks>net8.0;net9.0;netstandard2.0;netstandard2.1</TargetFrameworks>
<PackageIconUrl>https://camo.githubusercontent.com/fa6d5c12609ed8a3ba1163b96f9e9979b8f59b0d/687474703a2f2f7765732e696f2f566663732f636f6e74656e74</PackageIconUrl>
<Copyright>Copyright (c) .NET Foundation and Contributors</Copyright>
<PackageTags>Docker Container C# .NET</PackageTags>
Expand Down
5 changes: 4 additions & 1 deletion src/Docker.DotNet/Docker.DotNet.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
<AssemblyName>Docker.DotNet</AssemblyName>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<ItemGroup Condition="$(TargetFramework) == 'net8.0'">
<PackageReference Include="System.IO.Pipelines" Version="8.0.0" />
</ItemGroup>
<ItemGroup Condition="$(TargetFrameworkIdentifier) == '.NETStandard'">
<PackageReference Include="System.Buffers" Version="4.5.1" />
<PackageReference Include="System.IO.Pipelines" Version="8.0.0" />
<PackageReference Include="System.Net.Http.Json" Version="8.0.1" />
Expand Down
42 changes: 28 additions & 14 deletions src/Docker.DotNet/JsonEnumMemberConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,41 @@
using System.Text.Json;
using System.Text.Json.Serialization;

// https://github.com/dotnet/runtime/issues/74385#issuecomment-1705083109.
internal sealed class JsonEnumMemberConverter<TEnum> : JsonStringEnumConverter<TEnum> where TEnum : struct, Enum
internal sealed class JsonEnumMemberConverter<TEnum> : JsonConverter<TEnum> where TEnum : struct, Enum
{
public JsonEnumMemberConverter() : base(ResolveNamingPolicy())
{
}
private readonly Dictionary<string, string> _enumFields = typeof(TEnum).GetFields(BindingFlags.Public | BindingFlags.Static)
.Select(field => (Name: field.Name, Attribute: field.GetCustomAttribute<EnumMemberAttribute>()))
.Where(item => item.Attribute != null && item.Attribute.Value != null)
.ToDictionary(item => item.Name, item => item.Attribute.Value);

private static JsonNamingPolicy ResolveNamingPolicy()
public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new EnumMemberNamingPolicy(typeof(TEnum).GetFields(BindingFlags.Public | BindingFlags.Static)
.Select(fieldInfo => new KeyValuePair<string, string>(fieldInfo.Name, fieldInfo.GetCustomAttribute<EnumMemberAttribute>()?.Value))
.Where(kvp => kvp.Value != null)
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value));
var stringValue = reader.GetString();

var enumField = _enumFields.SingleOrDefault(item => item.Value.Equals(stringValue, StringComparison.Ordinal));

if (enumField.Key == null)
{
throw new JsonException($"Unknown enum value '{stringValue}' for enum type '{typeof(TEnum).Name}'.");
}

if (!Enum.TryParse(enumField.Key, out TEnum enumValue))
{
throw new JsonException($"Unable to convert '{stringValue}' to a valid enum value of type '{typeof(TEnum).Name}'.");
}

return enumValue;
}

private sealed class EnumMemberNamingPolicy : JsonNamingPolicy
public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
{
private readonly IReadOnlyDictionary<string, string> _map;
var enumName = value.ToString();

public EnumMemberNamingPolicy(IReadOnlyDictionary<string, string> map) => _map = map;
if (!_enumFields.TryGetValue(enumName, out var stringValue))
{
throw new JsonException($"Unable to convert '{enumName}' to a valid enum value of type '{nameof(String)}'.");
}

public override string ConvertName(string name) => _map.TryGetValue(name, out var newName) ? newName : name;
writer.WriteStringValue(stringValue);
}
}
2 changes: 1 addition & 1 deletion src/Docker.DotNet/JsonSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ internal sealed class JsonSerializer

private JsonSerializer()
{
_options.Converters.Add(new JsonEnumMemberConverter<TaskState>());
_options.Converters.Add(new JsonEnumMemberConverter<RestartPolicyKind>());
_options.Converters.Add(new JsonEnumMemberConverter<TaskState>());
_options.Converters.Add(new JsonDateTimeConverter());
_options.Converters.Add(new JsonNullableDateTimeConverter());
_options.Converters.Add(new JsonBase64Converter());
Expand Down
2 changes: 1 addition & 1 deletion test/Docker.DotNet.Tests/Docker.DotNet.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
<IsPackable>false</IsPackable>
<IsPublishable>false</IsPublishable>
</PropertyGroup>
Expand Down
2 changes: 1 addition & 1 deletion test/Docker.DotNet.Tests/ISystemOperations.Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ await _dockerClient.Images.DeleteImageAsync(

await cts.CancelAsync();

await Assert.ThrowsAsync<OperationCanceledException>(() => task).ConfigureAwait(false);
await Assert.ThrowsAsync<OperationCanceledException>(() => task);

Assert.True(wasProgressCalled);
}
Expand Down
44 changes: 44 additions & 0 deletions test/Docker.DotNet.Tests/JsonEnumMemberConverterTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
namespace Docker.DotNet.Tests;

using System.Text;
using Docker.DotNet.Models;
using Xunit;

public sealed class JsonEnumMemberConverterTests
{
[Theory]
[ClassData(typeof(RestartPolicyKindTestData))]
public void JsonSerialization_ShouldSerializeAndDeserializeCorrectly(RestartPolicyKind restartPolicyKind)
{
// Given
var parameters = new CreateContainerParameters
{
HostConfig = new HostConfig
{
RestartPolicy = new RestartPolicy
{
Name = restartPolicyKind
}
}
};

// When
var jsonString = JsonSerializer.Instance.Serialize(parameters);
var deserializedParameters = JsonSerializer.Instance.Deserialize<CreateContainerParameters>(Encoding.UTF8.GetBytes(jsonString));

// Then
Assert.Equal(restartPolicyKind, deserializedParameters.HostConfig.RestartPolicy.Name);
}

private sealed class RestartPolicyKindTestData : TheoryData<RestartPolicyKind>
{
public RestartPolicyKindTestData()
{
Add(RestartPolicyKind.Undefined);
Add(RestartPolicyKind.No);
Add(RestartPolicyKind.Always);
Add(RestartPolicyKind.OnFailure);
Add(RestartPolicyKind.UnlessStopped);
}
}
}
2 changes: 1 addition & 1 deletion version.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
"version": "3.126.0",
"version": "3.126.1",
"nugetPackageVersion": {
"semVer": 2
},
Expand Down

0 comments on commit c8d5bd4

Please sign in to comment.