Skip to content

Commit 4c76a56

Browse files
committed
Merge branch 'hotfix/improve-response-capture-semantics'
2 parents 1838162 + 2c7622c commit 4c76a56

File tree

1 file changed

+93
-87
lines changed

1 file changed

+93
-87
lines changed

libs/TrybeSDK/ApiClient.cs

Lines changed: 93 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
// For a copy, see <https://opensource.org/licenses/MIT>.
33

44
using System.Net;
5+
using System.Net.Http;
56
using System.Net.Http.Headers;
67
using System.Net.Http.Json;
8+
using System.Text;
79
using System.Text.Json;
810
using System.Text.Json.Serialization;
911

@@ -33,30 +35,23 @@ protected internal async Task<TrybeResponse> SendAsync(
3335
CancellationToken cancellationToken = default)
3436
{
3537
Ensure.IsNotNull(request, nameof(request));
36-
var httpReq = CreateHttpRequest(request);
38+
using var httpReq = CreateHttpRequest(request);
3739
HttpResponseMessage? httpResp = null;
3840

3941
try
4042
{
4143
httpResp = await _http.SendAsync(httpReq, cancellationToken)
4244
.ConfigureAwait(false);
4345

44-
var transformedResponse = await TransformResponse(
46+
var (transformedResponse, capturedResponseContent) = await TransformResponse(
4547
httpReq.Method,
4648
httpReq.RequestUri,
4749
httpResp)
4850
.ConfigureAwait(false);
4951

50-
if (_settings.CaptureRequestContent && httpReq.Content is not null)
51-
{
52-
transformedResponse.RequestContent = await httpReq.Content.ReadAsStringAsync()
53-
.ConfigureAwait(false);
54-
}
55-
56-
if (_settings.CaptureResponseContent && httpResp.Content is not null)
52+
if (_settings.CaptureResponseContent || !httpResp.IsSuccessStatusCode)
5753
{
58-
transformedResponse.ResponseContent = await httpResp.Content.ReadAsStringAsync()
59-
.ConfigureAwait(false); ;
54+
transformedResponse.ResponseContent = capturedResponseContent;
6055
}
6156

6257
return transformedResponse;
@@ -92,30 +87,28 @@ protected internal async Task<TrybeResponse> SendAsync<TRequest>(
9287
where TRequest : notnull
9388
{
9489
Ensure.IsNotNull(request, nameof(request));
95-
var httpReq = CreateHttpRequest(request);
90+
var (httpReq, capturedRequestContent) = CreateHttpRequest(request);
9691
HttpResponseMessage? httpResp = null;
9792

9893
try
9994
{
10095
httpResp = await _http.SendAsync(httpReq, cancellationToken)
10196
.ConfigureAwait(false);
10297

103-
var transformedResponse = await TransformResponse(
98+
var (transformedResponse, capturedResponseContent) = await TransformResponse(
10499
httpReq.Method,
105100
httpReq.RequestUri,
106101
httpResp)
107102
.ConfigureAwait(false); ;
108103

109-
if (_settings.CaptureRequestContent && httpReq.Content is not null)
104+
if (_settings.CaptureRequestContent)
110105
{
111-
transformedResponse.RequestContent = await httpReq.Content.ReadAsStringAsync()
112-
.ConfigureAwait(false);
106+
transformedResponse.RequestContent = capturedRequestContent;
113107
}
114108

115-
if (_settings.CaptureResponseContent && httpResp.Content is not null)
109+
if (_settings.CaptureResponseContent || !httpResp.IsSuccessStatusCode)
116110
{
117-
transformedResponse.ResponseContent = await httpResp.Content.ReadAsStringAsync()
118-
.ConfigureAwait(false);
111+
transformedResponse.ResponseContent = capturedResponseContent;
119112
}
120113

121114
return transformedResponse;
@@ -163,13 +156,7 @@ protected internal async Task<TrybeResponse<TResponse>> FetchAsync<TResponse>(
163156
httpReq.Method,
164157
httpReq.RequestUri,
165158
httpResp)
166-
.ConfigureAwait(false); ;
167-
168-
if ((_settings.CaptureRequestContent || !httpResp.IsSuccessStatusCode) && httpReq.Content is not null)
169-
{
170-
transformedResponse.RequestContent = await httpReq.Content.ReadAsStringAsync()
171-
.ConfigureAwait(false); ;
172-
}
159+
.ConfigureAwait(false);
173160

174161
if (_settings.CaptureResponseContent || !httpResp.IsSuccessStatusCode)
175162
{
@@ -201,6 +188,18 @@ protected internal async Task<TrybeResponse<TResponse>> FetchAsync<TResponse>(
201188

202189
return response;
203190
}
191+
finally
192+
{
193+
if (httpReq is not null)
194+
{
195+
httpReq.Dispose();
196+
}
197+
198+
if (httpResp is not null)
199+
{
200+
httpResp.Dispose();
201+
}
202+
}
204203
}
205204

206205
protected internal async Task<TrybeResponse<TResponse>> FetchAsync<TRequest, TResponse>(
@@ -210,7 +209,7 @@ protected internal async Task<TrybeResponse<TResponse>> FetchAsync<TRequest, TRe
210209
where TResponse : class
211210
{
212211
Ensure.IsNotNull(request, nameof(request));
213-
var httpReq = CreateHttpRequest(request);
212+
var (httpReq, capturedRequestContent) = CreateHttpRequest(request);
214213
HttpResponseMessage? httpResp = null;
215214

216215
try
@@ -224,10 +223,9 @@ protected internal async Task<TrybeResponse<TResponse>> FetchAsync<TRequest, TRe
224223
httpResp)
225224
.ConfigureAwait(false); ;
226225

227-
if ((_settings.CaptureRequestContent || !httpResp.IsSuccessStatusCode) && httpReq.Content is not null)
226+
if (_settings.CaptureRequestContent)
228227
{
229-
transformedResponse.RequestContent = await httpReq.Content.ReadAsStringAsync()
230-
.ConfigureAwait(false); ;
228+
transformedResponse.RequestContent = capturedRequestContent;
231229
}
232230

233231
if (_settings.CaptureResponseContent || !httpResp.IsSuccessStatusCode)
@@ -260,6 +258,18 @@ protected internal async Task<TrybeResponse<TResponse>> FetchAsync<TRequest, TRe
260258

261259
return response;
262260
}
261+
finally
262+
{
263+
if (httpReq is not null)
264+
{
265+
httpReq.Dispose();
266+
}
267+
268+
if (httpResp is not null)
269+
{
270+
httpResp.Dispose();
271+
}
272+
}
263273
}
264274
#endregion
265275

@@ -281,33 +291,54 @@ protected internal HttpRequestMessage CreateHttpRequest(
281291
return message;
282292
}
283293

284-
protected internal HttpRequestMessage CreateHttpRequest<TRequest>(
294+
protected internal (HttpRequestMessage, string?) CreateHttpRequest<TRequest>(
285295
TrybeRequest<TRequest> request)
286296
where TRequest : notnull
287297
{
288298
var message = CreateHttpRequest((TrybeRequest)request);
299+
string? capturedContent = null;
289300

290-
message.Content = JsonContent.Create(
291-
inputValue: request.Data, options: _serializerOptions);
301+
if (_settings.CaptureRequestContent)
302+
{
303+
capturedContent = JsonSerializer.Serialize(request.Data, _serializerOptions);
304+
message.Content = new StringContent(capturedContent, Encoding.UTF8, "application/json");
305+
}
306+
else
307+
{
308+
message.Content = JsonContent.Create(
309+
inputValue: request.Data, options: _serializerOptions);
310+
}
292311

293-
return message;
312+
return (message, capturedContent);
294313
}
295314
#endregion
296315

297316
#region Postprocessing
298-
protected internal async Task<TrybeResponse> TransformResponse(
317+
protected internal async Task<(TrybeResponse, string?)> TransformResponse(
299318
HttpMethod method,
300319
Uri uri,
301320
HttpResponseMessage response,
302321
CancellationToken cancellationToken = default)
303322
{
304-
async Task<Error> GetTrybeError()
323+
var rateLimiting = GetRateLimiting(response);
324+
325+
if (response.IsSuccessStatusCode)
326+
{
327+
return (new TrybeResponse(
328+
method,
329+
uri,
330+
response.IsSuccessStatusCode,
331+
response.StatusCode,
332+
rateLimiting: rateLimiting), null);
333+
}
334+
else
305335
{
306336
Error error;
307-
if (response.Content is not null)
308-
{
309-
var result = await response.Content.ReadFromJsonAsync<ErrorContainer>(cancellationToken)
337+
string? stringContent = await response.Content.ReadAsStringAsync()
310338
.ConfigureAwait(false);
339+
if (stringContent is { Length: >0 })
340+
{
341+
var result = JsonSerializer.Deserialize<ErrorContainer>(stringContent, _deserializerOptions);
311342

312343
if (result?.Message is not { Length: > 0 })
313344
{
@@ -323,29 +354,14 @@ async Task<Error> GetTrybeError()
323354
error = new Error(Resources.ApiClient_NoErrorMessage);
324355
}
325356

326-
return error;
327-
}
328-
329-
if (response.IsSuccessStatusCode)
330-
{
331-
return new TrybeResponse(
332-
method,
333-
uri,
334-
response.IsSuccessStatusCode,
335-
response.StatusCode);
336-
}
337-
else
338-
{
339-
Error? error = await GetTrybeError()
340-
.ConfigureAwait(false);
341-
342-
return new TrybeResponse(
357+
return (new TrybeResponse(
343358
method,
344359
uri,
345360
response.IsSuccessStatusCode,
346361
response.StatusCode,
362+
rateLimiting: rateLimiting,
347363
error: error
348-
);
364+
), stringContent);
349365
}
350366
}
351367

@@ -356,32 +372,6 @@ async Task<Error> GetTrybeError()
356372
CancellationToken cancellationToken = default)
357373
where TResponse : class
358374
{
359-
async Task<Error> GetTrybeError()
360-
{
361-
Error error;
362-
if (response.Content is not null)
363-
{
364-
var result = await response.Content.ReadFromJsonAsync<ErrorContainer>(
365-
_deserializerOptions, cancellationToken)
366-
.ConfigureAwait(false);
367-
368-
if (result?.Message is not { Length: > 0 })
369-
{
370-
error = new(Resources.ApiClient_UnknownResponse, result?.Errors);
371-
}
372-
else
373-
{
374-
error = new(result.Message, result.Errors);
375-
}
376-
}
377-
else
378-
{
379-
error = new Error(Resources.ApiClient_NoErrorMessage);
380-
}
381-
382-
return error;
383-
}
384-
385375
var rateLimiting = GetRateLimiting(response);
386376

387377
Stream? content = null;
@@ -404,11 +394,11 @@ async Task<Error> GetTrybeError()
404394
{
405395
DataContainer<TResponse>? data = default;
406396
Meta? meta = default;
407-
if (content is not null)
397+
if (content is not null || stringContent is { Length: >0 })
408398
{
409399
data = stringContent is { Length: > 0 }
410400
? JsonSerializer.Deserialize<DataContainer<TResponse>>(stringContent, _deserializerOptions)
411-
: await JsonSerializer.DeserializeAsync<DataContainer<TResponse>>(content, _deserializerOptions).ConfigureAwait(false);
401+
: await JsonSerializer.DeserializeAsync<DataContainer<TResponse>>(content!, _deserializerOptions).ConfigureAwait(false);
412402

413403
if (data?.Meta is not null)
414404
{
@@ -434,8 +424,24 @@ async Task<Error> GetTrybeError()
434424
}
435425
else
436426
{
437-
Error? error = await GetTrybeError()
438-
.ConfigureAwait(false);
427+
Error error;
428+
if (stringContent is not null)
429+
{
430+
var result = JsonSerializer.Deserialize<ErrorContainer>(stringContent, _deserializerOptions);
431+
432+
if (result?.Message is not { Length: > 0 })
433+
{
434+
error = new(Resources.ApiClient_UnknownResponse, result?.Errors);
435+
}
436+
else
437+
{
438+
error = new(result.Message, result.Errors);
439+
}
440+
}
441+
else
442+
{
443+
error = new Error(Resources.ApiClient_NoErrorMessage);
444+
}
439445

440446
return (new TrybeResponse<TResponse>(
441447
method,

0 commit comments

Comments
 (0)