From cfdf42547924ff36e879e62f7cc4fdf183f069f7 Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Sat, 27 Jun 2026 10:19:24 +0200 Subject: [PATCH] fix: Replace generic QueryStringMapParameter attribute with Type argument --- src/Docker.DotNet/JsonSerializer.cs | 5 +++++ .../ContainerEventsParameters.Generated.cs | 2 +- .../ContainersListParameters.Generated.cs | 2 +- .../ContainersPruneParameters.Generated.cs | 2 +- .../Models/ImageBuildParameters.Generated.cs | 4 ++-- .../Models/ImagesListParameters.Generated.cs | 2 +- .../Models/ImagesPruneParameters.Generated.cs | 2 +- .../ImagesSearchParameters.Generated.cs | 2 +- ...etworksDeleteUnusedParameters.Generated.cs | 2 +- .../NetworksListParameters.Generated.cs | 2 +- .../Models/PluginListParameters.Generated.cs | 2 +- .../Models/ServiceListParameters.Generated.cs | 2 +- .../Models/TasksListParameters.Generated.cs | 2 +- .../Models/VolumesListParameters.Generated.cs | 2 +- .../VolumesPruneParameters.Generated.cs | 2 +- .../QueryStringMapParameterAttribute.cs | 10 ++++----- .../JsonSerializerAnalyzer.cs | 22 ++++++++++++++++--- tools/specgen/specgen.go | 5 ++++- 18 files changed, 48 insertions(+), 24 deletions(-) diff --git a/src/Docker.DotNet/JsonSerializer.cs b/src/Docker.DotNet/JsonSerializer.cs index 17e1161f..8a4fb964 100644 --- a/src/Docker.DotNet/JsonSerializer.cs +++ b/src/Docker.DotNet/JsonSerializer.cs @@ -45,6 +45,11 @@ public string Serialize(T value) return System.Text.Json.JsonSerializer.Serialize(value, JsonTypeInfoCache.Value); } + public string Serialize(object? value, Type type) + { + return System.Text.Json.JsonSerializer.Serialize(value, _options.GetTypeInfo(type)); + } + public byte[] SerializeToUtf8Bytes(T value) { return System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(value, JsonTypeInfoCache.Value); diff --git a/src/Docker.DotNet/Models/ContainerEventsParameters.Generated.cs b/src/Docker.DotNet/Models/ContainerEventsParameters.Generated.cs index 952db26a..59273394 100644 --- a/src/Docker.DotNet/Models/ContainerEventsParameters.Generated.cs +++ b/src/Docker.DotNet/Models/ContainerEventsParameters.Generated.cs @@ -9,7 +9,7 @@ public class ContainerEventsParameters // (main.ContainerEventsParameters) [QueryStringParameter("until", false)] public string? Until { get; set; } - [QueryStringMapParameter>>("filters", false)] + [QueryStringMapParameter(typeof(IDictionary>), "filters", false)] public IDictionary>? Filters { get; set; } } } diff --git a/src/Docker.DotNet/Models/ContainersListParameters.Generated.cs b/src/Docker.DotNet/Models/ContainersListParameters.Generated.cs index 01d7b60a..a9fa4682 100644 --- a/src/Docker.DotNet/Models/ContainersListParameters.Generated.cs +++ b/src/Docker.DotNet/Models/ContainersListParameters.Generated.cs @@ -12,7 +12,7 @@ public class ContainersListParameters // (main.ContainersListParameters) [QueryStringBoolParameter("size", false)] public bool? Size { get; set; } - [QueryStringMapParameter>>("filters", false)] + [QueryStringMapParameter(typeof(IDictionary>), "filters", false)] public IDictionary>? Filters { get; set; } } } diff --git a/src/Docker.DotNet/Models/ContainersPruneParameters.Generated.cs b/src/Docker.DotNet/Models/ContainersPruneParameters.Generated.cs index 9cc67f0e..b4a1c47e 100644 --- a/src/Docker.DotNet/Models/ContainersPruneParameters.Generated.cs +++ b/src/Docker.DotNet/Models/ContainersPruneParameters.Generated.cs @@ -3,7 +3,7 @@ namespace Docker.DotNet.Models { public class ContainersPruneParameters // (main.ContainersPruneParameters) { - [QueryStringMapParameter>>("filters", false)] + [QueryStringMapParameter(typeof(IDictionary>), "filters", false)] public IDictionary>? Filters { get; set; } } } diff --git a/src/Docker.DotNet/Models/ImageBuildParameters.Generated.cs b/src/Docker.DotNet/Models/ImageBuildParameters.Generated.cs index 92e55b50..70480e7f 100644 --- a/src/Docker.DotNet/Models/ImageBuildParameters.Generated.cs +++ b/src/Docker.DotNet/Models/ImageBuildParameters.Generated.cs @@ -51,10 +51,10 @@ public class ImageBuildParameters // (main.ImageBuildParameters) [QueryStringParameter("dockerfile", false)] public string? Dockerfile { get; set; } - [QueryStringMapParameter>("buildargs", false)] + [QueryStringMapParameter(typeof(IDictionary), "buildargs", false)] public IDictionary? BuildArgs { get; set; } - [QueryStringMapParameter>("labels", false)] + [QueryStringMapParameter(typeof(IDictionary), "labels", false)] public IDictionary? Labels { get; set; } [QueryStringBoolParameter("squash", false)] diff --git a/src/Docker.DotNet/Models/ImagesListParameters.Generated.cs b/src/Docker.DotNet/Models/ImagesListParameters.Generated.cs index 1b0a3ee5..e8064605 100644 --- a/src/Docker.DotNet/Models/ImagesListParameters.Generated.cs +++ b/src/Docker.DotNet/Models/ImagesListParameters.Generated.cs @@ -6,7 +6,7 @@ public class ImagesListParameters // (main.ImagesListParameters) [QueryStringBoolParameter("all", false)] public bool? All { get; set; } - [QueryStringMapParameter>>("filters", false)] + [QueryStringMapParameter(typeof(IDictionary>), "filters", false)] public IDictionary>? Filters { get; set; } [QueryStringBoolParameter("shared-size", false)] diff --git a/src/Docker.DotNet/Models/ImagesPruneParameters.Generated.cs b/src/Docker.DotNet/Models/ImagesPruneParameters.Generated.cs index 58bb5bc8..37f83b20 100644 --- a/src/Docker.DotNet/Models/ImagesPruneParameters.Generated.cs +++ b/src/Docker.DotNet/Models/ImagesPruneParameters.Generated.cs @@ -3,7 +3,7 @@ namespace Docker.DotNet.Models { public class ImagesPruneParameters // (main.ImagesPruneParameters) { - [QueryStringMapParameter>>("filters", false)] + [QueryStringMapParameter(typeof(IDictionary>), "filters", false)] public IDictionary>? Filters { get; set; } } } diff --git a/src/Docker.DotNet/Models/ImagesSearchParameters.Generated.cs b/src/Docker.DotNet/Models/ImagesSearchParameters.Generated.cs index 338a41f8..e1fa0962 100644 --- a/src/Docker.DotNet/Models/ImagesSearchParameters.Generated.cs +++ b/src/Docker.DotNet/Models/ImagesSearchParameters.Generated.cs @@ -9,7 +9,7 @@ public class ImagesSearchParameters // (main.ImagesSearchParameters) [QueryStringParameter("limit", false)] public long? Limit { get; set; } - [QueryStringMapParameter>>("filters", false)] + [QueryStringMapParameter(typeof(IDictionary>), "filters", false)] public IDictionary>? Filters { get; set; } } } diff --git a/src/Docker.DotNet/Models/NetworksDeleteUnusedParameters.Generated.cs b/src/Docker.DotNet/Models/NetworksDeleteUnusedParameters.Generated.cs index 968a4368..01733a89 100644 --- a/src/Docker.DotNet/Models/NetworksDeleteUnusedParameters.Generated.cs +++ b/src/Docker.DotNet/Models/NetworksDeleteUnusedParameters.Generated.cs @@ -3,7 +3,7 @@ namespace Docker.DotNet.Models { public class NetworksDeleteUnusedParameters // (main.NetworksDeleteUnusedParameters) { - [QueryStringMapParameter>>("filters", false)] + [QueryStringMapParameter(typeof(IDictionary>), "filters", false)] public IDictionary>? Filters { get; set; } } } diff --git a/src/Docker.DotNet/Models/NetworksListParameters.Generated.cs b/src/Docker.DotNet/Models/NetworksListParameters.Generated.cs index aa0a0291..b0343566 100644 --- a/src/Docker.DotNet/Models/NetworksListParameters.Generated.cs +++ b/src/Docker.DotNet/Models/NetworksListParameters.Generated.cs @@ -3,7 +3,7 @@ namespace Docker.DotNet.Models { public class NetworksListParameters // (main.NetworksListParameters) { - [QueryStringMapParameter>>("filters", false)] + [QueryStringMapParameter(typeof(IDictionary>), "filters", false)] public IDictionary>? Filters { get; set; } } } diff --git a/src/Docker.DotNet/Models/PluginListParameters.Generated.cs b/src/Docker.DotNet/Models/PluginListParameters.Generated.cs index 8afee824..96146010 100644 --- a/src/Docker.DotNet/Models/PluginListParameters.Generated.cs +++ b/src/Docker.DotNet/Models/PluginListParameters.Generated.cs @@ -3,7 +3,7 @@ namespace Docker.DotNet.Models { public class PluginListParameters // (main.PluginListParameters) { - [QueryStringMapParameter>>("filters", false)] + [QueryStringMapParameter(typeof(IDictionary>), "filters", false)] public IDictionary>? Filters { get; set; } } } diff --git a/src/Docker.DotNet/Models/ServiceListParameters.Generated.cs b/src/Docker.DotNet/Models/ServiceListParameters.Generated.cs index be6a52b3..c793e867 100644 --- a/src/Docker.DotNet/Models/ServiceListParameters.Generated.cs +++ b/src/Docker.DotNet/Models/ServiceListParameters.Generated.cs @@ -3,7 +3,7 @@ namespace Docker.DotNet.Models { public class ServiceListParameters // (main.ServiceListParameters) { - [QueryStringMapParameter>>("filters", false)] + [QueryStringMapParameter(typeof(IDictionary>), "filters", false)] public IDictionary>? Filters { get; set; } [QueryStringBoolParameter("status", false)] diff --git a/src/Docker.DotNet/Models/TasksListParameters.Generated.cs b/src/Docker.DotNet/Models/TasksListParameters.Generated.cs index 6a8d5f9c..0639fe8c 100644 --- a/src/Docker.DotNet/Models/TasksListParameters.Generated.cs +++ b/src/Docker.DotNet/Models/TasksListParameters.Generated.cs @@ -3,7 +3,7 @@ namespace Docker.DotNet.Models { public class TasksListParameters // (main.TasksListParameters) { - [QueryStringMapParameter>>("filters", false)] + [QueryStringMapParameter(typeof(IDictionary>), "filters", false)] public IDictionary>? Filters { get; set; } } } diff --git a/src/Docker.DotNet/Models/VolumesListParameters.Generated.cs b/src/Docker.DotNet/Models/VolumesListParameters.Generated.cs index ebace0c1..fb637f84 100644 --- a/src/Docker.DotNet/Models/VolumesListParameters.Generated.cs +++ b/src/Docker.DotNet/Models/VolumesListParameters.Generated.cs @@ -3,7 +3,7 @@ namespace Docker.DotNet.Models { public class VolumesListParameters // (main.VolumesListParameters) { - [QueryStringMapParameter>>("filters", false)] + [QueryStringMapParameter(typeof(IDictionary>), "filters", false)] public IDictionary>? Filters { get; set; } } } diff --git a/src/Docker.DotNet/Models/VolumesPruneParameters.Generated.cs b/src/Docker.DotNet/Models/VolumesPruneParameters.Generated.cs index b787cd8b..f069c7d0 100644 --- a/src/Docker.DotNet/Models/VolumesPruneParameters.Generated.cs +++ b/src/Docker.DotNet/Models/VolumesPruneParameters.Generated.cs @@ -3,7 +3,7 @@ namespace Docker.DotNet.Models { public class VolumesPruneParameters // (main.VolumesPruneParameters) { - [QueryStringMapParameter>>("filters", false)] + [QueryStringMapParameter(typeof(IDictionary>), "filters", false)] public IDictionary>? Filters { get; set; } } } diff --git a/src/Docker.DotNet/QueryStringMapParameterAttribute.cs b/src/Docker.DotNet/QueryStringMapParameterAttribute.cs index 5fa1f4bb..43f4755d 100644 --- a/src/Docker.DotNet/QueryStringMapParameterAttribute.cs +++ b/src/Docker.DotNet/QueryStringMapParameterAttribute.cs @@ -1,17 +1,17 @@ namespace Docker.DotNet; -internal sealed class QueryStringMapParameterAttribute(string name, bool required) : QueryStringParameterAttribute(name, required) +internal sealed class QueryStringMapParameterAttribute(Type type, string name, bool required) : QueryStringParameterAttribute(name, required) { public override IEnumerable Convert(object value) { Debug.Assert(value != null); - Debug.Assert(value is T); + Debug.Assert(type.IsInstanceOfType(value)); - if (value is not T typedValue) + if (!type.IsInstanceOfType(value)) { - throw new ArgumentException($"Expected value of type '{typeof(T)}'.", nameof(value)); + throw new ArgumentException($"Expected value of type '{type}'.", nameof(value)); } - return [JsonSerializer.Instance.Serialize(typedValue)]; + return [JsonSerializer.Instance.Serialize(value, type)]; } } \ No newline at end of file diff --git a/tools/Docker.DotNet.SourceAnalyzers/JsonSerializerAnalyzer.cs b/tools/Docker.DotNet.SourceAnalyzers/JsonSerializerAnalyzer.cs index cdce4538..f90c0847 100644 --- a/tools/Docker.DotNet.SourceAnalyzers/JsonSerializerAnalyzer.cs +++ b/tools/Docker.DotNet.SourceAnalyzers/JsonSerializerAnalyzer.cs @@ -27,7 +27,7 @@ public override void Initialize(AnalysisContext context) // Handle new JsonRequestContent. context.RegisterSyntaxNodeAction(AnalyzeObjectCreation, SyntaxKind.ObjectCreationExpression); - // Handle [QueryStringMapParameter(...)]. + // Handle [QueryStringMapParameter(typeof(T), ...)]. context.RegisterSyntaxNodeAction(AnalyzeAttribute, SyntaxKind.Attribute); } @@ -89,9 +89,25 @@ private static void AnalyzeAttribute(SyntaxNodeAnalysisContext context) typeSymbol.Name == "QueryStringMapParameterAttribute" && typeSymbol.ContainingNamespace.ToDisplayString() == "Docker.DotNet"; - if (isStjEntryPoint && typeSymbol.TypeArguments.Length > 0) + if (!isStjEntryPoint) + { + return; + } + + // The mapped type is the first constructor argument: typeof(T). + var typeOfArgument = attribute.ArgumentList?.Arguments + .Select(arg => arg.Expression) + .OfType() + .FirstOrDefault(); + + if (typeOfArgument == null) + { + return; + } + + if (context.SemanticModel.GetTypeInfo(typeOfArgument.Type).Type is { } mappedType) { - CheckAndReportType(context, typeSymbol.TypeArguments[0], attribute.GetLocation(), typeSymbol.Name); + CheckAndReportType(context, mappedType, attribute.GetLocation(), typeSymbol.Name); } } diff --git a/tools/specgen/specgen.go b/tools/specgen/specgen.go index 7645d413..9730c8b9 100644 --- a/tools/specgen/specgen.go +++ b/tools/specgen/specgen.go @@ -850,6 +850,7 @@ func reflectTypeMembers(t reflect.Type, m *CSModelType) { } queryStringParameter := "QueryStringParameter" + var leadingArgs []CSArgument switch f.Type.Kind() { case reflect.Bool: @@ -857,10 +858,12 @@ func reflectTypeMembers(t reflect.Type, m *CSModelType) { case reflect.Slice, reflect.Array: queryStringParameter = "QueryStringListParameter" case reflect.Map: - queryStringParameter = "QueryStringMapParameter<" + csProp.Type.Name + ">" + queryStringParameter = "QueryStringMapParameter" + leadingArgs = append(leadingArgs, CSArgument{Value: "typeof(" + csProp.Type.Name + ")"}) } a := CSAttribute{Type: CSType{"", queryStringParameter}} + a.Arguments = append(a.Arguments, leadingArgs...) a.Arguments = append( a.Arguments, CSArgument{