Skip to content

Commit a7dee96

Browse files
committed
Add per request configuration and response builder support
Added parameters for local request configuration and custom response builders to the `ITransport` interface methods. Also enhanced nullable references and improved type safety in various classes and methods to accommodate these changes.
1 parent 63e90cb commit a7dee96

File tree

18 files changed

+166
-164
lines changed

18 files changed

+166
-164
lines changed

src/Elastic.Transport.VirtualizedCluster/Components/ExposingPipelineFactory.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
33
// See the LICENSE file in the project root for more information
44

5+
#nullable enable
56
namespace Elastic.Transport.VirtualizedCluster.Components;
67

78
/// <summary>
@@ -14,7 +15,7 @@ public ExposingPipelineFactory(TConfiguration configuration, DateTimeProvider da
1415
DateTimeProvider = dateTimeProvider;
1516
MemoryStreamFactory = TransportConfiguration.DefaultMemoryStreamFactory;
1617
Configuration = configuration;
17-
Pipeline = Create(Configuration, DateTimeProvider, MemoryStreamFactory, new DefaultRequestParameters());
18+
Pipeline = Create(Configuration, DateTimeProvider, MemoryStreamFactory, null);
1819
RequestHandler = new DistributedTransport<TConfiguration>(Configuration, this, DateTimeProvider, MemoryStreamFactory);
1920
}
2021

@@ -26,6 +27,6 @@ public ExposingPipelineFactory(TConfiguration configuration, DateTimeProvider da
2627
public ITransport<TConfiguration> RequestHandler { get; }
2728

2829
public override RequestPipeline Create(TConfiguration configurationValues, DateTimeProvider dateTimeProvider,
29-
MemoryStreamFactory memoryStreamFactory, RequestParameters requestParameters) =>
30-
new DefaultRequestPipeline<TConfiguration>(Configuration, DateTimeProvider, MemoryStreamFactory, requestParameters ?? new DefaultRequestParameters());
30+
MemoryStreamFactory memoryStreamFactory, IRequestConfiguration? requestConfiguration) =>
31+
new DefaultRequestPipeline<TConfiguration>(Configuration, DateTimeProvider, MemoryStreamFactory, requestConfiguration);
3132
}

src/Elastic.Transport.VirtualizedCluster/Components/VirtualizedCluster.cs

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,28 @@ internal VirtualizedCluster(TestableDateTimeProvider dateTimeProvider, Transport
2727
_exposingRequestPipeline = new ExposingPipelineFactory<ITransportConfiguration>(settings, _dateTimeProvider);
2828

2929
_syncCall = (t, r) => t.Request<VirtualResponse>(
30-
HttpMethod.GET, "/",
31-
PostData.Serializable(new {}), new DefaultRequestParameters()
32-
{
33-
RequestConfiguration = r?.Invoke(new RequestConfigurationDescriptor(null))
34-
});
30+
method: HttpMethod.GET,
31+
path: "/",
32+
postData: PostData.Serializable(new { }),
33+
requestParameters: new DefaultRequestParameters(),
34+
openTelemetryData: default,
35+
localConfiguration: r?.Invoke(new RequestConfigurationDescriptor(null)),
36+
responseBuilder: null
37+
);
3538
_asyncCall = async (t, r) =>
3639
{
3740
var res = await t.RequestAsync<VirtualResponse>
3841
(
39-
HttpMethod.GET, "/",
40-
PostData.Serializable(new { }),
41-
new DefaultRequestParameters()
42-
{
43-
RequestConfiguration = r?.Invoke(new RequestConfigurationDescriptor(null))
44-
},
42+
method: HttpMethod.GET,
43+
path: "/",
44+
postData: PostData.Serializable(new { }),
45+
requestParameters: new DefaultRequestParameters(),
46+
openTelemetryData: default,
47+
localConfiguration: r?.Invoke(new RequestConfigurationDescriptor(null)),
48+
responseBuilder: null,
4549
CancellationToken.None
4650
).ConfigureAwait(false);
47-
return (TransportResponse)res;
51+
return res;
4852
};
4953
}
5054

