Skip to content

Commit 52399a3

Browse files
committed
fix: Support empty string serialization / deserialization
1 parent e036666 commit 52399a3

File tree

8 files changed

+136
-17
lines changed

8 files changed

+136
-17
lines changed

Docker.DotNet.sln

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Docker.DotNet.X509", "src\D
1515
EndProject
1616
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Docker.DotNet.Tests", "test\Docker.DotNet.Tests\Docker.DotNet.Tests.csproj", "{248C5D51-2B33-4A06-A0EA-AA709F752E52}"
1717
EndProject
18+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Docker.DotNet.JsonSerializer8.Tests", "test\Docker.DotNet.JsonSerializer8.Tests\Docker.DotNet.JsonSerializer8.Tests.csproj", "{51D89ABF-A08A-4CE8-8AEA-DA65FEDA8676}"
19+
EndProject
20+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Docker.DotNet.JsonSerializer9.Tests", "test\Docker.DotNet.JsonSerializer9.Tests\Docker.DotNet.JsonSerializer9.Tests.csproj", "{F3F00DC7-2C56-4483-B97C-91940DF15178}"
21+
EndProject
1822
Global
1923
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2024
Debug|Any CPU = Debug|Any CPU
@@ -73,6 +77,30 @@ Global
7377
{248C5D51-2B33-4A06-A0EA-AA709F752E52}.Release|x64.Build.0 = Release|Any CPU
7478
{248C5D51-2B33-4A06-A0EA-AA709F752E52}.Release|x86.ActiveCfg = Release|Any CPU
7579
{248C5D51-2B33-4A06-A0EA-AA709F752E52}.Release|x86.Build.0 = Release|Any CPU
80+
{51D89ABF-A08A-4CE8-8AEA-DA65FEDA8676}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
81+
{51D89ABF-A08A-4CE8-8AEA-DA65FEDA8676}.Debug|Any CPU.Build.0 = Debug|Any CPU
82+
{51D89ABF-A08A-4CE8-8AEA-DA65FEDA8676}.Debug|x64.ActiveCfg = Debug|Any CPU
83+
{51D89ABF-A08A-4CE8-8AEA-DA65FEDA8676}.Debug|x64.Build.0 = Debug|Any CPU
84+
{51D89ABF-A08A-4CE8-8AEA-DA65FEDA8676}.Debug|x86.ActiveCfg = Debug|Any CPU
85+
{51D89ABF-A08A-4CE8-8AEA-DA65FEDA8676}.Debug|x86.Build.0 = Debug|Any CPU
86+
{51D89ABF-A08A-4CE8-8AEA-DA65FEDA8676}.Release|Any CPU.ActiveCfg = Release|Any CPU
87+
{51D89ABF-A08A-4CE8-8AEA-DA65FEDA8676}.Release|Any CPU.Build.0 = Release|Any CPU
88+
{51D89ABF-A08A-4CE8-8AEA-DA65FEDA8676}.Release|x64.ActiveCfg = Release|Any CPU
89+
{51D89ABF-A08A-4CE8-8AEA-DA65FEDA8676}.Release|x64.Build.0 = Release|Any CPU
90+
{51D89ABF-A08A-4CE8-8AEA-DA65FEDA8676}.Release|x86.ActiveCfg = Release|Any CPU
91+
{51D89ABF-A08A-4CE8-8AEA-DA65FEDA8676}.Release|x86.Build.0 = Release|Any CPU
92+
{F3F00DC7-2C56-4483-B97C-91940DF15178}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
93+
{F3F00DC7-2C56-4483-B97C-91940DF15178}.Debug|Any CPU.Build.0 = Debug|Any CPU
94+
{F3F00DC7-2C56-4483-B97C-91940DF15178}.Debug|x64.ActiveCfg = Debug|Any CPU
95+
{F3F00DC7-2C56-4483-B97C-91940DF15178}.Debug|x64.Build.0 = Debug|Any CPU
96+
{F3F00DC7-2C56-4483-B97C-91940DF15178}.Debug|x86.ActiveCfg = Debug|Any CPU
97+
{F3F00DC7-2C56-4483-B97C-91940DF15178}.Debug|x86.Build.0 = Debug|Any CPU
98+
{F3F00DC7-2C56-4483-B97C-91940DF15178}.Release|Any CPU.ActiveCfg = Release|Any CPU
99+
{F3F00DC7-2C56-4483-B97C-91940DF15178}.Release|Any CPU.Build.0 = Release|Any CPU
100+
{F3F00DC7-2C56-4483-B97C-91940DF15178}.Release|x64.ActiveCfg = Release|Any CPU
101+
{F3F00DC7-2C56-4483-B97C-91940DF15178}.Release|x64.Build.0 = Release|Any CPU
102+
{F3F00DC7-2C56-4483-B97C-91940DF15178}.Release|x86.ActiveCfg = Release|Any CPU
103+
{F3F00DC7-2C56-4483-B97C-91940DF15178}.Release|x86.Build.0 = Release|Any CPU
76104
EndGlobalSection
77105
GlobalSection(SolutionProperties) = preSolution
78106
HideSolutionNode = FALSE
@@ -82,5 +110,7 @@ Global
82110
{E1F24B25-E027-45E0-A6E1-E08138F1F95D} = {85990620-78A6-4381-8BD6-84E6D0CF0649}
83111
{89BD76AD-78C9-4E4A-96A2-E5DA6D4AFA44} = {85990620-78A6-4381-8BD6-84E6D0CF0649}
84112
{248C5D51-2B33-4A06-A0EA-AA709F752E52} = {AA4B8CC2-1431-4FC7-9DF3-533EC6C86D3A}
113+
{51D89ABF-A08A-4CE8-8AEA-DA65FEDA8676} = {AA4B8CC2-1431-4FC7-9DF3-533EC6C86D3A}
114+
{F3F00DC7-2C56-4483-B97C-91940DF15178} = {AA4B8CC2-1431-4FC7-9DF3-533EC6C86D3A}
85115
EndGlobalSection
86116
EndGlobal

