Skip to content

Commit a6bc8dd

Browse files
committed
Allow more control over transport features. (#8296)
1 parent 1ab3449 commit a6bc8dd

File tree

128 files changed

+4469
-114
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

128 files changed

+4469
-114
lines changed

src/StrawberryShake/Client/src/Core/OperationExecutor.cs

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,15 @@ public OperationExecutor(
3030
IOperationStore operationStore,
3131
ExecutionStrategy strategy = ExecutionStrategy.NetworkOnly)
3232
{
33-
_connection = connection ??
34-
throw new ArgumentNullException(nameof(connection));
35-
_resultBuilder = resultBuilder ??
36-
throw new ArgumentNullException(nameof(resultBuilder));
37-
_resultPatcher = resultPatcher ??
38-
throw new ArgumentNullException(nameof(resultPatcher));
39-
_operationStore = operationStore ??
40-
throw new ArgumentNullException(nameof(operationStore));
33+
ArgumentNullException.ThrowIfNull(connection);
34+
ArgumentNullException.ThrowIfNull(resultBuilder);
35+
ArgumentNullException.ThrowIfNull(resultPatcher);
36+
ArgumentNullException.ThrowIfNull(operationStore);
37+
38+
_connection = connection;
39+
_resultBuilder = resultBuilder;
40+
_resultPatcher = resultPatcher;
41+
_operationStore = operationStore;
4142
_strategy = strategy;
4243
}
4344

@@ -57,10 +58,7 @@ public async Task<IOperationResult<TResult>> ExecuteAsync(
5758
OperationRequest request,
5859
CancellationToken cancellationToken = default)
5960
{
60-
if (request is null)
61-
{
62-
throw new ArgumentNullException(nameof(request));
63-
}
61+
ArgumentNullException.ThrowIfNull(request);
6462

6563
IOperationResult<TResult>? result = null;
6664
var resultBuilder = _resultBuilder();
@@ -109,10 +107,7 @@ public IObservable<IOperationResult<TResult>> Watch(
109107
OperationRequest request,
110108
ExecutionStrategy? strategy = null)
111109
{
112-
if (request is null)
113-
{
114-
throw new ArgumentNullException(nameof(request));
115-
}
110+
ArgumentNullException.ThrowIfNull(request);
116111

117112
return new OperationExecutorObservable(
118113
_connection,

src/StrawberryShake/Client/src/Core/OperationRequest.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ public void Deconstruct(
121121
{
122122
get
123123
{
124-
return _extensions ??= new();
124+
return _extensions ??= [];
125125
}
126126
}
127127

@@ -132,7 +132,7 @@ public void Deconstruct(
132132
{
133133
get
134134
{
135-
return _contextData ??= new();
135+
return _contextData ??= [];
136136
}
137137
}
138138

src/StrawberryShake/Client/src/Transport.Http/HttpConnection.cs

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,78 @@
66

77
namespace StrawberryShake.Transport.Http;
88

9-
public sealed class HttpConnection : IHttpConnection
9+
public class HttpConnection : IHttpConnection
1010
{
11-
private readonly Func<HttpClient> _createClient;
11+
public const string RequestUri = "StrawberryShake.Transport.Http.HttpConnection.RequestUri";
12+
public const string HttpClient = "StrawberryShake.Transport.Http.HttpConnection.HttpClient";
13+
14+
private readonly Func<OperationRequest, object?, HttpClient> _createClient;
15+
private readonly object? _clientFactoryState;
1216

1317
public HttpConnection(Func<HttpClient> createClient)
1418
{
15-
_createClient = createClient ?? throw new ArgumentNullException(nameof(createClient));
19+
ArgumentNullException.ThrowIfNull(createClient);
20+
21+
_createClient = static (_, factory) => ((Func<HttpClient>)factory!).Invoke();
22+
_clientFactoryState = createClient;
23+
}
24+
25+
public HttpConnection(Func<OperationRequest, object?, HttpClient> clientFactory, object? clientFactoryState = null)
26+
{
27+
ArgumentNullException.ThrowIfNull(clientFactory);
28+
29+
_createClient = clientFactory;
30+
_clientFactoryState = clientFactoryState;
1631
}
1732

1833
public IAsyncEnumerable<Response<JsonDocument>> ExecuteAsync(OperationRequest request)
19-
=> Create(_createClient, () => MapRequest(request));
34+
{
35+
ArgumentNullException.ThrowIfNull(request);
36+
37+
return Create(
38+
CreateClient(request),
39+
CreateHttpRequest(request),
40+
CreateResponse);
41+
}
42+
43+
protected virtual HttpClient CreateClient(OperationRequest request)
44+
{
45+
var contextData = request.GetContextDataOrNull();
46+
HttpClient? httpClient = null;
47+
Uri? requestUri = null;
48+
49+
if (contextData is not null)
50+
{
51+
if (contextData.TryGetValue(RequestUri, out var value))
52+
{
53+
if (value is string stringValue)
54+
{
55+
requestUri = new Uri(stringValue);
56+
}
57+
else if (value is Uri uriValue)
58+
{
59+
requestUri = uriValue;
60+
}
61+
}
62+
63+
if (contextData.TryGetValue(HttpClient, out value)
64+
&& value is HttpClient httpClientValue)
65+
{
66+
httpClient = httpClientValue;
67+
}
68+
}
69+
70+
httpClient ??= _createClient(request, _clientFactoryState);
2071

21-
private static GraphQLHttpRequest MapRequest(OperationRequest request)
72+
if (requestUri is not null)
73+
{
74+
httpClient.BaseAddress = requestUri;
75+
}
76+
77+
return httpClient;
78+
}
79+
80+
protected virtual GraphQLHttpRequest CreateHttpRequest(OperationRequest request)
2281
{
2382
var (id, name, document, variables, extensions, _, files, strategy) = request;
2483

@@ -46,6 +105,18 @@ private static GraphQLHttpRequest MapRequest(OperationRequest request)
46105
return new GraphQLHttpRequest(operation) { EnableFileUploads = hasFiles, };
47106
}
48107

108+
protected virtual Response<JsonDocument> CreateResponse(
109+
HttpResponseContext responseContext)
110+
{
111+
return new Response<JsonDocument>(
112+
responseContext.Body,
113+
responseContext.Exception,
114+
responseContext.IsPatch,
115+
responseContext.HasNext,
116+
responseContext.Extensions,
117+
responseContext.ContextData);
118+
}
119+
49120
/// <summary>
50121
/// Converts the variables into a dictionary that can be serialized. This is necessary
51122
/// because the variables can contain lists of key value pairs which are not supported
@@ -54,7 +125,7 @@ private static GraphQLHttpRequest MapRequest(OperationRequest request)
54125
/// <remarks>
55126
/// We only convert the variables if necessary to avoid unnecessary allocations.
56127
/// </remarks>
57-
private static IReadOnlyDictionary<string, object?>? MapVariables(
128+
protected static IReadOnlyDictionary<string, object?>? MapVariables(
58129
IReadOnlyDictionary<string, object?> variables)
59130
{
60131
if (variables.Count == 0)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System.Text.Json;
2+
using HotChocolate.Transport.Http;
3+
4+
namespace StrawberryShake.Transport.Http;
5+
6+
public readonly struct HttpResponseContext
7+
{
8+
public HttpResponseContext(
9+
GraphQLHttpResponse response,
10+
JsonDocument? body,
11+
Exception? exception,
12+
bool isPatch = false,
13+
bool hasNext = false,
14+
IReadOnlyDictionary<string, object?>? extensions = null,
15+
IReadOnlyDictionary<string, object?>? contextData = null)
16+
{
17+
Response = response;
18+
Body = body;
19+
Exception = exception;
20+
IsPatch = isPatch;
21+
HasNext = hasNext;
22+
Extensions = extensions;
23+
ContextData = contextData;
24+
}
25+
26+
public GraphQLHttpResponse Response { get; }
27+
public JsonDocument? Body { get; }
28+
public Exception? Exception { get; }
29+
public bool IsPatch { get; }
30+
public bool HasNext { get; }
31+
public IReadOnlyDictionary<string, object?>? Extensions { get; }
32+
public IReadOnlyDictionary<string, object?>? ContextData { get; }
33+
}

src/StrawberryShake/Client/src/Transport.Http/ResponseEnumerable.cs

Lines changed: 22 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,25 @@ namespace StrawberryShake.Transport.Http;
88

99
internal sealed class ResponseEnumerable : IAsyncEnumerable<Response<JsonDocument>>
1010
{
11-
private readonly Func<HttpClient> _createClient;
12-
private readonly Func<GraphQLHttpRequest> _createRequest;
11+
private readonly HttpClient _httpClient;
12+
private readonly GraphQLHttpRequest _httpRequest;
13+
private readonly Func<HttpResponseContext, Response<JsonDocument>> _createResponse;
1314

1415
private ResponseEnumerable(
15-
Func<HttpClient> createClient,
16-
Func<GraphQLHttpRequest> createRequest)
16+
HttpClient httpClient,
17+
GraphQLHttpRequest httpRequest,
18+
Func<HttpResponseContext, Response<JsonDocument>> createResponse)
1719
{
18-
_createClient = createClient;
19-
_createRequest = createRequest;
20+
_httpClient = httpClient;
21+
_httpRequest = httpRequest;
22+
_createResponse = createResponse;
2023
}
2124

2225
public async IAsyncEnumerator<Response<JsonDocument>> GetAsyncEnumerator(
2326
CancellationToken cancellationToken = default)
2427
{
25-
using var client = new DefaultGraphQLHttpClient(_createClient());
26-
var request = _createRequest();
27-
28-
var result = await client.SendAsync(request, cancellationToken);
28+
using var client = new DefaultGraphQLHttpClient(_httpClient);
29+
var result = await client.SendAsync(_httpRequest, cancellationToken);
2930

3031
Exception? transportError = null;
3132
if (!result.IsSuccessStatusCode)
@@ -66,15 +67,15 @@ public async IAsyncEnumerator<Response<JsonDocument>> GetAsyncEnumerator(
6667
if (hasNext)
6768
{
6869
var parsedResult = ParseResult(enumerator.Current);
69-
70-
yield return new Response<JsonDocument>(parsedResult, transportError);
71-
70+
var responseContext = new HttpResponseContext(result, parsedResult, transportError);
71+
yield return _createResponse(responseContext);
7272
transportError = null;
7373
}
7474
else if (transportError is not null)
7575
{
7676
var errorBody = CreateBodyFromException(transportError);
77-
yield return new Response<JsonDocument>(errorBody, transportError);
77+
var responseContext = new HttpResponseContext(result, errorBody, transportError);
78+
yield return _createResponse(responseContext);
7879
}
7980
}
8081
}
@@ -86,7 +87,7 @@ public async IAsyncEnumerator<Response<JsonDocument>> GetAsyncEnumerator(
8687
return null;
8788
}
8889

89-
var buffer = new HotChocolate.Utilities.ArrayWriter();
90+
using var buffer = new HotChocolate.Utilities.ArrayWriter();
9091
using var writer = new Utf8JsonWriter(buffer);
9192

9293
writer.WriteStartObject();
@@ -123,21 +124,19 @@ private static void WriteProperty(Utf8JsonWriter writer, string propertyName, Js
123124
}
124125

125126
private static Exception CreateError(GraphQLHttpResponse response)
126-
{
127-
return new HttpRequestException(
127+
=> new HttpRequestException(
128128
string.Format(
129129
ResponseEnumerator_HttpNoSuccessStatusCode,
130130
(int)response.StatusCode,
131131
response.ReasonPhrase),
132132
null,
133133
response.StatusCode);
134-
}
135134

136135
internal static JsonDocument CreateBodyFromException(Exception exception)
137136
{
138137
using var bufferWriter = new ArrayWriter();
139-
140138
using var jsonWriter = new Utf8JsonWriter(bufferWriter);
139+
141140
jsonWriter.WriteStartObject();
142141
jsonWriter.WritePropertyName("errors");
143142
jsonWriter.WriteStartArray();
@@ -152,19 +151,8 @@ internal static JsonDocument CreateBodyFromException(Exception exception)
152151
}
153152

154153
public static ResponseEnumerable Create(
155-
Func<HttpClient> createClient,
156-
Func<GraphQLHttpRequest> createRequest)
157-
{
158-
if (createClient is null)
159-
{
160-
throw new ArgumentNullException(nameof(createClient));
161-
}
162-
163-
if (createRequest is null)
164-
{
165-
throw new ArgumentNullException(nameof(createRequest));
166-
}
167-
168-
return new ResponseEnumerable(createClient, createRequest);
169-
}
154+
HttpClient httpClient,
155+
GraphQLHttpRequest httpRequest,
156+
Func<HttpResponseContext, Response<JsonDocument>> createResponse)
157+
=> new(httpClient, httpRequest, createResponse);
170158
}

0 commit comments

Comments
 (0)