diff --git a/src/Elastic.Transport.VirtualizedCluster/Components/ExposingPipelineFactory.cs b/src/Elastic.Transport.VirtualizedCluster/Components/ExposingPipelineFactory.cs index 5882b5c..4fd0586 100644 --- a/src/Elastic.Transport.VirtualizedCluster/Components/ExposingPipelineFactory.cs +++ b/src/Elastic.Transport.VirtualizedCluster/Components/ExposingPipelineFactory.cs @@ -2,6 +2,7 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information +#nullable enable namespace Elastic.Transport.VirtualizedCluster.Components; /// @@ -14,7 +15,7 @@ public ExposingPipelineFactory(TConfiguration configuration, DateTimeProvider da DateTimeProvider = dateTimeProvider; MemoryStreamFactory = TransportConfiguration.DefaultMemoryStreamFactory; Configuration = configuration; - Pipeline = Create(Configuration, DateTimeProvider, MemoryStreamFactory, new DefaultRequestParameters()); + Pipeline = Create(Configuration, DateTimeProvider, MemoryStreamFactory, null); RequestHandler = new DistributedTransport(Configuration, this, DateTimeProvider, MemoryStreamFactory); } @@ -26,6 +27,6 @@ public ExposingPipelineFactory(TConfiguration configuration, DateTimeProvider da public ITransport RequestHandler { get; } public override RequestPipeline Create(TConfiguration configurationValues, DateTimeProvider dateTimeProvider, - MemoryStreamFactory memoryStreamFactory, RequestParameters requestParameters) => - new DefaultRequestPipeline(Configuration, DateTimeProvider, MemoryStreamFactory, requestParameters ?? new DefaultRequestParameters()); + MemoryStreamFactory memoryStreamFactory, IRequestConfiguration? requestConfiguration) => + new DefaultRequestPipeline(Configuration, DateTimeProvider, MemoryStreamFactory, requestConfiguration); } diff --git a/src/Elastic.Transport.VirtualizedCluster/Components/VirtualizedCluster.cs b/src/Elastic.Transport.VirtualizedCluster/Components/VirtualizedCluster.cs index bbb6ed2..7ede512 100644 --- a/src/Elastic.Transport.VirtualizedCluster/Components/VirtualizedCluster.cs +++ b/src/Elastic.Transport.VirtualizedCluster/Components/VirtualizedCluster.cs @@ -27,24 +27,28 @@ internal VirtualizedCluster(TestableDateTimeProvider dateTimeProvider, Transport _exposingRequestPipeline = new ExposingPipelineFactory(settings, _dateTimeProvider); _syncCall = (t, r) => t.Request( - HttpMethod.GET, "/", - PostData.Serializable(new {}), new DefaultRequestParameters() - { - RequestConfiguration = r?.Invoke(new RequestConfigurationDescriptor(null)) - }); + method: HttpMethod.GET, + path: "/", + postData: PostData.Serializable(new { }), + requestParameters: new DefaultRequestParameters(), + openTelemetryData: default, + localConfiguration: r?.Invoke(new RequestConfigurationDescriptor(null)), + responseBuilder: null + ); _asyncCall = async (t, r) => { var res = await t.RequestAsync ( - HttpMethod.GET, "/", - PostData.Serializable(new { }), - new DefaultRequestParameters() - { - RequestConfiguration = r?.Invoke(new RequestConfigurationDescriptor(null)) - }, + method: HttpMethod.GET, + path: "/", + postData: PostData.Serializable(new { }), + requestParameters: new DefaultRequestParameters(), + openTelemetryData: default, + localConfiguration: r?.Invoke(new RequestConfigurationDescriptor(null)), + responseBuilder: null, CancellationToken.None ).ConfigureAwait(false); - return (TransportResponse)res; + return res; }; } diff --git a/src/Elastic.Transport/Components/Pipeline/DefaultRequestPipeline.cs b/src/Elastic.Transport/Components/Pipeline/DefaultRequestPipeline.cs index 2a11b77..d1cf207 100644 --- a/src/Elastic.Transport/Components/Pipeline/DefaultRequestPipeline.cs +++ b/src/Elastic.Transport/Components/Pipeline/DefaultRequestPipeline.cs @@ -36,7 +36,7 @@ internal DefaultRequestPipeline( TConfiguration configurationValues, DateTimeProvider dateTimeProvider, MemoryStreamFactory memoryStreamFactory, - RequestParameters requestParameters + IRequestConfiguration? requestConfiguration ) { _settings = configurationValues; @@ -47,7 +47,7 @@ RequestParameters requestParameters _productRegistration = configurationValues.ProductRegistration; _responseBuilder = _productRegistration.ResponseBuilder; _nodePredicate = _settings.NodePredicate ?? _productRegistration.NodePredicate; - RequestConfiguration = requestParameters?.RequestConfiguration; + RequestConfiguration = requestConfiguration; StartedOn = dateTimeProvider.Now(); } diff --git a/src/Elastic.Transport/Components/Pipeline/RequestData.cs b/src/Elastic.Transport/Components/Pipeline/RequestData.cs index 93ccf91..cb9b0b0 100644 --- a/src/Elastic.Transport/Components/Pipeline/RequestData.cs +++ b/src/Elastic.Transport/Components/Pipeline/RequestData.cs @@ -28,37 +28,29 @@ public sealed class RequestData public const string OpaqueIdHeader = "X-Opaque-Id"; public const string RunAsSecurityHeader = "es-security-runas-user"; - private Uri _requestUri; - private Node _node; + private Uri? _requestUri; + private Node? _node; public RequestData( - HttpMethod method, string path, - PostData data, + HttpMethod method, + string pathAndQuery, + PostData? data, ITransportConfiguration global, - RequestParameters local, + IRequestConfiguration? local, + CustomResponseBuilder? customResponseBuilder, MemoryStreamFactory memoryStreamFactory, OpenTelemetryData openTelemetryData ) - : this(method, data, global, local?.RequestConfiguration, memoryStreamFactory) { - _path = path; OpenTelemetryData = openTelemetryData; - CustomResponseBuilder = local?.CustomResponseBuilder; - PathAndQuery = CreatePathWithQueryStrings(path, ConnectionSettings, local); - } - - private RequestData(HttpMethod method, - PostData data, - ITransportConfiguration global, - IRequestConfiguration local, - MemoryStreamFactory memoryStreamFactory - ) - { + CustomResponseBuilder = customResponseBuilder; ConnectionSettings = global; MemoryStreamFactory = memoryStreamFactory; Method = method; PostData = data; + PathAndQuery = pathAndQuery; + if (data != null) data.DisableDirectStreaming = local?.DisableDirectStreaming ?? global.DisableDirectStreaming; @@ -119,19 +111,15 @@ MemoryStreamFactory memoryStreamFactory ResponseHeadersToParse = new HeadersList(local.ResponseHeadersToParse, global.ResponseHeadersToParse); } else - { ResponseHeadersToParse = global.ResponseHeadersToParse; - } } - private readonly string _path; - public string Accept { get; } public IReadOnlyCollection AllowedStatusCodes { get; } public AuthorizationHeader AuthenticationHeader { get; } public X509CertificateCollection ClientCertificates { get; } public ITransportConfiguration ConnectionSettings { get; } - public CustomResponseBuilder CustomResponseBuilder { get; } + public CustomResponseBuilder? CustomResponseBuilder { get; } public bool DisableAutomaticProxyDetection { get; } public HeadersList ResponseHeadersToParse { get; } public bool ParseAllHeaders { get; } @@ -143,7 +131,7 @@ MemoryStreamFactory memoryStreamFactory public MemoryStreamFactory MemoryStreamFactory { get; } public HttpMethod Method { get; } - public Node? Node + public Node Node { get => _node; set @@ -159,13 +147,13 @@ public Node? Node public string PathAndQuery { get; } public TimeSpan PingTimeout { get; } public bool Pipelined { get; } - public PostData PostData { get; } + public PostData? PostData { get; } public string ProxyAddress { get; } public string ProxyPassword { get; } public string ProxyUsername { get; } public string ContentType { get; } public TimeSpan RequestTimeout { get; } - public string RunAs { get; } + public string? RunAs { get; } public IReadOnlyCollection SkipDeserializationForStatusCodes { get; } public bool ThrowExceptions { get; } public UserAgent UserAgent { get; } @@ -197,35 +185,7 @@ public Uri Uri internal OpenTelemetryData OpenTelemetryData { get; } - public override string ToString() => $"{Method.GetStringValue()} {_path}"; - - // TODO This feels like its in the wrong place - private string CreatePathWithQueryStrings(string path, ITransportConfiguration global, RequestParameters request) - { - path ??= string.Empty; - if (path.Contains("?")) - throw new ArgumentException($"{nameof(path)} can not contain querystring parameters and needs to be already escaped"); - - var g = global.QueryStringParameters; - var l = request?.QueryString; - - if ((g == null || g.Count == 0) && (l == null || l.Count == 0)) return path; - - //create a copy of the global query string collection if needed. - var nv = g == null ? new NameValueCollection() : new NameValueCollection(g); - - //set all querystring pairs from local `l` on the querystring collection - var formatter = ConnectionSettings.UrlFormatter; - nv.UpdateFromDictionary(l, formatter); - - //if nv has no keys simply return path as provided - if (!nv.HasKeys()) return path; - - //create string for query string collection where key and value are escaped properly. - var queryString = ToQueryString(nv); - path += queryString; - return path; - } + public override string ToString() => $"{Method.GetStringValue()} {PathAndQuery}"; internal bool ValidateResponseContentType(string responseMimeType) { @@ -248,6 +208,5 @@ internal bool ValidateResponseContentType(string responseMimeType) || trimmedAccept.Contains("application/vnd.elasticsearch+json") && trimmedResponseMimeType.StartsWith(DefaultMimeType, StringComparison.OrdinalIgnoreCase); } - public static string ToQueryString(NameValueCollection collection) => collection.ToQueryString(); #pragma warning restore 1591 } diff --git a/src/Elastic.Transport/Components/Providers/DefaultRequestPipelineFactory.cs b/src/Elastic.Transport/Components/Providers/DefaultRequestPipelineFactory.cs index 72df8a0..82779f2 100644 --- a/src/Elastic.Transport/Components/Providers/DefaultRequestPipelineFactory.cs +++ b/src/Elastic.Transport/Components/Providers/DefaultRequestPipelineFactory.cs @@ -14,6 +14,6 @@ internal sealed class DefaultRequestPipelineFactory : RequestPip /// returns instances of /// public override RequestPipeline Create(TConfiguration configurationValues, DateTimeProvider dateTimeProvider, - MemoryStreamFactory memoryStreamFactory, RequestParameters requestParameters) => - new DefaultRequestPipeline(configurationValues, dateTimeProvider, memoryStreamFactory, requestParameters); + MemoryStreamFactory memoryStreamFactory, IRequestConfiguration? requestConfiguration) => + new DefaultRequestPipeline(configurationValues, dateTimeProvider, memoryStreamFactory, requestConfiguration); } diff --git a/src/Elastic.Transport/Components/Providers/RequestPipelineFactory.cs b/src/Elastic.Transport/Components/Providers/RequestPipelineFactory.cs index 7c0ba62..25c8823 100644 --- a/src/Elastic.Transport/Components/Providers/RequestPipelineFactory.cs +++ b/src/Elastic.Transport/Components/Providers/RequestPipelineFactory.cs @@ -12,5 +12,5 @@ internal RequestPipelineFactory() { } /// Create an instance of public abstract RequestPipeline Create(TConfiguration configuration, DateTimeProvider dateTimeProvider, - MemoryStreamFactory memoryStreamFactory, RequestParameters requestParameters); + MemoryStreamFactory memoryStreamFactory, IRequestConfiguration? requestParameters); } diff --git a/src/Elastic.Transport/Configuration/ITransportConfiguration.cs b/src/Elastic.Transport/Configuration/ITransportConfiguration.cs index 207d810..d610098 100644 --- a/src/Elastic.Transport/Configuration/ITransportConfiguration.cs +++ b/src/Elastic.Transport/Configuration/ITransportConfiguration.cs @@ -93,7 +93,7 @@ public interface ITransportConfiguration : IDisposable /// /// Try to send these headers for every request /// - NameValueCollection Headers { get; } + NameValueCollection? Headers { get; } /// /// Whether HTTP pipelining is enabled. The default is true @@ -143,18 +143,18 @@ public interface ITransportConfiguration : IDisposable /// When using static or single node connection pooling it is assumed the list of node you instantiate the client with should be taken /// verbatim. /// - Func NodePredicate { get; } + Func? NodePredicate { get; } /// /// Allows you to register a callback every time a an API call is returned /// - Action OnRequestCompleted { get; } + Action? OnRequestCompleted { get; } /// /// An action to run when the for a request has been /// created. /// - Action OnRequestDataCreated { get; } + Action? OnRequestDataCreated { get; } /// /// When enabled, all headers from the HTTP response will be included in the . @@ -184,7 +184,7 @@ public interface ITransportConfiguration : IDisposable /// /// Append these query string parameters automatically to every request /// - NameValueCollection QueryStringParameters { get; } + NameValueCollection? QueryStringParameters { get; } /// The serializer to use to serialize requests and deserialize responses Serializer RequestResponseSerializer { get; } diff --git a/src/Elastic.Transport/Configuration/RequestConfiguration.cs b/src/Elastic.Transport/Configuration/RequestConfiguration.cs index eda0d6c..183d950 100644 --- a/src/Elastic.Transport/Configuration/RequestConfiguration.cs +++ b/src/Elastic.Transport/Configuration/RequestConfiguration.cs @@ -131,7 +131,7 @@ public interface IRequestConfiguration /// /// Holds additional meta data about the request. /// - RequestMetaData RequestMetaData { get; set; } + RequestMetaData? RequestMetaData { get; set; } } /// @@ -192,7 +192,7 @@ public class RequestConfiguration : IRequestConfiguration public class RequestConfigurationDescriptor : IRequestConfiguration { /// - public RequestConfigurationDescriptor(IRequestConfiguration config) + public RequestConfigurationDescriptor(IRequestConfiguration? config) { Self.RequestTimeout = config?.RequestTimeout; Self.PingTimeout = config?.PingTimeout; diff --git a/src/Elastic.Transport/Diagnostics/OpenTelemetry/OpenTelemetryData.cs b/src/Elastic.Transport/Diagnostics/OpenTelemetry/OpenTelemetryData.cs index 21dbfc0..aac594c 100644 --- a/src/Elastic.Transport/Diagnostics/OpenTelemetry/OpenTelemetryData.cs +++ b/src/Elastic.Transport/Diagnostics/OpenTelemetry/OpenTelemetryData.cs @@ -14,10 +14,10 @@ public readonly struct OpenTelemetryData /// /// The name to use for spans relating to a request. /// - public readonly string? SpanName { get; init; } + public string? SpanName { get; init; } /// /// Additional span attributes for transport spans relating to a request. /// - public readonly Dictionary? SpanAttributes { get; init; } + public Dictionary? SpanAttributes { get; init; } } diff --git a/src/Elastic.Transport/DistributedTransport.cs b/src/Elastic.Transport/DistributedTransport.cs index ea89b49..4739288 100644 --- a/src/Elastic.Transport/DistributedTransport.cs +++ b/src/Elastic.Transport/DistributedTransport.cs @@ -96,10 +96,13 @@ public TResponse Request( string path, PostData? data, RequestParameters? requestParameters, - in OpenTelemetryData openTelemetryData + in OpenTelemetryData openTelemetryData, + IRequestConfiguration? localConfiguration, + CustomResponseBuilder? responseBuilder ) where TResponse : TransportResponse, new() => - RequestCoreAsync(false, method, path, data, requestParameters, openTelemetryData).EnsureCompleted(); + RequestCoreAsync(isAsync: false, + method, path, data, requestParameters, openTelemetryData, localConfiguration, responseBuilder).EnsureCompleted(); /// public Task RequestAsync( @@ -108,10 +111,13 @@ public Task RequestAsync( PostData? data, RequestParameters? requestParameters, in OpenTelemetryData openTelemetryData, + IRequestConfiguration? localConfiguration, + CustomResponseBuilder? responseBuilder, CancellationToken cancellationToken = default ) where TResponse : TransportResponse, new() => - RequestCoreAsync(true, method, path, data, requestParameters, openTelemetryData, cancellationToken).AsTask(); + RequestCoreAsync(isAsync: true, + method, path, data, requestParameters, openTelemetryData, localConfiguration, responseBuilder, cancellationToken).AsTask(); private async ValueTask RequestCoreAsync( bool isAsync, @@ -120,6 +126,8 @@ private async ValueTask RequestCoreAsync( PostData? data, RequestParameters? requestParameters, OpenTelemetryData openTelemetryData, + IRequestConfiguration? localRequestConfiguration, + CustomResponseBuilder? customResponseBuilder, CancellationToken cancellationToken = default ) where TResponse : TransportResponse, new() @@ -132,15 +140,15 @@ private async ValueTask RequestCoreAsync( try { - using var pipeline = - PipelineProvider.Create(Configuration, DateTimeProvider, MemoryStreamFactory, requestParameters); + using var pipeline = PipelineProvider.Create(Configuration, DateTimeProvider, MemoryStreamFactory, localRequestConfiguration); if (isAsync) await pipeline.FirstPoolUsageAsync(Configuration.BootstrapLock, cancellationToken).ConfigureAwait(false); else pipeline.FirstPoolUsage(Configuration.BootstrapLock); - var requestData = new RequestData(method, path, data, Configuration, requestParameters, MemoryStreamFactory, openTelemetryData); + var pathAndQuery = requestParameters?.CreatePathWithQueryStrings(path, Configuration) ?? path; + var requestData = new RequestData(method, pathAndQuery, data, Configuration, localRequestConfiguration, customResponseBuilder, MemoryStreamFactory, openTelemetryData); Configuration.OnRequestDataCreated?.Invoke(requestData); TResponse response = null; diff --git a/src/Elastic.Transport/ITransport.cs b/src/Elastic.Transport/ITransport.cs index c4db867..ab5c7eb 100644 --- a/src/Elastic.Transport/ITransport.cs +++ b/src/Elastic.Transport/ITransport.cs @@ -23,13 +23,21 @@ public interface ITransport /// The data to be included as the body of the HTTP request. /// The parameters for the request. /// Data to be used to control the OpenTelemetry instrumentation. + /// Per request configuration + /// + /// Allows callers to override completely how `TResponse` should be deserialized to a `TResponse` that implements instance. + /// Expert setting only + /// /// The deserialized . public TResponse Request( HttpMethod method, string path, PostData? postData, RequestParameters? requestParameters, - in OpenTelemetryData openTelemetryData) + in OpenTelemetryData openTelemetryData, + IRequestConfiguration? localConfiguration, + CustomResponseBuilder? responseBuilder + ) where TResponse : TransportResponse, new(); @@ -43,6 +51,11 @@ public TResponse Request( /// The parameters for the request. /// The cancellation token to use. /// Data to be used to control the OpenTelemetry instrumentation. + /// Per request configuration + /// + /// Allows callers to override completely how `TResponse` should be deserialized to a `TResponse` that implements instance. + /// Expert setting only + /// /// The deserialized . public Task RequestAsync( HttpMethod method, @@ -50,7 +63,10 @@ public Task RequestAsync( PostData? postData, RequestParameters? requestParameters, in OpenTelemetryData openTelemetryData, - CancellationToken cancellationToken = default) + IRequestConfiguration? localConfiguration, + CustomResponseBuilder? responseBuilder, + CancellationToken cancellationToken = default + ) where TResponse : TransportResponse, new(); } @@ -79,7 +95,7 @@ public static TResponse Request( HttpMethod method, string path) where TResponse : TransportResponse, new() - => transport.Request(method, path, null, null, default); + => transport.Request(method, path, null, null, default, null, null); /// > public static TResponse Request( @@ -88,7 +104,7 @@ public static TResponse Request( string path, PostData? postData) where TResponse : TransportResponse, new() - => transport.Request(method, path, postData, null, default); + => transport.Request(method, path, postData, null, default, null, null); /// > public static TResponse Request( @@ -98,17 +114,37 @@ public static TResponse Request( PostData? postData, RequestParameters? requestParameters) where TResponse : TransportResponse, new() - => transport.Request(method, path, postData, requestParameters, default); + => transport.Request(method, path, postData, requestParameters, default, null, null); + + /// > + public static TResponse Request( + this ITransport transport, + HttpMethod method, + string path, + PostData? postData, + RequestParameters? requestParameters, + IRequestConfiguration localConfiguration) + where TResponse : TransportResponse, new() + => transport.Request(method, path, postData, requestParameters, default, localConfiguration, null); + /// > + public static Task RequestAsync( + this ITransport transport, + HttpMethod method, + string path, + CancellationToken cancellationToken = default) + where TResponse : TransportResponse, new() + => transport.RequestAsync(method, path, null, null, default, null, null, cancellationToken); /// > public static Task RequestAsync( this ITransport transport, HttpMethod method, string path, + PostData? postData, CancellationToken cancellationToken = default) where TResponse : TransportResponse, new() - => transport.RequestAsync(method, path, null, null, default, cancellationToken); + => transport.RequestAsync(method, path, postData, null, default, null, null, cancellationToken); /// > public static Task RequestAsync( @@ -116,9 +152,10 @@ public static Task RequestAsync( HttpMethod method, string path, PostData? postData, + RequestParameters? requestParameters, CancellationToken cancellationToken = default) where TResponse : TransportResponse, new() - => transport.RequestAsync(method, path, postData, null, default, cancellationToken); + => transport.RequestAsync(method, path, postData, requestParameters, default, null, null, cancellationToken); /// > public static Task RequestAsync( @@ -127,7 +164,8 @@ public static Task RequestAsync( string path, PostData? postData, RequestParameters? requestParameters, + IRequestConfiguration localConfiguration, CancellationToken cancellationToken = default) where TResponse : TransportResponse, new() - => transport.RequestAsync(method, path, postData, requestParameters, default, cancellationToken); + => transport.RequestAsync(method, path, postData, requestParameters, default, localConfiguration, null, cancellationToken); } diff --git a/src/Elastic.Transport/Products/Elasticsearch/ElasticsearchProductRegistration.cs b/src/Elastic.Transport/Products/Elasticsearch/ElasticsearchProductRegistration.cs index e233c23..12dfe7b 100644 --- a/src/Elastic.Transport/Products/Elasticsearch/ElasticsearchProductRegistration.cs +++ b/src/Elastic.Transport/Products/Elasticsearch/ElasticsearchProductRegistration.cs @@ -127,9 +127,10 @@ MemoryStreamFactory memoryStreamFactory { var requestParameters = new DefaultRequestParameters { - QueryString = { { "timeout", requestConfiguration.PingTimeout }, { "flat_settings", true }, } + QueryString = { { "timeout", requestConfiguration.PingTimeout }, { "flat_settings", true } } }; - return new RequestData(HttpMethod.GET, SniffPath, null, settings, requestParameters, memoryStreamFactory, default) + var sniffPath = requestParameters.CreatePathWithQueryStrings(SniffPath, settings); + return new RequestData(HttpMethod.GET, sniffPath, null, settings, requestConfiguration, null, memoryStreamFactory, default) { Node = node }; @@ -162,12 +163,7 @@ public override RequestData CreatePingRequestData(Node node, RequestConfiguratio MemoryStreamFactory memoryStreamFactory ) { - var requestParameters = new DefaultRequestParameters - { - RequestConfiguration = requestConfiguration - }; - - var data = new RequestData(HttpMethod.HEAD, string.Empty, null, global, requestParameters, memoryStreamFactory, default) + var data = new RequestData(HttpMethod.HEAD, string.Empty, null, global, requestConfiguration, null, memoryStreamFactory, default) { Node = node }; diff --git a/src/Elastic.Transport/Requests/Body/PostData.Serializable.cs b/src/Elastic.Transport/Requests/Body/PostData.Serializable.cs index 5f30c42..54dc048 100644 --- a/src/Elastic.Transport/Requests/Body/PostData.Serializable.cs +++ b/src/Elastic.Transport/Requests/Body/PostData.Serializable.cs @@ -42,8 +42,7 @@ public override void Write(Stream writableStream, ITransportConfiguration settin FinishStream(writableStream, buffer, settings); } - public override async Task WriteAsync(Stream writableStream, ITransportConfiguration settings, - CancellationToken cancellationToken) + public override async Task WriteAsync(Stream writableStream, ITransportConfiguration settings, CancellationToken cancellationToken) { MemoryStream buffer = null; var stream = writableStream; diff --git a/src/Elastic.Transport/Requests/RequestParameters.cs b/src/Elastic.Transport/Requests/RequestParameters.cs index 1443984..21e4dc6 100644 --- a/src/Elastic.Transport/Requests/RequestParameters.cs +++ b/src/Elastic.Transport/Requests/RequestParameters.cs @@ -2,28 +2,36 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information +using System; using System.Collections.Generic; +using System.Collections.Specialized; +using Elastic.Transport.Extensions; using static Elastic.Transport.UrlFormatter; // ReSharper disable once CheckNamespace namespace Elastic.Transport; /// -/// +/// /// public interface IStringable { /// - /// + /// /// /// string GetString(); } /// -/// +/// /// -public sealed class DefaultRequestParameters : RequestParameters +public sealed class DefaultRequestParameters : RequestParameters; + +/// +/// +/// +public static class RequestParameterExtensions { } @@ -34,27 +42,17 @@ public sealed class DefaultRequestParameters : RequestParameters public abstract class RequestParameters { /// - /// - /// - public CustomResponseBuilder CustomResponseBuilder { get; set; } - - /// - /// + /// /// - public Dictionary QueryString { get; internal set; } = new Dictionary(); + public Dictionary QueryString { get; internal set; } = new(); /// - /// - /// - public IRequestConfiguration RequestConfiguration { get; set; } - - /// - /// + /// /// public bool ContainsQueryString(string name) => QueryString != null && QueryString.ContainsKey(name); /// - /// + /// /// public TOut GetQueryStringValue(string name) { @@ -69,15 +67,9 @@ public TOut GetQueryStringValue(string name) } /// - /// + /// /// - public string GetResolvedQueryStringValue(string name, ITransportConfiguration transportConfiguration) => - CreateString(GetQueryStringValue(name), transportConfiguration); - - /// - /// - /// - public void SetQueryString(string name, object value) + public void SetQueryString(string name, object? value) { if (value == null) RemoveQueryString(name); else QueryString[name] = value; @@ -99,21 +91,36 @@ private void RemoveQueryString(string name) QueryString.Remove(name); } - /// - /// Makes sure is set before explicitly setting - /// - protected void SetAcceptHeader(string format) + /// + public virtual string CreatePathWithQueryStrings(string? path, ITransportConfiguration global) { - if (RequestConfiguration == null) - RequestConfiguration = new RequestConfiguration(); + path ??= string.Empty; + if (path.Contains("?")) + throw new ArgumentException($"{nameof(path)} can not contain querystring parameters and needs to be already escaped"); + + var g = global.QueryStringParameters; + var l = QueryString; + + if ((g == null || g.Count == 0) && (l == null || l.Count == 0)) return path; - RequestConfiguration.Accept = AcceptHeaderFromFormat(format); + //create a copy of the global query string collection if needed. + var nv = g == null ? new NameValueCollection() : new NameValueCollection(g); + + //set all querystring pairs from local `l` on the querystring collection + var formatter = global.UrlFormatter; + nv.UpdateFromDictionary(l, formatter); + + //if nv has no keys simply return path as provided + if (!nv.HasKeys()) return path; + + //create string for query string collection where key and value are escaped properly. + var queryString = nv.ToQueryString(); + path += queryString; + return path; } - /// - /// - /// - public string AcceptHeaderFromFormat(string format) + /// Create the specified accept-header based on the format sent over the wire + public string? AcceptHeaderFromFormat(string? format) { if (format == null) return null; diff --git a/tests/Elastic.Elasticsearch.IntegrationTests/SecurityClusterTests.cs b/tests/Elastic.Elasticsearch.IntegrationTests/SecurityClusterTests.cs index a6e04e4..5a57c7d 100644 --- a/tests/Elastic.Elasticsearch.IntegrationTests/SecurityClusterTests.cs +++ b/tests/Elastic.Elasticsearch.IntegrationTests/SecurityClusterTests.cs @@ -33,13 +33,13 @@ public void SyncRequestDoesNotThrow() [Fact] public void SyncRequestDoesNotThrowOnBadAuth() { - var response = RequestHandler.Request(GET, "/", null, new DefaultRequestParameters - { - RequestConfiguration = new RequestConfiguration + var response = RequestHandler.Request(GET, "/", null, + new DefaultRequestParameters(), + new RequestConfiguration { AuthenticationHeader = new BasicAuthentication("unknown-user", "bad-password") } - }); + ); response.ApiCallDetails.Should().NotBeNull(); response.ApiCallDetails.HasSuccessfulStatusCode.Should().BeFalse(); } diff --git a/tests/Elastic.Transport.Tests/OpenTelemetryTests.cs b/tests/Elastic.Transport.Tests/OpenTelemetryTests.cs index 60563d8..d60e709 100644 --- a/tests/Elastic.Transport.Tests/OpenTelemetryTests.cs +++ b/tests/Elastic.Transport.Tests/OpenTelemetryTests.cs @@ -129,7 +129,7 @@ private async Task TestCoreAsync(Action assertions, OpenTelemetryData var mre = new ManualResetEvent(false); var callCounter = 0; - using var listener = new ActivityListener() + using var listener = new ActivityListener { ActivityStarted = _ => { }, ActivityStopped = activity => @@ -149,7 +149,7 @@ private async Task TestCoreAsync(Action assertions, OpenTelemetryData transport ??= new DistributedTransport(InMemoryConnectionFactory.Create()); - _ = await transport.RequestAsync(HttpMethod.GET, "/", null, null, openTelemetryData); + _ = await transport.RequestAsync(HttpMethod.GET, "/", null, null, openTelemetryData, null, null, default); mre.WaitOne(TimeSpan.FromSeconds(1)).Should().BeTrue(); } diff --git a/tests/Elastic.Transport.Tests/ResponseBuilderDisposeTests.cs b/tests/Elastic.Transport.Tests/ResponseBuilderDisposeTests.cs index a64fdfe..db5f3e4 100644 --- a/tests/Elastic.Transport.Tests/ResponseBuilderDisposeTests.cs +++ b/tests/Elastic.Transport.Tests/ResponseBuilderDisposeTests.cs @@ -33,7 +33,7 @@ private async Task AssertRegularResponse(bool disableDirectStreaming, int? statu { var settings = disableDirectStreaming ? _settingsDisableDirectStream : _settings; var memoryStreamFactory = new TrackMemoryStreamFactory(); - var requestData = new RequestData(HttpMethod.GET, "/", null, settings, null, memoryStreamFactory, default) + var requestData = new RequestData(HttpMethod.GET, "/", null, settings, null, null, memoryStreamFactory, default) { Node = new Node(new Uri("http://localhost:9200")) }; @@ -80,7 +80,7 @@ private async Task AssertStreamResponse(bool disableDirectStreaming, int? status var settings = disableDirectStreaming ? _settingsDisableDirectStream : _settings; var memoryStreamFactory = new TrackMemoryStreamFactory(); - var requestData = new RequestData(HttpMethod.GET, "/", null, settings, null, memoryStreamFactory, default) + var requestData = new RequestData(HttpMethod.GET, "/", null, settings, null, null, memoryStreamFactory, default) { Node = new Node(new Uri("http://localhost:9200")) }; diff --git a/tests/Elastic.Transport.Tests/Test.cs b/tests/Elastic.Transport.Tests/Test.cs index 86ce4a7..8f096d7 100644 --- a/tests/Elastic.Transport.Tests/Test.cs +++ b/tests/Elastic.Transport.Tests/Test.cs @@ -74,16 +74,6 @@ public MyClientConfiguration( public MyClientConfiguration NewSettings(string value) => Assign(value, (c, v) => _setting = v); } - public class MyClientRequestPipeline : DefaultRequestPipeline - { - public MyClientRequestPipeline(MyClientConfiguration configurationValues, - DateTimeProvider dateTimeProvider, MemoryStreamFactory memoryStreamFactory, - RequestParameters requestParameters) - : base(configurationValues, dateTimeProvider, memoryStreamFactory, requestParameters) - { - } - } - public void ExtendingConfiguration() { var clientConfiguration = new MyClientConfiguration()