src/Docker.DotNet/JsonEnumMemberConverter.cs

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,35 @@
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 IReadOnlyDictionary<string, string> _map = 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, StringComparer.OrdinalIgnoreCase);
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));
24-
}
20+
var stringValue = reader.GetString();
2521

26-
private sealed class EnumMemberNamingPolicy : JsonNamingPolicy
27-
{
28-
private readonly IReadOnlyDictionary<string, string> _map;
22+
var enumValue = _map.SingleOrDefault(item => item.Value.Equals(stringValue, StringComparison.OrdinalIgnoreCase));
2923

30-
public EnumMemberNamingPolicy(IReadOnlyDictionary<string, string> map) => _map = map;
24+
if (enumValue.Key == null)
25+
{
26+
throw new JsonException($"Unknown enum value '{stringValue}' for enum type '{typeof(TEnum).Name}'.");
27+
}
3128

32-
public override string ConvertName(string name) => _map.TryGetValue(name, out var newName) ? newName : name;
29+
if (!Enum.TryParse(enumValue.Key, out TEnum result))
30+
{
31+
throw new JsonException($"Unable to convert '{stringValue}' to a valid enum value of type '{typeof(TEnum).Name}'.");
32+
}
33+
34+
return result;
35+
}
36+
37+
public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
38+
{
39+
var enumName = value.ToString();
40+
writer.WriteStringValue(_map.TryGetValue(enumName, out var stringValue) ? stringValue : enumName);
3341
}
3442
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
using System.Runtime.CompilerServices;
22

3+
[assembly: InternalsVisibleTo("Docker.DotNet.JsonSerializer8.Tests" + StrongNamePublicKeys.DockerDotNetPublicKey)]
4+
[assembly: InternalsVisibleTo("Docker.DotNet.JsonSerializer9.Tests" + StrongNamePublicKeys.DockerDotNetPublicKey)]
35
[assembly: InternalsVisibleTo("Docker.DotNet.Tests" + StrongNamePublicKeys.DockerDotNetPublicKey)]
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>net8.0</TargetFramework>
4+
<IsPackable>false</IsPackable>
5+
<IsPublishable>false</IsPublishable>
6+
</PropertyGroup>
7+
<ItemGroup>
8+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
9+
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
10+
<PackageReference Include="xunit" Version="2.9.2" />
11+
</ItemGroup>
12+
<ItemGroup>
13+
<ProjectReference Include="..\..\src\Docker.DotNet\Docker.DotNet.csproj" />
14+
</ItemGroup>
15+
</Project>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
namespace Docker.DotNet.JsonSerializer8.Tests;
2+
3+
public sealed class JsonEnumMemberConverterTests
4+
{
5+
[Theory]
6+
[ClassData(typeof(RestartPolicyKindTestData))]
7+
public void JsonSerialization_ShouldSerializeAndDeserializeCorrectly(RestartPolicyKind restartPolicyKind)
8+
{
9+
// Given
10+
var parameters = new CreateContainerParameters
11+
{
12+
HostConfig = new HostConfig
13+
{
14+
RestartPolicy = new RestartPolicy
15+
{
16+
Name = restartPolicyKind
17+
}
18+
}
19+
};
20+
21+
// When
22+
var jsonString = JsonSerializer.Instance.Serialize(parameters);
23+
var deserializedParameters = JsonSerializer.Instance.Deserialize<CreateContainerParameters>(Encoding.UTF8.GetBytes(jsonString));
24+
25+
// Then
26+
Assert.Equal(restartPolicyKind, deserializedParameters.HostConfig.RestartPolicy.Name);
27+
}
28+
29+
private sealed class RestartPolicyKindTestData : TheoryData<RestartPolicyKind>
30+
{
31+
public RestartPolicyKindTestData()
32+
{
33+
Add(RestartPolicyKind.Undefined);
34+
Add(RestartPolicyKind.No);
35+
Add(RestartPolicyKind.Always);
36+
Add(RestartPolicyKind.OnFailure);
37+
Add(RestartPolicyKind.UnlessStopped);
38+
}
39+
}
40+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
global using System.Text;
2+
global using Docker.DotNet.Models;
3+
global using Xunit;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>net8.0</TargetFramework>
4+
<IsPackable>false</IsPackable>
5+
<IsPublishable>false</IsPublishable>
6+
</PropertyGroup>
7+
<ItemGroup>
8+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
9+
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
10+
<PackageReference Include="xunit" Version="2.9.2" />
11+
<PackageReference Include="System.IO.Pipelines" Version="9.0.0" />
12+
<PackageReference Include="System.Net.Http.Json" Version="9.0.0" />
13+
<PackageReference Include="System.Text.Json" Version="9.0.0" />
14+
</ItemGroup>
15+
<ItemGroup>
16+
<ProjectReference Include="..\..\src\Docker.DotNet\Docker.DotNet.csproj" />
17+
</ItemGroup>
18+
<ItemGroup>
19+
<Compile Include="..\Docker.DotNet.JsonSerializer8.Tests\*.cs"/>
20+
</ItemGroup>
21+
</Project>

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
}

0 commit comments

Comments
 (0)