From 8976620205b0edb99b60efb1cbf8d2b64761d2cb Mon Sep 17 00:00:00 2001 From: TekH Date: Wed, 20 May 2026 11:24:01 +0200 Subject: [PATCH] Refactor HTTP response handling and add JSON support Enhanced `HandleResponseAsync` to return response body as a string and log successful responses. Introduced `JsonOptions` for consistent JSON serialization/deserialization. Added a generic `Deserialize` method for deserializing JSON responses. Updated method signatures to support nullable reference types. --- src/ReC.Client/ReCClientHelpers.cs | 68 +++++++++++++++++++----------- 1 file changed, 44 insertions(+), 24 deletions(-) diff --git a/src/ReC.Client/ReCClientHelpers.cs b/src/ReC.Client/ReCClientHelpers.cs index 0bf1ab3..a4f1706 100644 --- a/src/ReC.Client/ReCClientHelpers.cs +++ b/src/ReC.Client/ReCClientHelpers.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Net.Http; using System.Net.Http.Json; using System.Reflection; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -73,18 +74,22 @@ namespace ReC.Client } /// - /// Logs the outcome of an HTTP response. Throws a when the - /// response indicates a non-success status code; otherwise (optionally) writes an informational - /// log entry containing the request and response details. + /// JSON serializer options used when deserializing API responses. + /// + public static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true, + }; + + /// + /// Reads the response body and logs the outcome. Throws a when + /// the response indicates a non-success status code. /// - /// The HTTP response to inspect. - /// An optional logger used to record the outcome. May be . - /// When , successful responses are not logged. - /// A token to cancel the operation. #if NETFRAMEWORK - public static async Task HandleResponseAsync(HttpResponseMessage response, ILogger logger = null, bool logSuccess = true, CancellationToken cancel = default) + public static async Task HandleResponseAsync(HttpResponseMessage response, ILogger logger = null, bool logSuccess = true, CancellationToken cancel = default) #else - public static async Task HandleResponseAsync(HttpResponseMessage response, ILogger? logger = null, bool logSuccess = true, CancellationToken cancel = default) + public static async Task HandleResponseAsync(HttpResponseMessage response, ILogger? logger = null, bool logSuccess = true, CancellationToken cancel = default) #endif { var request = response.RequestMessage; @@ -92,20 +97,6 @@ namespace ReC.Client var uri = request?.RequestUri; var statusCode = (int)response.StatusCode; - if (response.IsSuccessStatusCode) - { - if (logSuccess) - { - logger?.LogInformation( - "ReC API request succeeded. {Method} {Uri} -> {StatusCode} ({ReasonPhrase})", - method, - uri, - statusCode, - response.ReasonPhrase); - } - return; - } - #if NETFRAMEWORK string body = null; #else @@ -123,15 +114,44 @@ namespace ReC.Client } catch { - // Swallow body read failures; status info is still propagated. } } + if (response.IsSuccessStatusCode) + { + if (logSuccess) + { + logger?.LogInformation( + "ReC API request succeeded. {Method} {Uri} -> {StatusCode} ({ReasonPhrase})", + method, + uri, + statusCode, + response.ReasonPhrase); + } + + return body; + } + var message = $"ReC API request failed with status {statusCode} ({response.ReasonPhrase}). " + $"{method} {uri}" + (string.IsNullOrWhiteSpace(body) ? string.Empty : $": {body}"); throw new ReCApiException(message, response.StatusCode, response.ReasonPhrase, body, method, uri); } + + /// + /// Deserializes a JSON body string into . + /// +#if NETFRAMEWORK + public static T Deserialize(string body) +#else + public static T? Deserialize(string? body) +#endif + { + if (string.IsNullOrWhiteSpace(body)) + return default; + + return JsonSerializer.Deserialize(body, JsonOptions); + } } }