diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0559e652..90d97e1b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -132,13 +132,9 @@ jobs: exit 1 - name: Test (${{ matrix.docker.name }}) - run: >- - dotnet test - --configuration Release - --framework ${{ matrix.dotnet.tfm }} - --no-restore - --no-build - --logger console + run: | + ./test/Docker.DotNet.Tests/bin/Release/${{ matrix.dotnet.tfm }}/linux-x64/publish/Docker.DotNet.Tests + ./test/Docker.DotNet.TestsV2/bin/Release/${{ matrix.dotnet.tfm }}/linux-x64/publish/Docker.DotNet.TestsV2 env: DOCKER_HOST: ${{ matrix.docker.docker_host }} DOCKER_TLS_VERIFY: ${{ matrix.docker.tls_verify }} diff --git a/Directory.Packages.props b/Directory.Packages.props index c8be7ea0..fccf6d9c 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -10,9 +10,10 @@ + + - - - + + diff --git a/Docker.DotNet.slnx b/Docker.DotNet.slnx index 2da18906..89c36932 100644 --- a/Docker.DotNet.slnx +++ b/Docker.DotNet.slnx @@ -23,4 +23,7 @@ + + + diff --git a/global.json b/global.json new file mode 100644 index 00000000..802ab217 --- /dev/null +++ b/global.json @@ -0,0 +1,5 @@ +{ + "test": { + "runner": "Microsoft.Testing.Platform" + } +} \ No newline at end of file diff --git a/src/Directory.Build.props b/src/Directory.Build.props index efc57594..4b3a7d31 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -3,6 +3,7 @@ + true true net8.0;net9.0;net10.0;netstandard2.0;netstandard2.1 Copyright (c) .NET Foundation and Contributors; Andre Hofmeister diff --git a/src/Docker.DotNet.NPipe/DockerHandlerFactory.cs b/src/Docker.DotNet.NPipe/DockerHandlerFactory.cs index 40d003f4..1310e069 100644 --- a/src/Docker.DotNet.NPipe/DockerHandlerFactory.cs +++ b/src/Docker.DotNet.NPipe/DockerHandlerFactory.cs @@ -45,10 +45,10 @@ public ResolvedTransport CreateHandler(NPipeTransportOptions transportOptions, C var dockerStream = new DockerPipeStream(clientStream); -#if NETSTANDARD - var namedPipeConnectTimeout = (int)transportOptions.ConnectTimeout.TotalMilliseconds; -#else +#if NET var namedPipeConnectTimeout = transportOptions.ConnectTimeout; +#else + var namedPipeConnectTimeout = (int)transportOptions.ConnectTimeout.TotalMilliseconds; #endif await clientStream.ConnectAsync(namedPipeConnectTimeout, cancellationToken) diff --git a/src/Docker.DotNet.NativeHttp/DockerHandlerFactory.cs b/src/Docker.DotNet.NativeHttp/DockerHandlerFactory.cs index 4ee2d2d1..a7c568f8 100644 --- a/src/Docker.DotNet.NativeHttp/DockerHandlerFactory.cs +++ b/src/Docker.DotNet.NativeHttp/DockerHandlerFactory.cs @@ -29,7 +29,7 @@ public ResolvedTransport CreateHandler(NativeHttpTransportOptions transportOptio var scheme = clientOptions.AuthProvider.TlsEnabled ? Uri.UriSchemeHttps : Uri.UriSchemeHttp; var uri = new UriBuilder(clientOptions.Endpoint) { Scheme = scheme }.Uri; -#if NET6_0_OR_GREATER +#if NET var handler = new SocketsHttpHandler { MaxConnectionsPerServer = MaxConnectionsPerServer, diff --git a/src/Docker.DotNet.NativeHttp/NativeHttpTransportOptions.cs b/src/Docker.DotNet.NativeHttp/NativeHttpTransportOptions.cs index 5bf408ce..45a295fa 100644 --- a/src/Docker.DotNet.NativeHttp/NativeHttpTransportOptions.cs +++ b/src/Docker.DotNet.NativeHttp/NativeHttpTransportOptions.cs @@ -5,7 +5,7 @@ namespace Docker.DotNet.NativeHttp; /// public sealed record NativeHttpTransportOptions { -#if NET6_0_OR_GREATER +#if NET /// /// Gets a callback that configures the created instance. /// diff --git a/src/Docker.DotNet.X509/CertificateCredentials.cs b/src/Docker.DotNet.X509/CertificateCredentials.cs index 1c732bbd..2b37fe04 100644 --- a/src/Docker.DotNet.X509/CertificateCredentials.cs +++ b/src/Docker.DotNet.X509/CertificateCredentials.cs @@ -15,7 +15,7 @@ public CertificateCredentials(X509Certificate2? certificate) public HttpMessageHandler ConfigureHandler(HttpMessageHandler handler) { -#if NET6_0_OR_GREATER +#if NET if (handler is SocketsHttpHandler socketsHandler) { if (_certificate != null) diff --git a/src/Docker.DotNet.X509/DockerTlsCertificates.cs b/src/Docker.DotNet.X509/DockerTlsCertificates.cs index 193fb85c..bbf58750 100644 --- a/src/Docker.DotNet.X509/DockerTlsCertificates.cs +++ b/src/Docker.DotNet.X509/DockerTlsCertificates.cs @@ -64,7 +64,7 @@ public static X509Certificate2 LoadCertificateFromPemFiles(string certPemPath, s } return certificate; -#elif NET6_0_OR_GREATER +#elif NET var certificate = X509Certificate2.CreateFromPemFile(certPemPath, keyPemPath); if (OperatingSystem.IsWindows()) @@ -75,10 +75,8 @@ public static X509Certificate2 LoadCertificateFromPemFiles(string certPemPath, s } return certificate; -#elif NETSTANDARD - return Polyfills.X509Certificate2.CreateFromPemFile(certPemPath, keyPemPath); #else - return X509Certificate2.CreateFromPemFile(certPemPath, keyPemPath); + return Polyfills.X509Certificate2.CreateFromPemFile(certPemPath, keyPemPath); #endif } @@ -141,7 +139,7 @@ public static RemoteCertificateValidationCallback CreateCertificateAuthorityVali using var chain = new X509Chain(); chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; -#if NET5_0_OR_GREATER +#if NET chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; chain.ChainPolicy.CustomTrustStore.Add(certificateAuthorityCertificate); return chain.Build(serverCertificate2); diff --git a/src/Docker.DotNet/Docker.DotNet.csproj b/src/Docker.DotNet/Docker.DotNet.csproj index da5c71ad..eb7b338d 100644 --- a/src/Docker.DotNet/Docker.DotNet.csproj +++ b/src/Docker.DotNet/Docker.DotNet.csproj @@ -21,6 +21,7 @@ + @@ -29,6 +30,7 @@ + @@ -49,6 +51,7 @@ + diff --git a/src/Docker.DotNet/DockerClient.cs b/src/Docker.DotNet/DockerClient.cs index 6754f9b6..6328e8f7 100644 --- a/src/Docker.DotNet/DockerClient.cs +++ b/src/Docker.DotNet/DockerClient.cs @@ -1,7 +1,5 @@ namespace Docker.DotNet; -using System; - public sealed class DockerClient : IDockerClient { internal readonly IEnumerable NoErrorHandlers = Enumerable.Empty(); @@ -370,7 +368,7 @@ private async Task PrivateMakeRequestAsync( if (Timeout.InfiniteTimeSpan == timeout) { -#if NET6_0_OR_GREATER +#if NET return await _client.SendAsync(request, completionOption, cancellationToken) .ConfigureAwait(false); #else diff --git a/src/Docker.DotNet/Endpoints/ConfigsOperations.cs b/src/Docker.DotNet/Endpoints/ConfigsOperations.cs index d3712136..744f1c8f 100644 --- a/src/Docker.DotNet/Endpoints/ConfigsOperations.cs +++ b/src/Docker.DotNet/Endpoints/ConfigsOperations.cs @@ -11,7 +11,7 @@ internal ConfigOperations(DockerClient client) public async Task> ListConfigsAsync(CancellationToken cancellationToken = default) { - return await _client.MakeRequestAsync>(_client.NoErrorHandlers, HttpMethod.Get, "configs", cancellationToken) + return await _client.MakeRequestAsync(_client.NoErrorHandlers, HttpMethod.Get, "configs", cancellationToken) .ConfigureAwait(false); } diff --git a/src/Docker.DotNet/Endpoints/ISwarmOperations.cs b/src/Docker.DotNet/Endpoints/ISwarmOperations.cs index 1648e5ee..e69c6dec 100644 --- a/src/Docker.DotNet/Endpoints/ISwarmOperations.cs +++ b/src/Docker.DotNet/Endpoints/ISwarmOperations.cs @@ -121,7 +121,7 @@ public interface ISwarmOperations /// 500 - Server error. /// 503 - Node is not part of a swarm. /// - Task> ListServicesAsync(ServiceListParameters? parameters = null, CancellationToken cancellationToken = default); + Task> ListServicesAsync(ServiceListParameters? parameters = null, CancellationToken cancellationToken = default); /// /// Update a service. @@ -198,7 +198,7 @@ public interface ISwarmOperations /// 503 - Node is not part of a swarm. /// /// - Task> ListNodesAsync(CancellationToken cancellationToken = default); + Task> ListNodesAsync(CancellationToken cancellationToken = default); /// /// Inspect a node. diff --git a/src/Docker.DotNet/Endpoints/ImageOperations.cs b/src/Docker.DotNet/Endpoints/ImageOperations.cs index 87e0b068..304c7a94 100644 --- a/src/Docker.DotNet/Endpoints/ImageOperations.cs +++ b/src/Docker.DotNet/Endpoints/ImageOperations.cs @@ -313,7 +313,7 @@ private static Dictionary RegistryConfigHeaders(IEnumerable 0) { registryAuthConfigurations[registryAddress] = registryAuthConfiguration; } diff --git a/src/Docker.DotNet/Endpoints/SecretsOperations.cs b/src/Docker.DotNet/Endpoints/SecretsOperations.cs index 01e36b2e..58b77750 100644 --- a/src/Docker.DotNet/Endpoints/SecretsOperations.cs +++ b/src/Docker.DotNet/Endpoints/SecretsOperations.cs @@ -11,7 +11,7 @@ internal SecretsOperations(DockerClient client) public async Task> ListAsync(CancellationToken cancellationToken = default) { - return await _client.MakeRequestAsync>(_client.NoErrorHandlers, HttpMethod.Get, "secrets", cancellationToken) + return await _client.MakeRequestAsync(_client.NoErrorHandlers, HttpMethod.Get, "secrets", cancellationToken) .ConfigureAwait(false); } diff --git a/src/Docker.DotNet/Endpoints/SwarmOperations.cs b/src/Docker.DotNet/Endpoints/SwarmOperations.cs index 6ff57d85..fa9a77cc 100644 --- a/src/Docker.DotNet/Endpoints/SwarmOperations.cs +++ b/src/Docker.DotNet/Endpoints/SwarmOperations.cs @@ -82,7 +82,7 @@ await _client.MakeRequestAsync([NotInSwarmResponseHandler], HttpMethod.Post, "sw .ConfigureAwait(false); } - public async Task> ListServicesAsync(ServiceListParameters? parameters = null, CancellationToken cancellationToken = default) + public async Task> ListServicesAsync(ServiceListParameters? parameters = null, CancellationToken cancellationToken = default) { var queryParameters = parameters == null ? null : new QueryString(parameters); @@ -193,7 +193,7 @@ private static Dictionary RegistryAuthHeaders(AuthConfig? authCo }; } - public async Task> ListNodesAsync(CancellationToken cancellationToken = default) + public async Task> ListNodesAsync(CancellationToken cancellationToken = default) { return await _client.MakeRequestAsync([NotInSwarmResponseHandler], HttpMethod.Get, "nodes", cancellationToken) .ConfigureAwait(false); diff --git a/src/Docker.DotNet/Endpoints/TasksOperations.cs b/src/Docker.DotNet/Endpoints/TasksOperations.cs index 1fe89878..c50a3c76 100644 --- a/src/Docker.DotNet/Endpoints/TasksOperations.cs +++ b/src/Docker.DotNet/Endpoints/TasksOperations.cs @@ -18,7 +18,7 @@ public async Task> ListAsync(TasksListParameters? parameters { var queryParameters = parameters == null ? null : new QueryString(parameters); - return await _client.MakeRequestAsync>(_client.NoErrorHandlers, HttpMethod.Get, "tasks", queryParameters, cancellationToken) + return await _client.MakeRequestAsync(_client.NoErrorHandlers, HttpMethod.Get, "tasks", queryParameters, cancellationToken) .ConfigureAwait(false); } diff --git a/src/Docker.DotNet/IQueryStringConverter.cs b/src/Docker.DotNet/IQueryStringConverter.cs deleted file mode 100644 index 4f07c50f..00000000 --- a/src/Docker.DotNet/IQueryStringConverter.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Docker.DotNet; - -internal interface IQueryStringConverter -{ - bool CanConvert(Type t); - - string[] Convert(object o); -} \ No newline at end of file diff --git a/src/Docker.DotNet/IQueryStringConverterInstanceFactory.cs b/src/Docker.DotNet/IQueryStringConverterInstanceFactory.cs deleted file mode 100644 index cd81a9c5..00000000 --- a/src/Docker.DotNet/IQueryStringConverterInstanceFactory.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Docker.DotNet; - -internal interface IQueryStringConverterInstanceFactory -{ - IQueryStringConverter GetConverterInstance(Type t); -} \ No newline at end of file diff --git a/src/Docker.DotNet/JsonSerializer.cs b/src/Docker.DotNet/JsonSerializer.cs index dca130a7..ccb60272 100644 --- a/src/Docker.DotNet/JsonSerializer.cs +++ b/src/Docker.DotNet/JsonSerializer.cs @@ -15,11 +15,15 @@ static JsonSerializer() private JsonSerializer() { + _options.TypeInfoResolver = JsonTypeInfoResolver.Combine( + DockerModelsJsonSerializerContext.Default, + DockerExtendedJsonSerializerContext.Default); _options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; _options.Converters.Add(new JsonEnumMemberConverter()); _options.Converters.Add(new JsonEnumMemberConverter()); _options.Converters.Add(new JsonDateTimeConverter()); _options.Converters.Add(new JsonTimeSpanNanosecondsConverter()); + _options.MakeReadOnly(); } public static JsonSerializer Instance { get; } @@ -38,26 +42,27 @@ public HttpContent GetHttpContent(T value) public string Serialize(T value) { - return System.Text.Json.JsonSerializer.Serialize(value, _options); + return System.Text.Json.JsonSerializer.Serialize(value, JsonTypeInfoCache.Value); } public byte[] SerializeToUtf8Bytes(T value) { - return System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(value, _options); + return System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(value, JsonTypeInfoCache.Value); } public T Deserialize(byte[] json) { - return System.Text.Json.JsonSerializer.Deserialize(json, _options)!; + return System.Text.Json.JsonSerializer.Deserialize(json, JsonTypeInfoCache.Value)!; } public Task DeserializeAsync(HttpContent content, CancellationToken cancellationToken) { - return content.ReadFromJsonAsync(_options, cancellationToken)!; + return content.ReadFromJsonAsync(JsonTypeInfoCache.Value, cancellationToken)!; } public async IAsyncEnumerable DeserializeAsync(Stream stream, [EnumeratorCancellation] CancellationToken cancellationToken) { + var jsonTypeInfo = JsonTypeInfoCache.Value; var reader = PipeReader.Create(stream); while (true) @@ -69,7 +74,7 @@ public async IAsyncEnumerable DeserializeAsync(Stream stream, [EnumeratorC while (!buffer.IsEmpty && TryParseJson(ref buffer, out var jsonDocument)) { - yield return jsonDocument!.Deserialize(_options)!; + yield return jsonDocument!.Deserialize(jsonTypeInfo)!; } if (result.IsCompleted) @@ -95,4 +100,51 @@ private static bool TryParseJson(ref ReadOnlySequence buffer, out JsonDocu return false; } -} \ No newline at end of file + + private static class JsonTypeInfoCache + { + public static readonly JsonTypeInfo Value = (JsonTypeInfo)Instance._options.GetTypeInfo(typeof(T)); + } +} + +// Additional source-generated metadata for collections and dictionaries used by operations. + +[JsonSerializable(typeof(string))] +[JsonSerializable(typeof(IList))] + +// Filters +[JsonSerializable(typeof(IDictionary>))] +[JsonSerializable(typeof(IDictionary))] + +// ConfigOperations +[JsonSerializable(typeof(SwarmConfig[]))] // ListConfigsAsync + +// ContainerOperations +[JsonSerializable(typeof(ContainerFileSystemChangeResponse[]))] // InspectChangesAsync +[JsonSerializable(typeof(ContainerListResponse[]))] // ListContainersAsync + +// ImageOperations +[JsonSerializable(typeof(Dictionary[]))] // DeleteImageAsync +[JsonSerializable(typeof(ImageHistoryResponse[]))] // GetImageHistoryAsync +[JsonSerializable(typeof(ImagesListResponse[]))] // ListImagesAsync +[JsonSerializable(typeof(Dictionary))] // RegistryConfigHeaders +[JsonSerializable(typeof(ImageSearchResponse[]))] // SearchImagesAsync + +// NetworkOperations +[JsonSerializable(typeof(NetworkResponse[]))] // ListNetworksAsync + +// PluginOperations +[JsonSerializable(typeof(PluginPrivilege[]))] // GetPrivilegesAsync +[JsonSerializable(typeof(IList))] // InstallPluginAsync +[JsonSerializable(typeof(Plugin[]))] // ListPluginsAsync + +// SecretOperations +[JsonSerializable(typeof(Secret[]))] // ListAsync + +// SwarmOperations +[JsonSerializable(typeof(NodeListResponse[]))] // ListNodesAsync +[JsonSerializable(typeof(SwarmService[]))] // ListServicesAsync + +// TaskOperations +[JsonSerializable(typeof(TaskResponse[]))] // ListAsync +internal sealed partial class DockerExtendedJsonSerializerContext : JsonSerializerContext { } diff --git a/src/Docker.DotNet/Models/CommitContainerChangesParameters.Generated.cs b/src/Docker.DotNet/Models/CommitContainerChangesParameters.Generated.cs index 340be0d6..de6442ac 100644 --- a/src/Docker.DotNet/Models/CommitContainerChangesParameters.Generated.cs +++ b/src/Docker.DotNet/Models/CommitContainerChangesParameters.Generated.cs @@ -53,10 +53,10 @@ public CommitContainerChangesParameters(ContainerConfig Config) [QueryStringParameter("author", false)] public string? Author { get; set; } - [QueryStringParameter("changes", false, typeof(QueryStringEnumerableConverter))] + [QueryStringListParameter("changes", false)] public IList? Changes { get; set; } - [QueryStringParameter("pause", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("pause", false)] public bool? Pause { get; set; } [JsonPropertyName("Hostname")] diff --git a/src/Docker.DotNet/Models/ContainerAttachParameters.Generated.cs b/src/Docker.DotNet/Models/ContainerAttachParameters.Generated.cs index 2dead30f..5206f540 100644 --- a/src/Docker.DotNet/Models/ContainerAttachParameters.Generated.cs +++ b/src/Docker.DotNet/Models/ContainerAttachParameters.Generated.cs @@ -3,22 +3,22 @@ namespace Docker.DotNet.Models { public class ContainerAttachParameters // (main.ContainerAttachParameters) { - [QueryStringParameter("stream", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("stream", false)] public bool? Stream { get; set; } - [QueryStringParameter("stdin", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("stdin", false)] public bool? Stdin { get; set; } - [QueryStringParameter("stdout", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("stdout", false)] public bool? Stdout { get; set; } - [QueryStringParameter("stderr", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("stderr", false)] public bool? Stderr { get; set; } [QueryStringParameter("detachKeys", false)] public string? DetachKeys { get; set; } - [QueryStringParameter("logs", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("logs", false)] public bool? Logs { get; set; } } } diff --git a/src/Docker.DotNet/Models/ContainerEventsParameters.Generated.cs b/src/Docker.DotNet/Models/ContainerEventsParameters.Generated.cs index 5f216e05..952db26a 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; } - [QueryStringParameter("filters", false, typeof(QueryStringMapConverter))] + [QueryStringMapParameter>>("filters", false)] public IDictionary>? Filters { get; set; } } } diff --git a/src/Docker.DotNet/Models/ContainerInspectParameters.Generated.cs b/src/Docker.DotNet/Models/ContainerInspectParameters.Generated.cs index 970c0d1a..3f04667a 100644 --- a/src/Docker.DotNet/Models/ContainerInspectParameters.Generated.cs +++ b/src/Docker.DotNet/Models/ContainerInspectParameters.Generated.cs @@ -3,7 +3,7 @@ namespace Docker.DotNet.Models { public class ContainerInspectParameters // (main.ContainerInspectParameters) { - [QueryStringParameter("size", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("size", false)] public bool? IncludeSize { get; set; } } } diff --git a/src/Docker.DotNet/Models/ContainerLogsParameters.Generated.cs b/src/Docker.DotNet/Models/ContainerLogsParameters.Generated.cs index 19b97809..f225c747 100644 --- a/src/Docker.DotNet/Models/ContainerLogsParameters.Generated.cs +++ b/src/Docker.DotNet/Models/ContainerLogsParameters.Generated.cs @@ -3,10 +3,10 @@ namespace Docker.DotNet.Models { public class ContainerLogsParameters // (main.ContainerLogsParameters) { - [QueryStringParameter("stdout", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("stdout", false)] public bool? ShowStdout { get; set; } - [QueryStringParameter("stderr", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("stderr", false)] public bool? ShowStderr { get; set; } [QueryStringParameter("since", false)] @@ -15,10 +15,10 @@ public class ContainerLogsParameters // (main.ContainerLogsParameters) [QueryStringParameter("until", false)] public string? Until { get; set; } - [QueryStringParameter("timestamps", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("timestamps", false)] public bool? Timestamps { get; set; } - [QueryStringParameter("follow", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("follow", false)] public bool? Follow { get; set; } [QueryStringParameter("tail", false)] diff --git a/src/Docker.DotNet/Models/ContainerRemoveParameters.Generated.cs b/src/Docker.DotNet/Models/ContainerRemoveParameters.Generated.cs index cbd6b01d..013de32e 100644 --- a/src/Docker.DotNet/Models/ContainerRemoveParameters.Generated.cs +++ b/src/Docker.DotNet/Models/ContainerRemoveParameters.Generated.cs @@ -3,13 +3,13 @@ namespace Docker.DotNet.Models { public class ContainerRemoveParameters // (main.ContainerRemoveParameters) { - [QueryStringParameter("v", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("v", false)] public bool? RemoveVolumes { get; set; } - [QueryStringParameter("link", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("link", false)] public bool? RemoveLinks { get; set; } - [QueryStringParameter("force", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("force", false)] public bool? Force { get; set; } } } diff --git a/src/Docker.DotNet/Models/ContainerStatsParameters.Generated.cs b/src/Docker.DotNet/Models/ContainerStatsParameters.Generated.cs index 2fe8bb69..1a084a3b 100644 --- a/src/Docker.DotNet/Models/ContainerStatsParameters.Generated.cs +++ b/src/Docker.DotNet/Models/ContainerStatsParameters.Generated.cs @@ -3,10 +3,10 @@ namespace Docker.DotNet.Models { public class ContainerStatsParameters // (main.ContainerStatsParameters) { - [QueryStringParameter("stream", true, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("stream", true)] public bool Stream { get; set; } = true; - [QueryStringParameter("one-shot", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("one-shot", false)] public bool? OneShot { get; set; } } } diff --git a/src/Docker.DotNet/Models/ContainersListParameters.Generated.cs b/src/Docker.DotNet/Models/ContainersListParameters.Generated.cs index 8998d1ba..01d7b60a 100644 --- a/src/Docker.DotNet/Models/ContainersListParameters.Generated.cs +++ b/src/Docker.DotNet/Models/ContainersListParameters.Generated.cs @@ -3,16 +3,16 @@ namespace Docker.DotNet.Models { public class ContainersListParameters // (main.ContainersListParameters) { - [QueryStringParameter("all", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("all", false)] public bool? All { get; set; } [QueryStringParameter("limit", false)] public long? Limit { get; set; } - [QueryStringParameter("size", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("size", false)] public bool? Size { get; set; } - [QueryStringParameter("filters", false, typeof(QueryStringMapConverter))] + [QueryStringMapParameter>>("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 43e316da..9cc67f0e 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) { - [QueryStringParameter("filters", false, typeof(QueryStringMapConverter))] + [QueryStringMapParameter>>("filters", false)] public IDictionary>? Filters { get; set; } } } diff --git a/src/Docker.DotNet/Models/CopyToContainerParameters.Generated.cs b/src/Docker.DotNet/Models/CopyToContainerParameters.Generated.cs index 8ae4bdda..059d5497 100644 --- a/src/Docker.DotNet/Models/CopyToContainerParameters.Generated.cs +++ b/src/Docker.DotNet/Models/CopyToContainerParameters.Generated.cs @@ -6,10 +6,10 @@ public class CopyToContainerParameters // (main.CopyToContainerParameters) [QueryStringParameter("path", true)] public string Path { get; set; } = default!; - [QueryStringParameter("noOverwriteDirNonDir", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("noOverwriteDirNonDir", false)] public bool? AllowOverwriteDirWithFile { get; set; } - [QueryStringParameter("copyUIDGID", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("copyUIDGID", false)] public bool? CopyUIDGID { get; set; } } } diff --git a/src/Docker.DotNet/Models/DockerModelsJsonSerializerContext.Generated.cs b/src/Docker.DotNet/Models/DockerModelsJsonSerializerContext.Generated.cs new file mode 100644 index 00000000..f00147c7 --- /dev/null +++ b/src/Docker.DotNet/Models/DockerModelsJsonSerializerContext.Generated.cs @@ -0,0 +1,262 @@ +namespace Docker.DotNet.Models +{ + [JsonSerializable(typeof(Actor))] + [JsonSerializable(typeof(Annotations))] + [JsonSerializable(typeof(AppArmorOpts))] + [JsonSerializable(typeof(AttestationProperties))] + [JsonSerializable(typeof(AuthConfig))] + [JsonSerializable(typeof(AuthResponse))] + [JsonSerializable(typeof(BindOptions))] + [JsonSerializable(typeof(BlkioStatEntry))] + [JsonSerializable(typeof(BlkioStats))] + [JsonSerializable(typeof(CAConfig))] + [JsonSerializable(typeof(CPUStats))] + [JsonSerializable(typeof(CPUUsage))] + [JsonSerializable(typeof(CapacityRange))] + [JsonSerializable(typeof(ClusterInfo))] + [JsonSerializable(typeof(ClusterOptions))] + [JsonSerializable(typeof(ClusterVolume))] + [JsonSerializable(typeof(ClusterVolumeSpec))] + [JsonSerializable(typeof(Commit))] + [JsonSerializable(typeof(CommitContainerChangesParameters))] + [JsonSerializable(typeof(CommitContainerChangesResponse))] + [JsonSerializable(typeof(ComponentVersion))] + [JsonSerializable(typeof(ConfigReference))] + [JsonSerializable(typeof(ConfigReferenceFileTarget))] + [JsonSerializable(typeof(ConfigReferenceRuntimeTarget))] + [JsonSerializable(typeof(ConsoleSize))] + [JsonSerializable(typeof(ContainerConfig))] + [JsonSerializable(typeof(ContainerExecCreateParameters))] + [JsonSerializable(typeof(ContainerExecCreateResponse))] + [JsonSerializable(typeof(ContainerExecInspectResponse))] + [JsonSerializable(typeof(ContainerExecStartParameters))] + [JsonSerializable(typeof(ContainerFileSystemChangeResponse))] + [JsonSerializable(typeof(ContainerInspectResponse))] + [JsonSerializable(typeof(ContainerListResponse))] + [JsonSerializable(typeof(ContainerPathStatResponse))] + [JsonSerializable(typeof(ContainerProcessesResponse))] + [JsonSerializable(typeof(ContainerSpec))] + [JsonSerializable(typeof(ContainerStatsResponse))] + [JsonSerializable(typeof(ContainerStatus))] + [JsonSerializable(typeof(ContainerUpdateParameters))] + [JsonSerializable(typeof(ContainerUpdateResponse))] + [JsonSerializable(typeof(ContainerWaitResponse))] + [JsonSerializable(typeof(ContainerdInfo))] + [JsonSerializable(typeof(ContainerdNamespaces))] + [JsonSerializable(typeof(ContainersPruneResponse))] + [JsonSerializable(typeof(CreateContainerParameters))] + [JsonSerializable(typeof(CreateContainerResponse))] + [JsonSerializable(typeof(CredentialSpec))] + [JsonSerializable(typeof(DNSConfig))] + [JsonSerializable(typeof(Descriptor))] + [JsonSerializable(typeof(DeviceInfo))] + [JsonSerializable(typeof(DeviceMapping))] + [JsonSerializable(typeof(DeviceRequest))] + [JsonSerializable(typeof(DiscreteGenericResource))] + [JsonSerializable(typeof(DispatcherConfig))] + [JsonSerializable(typeof(DockerOCIImageConfig))] + [JsonSerializable(typeof(DockerOCIImageConfigExt))] + [JsonSerializable(typeof(Driver))] + [JsonSerializable(typeof(DriverData))] + [JsonSerializable(typeof(EncryptionConfig))] + [JsonSerializable(typeof(Endpoint))] + [JsonSerializable(typeof(EndpointIPAMConfig))] + [JsonSerializable(typeof(EndpointResource))] + [JsonSerializable(typeof(EndpointSettings))] + [JsonSerializable(typeof(EndpointSpec))] + [JsonSerializable(typeof(EndpointVirtualIP))] + [JsonSerializable(typeof(EngineDescription))] + [JsonSerializable(typeof(ExecProcessConfig))] + [JsonSerializable(typeof(ExternalCA))] + [JsonSerializable(typeof(FirewallInfo))] + [JsonSerializable(typeof(GenericResource))] + [JsonSerializable(typeof(GlobalJob))] + [JsonSerializable(typeof(GlobalService))] + [JsonSerializable(typeof(Health))] + [JsonSerializable(typeof(HealthSummary))] + [JsonSerializable(typeof(HealthcheckConfig))] + [JsonSerializable(typeof(HealthcheckResult))] + [JsonSerializable(typeof(HostConfig))] + [JsonSerializable(typeof(IPAM))] + [JsonSerializable(typeof(IPAMConfig))] + [JsonSerializable(typeof(IPAMOptions))] + [JsonSerializable(typeof(IPAMStatus))] + [JsonSerializable(typeof(ImageBuildParameters))] + [JsonSerializable(typeof(ImageBuildResult))] + [JsonSerializable(typeof(ImageConfig))] + [JsonSerializable(typeof(ImageDeleteResponse))] + [JsonSerializable(typeof(ImageHistoryResponse))] + [JsonSerializable(typeof(ImageInspectResponse))] + [JsonSerializable(typeof(ImageOptions))] + [JsonSerializable(typeof(ImageProperties))] + [JsonSerializable(typeof(ImagePropertiesSize))] + [JsonSerializable(typeof(ImagePushParameters))] + [JsonSerializable(typeof(ImageSearchResponse))] + [JsonSerializable(typeof(ImagesCreateParameters))] + [JsonSerializable(typeof(ImagesListResponse))] + [JsonSerializable(typeof(ImagesLoadResponse))] + [JsonSerializable(typeof(ImagesPruneResponse))] + [JsonSerializable(typeof(IndexInfo))] + [JsonSerializable(typeof(Info))] + [JsonSerializable(typeof(JSONError))] + [JsonSerializable(typeof(JSONMessage))] + [JsonSerializable(typeof(JSONProgress))] + [JsonSerializable(typeof(JobStatus))] + [JsonSerializable(typeof(JoinTokens))] + [JsonSerializable(typeof(LogConfig))] + [JsonSerializable(typeof(ManagerStatus))] + [JsonSerializable(typeof(ManifestSummary))] + [JsonSerializable(typeof(ManifestSummarySize))] + [JsonSerializable(typeof(MemoryStats))] + [JsonSerializable(typeof(Message))] + [JsonSerializable(typeof(Meta))] + [JsonSerializable(typeof(Metadata))] + [JsonSerializable(typeof(Mount))] + [JsonSerializable(typeof(MountPoint))] + [JsonSerializable(typeof(NamedGenericResource))] + [JsonSerializable(typeof(Network))] + [JsonSerializable(typeof(NetworkAddressPool))] + [JsonSerializable(typeof(NetworkAttachment))] + [JsonSerializable(typeof(NetworkAttachmentConfig))] + [JsonSerializable(typeof(NetworkAttachmentSpec))] + [JsonSerializable(typeof(NetworkConnectParameters))] + [JsonSerializable(typeof(NetworkDisconnectParameters))] + [JsonSerializable(typeof(NetworkResponse))] + [JsonSerializable(typeof(NetworkSettings))] + [JsonSerializable(typeof(NetworkSettingsSummary))] + [JsonSerializable(typeof(NetworkSpec))] + [JsonSerializable(typeof(NetworkStats))] + [JsonSerializable(typeof(NetworkTask))] + [JsonSerializable(typeof(NetworkingConfig))] + [JsonSerializable(typeof(NetworksCreateParameters))] + [JsonSerializable(typeof(NetworksCreateResponse))] + [JsonSerializable(typeof(NetworksPruneResponse))] + [JsonSerializable(typeof(NodeCSIInfo))] + [JsonSerializable(typeof(NodeDescription))] + [JsonSerializable(typeof(NodeListResponse))] + [JsonSerializable(typeof(NodeStatus))] + [JsonSerializable(typeof(NodeUpdateParameters))] + [JsonSerializable(typeof(OrchestrationConfig))] + [JsonSerializable(typeof(Peer))] + [JsonSerializable(typeof(PeerInfo))] + [JsonSerializable(typeof(PidsStats))] + [JsonSerializable(typeof(Placement))] + [JsonSerializable(typeof(PlacementPreference))] + [JsonSerializable(typeof(Platform))] + [JsonSerializable(typeof(PlatformInfo))] + [JsonSerializable(typeof(Plugin))] + [JsonSerializable(typeof(PluginArgs))] + [JsonSerializable(typeof(PluginCapabilityID))] + [JsonSerializable(typeof(PluginConfig))] + [JsonSerializable(typeof(PluginConfigureParameters))] + [JsonSerializable(typeof(PluginDescription))] + [JsonSerializable(typeof(PluginDevice))] + [JsonSerializable(typeof(PluginEnv))] + [JsonSerializable(typeof(PluginInstallParameters))] + [JsonSerializable(typeof(PluginInterface))] + [JsonSerializable(typeof(PluginLinuxConfig))] + [JsonSerializable(typeof(PluginMount))] + [JsonSerializable(typeof(PluginNetworkConfig))] + [JsonSerializable(typeof(PluginPrivilege))] + [JsonSerializable(typeof(PluginRootFS))] + [JsonSerializable(typeof(PluginSettings))] + [JsonSerializable(typeof(PluginUpgradeParameters))] + [JsonSerializable(typeof(PluginUser))] + [JsonSerializable(typeof(PluginsInfo))] + [JsonSerializable(typeof(PortBinding))] + [JsonSerializable(typeof(PortConfig))] + [JsonSerializable(typeof(PortStatus))] + [JsonSerializable(typeof(PortSummary))] + [JsonSerializable(typeof(Privileges))] + [JsonSerializable(typeof(PublishStatus))] + [JsonSerializable(typeof(RaftConfig))] + [JsonSerializable(typeof(ReplicatedJob))] + [JsonSerializable(typeof(ReplicatedService))] + [JsonSerializable(typeof(ResourceRequirements))] + [JsonSerializable(typeof(Resources))] + [JsonSerializable(typeof(RestartPolicy))] + [JsonSerializable(typeof(RootFS))] + [JsonSerializable(typeof(RootFSStorage))] + [JsonSerializable(typeof(RootFSStorageSnapshot))] + [JsonSerializable(typeof(Runtime))] + [JsonSerializable(typeof(RuntimePrivilege))] + [JsonSerializable(typeof(RuntimeWithStatus))] + [JsonSerializable(typeof(SELinuxContext))] + [JsonSerializable(typeof(SeccompOpts))] + [JsonSerializable(typeof(Secret))] + [JsonSerializable(typeof(SecretCreateResponse))] + [JsonSerializable(typeof(SecretReference))] + [JsonSerializable(typeof(SecretReferenceFileTarget))] + [JsonSerializable(typeof(ServiceConfig))] + [JsonSerializable(typeof(ServiceCreateParameters))] + [JsonSerializable(typeof(ServiceCreateResponse))] + [JsonSerializable(typeof(ServiceInfo))] + [JsonSerializable(typeof(ServiceMode))] + [JsonSerializable(typeof(ServiceSpec))] + [JsonSerializable(typeof(ServiceStatus))] + [JsonSerializable(typeof(ServiceUpdateParameters))] + [JsonSerializable(typeof(ServiceUpdateResponse))] + [JsonSerializable(typeof(Spec))] + [JsonSerializable(typeof(SpreadOver))] + [JsonSerializable(typeof(State))] + [JsonSerializable(typeof(Status))] + [JsonSerializable(typeof(Storage))] + [JsonSerializable(typeof(StorageStats))] + [JsonSerializable(typeof(SubnetStatus))] + [JsonSerializable(typeof(SummaryHostConfig))] + [JsonSerializable(typeof(SwarmConfig))] + [JsonSerializable(typeof(SwarmConfigReference))] + [JsonSerializable(typeof(SwarmConfigSpec))] + [JsonSerializable(typeof(SwarmCreateConfigParameters))] + [JsonSerializable(typeof(SwarmCreateConfigResponse))] + [JsonSerializable(typeof(SwarmDriver))] + [JsonSerializable(typeof(SwarmIPAMConfig))] + [JsonSerializable(typeof(SwarmInitParameters))] + [JsonSerializable(typeof(SwarmInspectResponse))] + [JsonSerializable(typeof(SwarmJoinParameters))] + [JsonSerializable(typeof(SwarmLimit))] + [JsonSerializable(typeof(SwarmNetwork))] + [JsonSerializable(typeof(SwarmPlatform))] + [JsonSerializable(typeof(SwarmResources))] + [JsonSerializable(typeof(SwarmRestartPolicy))] + [JsonSerializable(typeof(SwarmRuntimeSpec))] + [JsonSerializable(typeof(SwarmSecretSpec))] + [JsonSerializable(typeof(SwarmService))] + [JsonSerializable(typeof(SwarmUnlockParameters))] + [JsonSerializable(typeof(SwarmUnlockResponse))] + [JsonSerializable(typeof(SwarmUpdateConfig))] + [JsonSerializable(typeof(SwarmUpdateConfigParameters))] + [JsonSerializable(typeof(SwarmUpdateParameters))] + [JsonSerializable(typeof(SystemInfoResponse))] + [JsonSerializable(typeof(TLSInfo))] + [JsonSerializable(typeof(TaskDefaults))] + [JsonSerializable(typeof(TaskResponse))] + [JsonSerializable(typeof(TaskSpec))] + [JsonSerializable(typeof(TaskStatus))] + [JsonSerializable(typeof(ThrottleDevice))] + [JsonSerializable(typeof(ThrottlingData))] + [JsonSerializable(typeof(TmpfsOptions))] + [JsonSerializable(typeof(Topology))] + [JsonSerializable(typeof(TopologyRequirement))] + [JsonSerializable(typeof(TypeBlock))] + [JsonSerializable(typeof(TypeMount))] + [JsonSerializable(typeof(Ulimit))] + [JsonSerializable(typeof(UpdateConfig))] + [JsonSerializable(typeof(UpdateStatus))] + [JsonSerializable(typeof(UsageData))] + [JsonSerializable(typeof(Version))] + [JsonSerializable(typeof(VersionResponse))] + [JsonSerializable(typeof(VolumeAccessMode))] + [JsonSerializable(typeof(VolumeAttachment))] + [JsonSerializable(typeof(VolumeInfo))] + [JsonSerializable(typeof(VolumeOptions))] + [JsonSerializable(typeof(VolumeResponse))] + [JsonSerializable(typeof(VolumeSecret))] + [JsonSerializable(typeof(VolumeTopology))] + [JsonSerializable(typeof(VolumesCreateParameters))] + [JsonSerializable(typeof(VolumesListResponse))] + [JsonSerializable(typeof(VolumesPruneResponse))] + [JsonSerializable(typeof(WaitExitError))] + [JsonSerializable(typeof(WeightDevice))] + internal sealed partial class DockerModelsJsonSerializerContext : JsonSerializerContext { } +} diff --git a/src/Docker.DotNet/Models/ImageBuildParameters.Generated.cs b/src/Docker.DotNet/Models/ImageBuildParameters.Generated.cs index 1ca44c6e..92e55b50 100644 --- a/src/Docker.DotNet/Models/ImageBuildParameters.Generated.cs +++ b/src/Docker.DotNet/Models/ImageBuildParameters.Generated.cs @@ -3,22 +3,22 @@ namespace Docker.DotNet.Models { public class ImageBuildParameters // (main.ImageBuildParameters) { - [QueryStringParameter("t", false, typeof(QueryStringEnumerableConverter))] + [QueryStringListParameter("t", false)] public IList? Tags { get; set; } - [QueryStringParameter("q", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("q", false)] public bool? SuppressOutput { get; set; } [QueryStringParameter("remote", false)] public string? RemoteContext { get; set; } - [QueryStringParameter("nocache", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("nocache", false)] public bool? NoCache { get; set; } - [QueryStringParameter("rm", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("rm", false)] public bool? Remove { get; set; } - [QueryStringParameter("forcerm", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("forcerm", false)] public bool? ForceRemove { get; set; } [QueryStringParameter("pull", false)] @@ -51,19 +51,19 @@ public class ImageBuildParameters // (main.ImageBuildParameters) [QueryStringParameter("dockerfile", false)] public string? Dockerfile { get; set; } - [QueryStringParameter("buildargs", false, typeof(QueryStringMapConverter))] + [QueryStringMapParameter>("buildargs", false)] public IDictionary? BuildArgs { get; set; } - [QueryStringParameter("labels", false, typeof(QueryStringMapConverter))] + [QueryStringMapParameter>("labels", false)] public IDictionary? Labels { get; set; } - [QueryStringParameter("squash", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("squash", false)] public bool? Squash { get; set; } - [QueryStringParameter("cachefrom", false, typeof(QueryStringEnumerableConverter))] + [QueryStringListParameter("cachefrom", false)] public IList? CacheFrom { get; set; } - [QueryStringParameter("extrahosts", false, typeof(QueryStringEnumerableConverter))] + [QueryStringListParameter("extrahosts", false)] public IList? ExtraHosts { get; set; } [QueryStringParameter("target", false)] diff --git a/src/Docker.DotNet/Models/ImageDeleteParameters.Generated.cs b/src/Docker.DotNet/Models/ImageDeleteParameters.Generated.cs index 5bcf4a6c..fbb496c1 100644 --- a/src/Docker.DotNet/Models/ImageDeleteParameters.Generated.cs +++ b/src/Docker.DotNet/Models/ImageDeleteParameters.Generated.cs @@ -3,10 +3,10 @@ namespace Docker.DotNet.Models { public class ImageDeleteParameters // (main.ImageDeleteParameters) { - [QueryStringParameter("force", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("force", false)] public bool? Force { get; set; } - [QueryStringParameter("noprune", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("noprune", false)] public bool? NoPrune { get; set; } } } diff --git a/src/Docker.DotNet/Models/ImageLoadParameters.Generated.cs b/src/Docker.DotNet/Models/ImageLoadParameters.Generated.cs index c1cbf62c..0f70d861 100644 --- a/src/Docker.DotNet/Models/ImageLoadParameters.Generated.cs +++ b/src/Docker.DotNet/Models/ImageLoadParameters.Generated.cs @@ -3,7 +3,7 @@ namespace Docker.DotNet.Models { public class ImageLoadParameters // (main.ImageLoadParameters) { - [QueryStringParameter("quiet", true, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("quiet", true)] public bool Quiet { get; set; } = default!; } } diff --git a/src/Docker.DotNet/Models/ImagesCreateParameters.Generated.cs b/src/Docker.DotNet/Models/ImagesCreateParameters.Generated.cs index 3ea125d6..4cc7c51b 100644 --- a/src/Docker.DotNet/Models/ImagesCreateParameters.Generated.cs +++ b/src/Docker.DotNet/Models/ImagesCreateParameters.Generated.cs @@ -18,7 +18,7 @@ public class ImagesCreateParameters // (main.ImagesCreateParameters) [QueryStringParameter("message", false)] public string? Message { get; set; } - [QueryStringParameter("changes", false, typeof(QueryStringEnumerableConverter))] + [QueryStringListParameter("changes", false)] public IList? Changes { get; set; } [QueryStringParameter("platform", false)] diff --git a/src/Docker.DotNet/Models/ImagesListParameters.Generated.cs b/src/Docker.DotNet/Models/ImagesListParameters.Generated.cs index 17f6fa8f..1b0a3ee5 100644 --- a/src/Docker.DotNet/Models/ImagesListParameters.Generated.cs +++ b/src/Docker.DotNet/Models/ImagesListParameters.Generated.cs @@ -3,19 +3,19 @@ namespace Docker.DotNet.Models { public class ImagesListParameters // (main.ImagesListParameters) { - [QueryStringParameter("all", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("all", false)] public bool? All { get; set; } - [QueryStringParameter("filters", false, typeof(QueryStringMapConverter))] + [QueryStringMapParameter>>("filters", false)] public IDictionary>? Filters { get; set; } - [QueryStringParameter("shared-size", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("shared-size", false)] public bool? SharedSize { get; set; } - [QueryStringParameter("digests", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("digests", false)] public bool? Digests { get; set; } - [QueryStringParameter("manifests", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("manifests", false)] public bool? Manifests { get; set; } } } diff --git a/src/Docker.DotNet/Models/ImagesPruneParameters.Generated.cs b/src/Docker.DotNet/Models/ImagesPruneParameters.Generated.cs index f498e702..58bb5bc8 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) { - [QueryStringParameter("filters", false, typeof(QueryStringMapConverter))] + [QueryStringMapParameter>>("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 b63c1c61..338a41f8 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; } - [QueryStringParameter("filters", false, typeof(QueryStringMapConverter))] + [QueryStringMapParameter>>("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 d3754980..968a4368 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) { - [QueryStringParameter("filters", false, typeof(QueryStringMapConverter))] + [QueryStringMapParameter>>("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 9094be91..aa0a0291 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) { - [QueryStringParameter("filters", false, typeof(QueryStringMapConverter))] + [QueryStringMapParameter>>("filters", false)] public IDictionary>? Filters { get; set; } } } diff --git a/src/Docker.DotNet/Models/NodeRemoveParameters.Generated.cs b/src/Docker.DotNet/Models/NodeRemoveParameters.Generated.cs index 48d0bcf9..7356ccb3 100644 --- a/src/Docker.DotNet/Models/NodeRemoveParameters.Generated.cs +++ b/src/Docker.DotNet/Models/NodeRemoveParameters.Generated.cs @@ -3,7 +3,7 @@ namespace Docker.DotNet.Models { public class NodeRemoveParameters // (main.NodeRemoveParameters) { - [QueryStringParameter("force", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("force", false)] public bool? Force { get; set; } } } diff --git a/src/Docker.DotNet/Models/PluginDisableParameters.Generated.cs b/src/Docker.DotNet/Models/PluginDisableParameters.Generated.cs index 93581509..c6b14f74 100644 --- a/src/Docker.DotNet/Models/PluginDisableParameters.Generated.cs +++ b/src/Docker.DotNet/Models/PluginDisableParameters.Generated.cs @@ -3,7 +3,7 @@ namespace Docker.DotNet.Models { public class PluginDisableParameters // (main.PluginDisableParameters) { - [QueryStringParameter("force", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("force", false)] public bool? Force { get; set; } } } diff --git a/src/Docker.DotNet/Models/PluginListParameters.Generated.cs b/src/Docker.DotNet/Models/PluginListParameters.Generated.cs index 64be9dac..8afee824 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) { - [QueryStringParameter("filters", false, typeof(QueryStringMapConverter))] + [QueryStringMapParameter>>("filters", false)] public IDictionary>? Filters { get; set; } } } diff --git a/src/Docker.DotNet/Models/PluginRemoveParameters.Generated.cs b/src/Docker.DotNet/Models/PluginRemoveParameters.Generated.cs index 37625105..3e426c5f 100644 --- a/src/Docker.DotNet/Models/PluginRemoveParameters.Generated.cs +++ b/src/Docker.DotNet/Models/PluginRemoveParameters.Generated.cs @@ -3,7 +3,7 @@ namespace Docker.DotNet.Models { public class PluginRemoveParameters // (main.PluginRemoveParameters) { - [QueryStringParameter("force", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("force", false)] public bool? Force { get; set; } } } diff --git a/src/Docker.DotNet/Models/ServiceListParameters.Generated.cs b/src/Docker.DotNet/Models/ServiceListParameters.Generated.cs index 4d3f8c57..be6a52b3 100644 --- a/src/Docker.DotNet/Models/ServiceListParameters.Generated.cs +++ b/src/Docker.DotNet/Models/ServiceListParameters.Generated.cs @@ -3,10 +3,10 @@ namespace Docker.DotNet.Models { public class ServiceListParameters // (main.ServiceListParameters) { - [QueryStringParameter("filters", false, typeof(QueryStringMapConverter))] + [QueryStringMapParameter>>("filters", false)] public IDictionary>? Filters { get; set; } - [QueryStringParameter("status", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("status", false)] public bool? Status { get; set; } } } diff --git a/src/Docker.DotNet/Models/ServiceLogsParameters.Generated.cs b/src/Docker.DotNet/Models/ServiceLogsParameters.Generated.cs index 67c65f50..2e1644e8 100644 --- a/src/Docker.DotNet/Models/ServiceLogsParameters.Generated.cs +++ b/src/Docker.DotNet/Models/ServiceLogsParameters.Generated.cs @@ -3,25 +3,25 @@ namespace Docker.DotNet.Models { public class ServiceLogsParameters // (main.ServiceLogsParameters) { - [QueryStringParameter("stdout", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("stdout", false)] public bool? ShowStdout { get; set; } - [QueryStringParameter("stderr", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("stderr", false)] public bool? ShowStderr { get; set; } [QueryStringParameter("since", false)] public string? Since { get; set; } - [QueryStringParameter("timestamps", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("timestamps", false)] public bool? Timestamps { get; set; } - [QueryStringParameter("follow", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("follow", false)] public bool? Follow { get; set; } [QueryStringParameter("tail", false)] public string? Tail { get; set; } - [QueryStringParameter("details", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("details", false)] public bool? Details { get; set; } } } diff --git a/src/Docker.DotNet/Models/SwarmLeaveParameters.Generated.cs b/src/Docker.DotNet/Models/SwarmLeaveParameters.Generated.cs index 3d97d292..cc0a381f 100644 --- a/src/Docker.DotNet/Models/SwarmLeaveParameters.Generated.cs +++ b/src/Docker.DotNet/Models/SwarmLeaveParameters.Generated.cs @@ -3,7 +3,7 @@ namespace Docker.DotNet.Models { public class SwarmLeaveParameters // (main.SwarmLeaveParameters) { - [QueryStringParameter("force", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("force", false)] public bool? Force { get; set; } } } diff --git a/src/Docker.DotNet/Models/SwarmUpdateParameters.Generated.cs b/src/Docker.DotNet/Models/SwarmUpdateParameters.Generated.cs index da849eae..0b1daf7a 100644 --- a/src/Docker.DotNet/Models/SwarmUpdateParameters.Generated.cs +++ b/src/Docker.DotNet/Models/SwarmUpdateParameters.Generated.cs @@ -9,13 +9,13 @@ public class SwarmUpdateParameters // (main.SwarmUpdateParameters) [QueryStringParameter("version", true)] public long Version { get; set; } = default!; - [QueryStringParameter("rotateworkertoken", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("rotateworkertoken", false)] public bool? RotateWorkerToken { get; set; } - [QueryStringParameter("rotatemanagertoken", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("rotatemanagertoken", false)] public bool? RotateManagerToken { get; set; } - [QueryStringParameter("rotatemanagerunlockkey", false, typeof(QueryStringBoolConverter))] + [QueryStringBoolParameter("rotatemanagerunlockkey", false)] public bool? RotateManagerUnlockKey { get; set; } } } diff --git a/src/Docker.DotNet/Models/TasksListParameters.Generated.cs b/src/Docker.DotNet/Models/TasksListParameters.Generated.cs index 7f2a0781..6a8d5f9c 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) { - [QueryStringParameter("filters", false, typeof(QueryStringMapConverter))] + [QueryStringMapParameter>>("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 23ee4075..ebace0c1 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) { - [QueryStringParameter("filters", false, typeof(QueryStringMapConverter))] + [QueryStringMapParameter>>("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 1f727c41..b787cd8b 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) { - [QueryStringParameter("filters", false, typeof(QueryStringMapConverter))] + [QueryStringMapParameter>>("filters", false)] public IDictionary>? Filters { get; set; } } } diff --git a/src/Docker.DotNet/QueryString.cs b/src/Docker.DotNet/QueryString.cs index e12869e5..74323ef9 100644 --- a/src/Docker.DotNet/QueryString.cs +++ b/src/Docker.DotNet/QueryString.cs @@ -1,22 +1,23 @@ namespace Docker.DotNet; -internal class QueryString : IQueryString where T : class +internal class QueryString< +#if NET + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] +#endif +T> : IQueryString where T : class { private T Object { get; } private Dictionary AttributedPublicProperties { get; } - private IQueryStringConverterInstanceFactory QueryStringConverterInstanceFactory { get; } - public QueryString(T value) { - if (EqualityComparer.Default.Equals(value)) + if (value is null) { throw new ArgumentNullException(nameof(value)); } Object = value; - QueryStringConverterInstanceFactory = new QueryStringConverterInstanceFactory(); AttributedPublicProperties = FindAttributedPublicProperties(); } @@ -32,7 +33,7 @@ public IDictionary GetKeyValuePairs() // 'Required' check if (attribute.IsRequired && value == null) { - string propertyFullName = $"{property.GetType().FullName}.{property.Name}"; + string propertyFullName = $"{property.DeclaringType?.FullName}.{property.Name}"; throw new ArgumentException("Got null/unset value for a required query parameter.", propertyFullName); } @@ -40,21 +41,7 @@ public IDictionary GetKeyValuePairs() if (attribute.IsRequired || !IsDefaultOfType(value)) { var keyStr = attribute.Name; - string[] valueStr; - if (attribute.ConverterType == null) - { - valueStr = [value!.ToString()!]; - } - else - { - var converter = QueryStringConverterInstanceFactory.GetConverterInstance(attribute.ConverterType); - valueStr = ConvertValue(converter, value!); - - if (valueStr == null) - { - throw new InvalidOperationException($"Got null from value converter '{attribute.ConverterType.FullName}'"); - } - } + var valueStr = attribute.Convert(value!); queryParameters[keyStr] = valueStr; } @@ -76,17 +63,11 @@ public string GetQueryString() v => $"{Uri.EscapeDataString(pair.Key)}={Uri.EscapeDataString(v)}")))); } - private static string[] ConvertValue(IQueryStringConverter converter, object value) - { - if (!converter.CanConvert(value.GetType())) - { - throw new InvalidOperationException( - $"Cannot convert type {value.GetType().FullName} using {converter.GetType().FullName}."); - } - return converter.Convert(value); - } - - private static Dictionary FindAttributedPublicProperties() where TAttribType : Attribute + private static Dictionary FindAttributedPublicProperties< +#if NET + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] +#endif + TValue, TAttribType>() where TAttribType : Attribute { Dictionary? attributedPublicProperties = null; @@ -113,6 +94,9 @@ private static Dictionary FindAttributedPublicPropert return attributedPublicProperties; } +#if NET + [UnconditionalSuppressMessage("Trimming", "IL2072", Justification = "Activator.CreateInstance is only used for value types here, safe for runtime usage.")] +#endif private static bool IsDefaultOfType(object? o) { if (o is ValueType) diff --git a/src/Docker.DotNet/QueryStringBoolConverter.cs b/src/Docker.DotNet/QueryStringBoolConverter.cs deleted file mode 100644 index 7ff3a44d..00000000 --- a/src/Docker.DotNet/QueryStringBoolConverter.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Docker.DotNet; - -internal class QueryStringBoolConverter : IQueryStringConverter -{ - public bool CanConvert(Type t) - { - return t == typeof (bool); - } - - public string[] Convert(object o) - { - Debug.Assert(o != null); - return new[] {System.Convert.ToInt32(System.Convert.ToBoolean(o)).ToString(CultureInfo.InvariantCulture)}; - } -} \ No newline at end of file diff --git a/src/Docker.DotNet/QueryStringBoolParameterAttribute.cs b/src/Docker.DotNet/QueryStringBoolParameterAttribute.cs new file mode 100644 index 00000000..368b497f --- /dev/null +++ b/src/Docker.DotNet/QueryStringBoolParameterAttribute.cs @@ -0,0 +1,11 @@ +namespace Docker.DotNet; + +internal sealed class QueryStringBoolParameterAttribute(string name, bool required) : QueryStringParameterAttribute(name, required) +{ + public override string[] Convert(object value) + { + Debug.Assert(value != null); + + return [System.Convert.ToInt32(System.Convert.ToBoolean(value)).ToString(CultureInfo.InvariantCulture)]; + } +} \ No newline at end of file diff --git a/src/Docker.DotNet/QueryStringConverterInstanceFactory.cs b/src/Docker.DotNet/QueryStringConverterInstanceFactory.cs deleted file mode 100644 index 1450f50e..00000000 --- a/src/Docker.DotNet/QueryStringConverterInstanceFactory.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Docker.DotNet; - -internal class QueryStringConverterInstanceFactory : IQueryStringConverterInstanceFactory -{ - private static readonly ConcurrentDictionary ConverterInstanceRegistry = new ConcurrentDictionary(); - - public IQueryStringConverter GetConverterInstance(Type t) - { - return ConverterInstanceRegistry.GetOrAdd( - t, - InitializeConverter); - } - - private IQueryStringConverter InitializeConverter(Type t) - { - var instance = Activator.CreateInstance(t) as IQueryStringConverter; - if (instance == null) - { - throw new InvalidOperationException($"Could not get instance of {t.FullName}"); - } - return instance; - } -} \ No newline at end of file diff --git a/src/Docker.DotNet/QueryStringEnumerableConverter.cs b/src/Docker.DotNet/QueryStringEnumerableConverter.cs deleted file mode 100644 index 7d21a955..00000000 --- a/src/Docker.DotNet/QueryStringEnumerableConverter.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace Docker.DotNet; - -/// -/// Handles serialization of objects like Lists, Arrays, etc. -/// -internal class QueryStringEnumerableConverter : IQueryStringConverter -{ - public bool CanConvert(Type t) - { - return typeof(IEnumerable).GetTypeInfo().IsAssignableFrom(t.GetTypeInfo()); - } - - public string[] Convert(object o) - { - Debug.Assert(o != null); - Debug.Assert(o is IEnumerable); - - var enumerable = (IEnumerable)o!; - - var items = new List(); - - foreach (var e in enumerable) - { - if (e is string or ValueType) - { - items.Add(e.ToString()!); - } - else - { - items.Add(JsonSerializer.Instance.Serialize(e)); - } - } - - return items.ToArray(); - } -} \ No newline at end of file diff --git a/src/Docker.DotNet/QueryStringListParameterAttribute.cs b/src/Docker.DotNet/QueryStringListParameterAttribute.cs new file mode 100644 index 00000000..16e130f4 --- /dev/null +++ b/src/Docker.DotNet/QueryStringListParameterAttribute.cs @@ -0,0 +1,17 @@ +namespace Docker.DotNet; + +internal sealed class QueryStringListParameterAttribute(string name, bool required) : QueryStringParameterAttribute(name, required) +{ + public override string[] Convert(object value) + { + Debug.Assert(value != null); + Debug.Assert(value is IList); + + if (value is not IList typedValue) + { + throw new ArgumentException($"Expected value of type '{typeof(IList)}'.", nameof(value)); + } + + return typedValue.ToArray(); + } +} \ No newline at end of file diff --git a/src/Docker.DotNet/QueryStringMapConverter.cs b/src/Docker.DotNet/QueryStringMapConverter.cs deleted file mode 100644 index 5a9826e1..00000000 --- a/src/Docker.DotNet/QueryStringMapConverter.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Docker.DotNet; - -internal class QueryStringMapConverter : IQueryStringConverter -{ - public bool CanConvert(Type t) - { - return typeof(IList).GetTypeInfo().IsAssignableFrom(t.GetTypeInfo()) || typeof(IDictionary).GetTypeInfo().IsAssignableFrom(t.GetTypeInfo()); - } - - public string[] Convert(object o) - { - Debug.Assert(o != null); - - return new[] { JsonSerializer.Instance.Serialize(o) }; - } -} \ No newline at end of file diff --git a/src/Docker.DotNet/QueryStringMapParameterAttribute.cs b/src/Docker.DotNet/QueryStringMapParameterAttribute.cs new file mode 100644 index 00000000..c8fc3933 --- /dev/null +++ b/src/Docker.DotNet/QueryStringMapParameterAttribute.cs @@ -0,0 +1,17 @@ +namespace Docker.DotNet; + +internal sealed class QueryStringMapParameterAttribute(string name, bool required) : QueryStringParameterAttribute(name, required) +{ + public override string[] Convert(object value) + { + Debug.Assert(value != null); + Debug.Assert(value is T); + + if (value is not T typedValue) + { + throw new ArgumentException($"Expected value of type '{typeof(T)}'.", nameof(value)); + } + + return [JsonSerializer.Instance.Serialize(typedValue)]; + } +} \ No newline at end of file diff --git a/src/Docker.DotNet/QueryStringParameterAttribute.cs b/src/Docker.DotNet/QueryStringParameterAttribute.cs index 6b08d259..11032a6b 100644 --- a/src/Docker.DotNet/QueryStringParameterAttribute.cs +++ b/src/Docker.DotNet/QueryStringParameterAttribute.cs @@ -1,28 +1,22 @@ namespace Docker.DotNet; [AttributeUsage(AttributeTargets.Property)] -internal sealed class QueryStringParameterAttribute : Attribute +internal class QueryStringParameterAttribute : Attribute { public string Name { get; private set; } public bool IsRequired { get; private set; } - public Type? ConverterType { get; private set; } + public virtual string[] Convert(object value) => [value.ToString()!]; - public QueryStringParameterAttribute(string name, bool required, Type? converterType = null) + public QueryStringParameterAttribute(string name, bool required) { if (string.IsNullOrEmpty(name)) { throw new ArgumentNullException(nameof(name)); } - if (converterType != null && !converterType.GetInterfaces().Contains(typeof (IQueryStringConverter))) - { - throw new ArgumentException($"Provided query string converter type is not '{typeof(IQueryStringConverter).FullName}'.", nameof(converterType)); - } - Name = name; IsRequired = required; - ConverterType = converterType; } } \ No newline at end of file diff --git a/src/Microsoft.Net.Http.Client/HttpConnection.cs b/src/Microsoft.Net.Http.Client/HttpConnection.cs index fb8c1ab2..85ad4b1f 100644 --- a/src/Microsoft.Net.Http.Client/HttpConnection.cs +++ b/src/Microsoft.Net.Http.Client/HttpConnection.cs @@ -26,7 +26,7 @@ await Transport.WriteAsync(requestBytes, 0, requestBytes.Length, cancellationTok { if (request.Content.Headers.ContentLength.HasValue) { -#if NET6_0_OR_GREATER +#if NET await request.Content.CopyToAsync(Transport, cancellationToken) .ConfigureAwait(false); #else @@ -39,7 +39,7 @@ await request.Content.CopyToAsync(Transport) // The length of the data is unknown. Send it in chunked mode. using (var chunkedStream = new ChunkedWriteStream(Transport)) { -#if NET6_0_OR_GREATER +#if NET await request.Content.CopyToAsync(chunkedStream, cancellationToken) .ConfigureAwait(false); #else @@ -121,7 +121,7 @@ private async Task> ReadResponseLinesAsync(CancellationToken cancel var line = await Transport.ReadLineAsync(cancellationToken) .ConfigureAwait(false); - if (string.IsNullOrEmpty(line)) + if (line is null || line.Length == 0) { break; } diff --git a/src/Microsoft.Net.Http.Client/HttpConnectionResponseContent.cs b/src/Microsoft.Net.Http.Client/HttpConnectionResponseContent.cs index 4fbaefc2..5bed410a 100644 --- a/src/Microsoft.Net.Http.Client/HttpConnectionResponseContent.cs +++ b/src/Microsoft.Net.Http.Client/HttpConnectionResponseContent.cs @@ -45,7 +45,7 @@ protected override Task SerializeToStreamAsync(Stream stream, TransportContext? return _responseStream!.CopyToAsync(stream); } -#if NET6_0_OR_GREATER +#if NET protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context, CancellationToken cancellationToken) { return _responseStream!.CopyToAsync(stream, cancellationToken); diff --git a/src/Microsoft.Net.Http.Client/RequestExtensions.cs b/src/Microsoft.Net.Http.Client/RequestExtensions.cs index c7872e17..e2be5054 100644 --- a/src/Microsoft.Net.Http.Client/RequestExtensions.cs +++ b/src/Microsoft.Net.Http.Client/RequestExtensions.cs @@ -84,7 +84,7 @@ public static void SetAddressLineProperty(this HttpRequestMessage request, strin public static T? GetProperty(this HttpRequestMessage request, string key) { -#if NET6_0_OR_GREATER +#if NET return request.Options.TryGetValue(new HttpRequestOptionsKey(key), out var obj) ? obj : default; #else return request.Properties.TryGetValue(key, out var obj) ? (T)obj : default; @@ -93,7 +93,7 @@ public static void SetAddressLineProperty(this HttpRequestMessage request, strin public static void SetProperty(this HttpRequestMessage request, string key, T value) { -#if NET6_0_OR_GREATER +#if NET request.Options.Set(new HttpRequestOptionsKey(key), value); #else request.Properties[key] = value; diff --git a/test/Docker.DotNet.Tests/Docker.DotNet.Tests.csproj b/test/Docker.DotNet.Tests/Docker.DotNet.Tests.csproj index 2d80e0e5..064c3b68 100644 --- a/test/Docker.DotNet.Tests/Docker.DotNet.Tests.csproj +++ b/test/Docker.DotNet.Tests/Docker.DotNet.Tests.csproj @@ -1,13 +1,21 @@ net8.0;net9.0;net10.0 + linux-arm64 + linux-x64 + osx-arm64 + osx-x64 + win-arm64 + win-x64 + Exe false - false + true + true + true - - - + + @@ -24,7 +32,6 @@ - @@ -40,7 +47,10 @@ - + + + + diff --git a/test/Docker.DotNet.Tests/IConfigOperationsTests.cs b/test/Docker.DotNet.Tests/IConfigOperationsTests.cs index 6a0ab254..8f2a5c43 100644 --- a/test/Docker.DotNet.Tests/IConfigOperationsTests.cs +++ b/test/Docker.DotNet.Tests/IConfigOperationsTests.cs @@ -15,7 +15,7 @@ public IConfigOperationsTests(TestFixture testFixture, ITestOutputHelper testOut [Fact] public async Task SwarmConfig_CanCreateAndRead() { - var currentConfigs = await _testFixture.DockerClient.Configs.ListConfigsAsync(); + var currentConfigs = await _testFixture.DockerClient.Configs.ListConfigsAsync(TestContext.Current.CancellationToken); _testOutputHelper.WriteLine($"Current Configs: {currentConfigs.Count}"); @@ -31,15 +31,15 @@ public async Task SwarmConfig_CanCreateAndRead() Config = testConfigSpec }; - var createdConfig = await _testFixture.DockerClient.Configs.CreateConfigAsync(configParameters); + var createdConfig = await _testFixture.DockerClient.Configs.CreateConfigAsync(configParameters, TestContext.Current.CancellationToken); Assert.NotNull(createdConfig.ID); _testOutputHelper.WriteLine($"Config created: {createdConfig.ID}"); - var configs = await _testFixture.DockerClient.Configs.ListConfigsAsync(); + var configs = await _testFixture.DockerClient.Configs.ListConfigsAsync(TestContext.Current.CancellationToken); Assert.Contains(configs, c => c.ID == createdConfig.ID); _testOutputHelper.WriteLine($"Current Configs: {configs.Count}"); - var configResponse = await _testFixture.DockerClient.Configs.InspectConfigAsync(createdConfig.ID); + var configResponse = await _testFixture.DockerClient.Configs.InspectConfigAsync(createdConfig.ID, TestContext.Current.CancellationToken); Assert.NotNull(configResponse); @@ -51,8 +51,8 @@ public async Task SwarmConfig_CanCreateAndRead() _testOutputHelper.WriteLine("Config created is the same."); - await _testFixture.DockerClient.Configs.RemoveConfigAsync(createdConfig.ID); + await _testFixture.DockerClient.Configs.RemoveConfigAsync(createdConfig.ID, TestContext.Current.CancellationToken); - await Assert.ThrowsAsync(() => _testFixture.DockerClient.Configs.InspectConfigAsync(createdConfig.ID)); + await Assert.ThrowsAsync(() => _testFixture.DockerClient.Configs.InspectConfigAsync(createdConfig.ID, TestContext.Current.CancellationToken)); } } \ No newline at end of file diff --git a/test/Docker.DotNet.Tests/IContainerOperationsTests.cs b/test/Docker.DotNet.Tests/IContainerOperationsTests.cs index ff3ebab9..c2e36892 100644 --- a/test/Docker.DotNet.Tests/IContainerOperationsTests.cs +++ b/test/Docker.DotNet.Tests/IContainerOperationsTests.cs @@ -94,7 +94,7 @@ await _testFixture.DockerClient.Containers.StartContainerAsync( _testFixture.Cts.Token ); - await Task.Delay(TimeSpan.FromSeconds(5)); + await Task.Delay(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); await _testFixture.DockerClient.Containers.GetContainerLogsAsync( createContainerResponse.ID, @@ -141,7 +141,7 @@ await _testFixture.DockerClient.Containers.StartContainerAsync( _testFixture.Cts.Token ); - await Task.Delay(TimeSpan.FromSeconds(5)); + await Task.Delay(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); await _testFixture.DockerClient.Containers.GetContainerLogsAsync( createContainerResponse.ID, @@ -280,7 +280,7 @@ await _testFixture.DockerClient.Containers.StartContainerAsync( containerLogsCts.Token ); - await Task.Delay(TimeSpan.FromSeconds(5)); + await Task.Delay(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); await _testFixture.DockerClient.Containers.StopContainerAsync( createContainerResponse.ID, @@ -328,7 +328,7 @@ await _testFixture.DockerClient.Containers.GetContainerStatsAsync( tcs.Token ); - await Task.Delay(TimeSpan.FromSeconds(10)); + await Task.Delay(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken); Assert.NotEmpty(containerStatsList); Assert.Single(containerStatsList); @@ -341,9 +341,7 @@ public async Task GetContainerStatsAsync_Tty_False_StreamStats() using var tcs = CancellationTokenSource.CreateLinkedTokenSource(_testFixture.Cts.Token); using (tcs.Token.Register(() => throw new TimeoutException("GetContainerStatsAsync_Tty_False_StreamStats"))) { - var method = MethodBase.GetCurrentMethod(); - - _testOutputHelper.WriteLine($"Running test '{method!.Module}' -> '{method!.Name}'"); + _testOutputHelper.WriteLine($"Running test GetContainerStatsAsync_Tty_False_StreamStats"); var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync( new CreateContainerParameters @@ -421,7 +419,7 @@ await _testFixture.DockerClient.Containers.GetContainerStatsAsync( tcs.Token ); - await Task.Delay(TimeSpan.FromSeconds(10)); + await Task.Delay(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken); Assert.NotEmpty(containerStatsList); Assert.Single(containerStatsList); @@ -475,7 +473,7 @@ await _testFixture.DockerClient.Containers.GetContainerStatsAsync( // This is expected to happen on task cancellation. } - await Task.Delay(TimeSpan.FromSeconds(1)); + await Task.Delay(TimeSpan.FromSeconds(1), TestContext.Current.CancellationToken); _testOutputHelper.WriteLine($"Container stats count: {containerStatsList.Count}"); Assert.NotEmpty(containerStatsList); } @@ -727,10 +725,10 @@ public async Task WriteAsync_OnMultiplexedStream_ForwardsInputToPid1Stdin_Comple containerAttachParameters.Stream = true; // When - var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync(createContainerParameters); - _ = await _testFixture.DockerClient.Containers.StartContainerAsync(createContainerResponse.ID, new ContainerStartParameters()); + var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync(createContainerParameters, TestContext.Current.CancellationToken); + _ = await _testFixture.DockerClient.Containers.StartContainerAsync(createContainerResponse.ID, new ContainerStartParameters(), TestContext.Current.CancellationToken); - using var stream = await _testFixture.DockerClient.Containers.AttachContainerAsync(createContainerResponse.ID, containerAttachParameters); + using var stream = await _testFixture.DockerClient.Containers.AttachContainerAsync(createContainerResponse.ID, containerAttachParameters, TestContext.Current.CancellationToken); await stream.WriteAsync(linefeedByte, 0, linefeedByte.Length, _testFixture.Cts.Token); @@ -762,12 +760,12 @@ public async Task WriteAsync_OnMultiplexedStream_ForwardsInputToExecStdin_Comple var containerExecStartParameters = new ContainerExecStartParameters(); - var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync(createContainerParameters); - _ = await _testFixture.DockerClient.Containers.StartContainerAsync(createContainerResponse.ID, new ContainerStartParameters()); + var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync(createContainerParameters, TestContext.Current.CancellationToken); + _ = await _testFixture.DockerClient.Containers.StartContainerAsync(createContainerResponse.ID, new ContainerStartParameters(), TestContext.Current.CancellationToken); // When - var containerExecCreateResponse = await _testFixture.DockerClient.Exec.CreateContainerExecAsync(createContainerResponse.ID, containerExecCreateParameters); - using var stream = await _testFixture.DockerClient.Exec.StartContainerExecAsync(containerExecCreateResponse.ID, containerExecStartParameters); + var containerExecCreateResponse = await _testFixture.DockerClient.Exec.CreateContainerExecAsync(createContainerResponse.ID, containerExecCreateParameters, TestContext.Current.CancellationToken); + using var stream = await _testFixture.DockerClient.Exec.StartContainerExecAsync(containerExecCreateResponse.ID, containerExecStartParameters, TestContext.Current.CancellationToken); await stream.WriteAsync(linefeedByte, 0, linefeedByte.Length, _testFixture.Cts.Token); diff --git a/test/Docker.DotNet.Tests/IImageOperationsTests.cs b/test/Docker.DotNet.Tests/IImageOperationsTests.cs index 50d522f7..9eed7749 100644 --- a/test/Docker.DotNet.Tests/IImageOperationsTests.cs +++ b/test/Docker.DotNet.Tests/IImageOperationsTests.cs @@ -50,12 +50,11 @@ await _testFixture.DockerClient.Images.TagImageAsync( [Fact] public Task CreateImageAsync_ErrorResponse_ThrowsDockerApiException() { - return Assert.ThrowsAsync(() => _testFixture.DockerClient.Images.CreateImageAsync( - new ImagesCreateParameters + return Assert.ThrowsAsync(() => _testFixture.DockerClient.Images.CreateImageAsync(new ImagesCreateParameters { FromImage = "1.2.3.Apparently&this$is+not-a_valid%repository//name", Tag = "ancient-one" - }, null, null)); + }, null, null, TestContext.Current.CancellationToken)); } [Fact] diff --git a/test/Docker.DotNet.Tests/ISwarmOperationsTests.cs b/test/Docker.DotNet.Tests/ISwarmOperationsTests.cs index e620446c..9e86e2ed 100644 --- a/test/Docker.DotNet.Tests/ISwarmOperationsTests.cs +++ b/test/Docker.DotNet.Tests/ISwarmOperationsTests.cs @@ -24,7 +24,7 @@ public async Task GetFilteredServicesByName_Succeeds() Name = serviceName, TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Image.ID } } } - })).ID; + }, TestContext.Current.CancellationToken)).ID; var secondServiceId = (await _testFixture.DockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters { @@ -33,7 +33,7 @@ public async Task GetFilteredServicesByName_Succeeds() Name = $"service2-{Guid.NewGuid().ToString().Substring(1, 10)}", TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Image.ID } } } - })).ID; + }, TestContext.Current.CancellationToken)).ID; var thirdServiceId = (await _testFixture.DockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters { @@ -42,7 +42,7 @@ public async Task GetFilteredServicesByName_Succeeds() Name = $"service3-{Guid.NewGuid().ToString().Substring(1, 10)}", TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Image.ID } } } - })).ID; + }, TestContext.Current.CancellationToken)).ID; var services = await _testFixture.DockerClient.Swarm.ListServicesAsync(new ServiceListParameters { @@ -57,9 +57,9 @@ public async Task GetFilteredServicesByName_Succeeds() Assert.Single(services); - await _testFixture.DockerClient.Swarm.RemoveServiceAsync(firstServiceId); - await _testFixture.DockerClient.Swarm.RemoveServiceAsync(secondServiceId); - await _testFixture.DockerClient.Swarm.RemoveServiceAsync(thirdServiceId); + await _testFixture.DockerClient.Swarm.RemoveServiceAsync(firstServiceId, TestContext.Current.CancellationToken); + await _testFixture.DockerClient.Swarm.RemoveServiceAsync(secondServiceId, TestContext.Current.CancellationToken); + await _testFixture.DockerClient.Swarm.RemoveServiceAsync(thirdServiceId, TestContext.Current.CancellationToken); } [Fact] @@ -72,7 +72,7 @@ public async Task GetFilteredServicesById_Succeeds() Name = $"service1-{Guid.NewGuid().ToString().Substring(1, 10)}", TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Image.ID } } } - })).ID; + }, TestContext.Current.CancellationToken)).ID; var secondServiceId = (await _testFixture.DockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters { @@ -81,7 +81,7 @@ public async Task GetFilteredServicesById_Succeeds() Name = $"service2-{Guid.NewGuid().ToString().Substring(1, 10)}", TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Image.ID } } } - })).ID; + }, TestContext.Current.CancellationToken)).ID; var thirdServiceId = (await _testFixture.DockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters { @@ -90,7 +90,7 @@ public async Task GetFilteredServicesById_Succeeds() Name = $"service3-{Guid.NewGuid().ToString().Substring(1, 10)}", TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Image.ID } } } - })).ID; + }, TestContext.Current.CancellationToken)).ID; var services = await _testFixture.DockerClient.Swarm.ListServicesAsync(new ServiceListParameters { @@ -105,9 +105,9 @@ public async Task GetFilteredServicesById_Succeeds() Assert.Single(services); - await _testFixture.DockerClient.Swarm.RemoveServiceAsync(firstServiceId); - await _testFixture.DockerClient.Swarm.RemoveServiceAsync(secondServiceId); - await _testFixture.DockerClient.Swarm.RemoveServiceAsync(thirdServiceId); + await _testFixture.DockerClient.Swarm.RemoveServiceAsync(firstServiceId, TestContext.Current.CancellationToken); + await _testFixture.DockerClient.Swarm.RemoveServiceAsync(secondServiceId, TestContext.Current.CancellationToken); + await _testFixture.DockerClient.Swarm.RemoveServiceAsync(thirdServiceId, TestContext.Current.CancellationToken); } [Fact] @@ -122,7 +122,7 @@ public async Task GetServices_Succeeds() Name = $"service1-{Guid.NewGuid().ToString().Substring(1, 10)}", TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Image.ID } } } - })).ID; + }, TestContext.Current.CancellationToken)).ID; var secondServiceId = (await _testFixture.DockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters { @@ -131,7 +131,7 @@ public async Task GetServices_Succeeds() Name = $"service2-{Guid.NewGuid().ToString().Substring(1, 10)}", TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Image.ID } } } - })).ID; + }, TestContext.Current.CancellationToken)).ID; var thirdServiceId = (await _testFixture.DockerClient.Swarm.CreateServiceAsync(new ServiceCreateParameters { @@ -140,15 +140,15 @@ public async Task GetServices_Succeeds() Name = $"service3-{Guid.NewGuid().ToString().Substring(1, 10)}", TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Image.ID } } } - })).ID; + }, TestContext.Current.CancellationToken)).ID; var services = await _testFixture.DockerClient.Swarm.ListServicesAsync(cancellationToken: CancellationToken.None); Assert.True(services.Count() > initialServiceCount); - await _testFixture.DockerClient.Swarm.RemoveServiceAsync(firstServiceId); - await _testFixture.DockerClient.Swarm.RemoveServiceAsync(secondServiceId); - await _testFixture.DockerClient.Swarm.RemoveServiceAsync(thirdServiceId); + await _testFixture.DockerClient.Swarm.RemoveServiceAsync(firstServiceId, TestContext.Current.CancellationToken); + await _testFixture.DockerClient.Swarm.RemoveServiceAsync(secondServiceId, TestContext.Current.CancellationToken); + await _testFixture.DockerClient.Swarm.RemoveServiceAsync(thirdServiceId, TestContext.Current.CancellationToken); } [Fact] @@ -165,14 +165,14 @@ public async Task GetServiceLogs_Succeeds() Name = serviceName, TaskTemplate = new TaskSpec { ContainerSpec = new ContainerSpec { Image = _testFixture.Image.ID, Command = CommonCommands.EchoToStdoutAndStderr } } } - })).ID; + }, TestContext.Current.CancellationToken)).ID; using var stream = await _testFixture.DockerClient.Swarm.GetServiceLogsAsync(serviceName, false, new ServiceLogsParameters { Follow = true, ShowStdout = true, ShowStderr = true - }); + }, TestContext.Current.CancellationToken); var maxRetries = 3; var currentRetry = 0; @@ -238,7 +238,7 @@ public async Task GetServiceLogs_Succeeds() currentRetry++; if (currentRetry < maxRetries) { - await Task.Delay(delayBetweenRetries); + await Task.Delay(delayBetweenRetries, TestContext.Current.CancellationToken); } } } @@ -246,6 +246,6 @@ public async Task GetServiceLogs_Succeeds() Assert.NotNull(logLines); Assert.NotEmpty(logLines); - await _testFixture.DockerClient.Swarm.RemoveServiceAsync(serviceId); + await _testFixture.DockerClient.Swarm.RemoveServiceAsync(serviceId, TestContext.Current.CancellationToken); } } \ 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 b94faa24..d1064779 100644 --- a/test/Docker.DotNet.Tests/ISystemOperations.Tests.cs +++ b/test/Docker.DotNet.Tests/ISystemOperations.Tests.cs @@ -22,14 +22,14 @@ public void Docker_IsRunning() [Fact] public async Task GetSystemInfoAsync_Succeeds() { - var info = await _testFixture.DockerClient.System.GetSystemInfoAsync(); + var info = await _testFixture.DockerClient.System.GetSystemInfoAsync(TestContext.Current.CancellationToken); Assert.NotNull(info.Architecture); } [Fact] public async Task GetVersionAsync_Succeeds() { - var version = await _testFixture.DockerClient.System.GetVersionAsync(); + var version = await _testFixture.DockerClient.System.GetVersionAsync(TestContext.Current.CancellationToken); Assert.NotNull(version.APIVersion); } @@ -40,7 +40,7 @@ public async Task MonitorEventsAsync_EmptyContainersList_CanBeCancelled() using var cts = new CancellationTokenSource(); await cts.CancelAsync(); - await Task.Delay(1); + await Task.Delay(1, TestContext.Current.CancellationToken); await Assert.ThrowsAsync(() => _testFixture.DockerClient.System.MonitorEventsAsync(new ContainerEventsParameters(), progress, cts.Token)); @@ -49,13 +49,13 @@ public async Task MonitorEventsAsync_EmptyContainersList_CanBeCancelled() [Fact] public async Task MonitorEventsAsync_NullParameters_Throws() { - await Assert.ThrowsAsync(() => _testFixture.DockerClient.System.MonitorEventsAsync(null, null)); + await Assert.ThrowsAsync(() => _testFixture.DockerClient.System.MonitorEventsAsync(null, null, TestContext.Current.CancellationToken)); } [Fact] public async Task MonitorEventsAsync_NullProgress_Throws() { - await Assert.ThrowsAsync(() => _testFixture.DockerClient.System.MonitorEventsAsync(new ContainerEventsParameters(), null)); + await Assert.ThrowsAsync(() => _testFixture.DockerClient.System.MonitorEventsAsync(new ContainerEventsParameters(), null, TestContext.Current.CancellationToken)); } [Fact] @@ -90,7 +90,7 @@ await _testFixture.DockerClient.Images.DeleteImageAsync( _testFixture.Cts.Token); // Give it some time for output operation to complete before cancelling task - await Task.Delay(TimeSpan.FromSeconds(1)); + await Task.Delay(TimeSpan.FromSeconds(1), TestContext.Current.CancellationToken); await cts.CancelAsync(); @@ -223,19 +223,19 @@ await _testFixture.DockerClient.Images.TagImageAsync( }); using var cts = CancellationTokenSource.CreateLinkedTokenSource(_testFixture.Cts.Token); - var task = Task.Run(() => _testFixture.DockerClient.System.MonitorEventsAsync(eventsParams, progress, cts.Token)); + var task = Task.Run(() => _testFixture.DockerClient.System.MonitorEventsAsync(eventsParams, progress, cts.Token), TestContext.Current.CancellationToken); // Wait briefly to ensure the monitoring task is fully established before triggering Docker events. // Ideally, the API would return (or signal) once monitoring is active. - await Task.Delay(TimeSpan.FromSeconds(1)); + await Task.Delay(TimeSpan.FromSeconds(1), TestContext.Current.CancellationToken); - await _testFixture.DockerClient.Images.TagImageAsync($"{_testFixture.Repository}:{_testFixture.Tag}", new ImageTagParameters { RepositoryName = _testFixture.Repository, Tag = newTag }); - await _testFixture.DockerClient.Images.DeleteImageAsync($"{_testFixture.Repository}:{newTag}", new ImageDeleteParameters()); + await _testFixture.DockerClient.Images.TagImageAsync($"{_testFixture.Repository}:{_testFixture.Tag}", new ImageTagParameters { RepositoryName = _testFixture.Repository, Tag = newTag }, TestContext.Current.CancellationToken); + await _testFixture.DockerClient.Images.DeleteImageAsync($"{_testFixture.Repository}:{newTag}", new ImageDeleteParameters(), TestContext.Current.CancellationToken); - var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync(new CreateContainerParameters { Image = $"{_testFixture.Repository}:{_testFixture.Tag}", Entrypoint = CommonCommands.SleepInfinity }); + var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync(new CreateContainerParameters { Image = $"{_testFixture.Repository}:{_testFixture.Tag}", Entrypoint = CommonCommands.SleepInfinity }, TestContext.Current.CancellationToken); await _testFixture.DockerClient.Containers.RemoveContainerAsync(createContainerResponse.ID, new ContainerRemoveParameters(), cts.Token); - await Task.Delay(TimeSpan.FromSeconds(1)); + await Task.Delay(TimeSpan.FromSeconds(1), TestContext.Current.CancellationToken); await cts.CancelAsync(); await Assert.ThrowsAnyAsync(() => task); @@ -247,6 +247,6 @@ await _testFixture.DockerClient.Images.TagImageAsync( [Fact] public async Task PingAsync_Succeeds() { - await _testFixture.DockerClient.System.PingAsync(); + await _testFixture.DockerClient.System.PingAsync(TestContext.Current.CancellationToken); } } \ No newline at end of file diff --git a/test/Docker.DotNet.Tests/JsonEnumMemberConverterTest.cs b/test/Docker.DotNet.Tests/JsonEnumMemberConverterTest.cs index ad26d82b..7a16d9d3 100644 --- a/test/Docker.DotNet.Tests/JsonEnumMemberConverterTest.cs +++ b/test/Docker.DotNet.Tests/JsonEnumMemberConverterTest.cs @@ -26,7 +26,7 @@ public void JsonSerialization_ShouldSerializeAndDeserializeCorrectly(RestartPoli Assert.Equal(restartPolicyKind, deserializedParameters.HostConfig.RestartPolicy.Name); } - private sealed class RestartPolicyKindTestData : TheoryData + public sealed class RestartPolicyKindTestData : TheoryData { public RestartPolicyKindTestData() { diff --git a/test/Docker.DotNet.Tests/JsonRequestContentTests.cs b/test/Docker.DotNet.Tests/JsonRequestContentTests.cs index 7731f612..3a9f1c25 100644 --- a/test/Docker.DotNet.Tests/JsonRequestContentTests.cs +++ b/test/Docker.DotNet.Tests/JsonRequestContentTests.cs @@ -5,22 +5,23 @@ public sealed class JsonRequestContentTests [Fact] public void Constructor_ThrowsArgumentNullException_WhenValueIsNull() { - Assert.Throws(() => new JsonRequestContent(null, JsonSerializer.Instance)); + Assert.Throws(() => new JsonRequestContent(null!, JsonSerializer.Instance)); } [Fact] public void Constructor_ThrowsArgumentNullException_WhenSerializerIsNull() { - Assert.Throws(() => new JsonRequestContent(new object(), null)); + Assert.Throws(() => new JsonRequestContent(new object(), null!)); } [Fact] public async Task GetContent_Succeeds_WhenValueAndSerializerAreValid() { - var content = new JsonRequestContent(new[] { 1 }, JsonSerializer.Instance); + var content = new JsonRequestContent[]>([new Dictionary { { "key", "value" } }], JsonSerializer.Instance); using var httpContent = content.GetContent(); + Assert.NotNull(httpContent.Headers.ContentType); Assert.Equal("application/json; charset=utf-8", httpContent.Headers.ContentType.ToString()); - var jsonString = await httpContent.ReadAsStringAsync(); - Assert.Equal("[1]", jsonString); + var jsonString = await httpContent.ReadAsStringAsync(TestContext.Current.CancellationToken); + Assert.Equal("""[{"key":"value"}]""", jsonString); } } \ No newline at end of file diff --git a/test/Docker.DotNet.Tests/TestFixture.cs b/test/Docker.DotNet.Tests/TestFixture.cs index 9f3c7280..47f59ec5 100644 --- a/test/Docker.DotNet.Tests/TestFixture.cs +++ b/test/Docker.DotNet.Tests/TestFixture.cs @@ -3,7 +3,7 @@ namespace Docker.DotNet.Tests; [CollectionDefinition(nameof(TestCollection))] public sealed class TestCollection : ICollectionFixture; -public sealed class TestFixture : Progress, IAsyncLifetime, IDisposable, ILogger +public sealed class TestFixture : Progress, IAsyncLifetime, ILogger { private const LogLevel MinLogLevel = LogLevel.Debug; @@ -54,7 +54,7 @@ public TestFixture(IMessageSink messageSink) public ImagesListResponse Image { get; private set; } /// - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { const string repository = "alpine"; @@ -102,7 +102,7 @@ await DockerClient.Images.CreateImageAsync(new ImagesCreateParameters { FromImag } /// - public async Task DisposeAsync() + public async ValueTask DisposeAsync() { if (_hasInitializedSwarm) { @@ -149,11 +149,7 @@ await DockerClient.Swarm.LeaveSwarmAsync(new SwarmLeaveParameters { Force = true await DockerClient.Images.DeleteImageAsync(image.ID, new ImageDeleteParameters { Force = true }, Cts.Token) .ConfigureAwait(false); } - } - /// - public void Dispose() - { Cts.Dispose(); DockerClient.Dispose(); } diff --git a/test/Docker.DotNet.TestsV2/Docker.DotNet.TestsV2.csproj b/test/Docker.DotNet.TestsV2/Docker.DotNet.TestsV2.csproj index 6f9298b7..b5a18285 100644 --- a/test/Docker.DotNet.TestsV2/Docker.DotNet.TestsV2.csproj +++ b/test/Docker.DotNet.TestsV2/Docker.DotNet.TestsV2.csproj @@ -1,13 +1,21 @@ net8.0;net9.0;net10.0 + linux-arm64 + linux-x64 + osx-arm64 + osx-x64 + win-arm64 + win-x64 + Exe false - false + true + true + true - - - + + @@ -18,7 +26,6 @@ - @@ -29,7 +36,8 @@ - - + + + diff --git a/tools/Docker.DotNet.SourceAnalyzers/Docker.DotNet.SourceAnalyzers.csproj b/tools/Docker.DotNet.SourceAnalyzers/Docker.DotNet.SourceAnalyzers.csproj new file mode 100644 index 00000000..9575123c --- /dev/null +++ b/tools/Docker.DotNet.SourceAnalyzers/Docker.DotNet.SourceAnalyzers.csproj @@ -0,0 +1,30 @@ + + + net8.0;net9.0;net10.0;netstandard2.0 + true + false + false + + + enable + $(NoWarn);RS1041;RS2008 + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + diff --git a/tools/Docker.DotNet.SourceAnalyzers/JsonSerializerAnalyzer.cs b/tools/Docker.DotNet.SourceAnalyzers/JsonSerializerAnalyzer.cs new file mode 100644 index 00000000..cdce4538 --- /dev/null +++ b/tools/Docker.DotNet.SourceAnalyzers/JsonSerializerAnalyzer.cs @@ -0,0 +1,147 @@ +namespace Docker.DotNet.SourceAnalyzers; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class JsonSerializerAnalyzer : DiagnosticAnalyzer +{ + private const string DiagnosticId = "DDN001"; + + private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( + DiagnosticId, + "Missing JsonSerializable attribute", + "Type '{0}' used in '{1}' is not registered in a Docker JSON serializer context", + "Usage", + DiagnosticSeverity.Error, + true); + + public override ImmutableArray SupportedDiagnostics => [Rule]; + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); + + context.EnableConcurrentExecution(); + + // Handle method calls (MakeRequestAsync, MonitorStreamForMessagesAsync and all methods call of JsonSerializer.*). + context.RegisterSyntaxNodeAction(AnalyzeInvocation, SyntaxKind.InvocationExpression); + + // Handle new JsonRequestContent. + context.RegisterSyntaxNodeAction(AnalyzeObjectCreation, SyntaxKind.ObjectCreationExpression); + + // Handle [QueryStringMapParameter(...)]. + context.RegisterSyntaxNodeAction(AnalyzeAttribute, SyntaxKind.Attribute); + } + + private static void AnalyzeInvocation(SyntaxNodeAnalysisContext context) + { + var invocation = (InvocationExpressionSyntax)context.Node; + + if (context.SemanticModel.GetSymbolInfo(invocation).Symbol is not IMethodSymbol methodSymbol) + { + return; + } + + var isStjEntryPoint = + (methodSymbol.Name == "MakeRequestAsync" && methodSymbol.ContainingType?.Name == "DockerClient") || + (methodSymbol.Name == "MonitorStreamForMessagesAsync" && methodSymbol.ContainingType?.Name == "StreamUtil") || + (methodSymbol.ContainingType?.Name == "JsonSerializer" && methodSymbol.ContainingNamespace.ToDisplayString() == "Docker.DotNet"); + + if (isStjEntryPoint && methodSymbol.TypeArguments.Length > 0) + { + var methodName = $"{methodSymbol.ContainingType!.Name}.{methodSymbol.Name}"; + CheckAndReportType(context, methodSymbol.TypeArguments[0], invocation.GetLocation(), methodName); + } + } + + private static void AnalyzeObjectCreation(SyntaxNodeAnalysisContext context) + { + var creation = (ObjectCreationExpressionSyntax)context.Node; + + if (context.SemanticModel.GetSymbolInfo(creation).Symbol is not IMethodSymbol constructorSymbol) + { + return; + } + + var typeSymbol = constructorSymbol.ContainingType; + + var isStjEntryPoint = + typeSymbol.Name == "JsonRequestContent" && + typeSymbol.ContainingNamespace.ToDisplayString() == "Docker.DotNet"; + + if (isStjEntryPoint && typeSymbol.TypeArguments.Length > 0) + { + var typeName = typeSymbol.Name; + CheckAndReportType(context, typeSymbol.TypeArguments[0], creation.GetLocation(), typeName); + } + } + + private static void AnalyzeAttribute(SyntaxNodeAnalysisContext context) + { + var attribute = (AttributeSyntax)context.Node; + + if (context.SemanticModel.GetSymbolInfo(attribute).Symbol is not IMethodSymbol constructorSymbol) + { + return; + } + + var typeSymbol = constructorSymbol.ContainingType; + + var isStjEntryPoint = + typeSymbol.Name == "QueryStringMapParameterAttribute" && + typeSymbol.ContainingNamespace.ToDisplayString() == "Docker.DotNet"; + + if (isStjEntryPoint && typeSymbol.TypeArguments.Length > 0) + { + CheckAndReportType(context, typeSymbol.TypeArguments[0], attribute.GetLocation(), typeSymbol.Name); + } + } + + private static void CheckAndReportType(SyntaxNodeAnalysisContext context, ITypeSymbol targetType, Location location, string sourceName) + { + // Skip generic type parameters and the internal NoContent marker. + if (targetType.TypeKind == TypeKind.TypeParameter || + targetType.Name == "NoContent") + { + return; + } + + var jsonSerializableAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonSerializableAttribute"); + if (jsonSerializableAttributeTypeSymbol == null) + { + return; + } + + var jsonSerializerContextNames = new[] + { + "Docker.DotNet.DockerExtendedJsonSerializerContext", + "Docker.DotNet.Models.DockerModelsJsonSerializerContext" + }; + + var isRegistered = false; + + foreach (var jsonSerializerContextName in jsonSerializerContextNames) + { + var jsonSerializerContextTypeSymbol = context.Compilation.GetTypeByMetadataName(jsonSerializerContextName); + if (jsonSerializerContextTypeSymbol == null) + { + continue; + } + + var isRegisteredInContext = + jsonSerializerContextTypeSymbol.GetAttributes().Any(attr => + SymbolEqualityComparer.Default.Equals(attr.AttributeClass, jsonSerializableAttributeTypeSymbol) && + attr.ConstructorArguments.Any(arg => + arg.Value is ITypeSymbol reg && SymbolEqualityComparer.Default.Equals(reg, targetType))); + + if (isRegisteredInContext) + { + isRegistered = true; + break; + } + } + + if (!isRegistered) + { + context.ReportDiagnostic(Diagnostic.Create(Rule, location, targetType.ToDisplayString(), sourceName)); + } + } +} \ No newline at end of file diff --git a/tools/specgen/csharptype.go b/tools/specgen/csharptype.go index 8ee20c4e..bc171e2a 100644 --- a/tools/specgen/csharptype.go +++ b/tools/specgen/csharptype.go @@ -192,7 +192,8 @@ type CSModelType struct { // yet. it is possible that given the recursive nature that it not be // completed but as long as this is true we will not attempt to generate the // type more than once. - IsStarted bool + IsStarted bool + HasJsonSerializableProperties bool } // NewModel creates a new model type with valid slices diff --git a/tools/specgen/specgen.go b/tools/specgen/specgen.go index 218fde67..2fa31c59 100644 --- a/tools/specgen/specgen.go +++ b/tools/specgen/specgen.go @@ -596,8 +596,9 @@ func reflectTypeMembers(t reflect.Type, m *CSModelType) { inlineStructName := t.Name() + f.Name inlineModel := &CSModelType{ - Name: inlineStructName, - SourceName: fmt.Sprintf("%s.%s", t, f.Name), + Name: inlineStructName, + SourceName: fmt.Sprintf("%s.%s", t, f.Name), + HasJsonSerializableProperties: true, } reflectTypeMembers(f.Type, inlineModel) @@ -623,6 +624,8 @@ func reflectTypeMembers(t reflect.Type, m *CSModelType) { }) m.Properties = append(m.Properties, csProp) + + m.HasJsonSerializableProperties = true } else if f.Anonymous { // If the type is anonymous we need to inline its values to this model. clen := len(m.Constructors) @@ -642,6 +645,10 @@ func reflectTypeMembers(t reflect.Type, m *CSModelType) { // Now we need to add in all of the inherited types parameters m.Properties = append(m.Properties, newType.Properties...) + + if newType.HasJsonSerializableProperties { + m.HasJsonSerializableProperties = true + } } else { // If we are referencing a struct that isnt inline or anonymous we need to update it too. if ut := ultimateType(f.Type); ut.Kind() == reflect.Struct { @@ -687,7 +694,18 @@ func reflectTypeMembers(t reflect.Type, m *CSModelType) { restTag.Name = strings.ToLower(f.Name) } - a := CSAttribute{Type: CSType{"", "QueryStringParameter"}} + queryStringParameter := "QueryStringParameter" + + switch f.Type.Kind() { + case reflect.Bool: + queryStringParameter = "QueryStringBoolParameter" + case reflect.Slice, reflect.Array: + queryStringParameter = "QueryStringListParameter" + case reflect.Map: + queryStringParameter = "QueryStringMapParameter<" + csProp.Type.Name + ">" + } + + a := CSAttribute{Type: CSType{"", queryStringParameter}} a.Arguments = append( a.Arguments, CSArgument{ @@ -696,15 +714,6 @@ func reflectTypeMembers(t reflect.Type, m *CSModelType) { CSArgument{strconv.FormatBool(restTag.Required), CSInboxTypesMap[reflect.Bool]}) - switch f.Type.Kind() { - case reflect.Bool: - a.Arguments = append(a.Arguments, CSArgument{Value: "typeof(QueryStringBoolConverter)"}) - case reflect.Slice, reflect.Array: - a.Arguments = append(a.Arguments, CSArgument{Value: "typeof(QueryStringEnumerableConverter)"}) - case reflect.Map: - a.Arguments = append(a.Arguments, CSArgument{Value: "typeof(QueryStringMapConverter)"}) - } - csProp.IsOpt = omitEmpty || !restTag.Required csProp.Attributes = append(csProp.Attributes, a) csProp.DefaultValue = restTag.Default @@ -713,6 +722,8 @@ func reflectTypeMembers(t reflect.Type, m *CSModelType) { a.Arguments = append(a.Arguments, CSArgument{jsonName, CSInboxTypesMap[reflect.String]}) csProp.IsOpt = omitEmpty || f.Type.Kind() == reflect.Ptr csProp.Attributes = append(csProp.Attributes, a) + + m.HasJsonSerializableProperties = true } if hasTypeCustomizations { @@ -728,6 +739,11 @@ func reflectTypeMembers(t reflect.Type, m *CSModelType) { m.Properties = append(m.Properties, csProp) } } + + // If we have no properties, we still want to generate a JsonSerializerContext for this type, so we mark it as having json serializable properties to ensure that happens. + if len(m.Properties) == 0 { + m.HasJsonSerializableProperties = true + } } func reflectType(t reflect.Type) { @@ -799,6 +815,8 @@ func main() { reflectType(t) } + jsonSerializableNames := make([]string, 0, len(reflectedTypes)) + for k, v := range reflectedTypes { if _, e := os.Stat(path.Join(sourcePath, v.Name+".Generated.cs")); e == nil { panic(fmt.Sprintf("File: (%s.Generated.cs) already exists. Failed to write key same name for key: (%s) type: (%s).", v.Name, k, v.SourceName)) @@ -821,5 +839,36 @@ func main() { f.Close() os.Rename(f.Name(), path.Join(sourcePath, v.Name+".Generated.cs")) + + if v.HasJsonSerializableProperties { + jsonSerializableNames = append(jsonSerializableNames, v.Name) + } + } + + slices.Sort(jsonSerializableNames) + + jscf, err := os.Create(path.Join(sourcePath, "DockerModelsJsonSerializerContext.Generated.cs")) + if err != nil { + panic(err) } + + defer jscf.Close() + + jscb := bufio.NewWriter(jscf) + + fmt.Fprintln(jscb, "namespace Docker.DotNet.Models") + fmt.Fprintln(jscb, "{") + for _, name := range jsonSerializableNames { + fmt.Fprintf(jscb, " [JsonSerializable(typeof(%s))]\n", name) + } + fmt.Fprintln(jscb, " internal sealed partial class DockerModelsJsonSerializerContext : JsonSerializerContext { }") + fmt.Fprintln(jscb, "}") + + err = jscb.Flush() + if err != nil { + os.Remove(jscf.Name()) + panic(err) + } + + jscf.Close() }