Skip to content

Commit c8d5bd4

Browse files
authored
fix: Support empty string enum serialization and deserialization (#8)
1 parent e036666 commit c8d5bd4

File tree

10 files changed

+90
-24
lines changed

10 files changed

+90
-24
lines changed

.github/workflows/ci.yml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,20 @@ on:
88
jobs:
99
build:
1010
runs-on: ubuntu-22.04
11+
strategy:
12+
matrix:
13+
framework:
14+
- net8.0
15+
- net9.0
1116
steps:
1217
- uses: actions/checkout@v4
1318
with:
1419
fetch-depth: 0
1520
- name: Setup .NET Core
1621
uses: actions/setup-dotnet@v4
1722
with:
18-
dotnet-version: 8.x
23+
dotnet-version: 9.x
1924
- name: Build
20-
run: dotnet build -c Release
25+
run: dotnet build -c Release --framework ${{ matrix.framework }}
2126
- name: Test
22-
run: dotnet test -c Release --no-build
27+
run: dotnet test -c Release --framework ${{ matrix.framework }} --no-build

.github/workflows/publish.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
- name: Setup .NET Core
1919
uses: actions/setup-dotnet@v4
2020
with:
21-
dotnet-version: 8.x
21+
dotnet-version: 9.x
2222
- name: Install NBGV tool
2323
run: dotnet tool install --tool-path . nbgv
2424
- name: Set Version

src/Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
<PropertyGroup>
66
<IsPackable>true</IsPackable>
7-
<TargetFrameworks>net6.0;net8.0;netstandard2.0;netstandard2.1</TargetFrameworks>
7+
<TargetFrameworks>net8.0;net9.0;netstandard2.0;netstandard2.1</TargetFrameworks>
88
<PackageIconUrl>https://camo.githubusercontent.com/fa6d5c12609ed8a3ba1163b96f9e9979b8f59b0d/687474703a2f2f7765732e696f2f566663732f636f6e74656e74</PackageIconUrl>
99
<Copyright>Copyright (c) .NET Foundation and Contributors</Copyright>
1010
<PackageTags>Docker Container C# .NET</PackageTags>

src/Docker.DotNet/Docker.DotNet.csproj

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
<AssemblyName>Docker.DotNet</AssemblyName>
66
<LangVersion>latest</LangVersion>
77
</PropertyGroup>
8-
<ItemGroup>
8+
<ItemGroup Condition="$(TargetFramework) == 'net8.0'">
9+
<PackageReference Include="System.IO.Pipelines" Version="8.0.0" />
10+
</ItemGroup>
11+
<ItemGroup Condition="$(TargetFrameworkIdentifier) == '.NETStandard'">
912
<PackageReference Include="System.Buffers" Version="4.5.1" />
1013
<PackageReference Include="System.IO.Pipelines" Version="8.0.0" />
1114
<PackageReference Include="System.Net.Http.Json" Version="8.0.1" />

src/Docker.DotNet/JsonEnumMemberConverter.cs

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,41 @@
88
using System.Text.Json;
99
using System.Text.Json.Serialization;
1010

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

18-
private static JsonNamingPolicy ResolveNamingPolicy()
18+
public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
1919
{
20-
return new EnumMemberNamingPolicy(typeof(TEnum).GetFields(BindingFlags.Public | BindingFlags.Static)
21-
.Select(fieldInfo => new KeyValuePair<string, string>(fieldInfo.Name, fieldInfo.GetCustomAttribute<EnumMemberAttribute>()?.Value))
22-
.Where(kvp => kvp.Value != null)
23-
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value));
20+
var stringValue = reader.GetString();
21+
22+
var enumField = _enumFields.SingleOrDefault(item => item.Value.Equals(stringValue, StringComparison.Ordinal));
23+
24+
if (enumField.Key == null)
25+
{
26+
throw new JsonException($"Unknown enum value '{stringValue}' for enum type '{typeof(TEnum).Name}'.");
27+
}
28+
29+
if (!Enum.TryParse(enumField.Key, out TEnum enumValue))
30+
{
31+
throw new JsonException($"Unable to convert '{stringValue}' to a valid enum value of type '{typeof(TEnum).Name}'.");
32+
}
33+
34+
return enumValue;
2435
}
2536

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

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

32-
public override string ConvertName(string name) => _map.TryGetValue(name, out var newName) ? newName : name;
46+
writer.WriteStringValue(stringValue);
3347
}
3448
}

src/Docker.DotNet/JsonSerializer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ internal sealed class JsonSerializer
1818

1919
private JsonSerializer()
2020
{
21-
_options.Converters.Add(new JsonEnumMemberConverter<TaskState>());
2221
_options.Converters.Add(new JsonEnumMemberConverter<RestartPolicyKind>());
22+
_options.Converters.Add(new JsonEnumMemberConverter<TaskState>());
2323
_options.Converters.Add(new JsonDateTimeConverter());
2424
_options.Converters.Add(new JsonNullableDateTimeConverter());
2525
_options.Converters.Add(new JsonBase64Converter());

test/Docker.DotNet.Tests/Docker.DotNet.Tests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3-
<TargetFramework>net8.0</TargetFramework>
3+
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
44
<IsPackable>false</IsPackable>
55
<IsPublishable>false</IsPublishable>
66
</PropertyGroup>

test/Docker.DotNet.Tests/ISystemOperations.Tests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ await _dockerClient.Images.DeleteImageAsync(
118118

119119
await cts.CancelAsync();
120120

121-
await Assert.ThrowsAsync<OperationCanceledException>(() => task).ConfigureAwait(false);
121+
await Assert.ThrowsAsync<OperationCanceledException>(() => task);
122122

123123
Assert.True(wasProgressCalled);
124124
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
namespace Docker.DotNet.Tests;
2+
3+
using System.Text;
4+
using Docker.DotNet.Models;
5+
using Xunit;
6+
7+
public sealed class JsonEnumMemberConverterTests
8+
{
9+
[Theory]
10+
[ClassData(typeof(RestartPolicyKindTestData))]
11+
public void JsonSerialization_ShouldSerializeAndDeserializeCorrectly(RestartPolicyKind restartPolicyKind)
12+
{
13+
// Given
14+
var parameters = new CreateContainerParameters
15+
{
16+
HostConfig = new HostConfig
17+
{
18+
RestartPolicy = new RestartPolicy
19+
{
20+
Name = restartPolicyKind
21+
}
22+
}
23+
};
24+
25+
// When
26+
var jsonString = JsonSerializer.Instance.Serialize(parameters);
27+
var deserializedParameters = JsonSerializer.Instance.Deserialize<CreateContainerParameters>(Encoding.UTF8.GetBytes(jsonString));
28+
29+
// Then
30+
Assert.Equal(restartPolicyKind, deserializedParameters.HostConfig.RestartPolicy.Name);
31+
}
32+
33+
private sealed class RestartPolicyKindTestData : TheoryData<RestartPolicyKind>
34+
{
35+
public RestartPolicyKindTestData()
36+
{
37+
Add(RestartPolicyKind.Undefined);
38+
Add(RestartPolicyKind.No);
39+
Add(RestartPolicyKind.Always);
40+
Add(RestartPolicyKind.OnFailure);
41+
Add(RestartPolicyKind.UnlessStopped);
42+
}
43+
}
44+
}

version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
3-
"version": "3.126.0",
3+
"version": "3.126.1",
44
"nugetPackageVersion": {
55
"semVer": 2
66
},

0 commit comments

Comments
 (0)