From ea373bfae480747f4083604e2ae21a0eaeb847a7 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Thu, 21 May 2026 20:36:45 +0100 Subject: [PATCH 1/2] fix work package issues --- ClientProxyBase/ClientProxyBase.cs | 1300 +++++++++++++++------------- 1 file changed, 693 insertions(+), 607 deletions(-) diff --git a/ClientProxyBase/ClientProxyBase.cs b/ClientProxyBase/ClientProxyBase.cs index 1dbc8e08..750023d6 100644 --- a/ClientProxyBase/ClientProxyBase.cs +++ b/ClientProxyBase/ClientProxyBase.cs @@ -9,704 +9,790 @@ using System.Threading; using System.Threading.Tasks; -namespace ClientProxyBase -{ - public abstract class ClientProxyBase - { - private readonly HttpClient HttpClient; - private readonly Func Serialise; - private readonly Func Deserialise; +namespace ClientProxyBase; + +public abstract class ClientProxyBase { + private readonly HttpClient HttpClient; + private readonly Func Serialise; + private readonly Func Deserialise; + + public ClientProxyBase(HttpClient httpClient, + Func serialise, + Func deserialise) { + HttpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); + Serialise = serialise ?? throw new ArgumentNullException(nameof(serialise)); + Deserialise = deserialise ?? throw new ArgumentNullException(nameof(deserialise)); + } - public ClientProxyBase( - HttpClient httpClient, - Func serialise, - Func deserialise) - { - HttpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); - Serialise = serialise ?? throw new ArgumentNullException(nameof(serialise)); - Deserialise = deserialise ?? throw new ArgumentNullException(nameof(deserialise)); + public Task> Get(string requestUri, + CancellationToken cancellationToken = default) => + SendForResponse(HttpMethod.Get, requestUri, body: null, accessToken: null, additionalHeaders: null, cancellationToken); + + public Task> Get(string requestUri, + List<(string header, string value)> additionalHeaders, + CancellationToken cancellationToken = default) => + SendForResponse(HttpMethod.Get, requestUri, body: null, accessToken: null, additionalHeaders, cancellationToken); + + public Task> Get(string requestUri, + string accessToken, + CancellationToken cancellationToken = default) => + SendForResponse(HttpMethod.Get, requestUri, body: null, accessToken, additionalHeaders: null, cancellationToken); + + public Task> Get(string requestUri, + string accessToken, + List<(string header, string value)> additionalHeaders, + CancellationToken cancellationToken = default) => + SendForResponse(HttpMethod.Get, requestUri, body: null, accessToken, additionalHeaders, cancellationToken); + + public Task Delete(string requestUri, + CancellationToken cancellationToken = default) => + SendWithoutResponse(HttpMethod.Delete, requestUri, body: null, accessToken: null, additionalHeaders: null, cancellationToken); + + public Task Delete(string requestUri, + List<(string header, string value)> additionalHeaders, + CancellationToken cancellationToken = default) => + SendWithoutResponse(HttpMethod.Delete, requestUri, body: null, accessToken: null, additionalHeaders, cancellationToken); + + public Task Delete(string requestUri, + string accessToken, + CancellationToken cancellationToken = default) => + SendWithoutResponse(HttpMethod.Delete, requestUri, body: null, accessToken, additionalHeaders: null, cancellationToken); + + public Task Delete(string requestUri, + string accessToken, + List<(string header, string value)> additionalHeaders, + CancellationToken cancellationToken = default) => + SendWithoutResponse(HttpMethod.Delete, requestUri, body: null, accessToken, additionalHeaders, cancellationToken); + + public Task Post(string requestUri, + TRequest request, + CancellationToken cancellationToken = default) => + SendWithoutResponse(HttpMethod.Post, requestUri, request, accessToken: null, additionalHeaders: null, cancellationToken); + + public Task Post(string requestUri, + TRequest request, + List<(string header, string value)> additionalHeaders, + CancellationToken cancellationToken = default) => + SendWithoutResponse(HttpMethod.Post, requestUri, request, accessToken: null, additionalHeaders, cancellationToken); + + public Task Post(string requestUri, + TRequest request, + string accessToken, + CancellationToken cancellationToken = default) => + SendWithoutResponse(HttpMethod.Post, requestUri, request, accessToken, additionalHeaders: null, cancellationToken); + + public Task Post(string requestUri, + TRequest request, + string accessToken, + List<(string header, string value)> additionalHeaders, + CancellationToken cancellationToken = default) => + SendWithoutResponse(HttpMethod.Post, requestUri, request, accessToken, additionalHeaders, cancellationToken); + + public Task Post(string requestUri, + HttpContent content, + CancellationToken cancellationToken = default) => + SendWithoutResponse(HttpMethod.Post, requestUri, content, accessToken: null, additionalHeaders: null, cancellationToken); + + public Task Post(string requestUri, + HttpContent content, + List<(string header, string value)> additionalHeaders, + CancellationToken cancellationToken = default) => + SendWithoutResponse(HttpMethod.Post, requestUri, content, accessToken: null, additionalHeaders, cancellationToken); + + public Task Post(string requestUri, + HttpContent content, + string accessToken, + CancellationToken cancellationToken = default) => + SendWithoutResponse(HttpMethod.Post, requestUri, content, accessToken, additionalHeaders: null, cancellationToken); + + public Task Post(string requestUri, + HttpContent content, + string accessToken, + List<(string header, string value)> additionalHeaders, + CancellationToken cancellationToken = default) => + SendWithoutResponse(HttpMethod.Post, requestUri, content, accessToken, additionalHeaders, cancellationToken); + + public Task Post(string requestUri, + byte[] fileData, + string fileName, + List<(string field, string value)> formFields, + string accessToken, + CancellationToken cancellationToken = default) => + SendMultipartWithoutResponse(requestUri, fileData, fileName, formFields, accessToken, additionalHeaders: null, cancellationToken); + + public Task Post(string requestUri, + byte[] fileData, + string fileName, + List<(string field, string value)> formFields, + string accessToken, + List<(string header, string value)> additionalHeaders, + CancellationToken cancellationToken = default) => + SendMultipartWithoutResponse(requestUri, fileData, fileName, formFields, accessToken, additionalHeaders, cancellationToken); + + public Task> Post(string requestUri, + TRequest request, + CancellationToken cancellationToken = default) => + SendForResponse(HttpMethod.Post, requestUri, request, accessToken: null, additionalHeaders: null, cancellationToken); + + public Task> Post(string requestUri, + TRequest request, + List<(string header, string value)> additionalHeaders, + CancellationToken cancellationToken = default) => + SendForResponse(HttpMethod.Post, requestUri, request, accessToken: null, additionalHeaders, cancellationToken); + + public Task> Post(string requestUri, + TRequest request, + string accessToken, + CancellationToken cancellationToken = default) => + SendForResponse(HttpMethod.Post, requestUri, request, accessToken, additionalHeaders: null, cancellationToken); + + public Task> Post(string requestUri, + TRequest request, + string accessToken, + List<(string header, string value)> additionalHeaders, + CancellationToken cancellationToken = default) => + SendForResponse(HttpMethod.Post, requestUri, request, accessToken, additionalHeaders, cancellationToken); + + public Task> Post(string requestUri, + HttpContent content, + CancellationToken cancellationToken = default) => + SendForResponse(HttpMethod.Post, requestUri, content, accessToken: null, additionalHeaders: null, cancellationToken); + + public Task> Post(string requestUri, + HttpContent content, + List<(string header, string value)> additionalHeaders, + CancellationToken cancellationToken = default) => + SendForResponse(HttpMethod.Post, requestUri, content, accessToken: null, additionalHeaders, cancellationToken); + + public Task> Post(string requestUri, + HttpContent content, + string accessToken, + CancellationToken cancellationToken = default) => + SendForResponse(HttpMethod.Post, requestUri, content, accessToken, additionalHeaders: null, cancellationToken); + + public Task> Post(string requestUri, + HttpContent content, + string accessToken, + List<(string header, string value)> additionalHeaders, + CancellationToken cancellationToken = default) => + SendForResponse(HttpMethod.Post, requestUri, content, accessToken, additionalHeaders, cancellationToken); + + public Task Put(string requestUri, + TRequest request, + CancellationToken cancellationToken = default) => + SendWithoutResponse(HttpMethod.Put, requestUri, request, accessToken: null, additionalHeaders: null, cancellationToken); + + public Task Put(string requestUri, + TRequest request, + List<(string header, string value)> additionalHeaders, + CancellationToken cancellationToken = default) => + SendWithoutResponse(HttpMethod.Put, requestUri, request, accessToken: null, additionalHeaders, cancellationToken); + + public Task Put(string requestUri, + TRequest request, + string accessToken, + CancellationToken cancellationToken = default) => + SendWithoutResponse(HttpMethod.Put, requestUri, request, accessToken, additionalHeaders: null, cancellationToken); + + public Task Put(string requestUri, + TRequest request, + string accessToken, + List<(string header, string value)> additionalHeaders, + CancellationToken cancellationToken = default) => + SendWithoutResponse(HttpMethod.Put, requestUri, request, accessToken, additionalHeaders, cancellationToken); + + public Task Put(string requestUri, + HttpContent content, + CancellationToken cancellationToken = default) => + SendWithoutResponse(HttpMethod.Put, requestUri, content, accessToken: null, additionalHeaders: null, cancellationToken); + + public Task Put(string requestUri, + HttpContent content, + List<(string header, string value)> additionalHeaders, + CancellationToken cancellationToken = default) => + SendWithoutResponse(HttpMethod.Put, requestUri, content, accessToken: null, additionalHeaders, cancellationToken); + + public Task Put(string requestUri, + HttpContent content, + string accessToken, + CancellationToken cancellationToken = default) => + SendWithoutResponse(HttpMethod.Put, requestUri, content, accessToken, additionalHeaders: null, cancellationToken); + + public Task Put(string requestUri, + HttpContent content, + string accessToken, + List<(string header, string value)> additionalHeaders, + CancellationToken cancellationToken = default) => + SendWithoutResponse(HttpMethod.Put, requestUri, content, accessToken, additionalHeaders, cancellationToken); + + public Task> Put(string requestUri, + TRequest request, + CancellationToken cancellationToken = default) => + SendForResponse(HttpMethod.Put, requestUri, request, accessToken: null, additionalHeaders: null, cancellationToken); + + public Task> Put(string requestUri, + TRequest request, + List<(string header, string value)> additionalHeaders, + CancellationToken cancellationToken = default) => + SendForResponse(HttpMethod.Put, requestUri, request, accessToken: null, additionalHeaders, cancellationToken); + + public Task> Put(string requestUri, + TRequest request, + string accessToken, + CancellationToken cancellationToken = default) => + SendForResponse(HttpMethod.Put, requestUri, request, accessToken, additionalHeaders: null, cancellationToken); + + public Task> Put(string requestUri, + TRequest request, + string accessToken, + List<(string header, string value)> additionalHeaders, + CancellationToken cancellationToken = default) => + SendForResponse(HttpMethod.Put, requestUri, request, accessToken, additionalHeaders, cancellationToken); + + public Task> Put(string requestUri, + HttpContent content, + CancellationToken cancellationToken = default) => + SendForResponse(HttpMethod.Put, requestUri, content, accessToken: null, additionalHeaders: null, cancellationToken); + + public Task> Put(string requestUri, + HttpContent content, + List<(string header, string value)> additionalHeaders, + CancellationToken cancellationToken = default) => + SendForResponse(HttpMethod.Put, requestUri, content, accessToken: null, additionalHeaders, cancellationToken); + + public Task> Put(string requestUri, + HttpContent content, + string accessToken, + CancellationToken cancellationToken = default) => + SendForResponse(HttpMethod.Put, requestUri, content, accessToken, additionalHeaders: null, cancellationToken); + + public Task> Put(string requestUri, + HttpContent content, + string accessToken, + List<(string header, string value)> additionalHeaders, + CancellationToken cancellationToken = default) => + SendForResponse(HttpMethod.Put, requestUri, content, accessToken, additionalHeaders, cancellationToken); + + public Task Patch(string requestUri, + TRequest request, + CancellationToken cancellationToken = default) => + SendWithoutResponse(HttpMethod.Patch, requestUri, request, accessToken: null, additionalHeaders: null, cancellationToken); + + public Task Patch(string requestUri, + TRequest request, + List<(string header, string value)> additionalHeaders, + CancellationToken cancellationToken = default) => + SendWithoutResponse(HttpMethod.Patch, requestUri, request, accessToken: null, additionalHeaders, cancellationToken); + + public Task Patch(string requestUri, + TRequest request, + string accessToken, + CancellationToken cancellationToken = default) => + SendWithoutResponse(HttpMethod.Patch, requestUri, request, accessToken, additionalHeaders: null, cancellationToken); + + public Task Patch(string requestUri, + TRequest request, + string accessToken, + List<(string header, string value)> additionalHeaders, + CancellationToken cancellationToken = default) => + SendWithoutResponse(HttpMethod.Patch, requestUri, request, accessToken, additionalHeaders, cancellationToken); + + public Task Patch(string requestUri, + HttpContent content, + CancellationToken cancellationToken = default) => + SendWithoutResponse(HttpMethod.Patch, requestUri, content, accessToken: null, additionalHeaders: null, cancellationToken); + + public Task Patch(string requestUri, + HttpContent content, + List<(string header, string value)> additionalHeaders, + CancellationToken cancellationToken = default) => + SendWithoutResponse(HttpMethod.Patch, requestUri, content, accessToken: null, additionalHeaders, cancellationToken); + + public Task Patch(string requestUri, + HttpContent content, + string accessToken, + CancellationToken cancellationToken = default) => + SendWithoutResponse(HttpMethod.Patch, requestUri, content, accessToken, additionalHeaders: null, cancellationToken); + + public Task Patch(string requestUri, + HttpContent content, + string accessToken, + List<(string header, string value)> additionalHeaders, + CancellationToken cancellationToken = default) => + SendWithoutResponse(HttpMethod.Patch, requestUri, content, accessToken, additionalHeaders, cancellationToken); + + public Task> Patch(string requestUri, + TRequest request, + CancellationToken cancellationToken = default) => + SendForResponse(HttpMethod.Patch, requestUri, request, accessToken: null, additionalHeaders: null, cancellationToken); + + public Task> Patch(string requestUri, + TRequest request, + List<(string header, string value)> additionalHeaders, + CancellationToken cancellationToken = default) => + SendForResponse(HttpMethod.Patch, requestUri, request, accessToken: null, additionalHeaders, cancellationToken); + + public Task> Patch(string requestUri, + TRequest request, + string accessToken, + CancellationToken cancellationToken = default) => + SendForResponse(HttpMethod.Patch, requestUri, request, accessToken, additionalHeaders: null, cancellationToken); + + public Task> Patch(string requestUri, + TRequest request, + string accessToken, + List<(string header, string value)> additionalHeaders, + CancellationToken cancellationToken = default) => + SendForResponse(HttpMethod.Patch, requestUri, request, accessToken, additionalHeaders, cancellationToken); + + public Task> Patch(string requestUri, + HttpContent content, + CancellationToken cancellationToken = default) => + SendForResponse(HttpMethod.Patch, requestUri, content, accessToken: null, additionalHeaders: null, cancellationToken); + + public Task> Patch(string requestUri, + HttpContent content, + List<(string header, string value)> additionalHeaders, + CancellationToken cancellationToken = default) => + SendForResponse(HttpMethod.Patch, requestUri, content, accessToken: null, additionalHeaders, cancellationToken); + + public Task> Patch(string requestUri, + HttpContent content, + string accessToken, + CancellationToken cancellationToken = default) => + SendForResponse(HttpMethod.Patch, requestUri, content, accessToken, additionalHeaders: null, cancellationToken); + + public Task> Patch(string requestUri, + HttpContent content, + string accessToken, + List<(string header, string value)> additionalHeaders, + CancellationToken cancellationToken = default) => + SendForResponse(HttpMethod.Patch, requestUri, content, accessToken, additionalHeaders, cancellationToken); + + private async Task SendWithoutResponse(HttpMethod method, + string requestUri, + TRequest? body, + string? accessToken, + List<(string header, string value)>? additionalHeaders, + CancellationToken cancellationToken) { + try { + using var request = CreateRequest(method, requestUri, body, accessToken, additionalHeaders); + using var response = await HttpClient.SendAsync(request, cancellationToken); + return await MapNonGenericResult(method, response, cancellationToken); } + catch (InvalidOperationException exception) { + return Result.Failure(exception.Message); + } + catch (HttpRequestException exception) { + return Result.CriticalError(exception.Message); + } + catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) { + return Result.CriticalError(HttpTimeoutErrorMessage); + } + } - public Task> Get(string requestUri, CancellationToken cancellationToken = default) - => SendForResponse(HttpMethod.Get, requestUri, body: null, accessToken: null, additionalHeaders: null, cancellationToken); - - public Task> Get(string requestUri, List<(string header, string value)> additionalHeaders, CancellationToken cancellationToken = default) - => SendForResponse(HttpMethod.Get, requestUri, body: null, accessToken: null, additionalHeaders, cancellationToken); - - public Task> Get(string requestUri, string accessToken, CancellationToken cancellationToken = default) - => SendForResponse(HttpMethod.Get, requestUri, body: null, accessToken, additionalHeaders: null, cancellationToken); - - public Task> Get(string requestUri, string accessToken, List<(string header, string value)> additionalHeaders, CancellationToken cancellationToken = default) - => SendForResponse(HttpMethod.Get, requestUri, body: null, accessToken, additionalHeaders, cancellationToken); - - public Task Delete(string requestUri, CancellationToken cancellationToken = default) - => SendWithoutResponse(HttpMethod.Delete, requestUri, body: null, accessToken: null, additionalHeaders: null, cancellationToken); - - public Task Delete(string requestUri, List<(string header, string value)> additionalHeaders, CancellationToken cancellationToken = default) - => SendWithoutResponse(HttpMethod.Delete, requestUri, body: null, accessToken: null, additionalHeaders, cancellationToken); - - public Task Delete(string requestUri, string accessToken, CancellationToken cancellationToken = default) - => SendWithoutResponse(HttpMethod.Delete, requestUri, body: null, accessToken, additionalHeaders: null, cancellationToken); - - public Task Delete(string requestUri, string accessToken, List<(string header, string value)> additionalHeaders, CancellationToken cancellationToken = default) - => SendWithoutResponse(HttpMethod.Delete, requestUri, body: null, accessToken, additionalHeaders, cancellationToken); - - public Task Post(string requestUri, TRequest request, CancellationToken cancellationToken = default) - => SendWithoutResponse(HttpMethod.Post, requestUri, request, accessToken: null, additionalHeaders: null, cancellationToken); - - public Task Post(string requestUri, TRequest request, List<(string header, string value)> additionalHeaders, CancellationToken cancellationToken = default) - => SendWithoutResponse(HttpMethod.Post, requestUri, request, accessToken: null, additionalHeaders, cancellationToken); - - public Task Post(string requestUri, TRequest request, string accessToken, CancellationToken cancellationToken = default) - => SendWithoutResponse(HttpMethod.Post, requestUri, request, accessToken, additionalHeaders: null, cancellationToken); - - public Task Post(string requestUri, TRequest request, string accessToken, List<(string header, string value)> additionalHeaders, CancellationToken cancellationToken = default) - => SendWithoutResponse(HttpMethod.Post, requestUri, request, accessToken, additionalHeaders, cancellationToken); - - public Task Post(string requestUri, HttpContent content, CancellationToken cancellationToken = default) - => SendWithoutResponse(HttpMethod.Post, requestUri, content, accessToken: null, additionalHeaders: null, cancellationToken); - - public Task Post(string requestUri, HttpContent content, List<(string header, string value)> additionalHeaders, CancellationToken cancellationToken = default) - => SendWithoutResponse(HttpMethod.Post, requestUri, content, accessToken: null, additionalHeaders, cancellationToken); - - public Task Post(string requestUri, HttpContent content, string accessToken, CancellationToken cancellationToken = default) - => SendWithoutResponse(HttpMethod.Post, requestUri, content, accessToken, additionalHeaders: null, cancellationToken); - - public Task Post(string requestUri, HttpContent content, string accessToken, List<(string header, string value)> additionalHeaders, CancellationToken cancellationToken = default) - => SendWithoutResponse(HttpMethod.Post, requestUri, content, accessToken, additionalHeaders, cancellationToken); - - public Task Post(string requestUri, byte[] fileData, string fileName, List<(string field, string value)> formFields, string accessToken, CancellationToken cancellationToken = default) - => SendMultipartWithoutResponse(requestUri, fileData, fileName, formFields, accessToken, additionalHeaders: null, cancellationToken); - - public Task Post(string requestUri, byte[] fileData, string fileName, List<(string field, string value)> formFields, string accessToken, List<(string header, string value)> additionalHeaders, CancellationToken cancellationToken = default) - => SendMultipartWithoutResponse(requestUri, fileData, fileName, formFields, accessToken, additionalHeaders, cancellationToken); - - public Task> Post(string requestUri, TRequest request, CancellationToken cancellationToken = default) - => SendForResponse(HttpMethod.Post, requestUri, request, accessToken: null, additionalHeaders: null, cancellationToken); - - public Task> Post(string requestUri, TRequest request, List<(string header, string value)> additionalHeaders, CancellationToken cancellationToken = default) - => SendForResponse(HttpMethod.Post, requestUri, request, accessToken: null, additionalHeaders, cancellationToken); - - public Task> Post(string requestUri, TRequest request, string accessToken, CancellationToken cancellationToken = default) - => SendForResponse(HttpMethod.Post, requestUri, request, accessToken, additionalHeaders: null, cancellationToken); - - public Task> Post(string requestUri, TRequest request, string accessToken, List<(string header, string value)> additionalHeaders, CancellationToken cancellationToken = default) - => SendForResponse(HttpMethod.Post, requestUri, request, accessToken, additionalHeaders, cancellationToken); - - public Task> Post(string requestUri, HttpContent content, CancellationToken cancellationToken = default) - => SendForResponse(HttpMethod.Post, requestUri, content, accessToken: null, additionalHeaders: null, cancellationToken); - - public Task> Post(string requestUri, HttpContent content, List<(string header, string value)> additionalHeaders, CancellationToken cancellationToken = default) - => SendForResponse(HttpMethod.Post, requestUri, content, accessToken: null, additionalHeaders, cancellationToken); - - public Task> Post(string requestUri, HttpContent content, string accessToken, CancellationToken cancellationToken = default) - => SendForResponse(HttpMethod.Post, requestUri, content, accessToken, additionalHeaders: null, cancellationToken); - - public Task> Post(string requestUri, HttpContent content, string accessToken, List<(string header, string value)> additionalHeaders, CancellationToken cancellationToken = default) - => SendForResponse(HttpMethod.Post, requestUri, content, accessToken, additionalHeaders, cancellationToken); - - public Task Put(string requestUri, TRequest request, CancellationToken cancellationToken = default) - => SendWithoutResponse(HttpMethod.Put, requestUri, request, accessToken: null, additionalHeaders: null, cancellationToken); - - public Task Put(string requestUri, TRequest request, List<(string header, string value)> additionalHeaders, CancellationToken cancellationToken = default) - => SendWithoutResponse(HttpMethod.Put, requestUri, request, accessToken: null, additionalHeaders, cancellationToken); - - public Task Put(string requestUri, TRequest request, string accessToken, CancellationToken cancellationToken = default) - => SendWithoutResponse(HttpMethod.Put, requestUri, request, accessToken, additionalHeaders: null, cancellationToken); - - public Task Put(string requestUri, TRequest request, string accessToken, List<(string header, string value)> additionalHeaders, CancellationToken cancellationToken = default) - => SendWithoutResponse(HttpMethod.Put, requestUri, request, accessToken, additionalHeaders, cancellationToken); - - public Task Put(string requestUri, HttpContent content, CancellationToken cancellationToken = default) - => SendWithoutResponse(HttpMethod.Put, requestUri, content, accessToken: null, additionalHeaders: null, cancellationToken); - - public Task Put(string requestUri, HttpContent content, List<(string header, string value)> additionalHeaders, CancellationToken cancellationToken = default) - => SendWithoutResponse(HttpMethod.Put, requestUri, content, accessToken: null, additionalHeaders, cancellationToken); - - public Task Put(string requestUri, HttpContent content, string accessToken, CancellationToken cancellationToken = default) - => SendWithoutResponse(HttpMethod.Put, requestUri, content, accessToken, additionalHeaders: null, cancellationToken); - - public Task Put(string requestUri, HttpContent content, string accessToken, List<(string header, string value)> additionalHeaders, CancellationToken cancellationToken = default) - => SendWithoutResponse(HttpMethod.Put, requestUri, content, accessToken, additionalHeaders, cancellationToken); - - public Task> Put(string requestUri, TRequest request, CancellationToken cancellationToken = default) - => SendForResponse(HttpMethod.Put, requestUri, request, accessToken: null, additionalHeaders: null, cancellationToken); - - public Task> Put(string requestUri, TRequest request, List<(string header, string value)> additionalHeaders, CancellationToken cancellationToken = default) - => SendForResponse(HttpMethod.Put, requestUri, request, accessToken: null, additionalHeaders, cancellationToken); - - public Task> Put(string requestUri, TRequest request, string accessToken, CancellationToken cancellationToken = default) - => SendForResponse(HttpMethod.Put, requestUri, request, accessToken, additionalHeaders: null, cancellationToken); - - public Task> Put(string requestUri, TRequest request, string accessToken, List<(string header, string value)> additionalHeaders, CancellationToken cancellationToken = default) - => SendForResponse(HttpMethod.Put, requestUri, request, accessToken, additionalHeaders, cancellationToken); - - public Task> Put(string requestUri, HttpContent content, CancellationToken cancellationToken = default) - => SendForResponse(HttpMethod.Put, requestUri, content, accessToken: null, additionalHeaders: null, cancellationToken); - - public Task> Put(string requestUri, HttpContent content, List<(string header, string value)> additionalHeaders, CancellationToken cancellationToken = default) - => SendForResponse(HttpMethod.Put, requestUri, content, accessToken: null, additionalHeaders, cancellationToken); - - public Task> Put(string requestUri, HttpContent content, string accessToken, CancellationToken cancellationToken = default) - => SendForResponse(HttpMethod.Put, requestUri, content, accessToken, additionalHeaders: null, cancellationToken); - - public Task> Put(string requestUri, HttpContent content, string accessToken, List<(string header, string value)> additionalHeaders, CancellationToken cancellationToken = default) - => SendForResponse(HttpMethod.Put, requestUri, content, accessToken, additionalHeaders, cancellationToken); - - public Task Patch(string requestUri, TRequest request, CancellationToken cancellationToken = default) - => SendWithoutResponse(HttpMethod.Patch, requestUri, request, accessToken: null, additionalHeaders: null, cancellationToken); - - public Task Patch(string requestUri, TRequest request, List<(string header, string value)> additionalHeaders, CancellationToken cancellationToken = default) - => SendWithoutResponse(HttpMethod.Patch, requestUri, request, accessToken: null, additionalHeaders, cancellationToken); - - public Task Patch(string requestUri, TRequest request, string accessToken, CancellationToken cancellationToken = default) - => SendWithoutResponse(HttpMethod.Patch, requestUri, request, accessToken, additionalHeaders: null, cancellationToken); - - public Task Patch(string requestUri, TRequest request, string accessToken, List<(string header, string value)> additionalHeaders, CancellationToken cancellationToken = default) - => SendWithoutResponse(HttpMethod.Patch, requestUri, request, accessToken, additionalHeaders, cancellationToken); - - public Task Patch(string requestUri, HttpContent content, CancellationToken cancellationToken = default) - => SendWithoutResponse(HttpMethod.Patch, requestUri, content, accessToken: null, additionalHeaders: null, cancellationToken); - - public Task Patch(string requestUri, HttpContent content, List<(string header, string value)> additionalHeaders, CancellationToken cancellationToken = default) - => SendWithoutResponse(HttpMethod.Patch, requestUri, content, accessToken: null, additionalHeaders, cancellationToken); - - public Task Patch(string requestUri, HttpContent content, string accessToken, CancellationToken cancellationToken = default) - => SendWithoutResponse(HttpMethod.Patch, requestUri, content, accessToken, additionalHeaders: null, cancellationToken); - - public Task Patch(string requestUri, HttpContent content, string accessToken, List<(string header, string value)> additionalHeaders, CancellationToken cancellationToken = default) - => SendWithoutResponse(HttpMethod.Patch, requestUri, content, accessToken, additionalHeaders, cancellationToken); - - public Task> Patch(string requestUri, TRequest request, CancellationToken cancellationToken = default) - => SendForResponse(HttpMethod.Patch, requestUri, request, accessToken: null, additionalHeaders: null, cancellationToken); - - public Task> Patch(string requestUri, TRequest request, List<(string header, string value)> additionalHeaders, CancellationToken cancellationToken = default) - => SendForResponse(HttpMethod.Patch, requestUri, request, accessToken: null, additionalHeaders, cancellationToken); - - public Task> Patch(string requestUri, TRequest request, string accessToken, CancellationToken cancellationToken = default) - => SendForResponse(HttpMethod.Patch, requestUri, request, accessToken, additionalHeaders: null, cancellationToken); + private const String HttpTimeoutErrorMessage = "The HTTP request timed out."; + + private async Task SendWithoutResponse(HttpMethod method, + string requestUri, + HttpContent content, + string? accessToken, + List<(string header, string value)>? additionalHeaders, + CancellationToken cancellationToken) { + try { + using var request = CreateRequest(method, requestUri, content, accessToken, additionalHeaders); + using var response = await HttpClient.SendAsync(request, cancellationToken); + return await MapNonGenericResult(method, response, cancellationToken); + } + catch (InvalidOperationException exception) { + return Result.Failure(exception.Message); + } + catch (HttpRequestException exception) { + return Result.CriticalError(exception.Message); + } + catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) { + return Result.CriticalError(HttpTimeoutErrorMessage); + } + } - public Task> Patch(string requestUri, TRequest request, string accessToken, List<(string header, string value)> additionalHeaders, CancellationToken cancellationToken = default) - => SendForResponse(HttpMethod.Patch, requestUri, request, accessToken, additionalHeaders, cancellationToken); + private async Task SendMultipartWithoutResponse(string requestUri, + byte[] fileData, + string fileName, + List<(string field, string value)> formFields, + string accessToken, + List<(string header, string value)>? additionalHeaders, + CancellationToken cancellationToken) { + try { + using var request = CreateMultipartRequest(requestUri, fileData, fileName, formFields, accessToken, additionalHeaders); + using var response = await HttpClient.SendAsync(request, cancellationToken); + return await MapNonGenericResult(HttpMethod.Post, response, cancellationToken); + } + catch (InvalidOperationException exception) { + return Result.Failure(exception.Message); + } + catch (HttpRequestException exception) { + return Result.CriticalError(exception.Message); + } + catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) { + return Result.CriticalError(HttpTimeoutErrorMessage); + } + } - public Task> Patch(string requestUri, HttpContent content, CancellationToken cancellationToken = default) - => SendForResponse(HttpMethod.Patch, requestUri, content, accessToken: null, additionalHeaders: null, cancellationToken); + private async Task> SendForResponse(HttpMethod method, + string requestUri, + object? body, + string? accessToken, + List<(string header, string value)>? additionalHeaders, + CancellationToken cancellationToken) { + try { + using var request = CreateRequest(method, requestUri, body, accessToken, additionalHeaders); + using var response = await HttpClient.SendAsync(request, cancellationToken); + return await MapGenericResult(method, response, cancellationToken); + } + catch (InvalidOperationException exception) { + return Result.Failure(exception.Message); + } + catch (HttpRequestException exception) { + return Result.CriticalError(exception.Message); + } + catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) { + return Result.CriticalError(HttpTimeoutErrorMessage); + } + } - public Task> Patch(string requestUri, HttpContent content, List<(string header, string value)> additionalHeaders, CancellationToken cancellationToken = default) - => SendForResponse(HttpMethod.Patch, requestUri, content, accessToken: null, additionalHeaders, cancellationToken); + private async Task> SendForResponse(HttpMethod method, + string requestUri, + HttpContent content, + string? accessToken, + List<(string header, string value)>? additionalHeaders, + CancellationToken cancellationToken) { + try { + using var request = CreateRequest(method, requestUri, content, accessToken, additionalHeaders); + using var response = await HttpClient.SendAsync(request, cancellationToken); + return await MapGenericResult(method, response, cancellationToken); + } + catch (InvalidOperationException exception) { + return Result.Failure(exception.Message); + } + catch (HttpRequestException exception) { + return Result.CriticalError(exception.Message); + } + catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) { + return Result.CriticalError(HttpTimeoutErrorMessage); + } + } - public Task> Patch(string requestUri, HttpContent content, string accessToken, CancellationToken cancellationToken = default) - => SendForResponse(HttpMethod.Patch, requestUri, content, accessToken, additionalHeaders: null, cancellationToken); + private HttpRequestMessage CreateRequest(HttpMethod method, + string requestUri, + object? body, + string? accessToken, + List<(string header, string value)>? additionalHeaders) { + if (string.IsNullOrWhiteSpace(requestUri)) { + throw new ArgumentException("Request URI cannot be null, empty, or whitespace.", nameof(requestUri)); + } - public Task> Patch(string requestUri, HttpContent content, string accessToken, List<(string header, string value)> additionalHeaders, CancellationToken cancellationToken = default) - => SendForResponse(HttpMethod.Patch, requestUri, content, accessToken, additionalHeaders, cancellationToken); + var request = new HttpRequestMessage(method, requestUri); - private async Task SendWithoutResponse(HttpMethod method, - string requestUri, - TRequest? body, - string? accessToken, - List<(string header, string value)>? additionalHeaders, - CancellationToken cancellationToken) - { - try - { - using var request = CreateRequest(method, requestUri, body, accessToken, additionalHeaders); - using var response = await HttpClient.SendAsync(request, cancellationToken); - return await MapNonGenericResult(method, response, cancellationToken); + if (accessToken is not null) { + if (string.IsNullOrWhiteSpace(accessToken)) { + throw new ArgumentException("Access token cannot be null, empty, or whitespace.", nameof(accessToken)); } - catch (InvalidOperationException exception) - { - return Result.Failure(exception.Message); - } - catch (HttpRequestException exception) - { - return Result.CriticalError(exception.Message); - } - catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) - { - return Result.CriticalError("The HTTP request timed out."); - } - } - private async Task SendWithoutResponse(HttpMethod method, - string requestUri, - HttpContent content, - string? accessToken, - List<(string header, string value)>? additionalHeaders, - CancellationToken cancellationToken) - { - try - { - using var request = CreateRequest(method, requestUri, content, accessToken, additionalHeaders); - using var response = await HttpClient.SendAsync(request, cancellationToken); - return await MapNonGenericResult(method, response, cancellationToken); - } - catch (InvalidOperationException exception) - { - return Result.Failure(exception.Message); - } - catch (HttpRequestException exception) - { - return Result.CriticalError(exception.Message); - } - catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) - { - return Result.CriticalError("The HTTP request timed out."); - } + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); } - private async Task SendMultipartWithoutResponse( - string requestUri, - byte[] fileData, - string fileName, - List<(string field, string value)> formFields, - string accessToken, - List<(string header, string value)>? additionalHeaders, - CancellationToken cancellationToken) - { - try - { - using var request = CreateMultipartRequest(requestUri, fileData, fileName, formFields, accessToken, additionalHeaders); - using var response = await HttpClient.SendAsync(request, cancellationToken); - return await MapNonGenericResult(HttpMethod.Post, response, cancellationToken); - } - catch (InvalidOperationException exception) - { - return Result.Failure(exception.Message); - } - catch (HttpRequestException exception) - { - return Result.CriticalError(exception.Message); - } - catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) - { - return Result.CriticalError("The HTTP request timed out."); - } + if (body is HttpContent httpContent) { + request.Content = httpContent; } + else if (body is not null) { + string serialisedBody; - private async Task> SendForResponse(HttpMethod method, - string requestUri, - object? body, - string? accessToken, - List<(string header, string value)>? additionalHeaders, - CancellationToken cancellationToken) - { - try - { - using var request = CreateRequest(method, requestUri, body, accessToken, additionalHeaders); - using var response = await HttpClient.SendAsync(request, cancellationToken); - return await MapGenericResult(method, response, cancellationToken); + try { + serialisedBody = Serialise(body); } - catch (InvalidOperationException exception) - { - return Result.Failure(exception.Message); + catch (Exception exception) { + throw new InvalidOperationException($"The request body could not be serialized: {exception.Message}", exception); } - catch (HttpRequestException exception) - { - return Result.CriticalError(exception.Message); - } - catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) - { - return Result.CriticalError("The HTTP request timed out."); - } - } - private async Task> SendForResponse(HttpMethod method, - string requestUri, - HttpContent content, - string? accessToken, - List<(string header, string value)>? additionalHeaders, - CancellationToken cancellationToken) - { - try - { - using var request = CreateRequest(method, requestUri, content, accessToken, additionalHeaders); - using var response = await HttpClient.SendAsync(request, cancellationToken); - return await MapGenericResult(method, response, cancellationToken); - } - catch (InvalidOperationException exception) - { - return Result.Failure(exception.Message); - } - catch (HttpRequestException exception) - { - return Result.CriticalError(exception.Message); - } - catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) - { - return Result.CriticalError("The HTTP request timed out."); - } - } - - private HttpRequestMessage CreateRequest(HttpMethod method, - string requestUri, - object? body, - string? accessToken, - List<(string header, string value)>? additionalHeaders) - { - if (string.IsNullOrWhiteSpace(requestUri)) - { - throw new ArgumentException("Request URI cannot be null, empty, or whitespace.", nameof(requestUri)); + if (serialisedBody is null) { + throw new InvalidOperationException("The request body could not be serialized."); } - var request = new HttpRequestMessage(method, requestUri); - - if (accessToken is not null) - { - if (string.IsNullOrWhiteSpace(accessToken)) - { - throw new ArgumentException("Access token cannot be null, empty, or whitespace.", nameof(accessToken)); - } + request.Content = new StringContent(serialisedBody, Encoding.UTF8, "application/json"); + } - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); - } + if (additionalHeaders is not null) { + AddHeaders(request, additionalHeaders); + } - if (body is HttpContent httpContent) - { - request.Content = httpContent; - } - else if (body is not null) - { - string serialisedBody; + return request; + } - try - { - serialisedBody = Serialise(body); - } - catch (Exception exception) - { - throw new InvalidOperationException($"The request body could not be serialized: {exception.Message}", exception); - } + private HttpRequestMessage CreateRequest(HttpMethod method, + string requestUri, + HttpContent content, + string? accessToken, + List<(string header, string value)>? additionalHeaders) { + if (content is null) { + throw new ArgumentNullException(nameof(content)); + } - if (serialisedBody is null) - { - throw new InvalidOperationException("The request body could not be serialized."); - } + var request = CreateRequest(method, requestUri, body: null, accessToken, additionalHeaders: null); + request.Content = content; - request.Content = new StringContent(serialisedBody, Encoding.UTF8, "application/json"); - } + if (additionalHeaders is not null) { + AddHeaders(request, additionalHeaders); + } - if (additionalHeaders is not null) - { - AddHeaders(request, additionalHeaders); - } + return request; + } - return request; + private HttpRequestMessage CreateMultipartRequest(string requestUri, + byte[] fileData, + string fileName, + List<(string field, string value)> formFields, + string accessToken, + List<(string header, string value)>? additionalHeaders) { + if (fileData is null) { + throw new ArgumentNullException(nameof(fileData)); } - private HttpRequestMessage CreateRequest(HttpMethod method, - string requestUri, - HttpContent content, - string? accessToken, - List<(string header, string value)>? additionalHeaders) - { - if (content is null) - { - throw new ArgumentNullException(nameof(content)); - } + if (string.IsNullOrWhiteSpace(fileName)) { + throw new ArgumentException("File name cannot be null, empty, or whitespace.", nameof(fileName)); + } - var request = CreateRequest(method, requestUri, body: null, accessToken, additionalHeaders: null); - request.Content = content; + if (formFields is null) { + throw new ArgumentNullException(nameof(formFields)); + } - if (additionalHeaders is not null) - { - AddHeaders(request, additionalHeaders); - } + var request = CreateRequest(HttpMethod.Post, requestUri, body: null, accessToken, additionalHeaders: null); + request.Content = CreateMultipartContent(fileData, fileName, formFields); - return request; + if (additionalHeaders is not null) { + AddHeaders(request, additionalHeaders); } - private HttpRequestMessage CreateMultipartRequest( - string requestUri, - byte[] fileData, - string fileName, - List<(string field, string value)> formFields, - string accessToken, - List<(string header, string value)>? additionalHeaders) - { - if (fileData is null) - { - throw new ArgumentNullException(nameof(fileData)); - } + return request; + } - if (string.IsNullOrWhiteSpace(fileName)) - { - throw new ArgumentException("File name cannot be null, empty, or whitespace.", nameof(fileName)); + private static void AddHeaders(HttpRequestMessage request, + List<(string header, string value)> additionalHeaders) { + foreach (var (header, value) in additionalHeaders) { + if (string.IsNullOrWhiteSpace(header)) { + throw new ArgumentException("Header name cannot be null, empty, or whitespace.", nameof(additionalHeaders)); } - if (formFields is null) - { - throw new ArgumentNullException(nameof(formFields)); + if (string.IsNullOrWhiteSpace(value)) { + throw new ArgumentException($"Header value for '{header}' cannot be null, empty, or whitespace.", nameof(additionalHeaders)); } - var request = CreateRequest(HttpMethod.Post, requestUri, body: null, accessToken, additionalHeaders: null); - request.Content = CreateMultipartContent(fileData, fileName, formFields); + if (request.Headers.TryAddWithoutValidation(header, value)) { + continue; + } - if (additionalHeaders is not null) - { - AddHeaders(request, additionalHeaders); + if (request.Content is not null && request.Content.Headers.TryAddWithoutValidation(header, value)) { + continue; } - return request; + throw new InvalidOperationException($"The header '{header}' could not be added to the request."); } + } - private static void AddHeaders(HttpRequestMessage request, List<(string header, string value)> additionalHeaders) - { - foreach (var (header, value) in additionalHeaders) - { - if (string.IsNullOrWhiteSpace(header)) - { - throw new ArgumentException("Header name cannot be null, empty, or whitespace.", nameof(additionalHeaders)); - } - - if (string.IsNullOrWhiteSpace(value)) - { - throw new ArgumentException($"Header value for '{header}' cannot be null, empty, or whitespace.", nameof(additionalHeaders)); - } - - if (request.Headers.TryAddWithoutValidation(header, value)) - { - continue; - } + private static MultipartFormDataContent CreateMultipartContent(byte[] fileData, + string fileName, + List<(string field, string value)> formFields) { + var multipartContent = new MultipartFormDataContent(); - if (request.Content is not null && request.Content.Headers.TryAddWithoutValidation(header, value)) - { - continue; - } + var fileContent = new ByteArrayContent(fileData); + fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("multipart/form-data"); + multipartContent.Add(fileContent, "file", fileName); - throw new InvalidOperationException($"The header '{header}' could not be added to the request."); + foreach (var (field, value) in formFields) { + if (string.IsNullOrWhiteSpace(field)) { + throw new ArgumentException("Form field name cannot be null, empty, or whitespace.", nameof(formFields)); } - } - - private static MultipartFormDataContent CreateMultipartContent( - byte[] fileData, - string fileName, - List<(string field, string value)> formFields) - { - var multipartContent = new MultipartFormDataContent(); - - var fileContent = new ByteArrayContent(fileData); - fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("multipart/form-data"); - multipartContent.Add(fileContent, "file", fileName); - - foreach (var (field, value) in formFields) - { - if (string.IsNullOrWhiteSpace(field)) - { - throw new ArgumentException("Form field name cannot be null, empty, or whitespace.", nameof(formFields)); - } - - if (string.IsNullOrWhiteSpace(value)) - { - throw new ArgumentException($"Form field value for '{field}' cannot be null, empty, or whitespace.", nameof(formFields)); - } - multipartContent.Add(new StringContent(value), field); + if (string.IsNullOrWhiteSpace(value)) { + throw new ArgumentException($"Form field value for '{field}' cannot be null, empty, or whitespace.", nameof(formFields)); } - return multipartContent; + multipartContent.Add(new StringContent(value), field); } - private async Task MapNonGenericResult(HttpMethod method, - HttpResponseMessage response, - CancellationToken cancellationToken) - { - if (response.IsSuccessStatusCode) - { - return MapSuccessfulResult(method, response.StatusCode); - } + return multipartContent; + } - return await MapFailedResult(response, cancellationToken); + private async Task MapNonGenericResult(HttpMethod method, + HttpResponseMessage response, + CancellationToken cancellationToken) { + if (response.IsSuccessStatusCode) { + return MapSuccessfulResult(method, response.StatusCode); } - private async Task> MapGenericResult(HttpMethod method, - HttpResponseMessage response, - CancellationToken cancellationToken) - { - if (!response.IsSuccessStatusCode) - { - return await MapFailedResult(response, cancellationToken); - } - - var rawContent = await ReadContent(response, cancellationToken); - if (string.IsNullOrWhiteSpace(rawContent)) - { - return Result.Failure("The response content was empty."); - } - - if (typeof(TResponse) == typeof(string)) - { - return Result.Success((TResponse)(object)rawContent); - } + return await MapFailedResult(response, cancellationToken); + } - object deserialisedValue; + private async Task> MapGenericResult(HttpMethod method, + HttpResponseMessage response, + CancellationToken cancellationToken) { + if (!response.IsSuccessStatusCode) { + return await MapFailedResult(response, cancellationToken); + } - try - { - deserialisedValue = Deserialise(rawContent, typeof(TResponse)); - } - catch (Exception exception) - { - return Result.Failure($"The response body could not be deserialized: {exception.Message}"); - } + var rawContent = await ReadContent(response, cancellationToken); + if (string.IsNullOrWhiteSpace(rawContent)) { + return Result.Failure("The response content was empty."); + } - if (deserialisedValue is null) - { - return Result.Failure("The response body could not be deserialized."); - } + if (typeof(TResponse) == typeof(string)) { + return Result.Success((TResponse)(object)rawContent); + } - if (deserialisedValue is not TResponse value) - { - return Result.Failure( - $"The deserialized response type '{deserialisedValue.GetType().FullName}' is not assignable to '{typeof(TResponse).FullName}'."); - } + object deserialisedValue; - return method == HttpMethod.Get - ? Result.ObtainedResource(value) - : Result.Success(value); + try { + deserialisedValue = Deserialise(rawContent, typeof(TResponse)); + } + catch (Exception exception) { + return Result.Failure($"The response body could not be deserialized: {exception.Message}"); } - private static Result MapSuccessfulResult(HttpMethod method, HttpStatusCode statusCode) - { - if (method == HttpMethod.Delete) - { - return Result.DeletedResource(); - } + if (deserialisedValue is null) { + return Result.Failure("The response body could not be deserialized."); + } - if (method == HttpMethod.Post && statusCode == HttpStatusCode.Created) - { - return Result.CreatedResource(); - } + if (deserialisedValue is not TResponse value) { + return Result.Failure($"The deserialized response type '{deserialisedValue.GetType().FullName}' is not assignable to '{typeof(TResponse).FullName}'."); + } - if (method == HttpMethod.Put || method == HttpMethod.Patch) - { - return Result.UpdatedResource(); - } + return method == HttpMethod.Get ? Result.ObtainedResource(value) : Result.Success(value); + } - return Result.Success(); + private static Result MapSuccessfulResult(HttpMethod method, + HttpStatusCode statusCode) { + if (method == HttpMethod.Delete) { + return Result.DeletedResource(); } - private static async Task MapFailedResult(HttpResponseMessage response, CancellationToken cancellationToken) - { - var rawContent = await ReadContent(response, cancellationToken); - var errorMessages = ExtractErrors(rawContent); - var primaryMessage = BuildPrimaryMessage(response, errorMessages); + if (method == HttpMethod.Post && statusCode == HttpStatusCode.Created) { + return Result.CreatedResource(); + } - return response.StatusCode switch - { - HttpStatusCode.BadRequest => Result.Invalid(primaryMessage, errorMessages), - HttpStatusCode.Unauthorized => Result.Unauthorized(primaryMessage, errorMessages), - HttpStatusCode.Forbidden => Result.Forbidden(primaryMessage, errorMessages), - HttpStatusCode.NotFound => Result.NotFound(primaryMessage, errorMessages), - HttpStatusCode.Conflict => Result.Conflict(primaryMessage, errorMessages), - _ when (int)response.StatusCode >= 500 => Result.CriticalError(primaryMessage, errorMessages), - _ => Result.Failure(primaryMessage, errorMessages) - }; + if (method == HttpMethod.Put || method == HttpMethod.Patch) { + return Result.UpdatedResource(); } - private static async Task ReadContent(HttpResponseMessage response, CancellationToken cancellationToken) - { - if (response.Content is null) - { - return null; - } + return Result.Success(); + } - return await response.Content.ReadAsStringAsync(cancellationToken); - } + private static async Task MapFailedResult(HttpResponseMessage response, + CancellationToken cancellationToken) { + var rawContent = await ReadContent(response, cancellationToken); + var errorMessages = ExtractErrors(rawContent); + var primaryMessage = BuildPrimaryMessage(response, errorMessages); + + return response.StatusCode switch { + HttpStatusCode.BadRequest => Result.Invalid(primaryMessage, errorMessages), + HttpStatusCode.Unauthorized => Result.Unauthorized(primaryMessage, errorMessages), + HttpStatusCode.Forbidden => Result.Forbidden(primaryMessage, errorMessages), + HttpStatusCode.NotFound => Result.NotFound(primaryMessage, errorMessages), + HttpStatusCode.Conflict => Result.Conflict(primaryMessage, errorMessages), + _ when (int)response.StatusCode >= 500 => Result.CriticalError(primaryMessage, errorMessages), + _ => Result.Failure(primaryMessage, errorMessages) + }; + } - private static IReadOnlyList ExtractErrors(string? rawContent) - { - if (string.IsNullOrWhiteSpace(rawContent)) - { - return []; - } + private static async Task ReadContent(HttpResponseMessage response, + CancellationToken cancellationToken) { + if (response.Content is null) { + return null; + } - try - { - using var document = JsonDocument.Parse(rawContent); - var errors = new List(); - AppendErrors(document.RootElement, errors); + return await response.Content.ReadAsStringAsync(cancellationToken); + } - return errors.Count == 0 ? [rawContent] : errors; - } - catch (JsonException) - { - return [rawContent]; - } + private static IReadOnlyList ExtractErrors(string? rawContent) { + if (string.IsNullOrWhiteSpace(rawContent)) { + return []; } - private static void AppendErrors(JsonElement element, ICollection errors) - { - if (element.ValueKind == JsonValueKind.Object) - { - if (element.TryGetProperty("title", out var title) && title.ValueKind == JsonValueKind.String) - { - AddIfPresent(errors, title.GetString()); - } + try { + using var document = JsonDocument.Parse(rawContent); + var errors = new List(); + AppendErrors(document.RootElement, errors); - if (element.TryGetProperty("detail", out var detail) && detail.ValueKind == JsonValueKind.String) - { - AddIfPresent(errors, detail.GetString()); - } + return errors.Count == 0 ? [rawContent] : errors; + } + catch (JsonException) { + return [rawContent]; + } + } - if (element.TryGetProperty("message", out var message) && message.ValueKind == JsonValueKind.String) - { - AddIfPresent(errors, message.GetString()); + private static readonly string[] MessageProperties = [ + "title", + "detail", + "message" + ]; + + private static void AppendErrors(JsonElement element, + ICollection errors) { + switch (element.ValueKind) { + case JsonValueKind.Object: + AppendObjectErrors(element, errors); + break; + + case JsonValueKind.Array: + foreach (var item in element.EnumerateArray()) { + AppendErrors(item, errors); } - if (element.TryGetProperty("errors", out var nestedErrors)) - { - AppendErrors(nestedErrors, errors); - } + break; - foreach (var property in element.EnumerateObject()) - { - if (property.NameEquals("title") || property.NameEquals("detail") || property.NameEquals("message") || property.NameEquals("errors")) - { - continue; - } - - if (property.Value.ValueKind is JsonValueKind.Array or JsonValueKind.Object) - { - AppendErrors(property.Value, errors); - } - } + case JsonValueKind.String: + AddIfPresent(errors, element.GetString()); + break; + } + } - return; + private static void AppendObjectErrors(JsonElement element, + ICollection errors) { + foreach (var propertyName in MessageProperties) { + if (element.TryGetProperty(propertyName, out var value) && value.ValueKind == JsonValueKind.String) { + AddIfPresent(errors, value.GetString()); } + } - if (element.ValueKind == JsonValueKind.Array) - { - foreach (var item in element.EnumerateArray()) - { - AppendErrors(item, errors); - } + if (element.TryGetProperty("errors", out var nestedErrors)) { + AppendErrors(nestedErrors, errors); + } - return; + foreach (var property in element.EnumerateObject()) { + if (IsReservedProperty(property.Name)) { + continue; } - if (element.ValueKind == JsonValueKind.String) - { - AddIfPresent(errors, element.GetString()); + if (property.Value.ValueKind is JsonValueKind.Object or JsonValueKind.Array) { + AppendErrors(property.Value, errors); } } + } - private static void AddIfPresent(ICollection errors, string? value) - { - if (!string.IsNullOrWhiteSpace(value)) - { - errors.Add(value); - } + private static bool IsReservedProperty(string propertyName) => propertyName is "title" or "detail" or "message" or "errors"; + + private static void AddIfPresent(ICollection errors, + string? value) { + if (!string.IsNullOrWhiteSpace(value)) { + errors.Add(value); } + } - private static string BuildPrimaryMessage(HttpResponseMessage response, IReadOnlyList errors) - { - if (errors.Count > 0) - { - return errors[0]; - } + private static string BuildPrimaryMessage(HttpResponseMessage response, + IReadOnlyList errors) { - return response.ReasonPhrase ?? $"Request failed with status code {(int)response.StatusCode}."; - } + return errors switch { + [] => response.ReasonPhrase ?? $"Request failed with status code {(int)response.StatusCode}.", + [var singleError] => singleError, + _ => $"Request failed with status code {(int)response.StatusCode}. Errors: {string.Join("; ", errors)}" + }; } -} +} \ No newline at end of file From 7e696fcd95ca8fae14d7f2afeeba7b64ed822d19 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Thu, 21 May 2026 21:05:37 +0100 Subject: [PATCH 2/2] fix pr issues and failed test --- ClientProxyBase/ClientProxyBase.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/ClientProxyBase/ClientProxyBase.cs b/ClientProxyBase/ClientProxyBase.cs index 750023d6..3ce79987 100644 --- a/ClientProxyBase/ClientProxyBase.cs +++ b/ClientProxyBase/ClientProxyBase.cs @@ -745,12 +745,13 @@ private static void AppendErrors(JsonElement element, foreach (var item in element.EnumerateArray()) { AppendErrors(item, errors); } - break; case JsonValueKind.String: AddIfPresent(errors, element.GetString()); break; + default: + throw new ArgumentOutOfRangeException(nameof(element.ValueKind), element.ValueKind, null); } } @@ -786,13 +787,13 @@ private static void AddIfPresent(ICollection errors, } } - private static string BuildPrimaryMessage(HttpResponseMessage response, - IReadOnlyList errors) { + private static string BuildPrimaryMessage(HttpResponseMessage response, IReadOnlyList errors) + { + if (errors.Count > 0) + { + return errors[0]; + } - return errors switch { - [] => response.ReasonPhrase ?? $"Request failed with status code {(int)response.StatusCode}.", - [var singleError] => singleError, - _ => $"Request failed with status code {(int)response.StatusCode}. Errors: {string.Join("; ", errors)}" - }; + return response.ReasonPhrase ?? $"Request failed with status code {(int)response.StatusCode}."; } } \ No newline at end of file