From c8d5bd4c51c3970bda624fd69a5105801ea1c8b0 Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Sat, 30 Nov 2024 10:52:29 +0100 Subject: [PATCH] fix: Support empty string enum serialization and deserialization (#8) --- .github/workflows/ci.yml | 11 +++-- .github/workflows/publish.yml | 2 +- src/Directory.Build.props | 2 +- src/Docker.DotNet/Docker.DotNet.csproj | 5 ++- src/Docker.DotNet/JsonEnumMemberConverter.cs | 42 ++++++++++++------ src/Docker.DotNet/JsonSerializer.cs | 2 +- .../Docker.DotNet.Tests.csproj | 2 +- .../ISystemOperations.Tests.cs | 2 +- .../JsonEnumMemberConverterTest.cs | 44 +++++++++++++++++++ version.json | 2 +- 10 files changed, 90 insertions(+), 24 deletions(-) create mode 100644 test/Docker.DotNet.Tests/JsonEnumMemberConverterTest.cs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b5d3f49e..301c59f0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,11 @@ on: jobs: build: runs-on: ubuntu-22.04 + strategy: + matrix: + framework: + - net8.0 + - net9.0 steps: - uses: actions/checkout@v4 with: @@ -15,8 +20,8 @@ jobs: - 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 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index efb71227..f6e7330d 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -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 diff --git a/src/Directory.Build.props b/src/Directory.Build.props index c6ac8527..7a0e7a11 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -4,7 +4,7 @@ true - net6.0;net8.0;netstandard2.0;netstandard2.1 + net8.0;net9.0;netstandard2.0;netstandard2.1 https://camo.githubusercontent.com/fa6d5c12609ed8a3ba1163b96f9e9979b8f59b0d/687474703a2f2f7765732e696f2f566663732f636f6e74656e74 Copyright (c) .NET Foundation and Contributors Docker Container C# .NET diff --git a/src/Docker.DotNet/Docker.DotNet.csproj b/src/Docker.DotNet/Docker.DotNet.csproj index 07a06a97..a380a0ae 100644 --- a/src/Docker.DotNet/Docker.DotNet.csproj +++ b/src/Docker.DotNet/Docker.DotNet.csproj @@ -5,7 +5,10 @@ Docker.DotNet latest - + + + + diff --git a/src/Docker.DotNet/JsonEnumMemberConverter.cs b/src/Docker.DotNet/JsonEnumMemberConverter.cs index 0b8f9aff..7b56dd2e 100644 --- a/src/Docker.DotNet/JsonEnumMemberConverter.cs +++ b/src/Docker.DotNet/JsonEnumMemberConverter.cs @@ -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 : JsonStringEnumConverter where TEnum : struct, Enum +internal sealed class JsonEnumMemberConverter : JsonConverter where TEnum : struct, Enum { - public JsonEnumMemberConverter() : base(ResolveNamingPolicy()) - { - } + private readonly Dictionary _enumFields = 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); - 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(); + + 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 _map; + var enumName = value.ToString(); - public EnumMemberNamingPolicy(IReadOnlyDictionary 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); } } \ No newline at end of file diff --git a/src/Docker.DotNet/JsonSerializer.cs b/src/Docker.DotNet/JsonSerializer.cs index e64dde62..033c6144 100644 --- a/src/Docker.DotNet/JsonSerializer.cs +++ b/src/Docker.DotNet/JsonSerializer.cs @@ -18,8 +18,8 @@ internal sealed class JsonSerializer private JsonSerializer() { - _options.Converters.Add(new JsonEnumMemberConverter()); _options.Converters.Add(new JsonEnumMemberConverter()); + _options.Converters.Add(new JsonEnumMemberConverter()); _options.Converters.Add(new JsonDateTimeConverter()); _options.Converters.Add(new JsonNullableDateTimeConverter()); _options.Converters.Add(new JsonBase64Converter()); diff --git a/test/Docker.DotNet.Tests/Docker.DotNet.Tests.csproj b/test/Docker.DotNet.Tests/Docker.DotNet.Tests.csproj index cf8e7333..8408738a 100644 --- a/test/Docker.DotNet.Tests/Docker.DotNet.Tests.csproj +++ b/test/Docker.DotNet.Tests/Docker.DotNet.Tests.csproj @@ -1,6 +1,6 @@  - net8.0 + net8.0;net9.0 false false diff --git a/test/Docker.DotNet.Tests/ISystemOperations.Tests.cs b/test/Docker.DotNet.Tests/ISystemOperations.Tests.cs index d32e0a34..0ad75ebd 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); } diff --git a/test/Docker.DotNet.Tests/JsonEnumMemberConverterTest.cs b/test/Docker.DotNet.Tests/JsonEnumMemberConverterTest.cs new file mode 100644 index 00000000..53a001c0 --- /dev/null +++ b/test/Docker.DotNet.Tests/JsonEnumMemberConverterTest.cs @@ -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(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/version.json b/version.json index 165ac236..423da8f4 100644 --- a/version.json +++ b/version.json @@ -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 },