From 39884413b603851e18f206eb5c2e2de25194027b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Dybvik=20Langfors?= Date: Mon, 29 Apr 2024 19:45:35 +0200 Subject: [PATCH 1/6] Initial implementation --- .../AltinnAuthorizationClient.cs | 17 +----- .../Altinn/Events/AltinnEventsClient.cs | 18 +++--- .../Altinn/NameRegistry/NameRegistryClient.cs | 35 +++-------- .../OrganizationRegistryClient.cs | 11 +--- .../ResourceRegistryClient.cs | 5 +- .../HttpClientExtensions.cs | 61 +++++++++++++++++++ 6 files changed, 83 insertions(+), 64 deletions(-) create mode 100644 src/Digdir.Domain.Dialogporten.Infrastructure/HttpClientExtensions.cs diff --git a/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/Authorization/AltinnAuthorizationClient.cs b/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/Authorization/AltinnAuthorizationClient.cs index 9d5f9ee6b..1170ec1b6 100644 --- a/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/Authorization/AltinnAuthorizationClient.cs +++ b/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/Authorization/AltinnAuthorizationClient.cs @@ -170,20 +170,7 @@ await SendRequest>( private async Task SendRequest(string url, object request, CancellationToken cancellationToken) { - var requestJson = JsonSerializer.Serialize(request, SerializerOptions); - _logger.LogDebug("Authorization request to {Url}: {RequestJson}", url, requestJson); - var httpContent = new StringContent(requestJson, Encoding.UTF8, "application/json"); - var response = await _httpClient.PostAsync(url, httpContent, cancellationToken); - if (response.StatusCode != HttpStatusCode.OK) - { - var errorResponse = await response.Content.ReadAsStringAsync(cancellationToken); - _logger.LogWarning("AltinnAuthorizationClient.SendRequest failed with non-successful status code: {StatusCode} {Response}", - response.StatusCode, errorResponse); - - return default; - } - - var responseData = await response.Content.ReadAsStringAsync(cancellationToken); - return JsonSerializer.Deserialize(responseData, SerializerOptions); + _logger.LogDebug("Authorization request to {Url}: {RequestJson}", url, JsonSerializer.Serialize(request, SerializerOptions)); + return await _httpClient.PostAsJsonEnsuredAsync(url, request, serializerOptions: SerializerOptions, cancellationToken: cancellationToken); } } diff --git a/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/Events/AltinnEventsClient.cs b/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/Events/AltinnEventsClient.cs index b14c0b218..ea66ede2c 100644 --- a/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/Events/AltinnEventsClient.cs +++ b/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/Events/AltinnEventsClient.cs @@ -1,5 +1,4 @@ using System.Net.Http.Headers; -using System.Net.Http.Json; using Digdir.Domain.Dialogporten.Application.Externals; using Digdir.Domain.Dialogporten.Infrastructure.Common.Serialization; using Microsoft.Extensions.Logging; @@ -16,16 +15,13 @@ public AltinnEventsClient(HttpClient client) } public async Task Publish(CloudEvent cloudEvent, CancellationToken cancellationToken) - { - var uriBuilder = new UriBuilder(_client.BaseAddress!) { Path = "/events/api/v1/events" }; - var msg = new HttpRequestMessage(HttpMethod.Post, uriBuilder.Uri) - { - Content = JsonContent.Create(cloudEvent, options: SerializerOptions.CloudEventSerializerOptions) - }; - msg.Content.Headers.ContentType = new MediaTypeHeaderValue("application/cloudevents+json"); - var response = await _client.SendAsync(msg, cancellationToken); - response.EnsureSuccessStatusCode(); - } + => await _client.PostAsJsonEnsuredAsync( + "/events/api/v1/events", + cloudEvent, + serializerOptions: SerializerOptions.CloudEventSerializerOptions, + configureContentHeaders: h + => h.ContentType = new MediaTypeHeaderValue("application/cloudevents+json"), + cancellationToken: cancellationToken); } internal class ConsoleLogEventBus : ICloudEventBus diff --git a/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/NameRegistry/NameRegistryClient.cs b/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/NameRegistry/NameRegistryClient.cs index da0579782..fd1d48f08 100644 --- a/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/NameRegistry/NameRegistryClient.cs +++ b/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/NameRegistry/NameRegistryClient.cs @@ -1,35 +1,25 @@ -using System.Net; -using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using Digdir.Domain.Dialogporten.Application.Externals; -using Microsoft.Extensions.Caching.Distributed; -using Microsoft.Extensions.Logging; using ZiggyCreatures.Caching.Fusion; namespace Digdir.Domain.Dialogporten.Infrastructure.Altinn.NameRegistry; internal class NameRegistryClient : INameRegistry { - private static readonly DistributedCacheEntryOptions OneDayCacheDuration = new() { AbsoluteExpiration = DateTimeOffset.UtcNow.AddDays(1) }; - private static readonly DistributedCacheEntryOptions ZeroCacheDuration = new() { AbsoluteExpiration = DateTimeOffset.MinValue }; - private readonly IFusionCache _cache; private readonly HttpClient _client; - private readonly ILogger _logger; private static readonly JsonSerializerOptions SerializerOptions = new() - { PropertyNameCaseInsensitive = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault }; - public NameRegistryClient(HttpClient client, IFusionCacheProvider cacheProvider, ILogger logger) + public NameRegistryClient(HttpClient client, IFusionCacheProvider cacheProvider) { _client = client ?? throw new ArgumentNullException(nameof(client)); _cache = cacheProvider.GetCache(nameof(NameRegistry)) ?? throw new ArgumentNullException(nameof(cacheProvider)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task GetName(string personalIdentificationNumber, CancellationToken cancellationToken) @@ -51,24 +41,13 @@ public NameRegistryClient(HttpClient client, IFusionCacheProvider cacheProvider, ] }; - var requestJson = JsonSerializer.Serialize(nameLookup, SerializerOptions); - var httpContent = new StringContent(requestJson, Encoding.UTF8, "application/json"); - - var response = await _client.PostAsync(apiUrl, httpContent, cancellationToken); - - if (response.StatusCode != HttpStatusCode.OK) - { - var errorResponse = await response.Content.ReadAsStringAsync(cancellationToken); - _logger.LogWarning( - nameof(NameRegistryClient) + ".SendRequest failed with non-successful status code: {StatusCode} {Response}", - response.StatusCode, errorResponse); - - return null; - } + var nameLookupResult = await _client.PostAsJsonEnsuredAsync( + apiUrl, + nameLookup, + serializerOptions: SerializerOptions, + cancellationToken: cancellationToken); - var responseData = await response.Content.ReadAsStringAsync(cancellationToken); - var nameLookupResult = JsonSerializer.Deserialize(responseData, SerializerOptions); - return nameLookupResult?.PartyNames.FirstOrDefault()?.Name; + return nameLookupResult.PartyNames.FirstOrDefault()?.Name; } private sealed class NameLookup diff --git a/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/OrganizationRegistry/OrganizationRegistryClient.cs b/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/OrganizationRegistry/OrganizationRegistryClient.cs index a371f9dcf..316c4aeb1 100644 --- a/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/OrganizationRegistry/OrganizationRegistryClient.cs +++ b/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/OrganizationRegistry/OrganizationRegistryClient.cs @@ -1,7 +1,4 @@ -using System.Diagnostics; -using System.Net.Http.Json; using Digdir.Domain.Dialogporten.Application.Externals; -using Microsoft.Extensions.Caching.Distributed; using ZiggyCreatures.Caching.Fusion; namespace Digdir.Domain.Dialogporten.Infrastructure.Altinn.OrganizationRegistry; @@ -10,9 +7,6 @@ internal class OrganizationRegistryClient : IOrganizationRegistry { private const string OrgNameReferenceCacheKey = "OrgNameReference"; - private static readonly DistributedCacheEntryOptions OneDayCacheDuration = new() { AbsoluteExpiration = DateTimeOffset.UtcNow.AddDays(1) }; - private static readonly DistributedCacheEntryOptions ZeroCacheDuration = new() { AbsoluteExpiration = DateTimeOffset.MinValue }; - private readonly IFusionCache _cache; private readonly HttpClient _client; @@ -33,8 +27,9 @@ public OrganizationRegistryClient(HttpClient client, IFusionCacheProvider cacheP private async Task> GetOrgInfo(CancellationToken cancellationToken) { const string searchEndpoint = "orgs/altinn-orgs.json"; + var response = await _client - .GetFromJsonAsync(searchEndpoint, cancellationToken) ?? throw new UnreachableException(); + .GetFromJsonEnsuredAsync(searchEndpoint, cancellationToken: cancellationToken); var orgInfoByOrgNumber = response .Orgs @@ -65,4 +60,4 @@ private sealed class OrganizationDetails public string? Homepage { get; init; } public IList? Environments { get; init; } } -} \ No newline at end of file +} diff --git a/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/ResourceRegistry/ResourceRegistryClient.cs b/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/ResourceRegistry/ResourceRegistryClient.cs index 903636dbd..21a0cb193 100644 --- a/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/ResourceRegistry/ResourceRegistryClient.cs +++ b/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/ResourceRegistry/ResourceRegistryClient.cs @@ -34,9 +34,10 @@ public async Task> GetResourceIds(string org, Cancel private async Task> GetResourceIdsByOrg(CancellationToken cancellationToken) { const string searchEndpoint = "resourceregistry/api/v1/resource/resourcelist"; + var response = await _client - .GetFromJsonAsync>(searchEndpoint, cancellationToken) - ?? throw new UnreachableException(); + .GetFromJsonEnsuredAsync>(searchEndpoint, + cancellationToken: cancellationToken); var resourceIdsByOrg = response .Where(x => x.ResourceType is ResourceTypeGenericAccess or ResourceTypeAltinnApp) diff --git a/src/Digdir.Domain.Dialogporten.Infrastructure/HttpClientExtensions.cs b/src/Digdir.Domain.Dialogporten.Infrastructure/HttpClientExtensions.cs new file mode 100644 index 000000000..f00d57453 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Infrastructure/HttpClientExtensions.cs @@ -0,0 +1,61 @@ +using System.Net.Http.Headers; +using System.Net.Http.Json; +using System.Text.Json; + +namespace Digdir.Domain.Dialogporten.Infrastructure; + +public static class HttpClientExtensions +{ + public static async Task GetFromJsonEnsuredAsync( + this HttpClient client, + string requestUri, + Action? configureHeaders = null, + CancellationToken cancellationToken = default) + { + var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, requestUri); + configureHeaders?.Invoke(httpRequestMessage.Headers); + var response = await client.SendAsync(httpRequestMessage, cancellationToken); + response.EnsureSuccessStatusCode(); + var result = await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken); + return result is null + ? throw new InvalidOperationException($"Failed to deserialize JSON from {requestUri}") + : result; + } + + public static async Task PostAsJsonEnsuredAsync( + this HttpClient client, + string requestUri, + object content, + Action? configureHeaders = null, + Action? configureContentHeaders = null, + JsonSerializerOptions? serializerOptions = null, + CancellationToken cancellationToken = default) + { + var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, requestUri) + { + Content = JsonContent.Create(content, options: serializerOptions) + }; + configureHeaders?.Invoke(httpRequestMessage.Headers); + configureContentHeaders?.Invoke(httpRequestMessage.Content.Headers); + var response = await client.SendAsync(httpRequestMessage, cancellationToken); + response.EnsureSuccessStatusCode(); + return response; + } + + public static async Task PostAsJsonEnsuredAsync( + this HttpClient client, + string requestUri, + object content, + Action? configureHeaders = null, + Action? configureContentHeaders = null, + JsonSerializerOptions? serializerOptions = null, + CancellationToken cancellationToken = default) + { + var response = await client.PostAsJsonEnsuredAsync(requestUri, content, configureHeaders, + configureContentHeaders, serializerOptions, cancellationToken); + var result = await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken); + return result is null + ? throw new InvalidOperationException($"Failed to deserialize JSON to type {typeof(T).FullName} from {requestUri}") + : result; + } +} From a8ee1c2bbf4a46645686d54e2e8005d39a017090 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Dybvik=20Langfors?= Date: Mon, 29 Apr 2024 20:14:47 +0200 Subject: [PATCH 2/6] Attempt to handle upstream HttpRequestExceptions as 502s downstream --- .../Extensions/ErrorResponseBuilderExtensions.cs | 10 ++++++++++ .../Common/Extensions/ExceptionHandlerExtensions.cs | 4 +++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Digdir.Domain.Dialogporten.WebApi/Common/Extensions/ErrorResponseBuilderExtensions.cs b/src/Digdir.Domain.Dialogporten.WebApi/Common/Extensions/ErrorResponseBuilderExtensions.cs index c092029de..6f137bbbc 100644 --- a/src/Digdir.Domain.Dialogporten.WebApi/Common/Extensions/ErrorResponseBuilderExtensions.cs +++ b/src/Digdir.Domain.Dialogporten.WebApi/Common/Extensions/ErrorResponseBuilderExtensions.cs @@ -1,6 +1,7 @@ using FluentValidation.Results; using Microsoft.AspNetCore.Mvc; using System.Diagnostics; +using Altinn.Authorization.ABAC.Constants; namespace Digdir.Domain.Dialogporten.WebApi.Common.Extensions; @@ -64,6 +65,15 @@ public static object ResponseBuilder(List failures, HttpConte Instance = ctx.Request.Path, Extensions = { { "traceId", Activity.Current?.Id ?? ctx.TraceIdentifier } } }, + StatusCodes.Status502BadGateway => new ProblemDetails + { + Title = "Bad gateway.", + Detail = "An upstream server returned an invalid response. Please try again later.", + Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.6.3", + Status = statusCode, + Instance = ctx.Request.Path, + Extensions = { { "traceId", Activity.Current?.Id ?? ctx.TraceIdentifier } } + }, _ => new ProblemDetails { Title = "An error occurred while processing the request.", diff --git a/src/Digdir.Domain.Dialogporten.WebApi/Common/Extensions/ExceptionHandlerExtensions.cs b/src/Digdir.Domain.Dialogporten.WebApi/Common/Extensions/ExceptionHandlerExtensions.cs index e7bf33b5a..57e2fdf56 100644 --- a/src/Digdir.Domain.Dialogporten.WebApi/Common/Extensions/ExceptionHandlerExtensions.cs +++ b/src/Digdir.Domain.Dialogporten.WebApi/Common/Extensions/ExceptionHandlerExtensions.cs @@ -22,7 +22,9 @@ public static IApplicationBuilder UseProblemDetailsExceptionHandler(this IApplic var error = exHandlerFeature.Error.Message; var logger = ctx.Resolve>(); logger.LogError(exHandlerFeature.Error, "{@Http}{@Type}{@Reason}", http, type, error); - ctx.Response.StatusCode = StatusCodes.Status500InternalServerError; + ctx.Response.StatusCode = type == nameof(HttpRequestException) + ? StatusCodes.Status502BadGateway + : StatusCodes.Status500InternalServerError; ctx.Response.ContentType = "application/problem+json"; await ctx.Response.WriteAsJsonAsync(ctx.ResponseBuilder(ctx.Response.StatusCode)); }); From b50a7acb346e970931a18d64d245d8f4d835b972 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Dybvik=20Langfors?= Date: Tue, 30 Apr 2024 08:23:19 +0200 Subject: [PATCH 3/6] Improve error message --- .../Common/Extensions/ErrorResponseBuilderExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Digdir.Domain.Dialogporten.WebApi/Common/Extensions/ErrorResponseBuilderExtensions.cs b/src/Digdir.Domain.Dialogporten.WebApi/Common/Extensions/ErrorResponseBuilderExtensions.cs index 6f137bbbc..5061868af 100644 --- a/src/Digdir.Domain.Dialogporten.WebApi/Common/Extensions/ErrorResponseBuilderExtensions.cs +++ b/src/Digdir.Domain.Dialogporten.WebApi/Common/Extensions/ErrorResponseBuilderExtensions.cs @@ -68,7 +68,7 @@ public static object ResponseBuilder(List failures, HttpConte StatusCodes.Status502BadGateway => new ProblemDetails { Title = "Bad gateway.", - Detail = "An upstream server returned an invalid response. Please try again later.", + Detail = "An upstream server is down or returned an invalid response. Please try again later.", Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.6.3", Status = statusCode, Instance = ctx.Request.Path, From abc756b8c88ec7dbef15eec5ba3f88d9e9d63f55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Dybvik=20Langfors?= Date: Tue, 30 Apr 2024 13:00:26 +0200 Subject: [PATCH 4/6] Update src/Digdir.Domain.Dialogporten.WebApi/Common/Extensions/ErrorResponseBuilderExtensions.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ole Jørgen Skogstad --- .../Common/Extensions/ErrorResponseBuilderExtensions.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Digdir.Domain.Dialogporten.WebApi/Common/Extensions/ErrorResponseBuilderExtensions.cs b/src/Digdir.Domain.Dialogporten.WebApi/Common/Extensions/ErrorResponseBuilderExtensions.cs index 5061868af..08ca550e4 100644 --- a/src/Digdir.Domain.Dialogporten.WebApi/Common/Extensions/ErrorResponseBuilderExtensions.cs +++ b/src/Digdir.Domain.Dialogporten.WebApi/Common/Extensions/ErrorResponseBuilderExtensions.cs @@ -1,7 +1,6 @@ using FluentValidation.Results; using Microsoft.AspNetCore.Mvc; using System.Diagnostics; -using Altinn.Authorization.ABAC.Constants; namespace Digdir.Domain.Dialogporten.WebApi.Common.Extensions; From 497abcfc3f6bd41ecc6be7d5a4a925839d0e5c26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Dybvik=20Langfors?= Date: Tue, 30 Apr 2024 13:34:38 +0200 Subject: [PATCH 5/6] Change exception type --- .../HttpClientExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Digdir.Domain.Dialogporten.Infrastructure/HttpClientExtensions.cs b/src/Digdir.Domain.Dialogporten.Infrastructure/HttpClientExtensions.cs index f00d57453..00ddbb1a9 100644 --- a/src/Digdir.Domain.Dialogporten.Infrastructure/HttpClientExtensions.cs +++ b/src/Digdir.Domain.Dialogporten.Infrastructure/HttpClientExtensions.cs @@ -18,7 +18,7 @@ public static async Task GetFromJsonEnsuredAsync( response.EnsureSuccessStatusCode(); var result = await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken); return result is null - ? throw new InvalidOperationException($"Failed to deserialize JSON from {requestUri}") + ? throw new JsonException($"Failed to deserialize JSON to type {typeof(T).FullName} from {requestUri}") : result; } @@ -55,7 +55,7 @@ public static async Task PostAsJsonEnsuredAsync( configureContentHeaders, serializerOptions, cancellationToken); var result = await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken); return result is null - ? throw new InvalidOperationException($"Failed to deserialize JSON to type {typeof(T).FullName} from {requestUri}") + ? throw new JsonException($"Failed to deserialize JSON to type {typeof(T).FullName} from {requestUri}") : result; } } From 2127c1c1fdaf35db4de7cba51ff2289d8fa69101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Dybvik=20Langfors?= Date: Tue, 30 Apr 2024 14:01:45 +0200 Subject: [PATCH 6/6] Add upstream error exception wrapper --- .../Exceptions/UpstreamServiceException.cs | 5 ++ .../HttpClientExtensions.cs | 62 +++++++++++++------ .../Extensions/ExceptionHandlerExtensions.cs | 5 +- 3 files changed, 50 insertions(+), 22 deletions(-) create mode 100644 src/Digdir.Domain.Dialogporten.Infrastructure/Common/Exceptions/UpstreamServiceException.cs diff --git a/src/Digdir.Domain.Dialogporten.Infrastructure/Common/Exceptions/UpstreamServiceException.cs b/src/Digdir.Domain.Dialogporten.Infrastructure/Common/Exceptions/UpstreamServiceException.cs new file mode 100644 index 000000000..8505d6f2e --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Infrastructure/Common/Exceptions/UpstreamServiceException.cs @@ -0,0 +1,5 @@ +namespace Digdir.Domain.Dialogporten.Infrastructure.Common.Exceptions; + +public interface IUpstreamServiceError; +public class UpstreamServiceException(Exception innerException) + : Exception(innerException.Message, innerException), IUpstreamServiceError; diff --git a/src/Digdir.Domain.Dialogporten.Infrastructure/HttpClientExtensions.cs b/src/Digdir.Domain.Dialogporten.Infrastructure/HttpClientExtensions.cs index 00ddbb1a9..0cb237e07 100644 --- a/src/Digdir.Domain.Dialogporten.Infrastructure/HttpClientExtensions.cs +++ b/src/Digdir.Domain.Dialogporten.Infrastructure/HttpClientExtensions.cs @@ -1,6 +1,7 @@ using System.Net.Http.Headers; using System.Net.Http.Json; using System.Text.Json; +using Digdir.Domain.Dialogporten.Infrastructure.Common.Exceptions; namespace Digdir.Domain.Dialogporten.Infrastructure; @@ -12,14 +13,21 @@ public static async Task GetFromJsonEnsuredAsync( Action? configureHeaders = null, CancellationToken cancellationToken = default) { - var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, requestUri); - configureHeaders?.Invoke(httpRequestMessage.Headers); - var response = await client.SendAsync(httpRequestMessage, cancellationToken); - response.EnsureSuccessStatusCode(); - var result = await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken); - return result is null - ? throw new JsonException($"Failed to deserialize JSON to type {typeof(T).FullName} from {requestUri}") - : result; + try + { + var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, requestUri); + configureHeaders?.Invoke(httpRequestMessage.Headers); + var response = await client.SendAsync(httpRequestMessage, cancellationToken); + response.EnsureSuccessStatusCode(); + var result = await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken); + return result is null + ? throw new JsonException($"Failed to deserialize JSON to type {typeof(T).FullName} from {requestUri}") + : result; + } + catch (Exception e) + { + throw new UpstreamServiceException(e); + } } public static async Task PostAsJsonEnsuredAsync( @@ -31,15 +39,22 @@ public static async Task PostAsJsonEnsuredAsync( JsonSerializerOptions? serializerOptions = null, CancellationToken cancellationToken = default) { - var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, requestUri) + try + { + var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, requestUri) + { + Content = JsonContent.Create(content, options: serializerOptions) + }; + configureHeaders?.Invoke(httpRequestMessage.Headers); + configureContentHeaders?.Invoke(httpRequestMessage.Content.Headers); + var response = await client.SendAsync(httpRequestMessage, cancellationToken); + response.EnsureSuccessStatusCode(); + return response; + } + catch (Exception e) { - Content = JsonContent.Create(content, options: serializerOptions) - }; - configureHeaders?.Invoke(httpRequestMessage.Headers); - configureContentHeaders?.Invoke(httpRequestMessage.Content.Headers); - var response = await client.SendAsync(httpRequestMessage, cancellationToken); - response.EnsureSuccessStatusCode(); - return response; + throw new UpstreamServiceException(e); + } } public static async Task PostAsJsonEnsuredAsync( @@ -53,9 +68,16 @@ public static async Task PostAsJsonEnsuredAsync( { var response = await client.PostAsJsonEnsuredAsync(requestUri, content, configureHeaders, configureContentHeaders, serializerOptions, cancellationToken); - var result = await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken); - return result is null - ? throw new JsonException($"Failed to deserialize JSON to type {typeof(T).FullName} from {requestUri}") - : result; + try + { + var result = await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken); + return result is null + ? throw new JsonException($"Failed to deserialize JSON to type {typeof(T).FullName} from {requestUri}") + : result; + } + catch (Exception e) + { + throw new UpstreamServiceException(e); + } } } diff --git a/src/Digdir.Domain.Dialogporten.WebApi/Common/Extensions/ExceptionHandlerExtensions.cs b/src/Digdir.Domain.Dialogporten.WebApi/Common/Extensions/ExceptionHandlerExtensions.cs index 57e2fdf56..9e447c760 100644 --- a/src/Digdir.Domain.Dialogporten.WebApi/Common/Extensions/ExceptionHandlerExtensions.cs +++ b/src/Digdir.Domain.Dialogporten.WebApi/Common/Extensions/ExceptionHandlerExtensions.cs @@ -1,4 +1,5 @@ -using FastEndpoints; +using Digdir.Domain.Dialogporten.Infrastructure.Common.Exceptions; +using FastEndpoints; using Microsoft.AspNetCore.Diagnostics; namespace Digdir.Domain.Dialogporten.WebApi.Common.Extensions; @@ -22,7 +23,7 @@ public static IApplicationBuilder UseProblemDetailsExceptionHandler(this IApplic var error = exHandlerFeature.Error.Message; var logger = ctx.Resolve>(); logger.LogError(exHandlerFeature.Error, "{@Http}{@Type}{@Reason}", http, type, error); - ctx.Response.StatusCode = type == nameof(HttpRequestException) + ctx.Response.StatusCode = exHandlerFeature.Error is IUpstreamServiceError ? StatusCodes.Status502BadGateway : StatusCodes.Status500InternalServerError; ctx.Response.ContentType = "application/problem+json";