src/Elastic.Transport/Components/Pipeline/DefaultRequestPipeline.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ internal DefaultRequestPipeline(
3636
TConfiguration configurationValues,
3737
DateTimeProvider dateTimeProvider,
3838
MemoryStreamFactory memoryStreamFactory,
39-
RequestParameters requestParameters
39+
IRequestConfiguration? requestConfiguration
4040
)
4141
{
4242
_settings = configurationValues;
@@ -47,7 +47,7 @@ RequestParameters requestParameters
4747
_productRegistration = configurationValues.ProductRegistration;
4848
_responseBuilder = _productRegistration.ResponseBuilder;
4949
_nodePredicate = _settings.NodePredicate ?? _productRegistration.NodePredicate;
50-
RequestConfiguration = requestParameters?.RequestConfiguration;
50+
RequestConfiguration = requestConfiguration;
5151
StartedOn = dateTimeProvider.Now();
5252
}
5353

src/Elastic.Transport/Components/Pipeline/RequestData.cs

Lines changed: 15 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -28,37 +28,29 @@ public sealed class RequestData
2828
public const string OpaqueIdHeader = "X-Opaque-Id";
2929
public const string RunAsSecurityHeader = "es-security-runas-user";
3030

31-
private Uri _requestUri;
32-
private Node _node;
31+
private Uri? _requestUri;
32+
private Node? _node;
3333

3434
public RequestData(
35-
HttpMethod method, string path,
36-
PostData data,
35+
HttpMethod method,
36+
string pathAndQuery,
37+
PostData? data,
3738
ITransportConfiguration global,
38-
RequestParameters local,
39+
IRequestConfiguration? local,
40+
CustomResponseBuilder? customResponseBuilder,
3941
MemoryStreamFactory memoryStreamFactory,
4042
OpenTelemetryData openTelemetryData
4143
)
42-
: this(method, data, global, local?.RequestConfiguration, memoryStreamFactory)
4344
{
44-
_path = path;
4545
OpenTelemetryData = openTelemetryData;
46-
CustomResponseBuilder = local?.CustomResponseBuilder;
47-
PathAndQuery = CreatePathWithQueryStrings(path, ConnectionSettings, local);
48-
}
49-
50-
private RequestData(HttpMethod method,
51-
PostData data,
52-
ITransportConfiguration global,
53-
IRequestConfiguration local,
54-
MemoryStreamFactory memoryStreamFactory
55-
)
56-
{
46+
CustomResponseBuilder = customResponseBuilder;
5747
ConnectionSettings = global;
5848
MemoryStreamFactory = memoryStreamFactory;
5949
Method = method;
6050
PostData = data;
6151

52+
PathAndQuery = pathAndQuery;
53+
6254
if (data != null)
6355
data.DisableDirectStreaming = local?.DisableDirectStreaming ?? global.DisableDirectStreaming;
6456

@@ -119,19 +111,15 @@ MemoryStreamFactory memoryStreamFactory
119111
ResponseHeadersToParse = new HeadersList(local.ResponseHeadersToParse, global.ResponseHeadersToParse);
120112
}
121113
else
122-
{
123114
ResponseHeadersToParse = global.ResponseHeadersToParse;
124-
}
125115
}
126116

127-
private readonly string _path;
128-
129117
public string Accept { get; }
130118
public IReadOnlyCollection<int> AllowedStatusCodes { get; }
131119
public AuthorizationHeader AuthenticationHeader { get; }
132120
public X509CertificateCollection ClientCertificates { get; }
133121
public ITransportConfiguration ConnectionSettings { get; }
134-
public CustomResponseBuilder CustomResponseBuilder { get; }
122+
public CustomResponseBuilder? CustomResponseBuilder { get; }
135123
public bool DisableAutomaticProxyDetection { get; }
136124
public HeadersList ResponseHeadersToParse { get; }
137125
public bool ParseAllHeaders { get; }
@@ -143,7 +131,7 @@ MemoryStreamFactory memoryStreamFactory
143131
public MemoryStreamFactory MemoryStreamFactory { get; }
144132
public HttpMethod Method { get; }
145133

