Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Support empty string enum serialization and deserialization #8

Merged
merged 5 commits into from
Nov 30, 2024
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
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
Loading