diff --git a/Docker.DotNet.sln b/Docker.DotNet.sln index 4f5c83527..1fce29559 100644 --- a/Docker.DotNet.sln +++ b/Docker.DotNet.sln @@ -15,6 +15,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Docker.DotNet.X509", "src\D EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Docker.DotNet.Tests", "test\Docker.DotNet.Tests\Docker.DotNet.Tests.csproj", "{248C5D51-2B33-4A06-A0EA-AA709F752E52}" EndProject +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}" +EndProject +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}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -73,6 +77,30 @@ Global {248C5D51-2B33-4A06-A0EA-AA709F752E52}.Release|x64.Build.0 = Release|Any CPU {248C5D51-2B33-4A06-A0EA-AA709F752E52}.Release|x86.ActiveCfg = Release|Any CPU {248C5D51-2B33-4A06-A0EA-AA709F752E52}.Release|x86.Build.0 = Release|Any CPU + {51D89ABF-A08A-4CE8-8AEA-DA65FEDA8676}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {51D89ABF-A08A-4CE8-8AEA-DA65FEDA8676}.Debug|Any CPU.Build.0 = Debug|Any CPU + {51D89ABF-A08A-4CE8-8AEA-DA65FEDA8676}.Debug|x64.ActiveCfg = Debug|Any CPU + {51D89ABF-A08A-4CE8-8AEA-DA65FEDA8676}.Debug|x64.Build.0 = Debug|Any CPU + {51D89ABF-A08A-4CE8-8AEA-DA65FEDA8676}.Debug|x86.ActiveCfg = Debug|Any CPU + {51D89ABF-A08A-4CE8-8AEA-DA65FEDA8676}.Debug|x86.Build.0 = Debug|Any CPU + {51D89ABF-A08A-4CE8-8AEA-DA65FEDA8676}.Release|Any CPU.ActiveCfg = Release|Any CPU + {51D89ABF-A08A-4CE8-8AEA-DA65FEDA8676}.Release|Any CPU.Build.0 = Release|Any CPU + {51D89ABF-A08A-4CE8-8AEA-DA65FEDA8676}.Release|x64.ActiveCfg = Release|Any CPU + {51D89ABF-A08A-4CE8-8AEA-DA65FEDA8676}.Release|x64.Build.0 = Release|Any CPU + {51D89ABF-A08A-4CE8-8AEA-DA65FEDA8676}.Release|x86.ActiveCfg = Release|Any CPU + {51D89ABF-A08A-4CE8-8AEA-DA65FEDA8676}.Release|x86.Build.0 = Release|Any CPU + {F3F00DC7-2C56-4483-B97C-91940DF15178}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F3F00DC7-2C56-4483-B97C-91940DF15178}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F3F00DC7-2C56-4483-B97C-91940DF15178}.Debug|x64.ActiveCfg = Debug|Any CPU + {F3F00DC7-2C56-4483-B97C-91940DF15178}.Debug|x64.Build.0 = Debug|Any CPU + {F3F00DC7-2C56-4483-B97C-91940DF15178}.Debug|x86.ActiveCfg = Debug|Any CPU + {F3F00DC7-2C56-4483-B97C-91940DF15178}.Debug|x86.Build.0 = Debug|Any CPU + {F3F00DC7-2C56-4483-B97C-91940DF15178}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F3F00DC7-2C56-4483-B97C-91940DF15178}.Release|Any CPU.Build.0 = Release|Any CPU + {F3F00DC7-2C56-4483-B97C-91940DF15178}.Release|x64.ActiveCfg = Release|Any CPU + {F3F00DC7-2C56-4483-B97C-91940DF15178}.Release|x64.Build.0 = Release|Any CPU + {F3F00DC7-2C56-4483-B97C-91940DF15178}.Release|x86.ActiveCfg = Release|Any CPU + {F3F00DC7-2C56-4483-B97C-91940DF15178}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -82,5 +110,7 @@ Global {E1F24B25-E027-45E0-A6E1-E08138F1F95D} = {85990620-78A6-4381-8BD6-84E6D0CF0649} {89BD76AD-78C9-4E4A-96A2-E5DA6D4AFA44} = {85990620-78A6-4381-8BD6-84E6D0CF0649} {248C5D51-2B33-4A06-A0EA-AA709F752E52} = {AA4B8CC2-1431-4FC7-9DF3-533EC6C86D3A} + {51D89ABF-A08A-4CE8-8AEA-DA65FEDA8676} = {AA4B8CC2-1431-4FC7-9DF3-533EC6C86D3A} + {F3F00DC7-2C56-4483-B97C-91940DF15178} = {AA4B8CC2-1431-4FC7-9DF3-533EC6C86D3A} EndGlobalSection EndGlobal diff --git a/src/Docker.DotNet/JsonEnumMemberConverter.cs b/src/Docker.DotNet/JsonEnumMemberConverter.cs index 0b8f9aff0..4da70f40c 100644 --- a/src/Docker.DotNet/JsonEnumMemberConverter.cs +++ b/src/Docker.DotNet/JsonEnumMemberConverter.cs @@ -8,27 +8,35 @@ using System.Text.Json; using System.Text.Json.Serialization; -// https://github.com/dotnet/runtime/issues/74385#issuecomment-1705083109. -internal sealed class JsonEnumMemberConverter : JsonStringEnumConverter where TEnum : struct, Enum +internal sealed class JsonEnumMemberConverter : JsonConverter where TEnum : struct, Enum { - public JsonEnumMemberConverter() : base(ResolveNamingPolicy()) - { - } + private readonly IReadOnlyDictionary _map = typeof(TEnum).GetFields(BindingFlags.Public | BindingFlags.Static) + .Select(field => (Name: field.Name, Attribute: field.GetCustomAttribute())) + .Where(item => item.Attribute != null && item.Attribute.Value != null) + .ToDictionary(item => item.Name, item => item.Attribute.Value, StringComparer.OrdinalIgnoreCase); - 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(fieldInfo.Name, fieldInfo.GetCustomAttribute()?.Value)) - .Where(kvp => kvp.Value != null) - .ToDictionary(kvp => kvp.Key, kvp => kvp.Value)); - } + var stringValue = reader.GetString(); - private sealed class EnumMemberNamingPolicy : JsonNamingPolicy - { - private readonly IReadOnlyDictionary _map; + var enumValue = _map.SingleOrDefault(item => item.Value.Equals(stringValue, StringComparison.OrdinalIgnoreCase)); - public EnumMemberNamingPolicy(IReadOnlyDictionary map) => _map = map; + if (enumValue.Key == null) + { + throw new JsonException($"Unknown enum value '{stringValue}' for enum type '{typeof(TEnum).Name}'."); + } - public override string ConvertName(string name) => _map.TryGetValue(name, out var newName) ? newName : name; + if (!Enum.TryParse(enumValue.Key, out TEnum result)) + { + throw new JsonException($"Unable to convert '{stringValue}' to a valid enum value of type '{typeof(TEnum).Name}'."); + } + + return result; + } + + public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options) + { + var enumName = value.ToString(); + writer.WriteStringValue(_map.TryGetValue(enumName, out var stringValue) ? stringValue : enumName); } } \ No newline at end of file diff --git a/src/Docker.DotNet/Properties/InternalVisibleTo.cs b/src/Docker.DotNet/Properties/InternalVisibleTo.cs index 91e78d106..a3424fbba 100644 --- a/src/Docker.DotNet/Properties/InternalVisibleTo.cs +++ b/src/Docker.DotNet/Properties/InternalVisibleTo.cs @@ -1,3 +1,5 @@ using System.Runtime.CompilerServices; +[assembly: InternalsVisibleTo("Docker.DotNet.JsonSerializer8.Tests" + StrongNamePublicKeys.DockerDotNetPublicKey)] +[assembly: InternalsVisibleTo("Docker.DotNet.JsonSerializer9.Tests" + StrongNamePublicKeys.DockerDotNetPublicKey)] [assembly: InternalsVisibleTo("Docker.DotNet.Tests" + StrongNamePublicKeys.DockerDotNetPublicKey)] \ No newline at end of file diff --git a/test/Docker.DotNet.JsonSerializer8.Tests/Docker.DotNet.JsonSerializer8.Tests.csproj b/test/Docker.DotNet.JsonSerializer8.Tests/Docker.DotNet.JsonSerializer8.Tests.csproj new file mode 100644 index 000000000..26e85ac29 --- /dev/null +++ b/test/Docker.DotNet.JsonSerializer8.Tests/Docker.DotNet.JsonSerializer8.Tests.csproj @@ -0,0 +1,15 @@ + + + net8.0 + false + false + + + + + + + + + + \ No newline at end of file diff --git a/test/Docker.DotNet.JsonSerializer8.Tests/JsonEnumMemberConverterTest.cs b/test/Docker.DotNet.JsonSerializer8.Tests/JsonEnumMemberConverterTest.cs new file mode 100644 index 000000000..ec23c4164 --- /dev/null +++ b/test/Docker.DotNet.JsonSerializer8.Tests/JsonEnumMemberConverterTest.cs @@ -0,0 +1,40 @@ +namespace Docker.DotNet.JsonSerializer8.Tests; + +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(Encoding.UTF8.GetBytes(jsonString)); + + // Then + Assert.Equal(restartPolicyKind, deserializedParameters.HostConfig.RestartPolicy.Name); + } + + private sealed class RestartPolicyKindTestData : TheoryData + { + public RestartPolicyKindTestData() + { + Add(RestartPolicyKind.Undefined); + Add(RestartPolicyKind.No); + Add(RestartPolicyKind.Always); + Add(RestartPolicyKind.OnFailure); + Add(RestartPolicyKind.UnlessStopped); + } + } +} \ No newline at end of file diff --git a/test/Docker.DotNet.JsonSerializer8.Tests/Usings.cs b/test/Docker.DotNet.JsonSerializer8.Tests/Usings.cs new file mode 100644 index 000000000..b8377da7b --- /dev/null +++ b/test/Docker.DotNet.JsonSerializer8.Tests/Usings.cs @@ -0,0 +1,3 @@ +global using System.Text; +global using Docker.DotNet.Models; +global using Xunit; \ No newline at end of file diff --git a/test/Docker.DotNet.JsonSerializer9.Tests/Docker.DotNet.JsonSerializer9.Tests.csproj b/test/Docker.DotNet.JsonSerializer9.Tests/Docker.DotNet.JsonSerializer9.Tests.csproj new file mode 100644 index 000000000..a89096c82 --- /dev/null +++ b/test/Docker.DotNet.JsonSerializer9.Tests/Docker.DotNet.JsonSerializer9.Tests.csproj @@ -0,0 +1,21 @@ + + + net8.0 + false + false + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/Docker.DotNet.Tests/ISystemOperations.Tests.cs b/test/Docker.DotNet.Tests/ISystemOperations.Tests.cs index d32e0a34e..0ad75ebd9 100644 --- a/test/Docker.DotNet.Tests/ISystemOperations.Tests.cs +++ b/test/Docker.DotNet.Tests/ISystemOperations.Tests.cs @@ -118,7 +118,7 @@ await _dockerClient.Images.DeleteImageAsync( await cts.CancelAsync(); - await Assert.ThrowsAsync(() => task).ConfigureAwait(false); + await Assert.ThrowsAsync(() => task); Assert.True(wasProgressCalled); }