146-
public Node? Node
134+
public Node Node
147135
{
148136
get => _node;
149137
set
@@ -159,13 +147,13 @@ public Node? Node
159147
public string PathAndQuery { get; }
160148
public TimeSpan PingTimeout { get; }
161149
public bool Pipelined { get; }
162-
public PostData PostData { get; }
150+
public PostData? PostData { get; }
163151
public string ProxyAddress { get; }
164152
public string ProxyPassword { get; }
165153
public string ProxyUsername { get; }
166154
public string ContentType { get; }
167155
public TimeSpan RequestTimeout { get; }
168-
public string RunAs { get; }
156+
public string? RunAs { get; }
169157
public IReadOnlyCollection<int> SkipDeserializationForStatusCodes { get; }
170158
public bool ThrowExceptions { get; }
171159
public UserAgent UserAgent { get; }
@@ -197,35 +185,7 @@ public Uri Uri
197185

198186
internal OpenTelemetryData OpenTelemetryData { get; }
199187

200-
public override string ToString() => $"{Method.GetStringValue()} {_path}";
201-
202-
// TODO This feels like its in the wrong place
203-
private string CreatePathWithQueryStrings(string path, ITransportConfiguration global, RequestParameters request)
204-
{
205-
path ??= string.Empty;
206-
if (path.Contains("?"))
207-
throw new ArgumentException($"{nameof(path)} can not contain querystring parameters and needs to be already escaped");
208-
209-
var g = global.QueryStringParameters;
210-
var l = request?.QueryString;
211-
212-
if ((g == null || g.Count == 0) && (l == null || l.Count == 0)) return path;
213-
214-
//create a copy of the global query string collection if needed.
215-
var nv = g == null ? new NameValueCollection() : new NameValueCollection(g);
216-
217-
//set all querystring pairs from local `l` on the querystring collection
218-
var formatter = ConnectionSettings.UrlFormatter;
219-
nv.UpdateFromDictionary(l, formatter);
220-
221-
//if nv has no keys simply return path as provided
222-
if (!nv.HasKeys()) return path;
223-
224-
//create string for query string collection where key and value are escaped properly.
225-
var queryString = ToQueryString(nv);
226-
path += queryString;
227-
return path;
228-
}
188+
public override string ToString() => $"{Method.GetStringValue()} {PathAndQuery}";
229189

230190
internal bool ValidateResponseContentType(string responseMimeType)
231191
{
@@ -248,6 +208,5 @@ internal bool ValidateResponseContentType(string responseMimeType)
248208
|| trimmedAccept.Contains("application/vnd.elasticsearch+json") && trimmedResponseMimeType.StartsWith(DefaultMimeType, StringComparison.OrdinalIgnoreCase);
249209
}
250210

251-
public static string ToQueryString(NameValueCollection collection) => collection.ToQueryString();
252211
#pragma warning restore 1591
253212
}

src/Elastic.Transport/Components/Providers/DefaultRequestPipelineFactory.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ internal sealed class DefaultRequestPipelineFactory<TConfiguration> : RequestPip
1414
/// returns instances of <see cref="DefaultRequestPipeline{TConfiguration}"/>
1515
/// </summary>
1616
public override RequestPipeline Create(TConfiguration configurationValues, DateTimeProvider dateTimeProvider,
17-
MemoryStreamFactory memoryStreamFactory, RequestParameters requestParameters) =>
18-
new DefaultRequestPipeline<TConfiguration>(configurationValues, dateTimeProvider, memoryStreamFactory, requestParameters);
17+
MemoryStreamFactory memoryStreamFactory, IRequestConfiguration? requestConfiguration) =>
18+
new DefaultRequestPipeline<TConfiguration>(configurationValues, dateTimeProvider, memoryStreamFactory, requestConfiguration);
1919
}

src/Elastic.Transport/Components/Providers/RequestPipelineFactory.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ internal RequestPipelineFactory() { }
1212

1313
/// <summary> Create an instance of <see cref="RequestPipeline"/> </summary>
1414
public abstract RequestPipeline Create(TConfiguration configuration, DateTimeProvider dateTimeProvider,
15-
MemoryStreamFactory memoryStreamFactory, RequestParameters requestParameters);
15+
MemoryStreamFactory memoryStreamFactory, IRequestConfiguration? requestParameters);
1616
}

src/Elastic.Transport/Configuration/ITransportConfiguration.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public interface ITransportConfiguration : IDisposable
9393
/// <summary>
9494
/// Try to send these headers for every request
9595
/// </summary>
96-
NameValueCollection Headers { get; }
96+
NameValueCollection? Headers { get; }
9797

9898
/// <summary>
9999
/// Whether HTTP pipelining is enabled. The default is <c>true</c>
@@ -143,18 +143,18 @@ public interface ITransportConfiguration : IDisposable
143143
/// When using static or single node connection pooling it is assumed the list of node you instantiate the client with should be taken
144144
/// verbatim.
145145
/// </summary>
146-
Func<Node, bool> NodePredicate { get; }
146+
Func<Node, bool>? NodePredicate { get; }
147147

148148
/// <summary>
149149
/// Allows you to register a callback every time a an API call is returned
150150
/// </summary>
151-
Action<ApiCallDetails> OnRequestCompleted { get; }
151+
Action<ApiCallDetails>? OnRequestCompleted { get; }
152152

153153
/// <summary>
154154
/// An action to run when the <see cref="RequestData" /> for a request has been
155155
/// created.
156156
/// </summary>
157-
Action<RequestData> OnRequestDataCreated { get; }
157+
Action<RequestData>? OnRequestDataCreated { get; }
158158

159159
/// <summary>
160160
/// When enabled, all headers from the HTTP response will be included in the <see cref="ApiCallDetails"/>.
@@ -184,7 +184,7 @@ public interface ITransportConfiguration : IDisposable
184184
/// <summary>
185185
/// Append these query string parameters automatically to every request
186186
/// </summary>
187-
NameValueCollection QueryStringParameters { get; }
187+
NameValueCollection? QueryStringParameters { get; }
188188

189189
/// <summary>The serializer to use to serialize requests and deserialize responses</summary>
190190
Serializer RequestResponseSerializer { get; }

src/Elastic.Transport/Configuration/RequestConfiguration.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ public interface IRequestConfiguration
131131
/// <summary>
132132
/// Holds additional meta data about the request.
133133
/// </summary>
134-
RequestMetaData RequestMetaData { get; set; }
134+
RequestMetaData? RequestMetaData { get; set; }
135135
}
136136

137137
/// <inheritdoc cref="IRequestConfiguration"/>
@@ -192,7 +192,7 @@ public class RequestConfiguration : IRequestConfiguration
192192
public class RequestConfigurationDescriptor : IRequestConfiguration
193193
{
194194
/// <inheritdoc cref="IRequestConfiguration"/>
195-
public RequestConfigurationDescriptor(IRequestConfiguration config)
195+
public RequestConfigurationDescriptor(IRequestConfiguration? config)
196196
{
197197
Self.RequestTimeout = config?.RequestTimeout;
198198
Self.PingTimeout = config?.PingTimeout;

src/Elastic.Transport/Diagnostics/OpenTelemetry/OpenTelemetryData.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ public readonly struct OpenTelemetryData
1414
/// <summary>
1515
/// The name to use for spans relating to a request.
1616
/// </summary>
17-
public readonly string? SpanName { get; init; }
17+
public string? SpanName { get; init; }
1818

1919
/// <summary>
2020
/// Additional span attributes for transport spans relating to a request.
2121
/// </summary>
22-
public readonly Dictionary<string, object>? SpanAttributes { get; init; }
22+
public Dictionary<string, object>? SpanAttributes { get; init; }
2323
}

src/Elastic.Transport/DistributedTransport.cs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,13 @@ public TResponse Request<TResponse>(
9696
string path,
9797
PostData? data,
9898
RequestParameters? requestParameters,
99-
in OpenTelemetryData openTelemetryData
99+
in OpenTelemetryData openTelemetryData,
100+
IRequestConfiguration? localConfiguration,
101+
CustomResponseBuilder? responseBuilder
100102
)
101103
where TResponse : TransportResponse, new() =>
102-
RequestCoreAsync<TResponse>(false, method, path, data, requestParameters, openTelemetryData).EnsureCompleted();
104+
RequestCoreAsync<TResponse>(isAsync: false,
105+
method, path, data, requestParameters, openTelemetryData, localConfiguration, responseBuilder).EnsureCompleted();
103106

104107
/// <inheritdoc cref="ITransport.RequestAsync{TResponse}"/>
105108
public Task<TResponse> RequestAsync<TResponse>(
@@ -108,10 +111,13 @@ public Task<TResponse> RequestAsync<TResponse>(
108111
PostData? data,
109112
RequestParameters? requestParameters,
110113
in OpenTelemetryData openTelemetryData,
114+
IRequestConfiguration? localConfiguration,
115+
CustomResponseBuilder? responseBuilder,
111116
CancellationToken cancellationToken = default
112117
)
113118
where TResponse : TransportResponse, new() =>
114-
RequestCoreAsync<TResponse>(true, method, path, data, requestParameters, openTelemetryData, cancellationToken).AsTask();
119+
RequestCoreAsync<TResponse>(isAsync: true,
120+
method, path, data, requestParameters, openTelemetryData, localConfiguration, responseBuilder, cancellationToken).AsTask();
115121

116122
private async ValueTask<TResponse> RequestCoreAsync<TResponse>(
117123
bool isAsync,
@@ -120,6 +126,8 @@ private async ValueTask<TResponse> RequestCoreAsync<TResponse>(
120126
PostData? data,
121127
RequestParameters? requestParameters,
122128
OpenTelemetryData openTelemetryData,
129+
IRequestConfiguration? localRequestConfiguration,
130+
CustomResponseBuilder? customResponseBuilder,
123131
CancellationToken cancellationToken = default
124132
)
125133
where TResponse : TransportResponse, new()
@@ -132,15 +140,15 @@ private async ValueTask<TResponse> RequestCoreAsync<TResponse>(
132140

133141
try
134142
{
135-
using var pipeline =
136-
PipelineProvider.Create(Configuration, DateTimeProvider, MemoryStreamFactory, requestParameters);
143+
using var pipeline = PipelineProvider.Create(Configuration, DateTimeProvider, MemoryStreamFactory, localRequestConfiguration);
137144

138145
if (isAsync)
139146
await pipeline.FirstPoolUsageAsync(Configuration.BootstrapLock, cancellationToken).ConfigureAwait(false);
140147
else
141148
pipeline.FirstPoolUsage(Configuration.BootstrapLock);
142149

143-
var requestData = new RequestData(method, path, data, Configuration, requestParameters, MemoryStreamFactory, openTelemetryData);
150+
var pathAndQuery = requestParameters?.CreatePathWithQueryStrings(path, Configuration) ?? path;
151+
var requestData = new RequestData(method, pathAndQuery, data, Configuration, localRequestConfiguration, customResponseBuilder, MemoryStreamFactory, openTelemetryData);
144152
Configuration.OnRequestDataCreated?.Invoke(requestData);
145153
TResponse response = null;
146154

0 commit comments

Comments
 (0)