From 6391eeed236020299b6342f34e9b1ec088759fd5 Mon Sep 17 00:00:00 2001 From: Phil Schneider Date: Thu, 13 Feb 2025 13:39:29 +0100 Subject: [PATCH] fix: adjust error handling (#261) * use framework-version of GeneralHttpErrorHandler --- .../PolicyHub.DbAccess.csproj | 4 +- .../PolicyHub.Migrations.csproj | 6 +- .../ErrorHandling/GeneralHttpErrorHandler.cs | 144 ------------------ .../PolicyHub.Service.csproj | 4 +- src/hub/PolicyHub.Service/Program.cs | 4 +- 5 files changed, 10 insertions(+), 152 deletions(-) delete mode 100644 src/hub/PolicyHub.Service/ErrorHandling/GeneralHttpErrorHandler.cs diff --git a/src/database/PolicyHub.DbAccess/PolicyHub.DbAccess.csproj b/src/database/PolicyHub.DbAccess/PolicyHub.DbAccess.csproj index 03102e2..5f438e5 100644 --- a/src/database/PolicyHub.DbAccess/PolicyHub.DbAccess.csproj +++ b/src/database/PolicyHub.DbAccess/PolicyHub.DbAccess.csproj @@ -33,8 +33,8 @@ - - + + diff --git a/src/database/PolicyHub.Migrations/PolicyHub.Migrations.csproj b/src/database/PolicyHub.Migrations/PolicyHub.Migrations.csproj index 70f5315..04dd968 100644 --- a/src/database/PolicyHub.Migrations/PolicyHub.Migrations.csproj +++ b/src/database/PolicyHub.Migrations/PolicyHub.Migrations.csproj @@ -45,9 +45,9 @@ - - - + + + diff --git a/src/hub/PolicyHub.Service/ErrorHandling/GeneralHttpErrorHandler.cs b/src/hub/PolicyHub.Service/ErrorHandling/GeneralHttpErrorHandler.cs deleted file mode 100644 index 64001ec..0000000 --- a/src/hub/PolicyHub.Service/ErrorHandling/GeneralHttpErrorHandler.cs +++ /dev/null @@ -1,144 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2025 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ - -using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; -using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling.Service; -using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling.Web; -using Serilog.Context; -using System.Collections.Immutable; -using System.Net; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Org.Eclipse.TractusX.PolicyHub.Service.ErrorHandling; - -public class GeneralHttpErrorHandler( - RequestDelegate next, - ILogger logger, - IErrorMessageService errorMessageService) -{ - private static readonly JsonSerializerOptions Options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; - private readonly ILogger _logger = logger; - - private static readonly IReadOnlyDictionary Metadata = ImmutableDictionary.CreateRange(new[] - { - KeyValuePair.Create(HttpStatusCode.BadRequest, new MetaData("https://tools.ietf.org/html/rfc7231#section-6.5.1", "One or more validation errors occurred.")), - KeyValuePair.Create(HttpStatusCode.Conflict, new MetaData("https://tools.ietf.org/html/rfc7231#section-6.5.8", "The resource is in conflict with the current request.")), - KeyValuePair.Create(HttpStatusCode.NotFound, new MetaData("https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.4", "Cannot find representation of target resource.")), - KeyValuePair.Create(HttpStatusCode.Forbidden, new MetaData("https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.3", "Access to requested resource is not permitted.")), - KeyValuePair.Create(HttpStatusCode.UnsupportedMediaType, new MetaData("https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.13", "The server cannot process this type of content")), - KeyValuePair.Create(HttpStatusCode.BadGateway, new MetaData("https://datatracker.ietf.org/doc/html/rfc7231#section-6.6.3", "Error accessing external resource.")), - KeyValuePair.Create(HttpStatusCode.ServiceUnavailable, new MetaData("https://datatracker.ietf.org/doc/html/rfc7231#section-6.6.4", "Service is currently unavailable.")), - KeyValuePair.Create(HttpStatusCode.InternalServerError, new MetaData("https://datatracker.ietf.org/doc/html/rfc7231#section-6.6.1", "The server encountered an unexpected condition.")), - }); - - private static KeyValuePair)>? MessageFunc, LogLevel LogLevel)> CreateErrorEntry( - HttpStatusCode httpStatusCode, - Func)>? messageFunc = null, - LogLevel logLevel = LogLevel.Information - ) where T : class => - KeyValuePair.Create)>?, LogLevel)>( - typeof(T), - (httpStatusCode, - messageFunc == null - ? null - : e => messageFunc.Invoke(e as T ?? throw new UnexpectedConditionException($"Exception type {e.GetType()} should always be of type {typeof(T)} here")), - logLevel)); - - private static readonly IReadOnlyDictionary)>? MessageFunc, LogLevel LogLevel)> ErrorTypes = ImmutableDictionary.CreateRange(new[] - { - CreateErrorEntry(HttpStatusCode.BadRequest, argumentException => (argumentException.ParamName, Enumerable.Repeat(argumentException.Message, 1))), - CreateErrorEntry(HttpStatusCode.BadRequest, caException => (caException.ParamName, Enumerable.Repeat(caException.Message, 1))), - CreateErrorEntry(HttpStatusCode.NotFound), - CreateErrorEntry(HttpStatusCode.Conflict), - CreateErrorEntry(HttpStatusCode.Forbidden), - CreateErrorEntry(HttpStatusCode.BadGateway, serviceException => (serviceException.Source, new[] { serviceException.StatusCode == null ? "remote service call failed" : $"remote service returned status code: {(int) serviceException.StatusCode} {serviceException.StatusCode}", serviceException.Message })), - CreateErrorEntry(HttpStatusCode.UnsupportedMediaType), - CreateErrorEntry(HttpStatusCode.InternalServerError, configurationException => (configurationException.Source, new[] { $"Invalid service configuration: {configurationException.Message}" })) - }); - - public async Task Invoke(HttpContext context) - { - try - { - await next(context).ConfigureAwait(ConfigureAwaitOptions.None); - } - catch (Exception error) - { - var errorId = Guid.NewGuid().ToString(); - var details = GetErrorDetails(error); - var message = GetErrorMessage(error); - LogErrorInformation(errorId, error); - var (statusCode, messageFunc, logLevel) = GetErrorInformation(error); - _logger.Log(logLevel, error, "GeneralErrorHandler caught {Error} with errorId: {ErrorId} resulting in response status code {StatusCode}, message '{Message}'", error.GetType().Name, errorId, (int)statusCode, message); - context.Response.ContentType = "application/json"; - context.Response.StatusCode = (int)statusCode; - await context.Response.WriteAsync(JsonSerializer.Serialize(CreateErrorResponse(statusCode, error, errorId, message, details, messageFunc), Options)).ConfigureAwait(ConfigureAwaitOptions.None); - } - } - - private static (HttpStatusCode StatusCode, Func)>? MessageFunc, LogLevel LogLevel) GetErrorInformation(Exception error) => - ErrorTypes.TryGetValue(error.GetType(), out var mapping) - ? mapping - : (HttpStatusCode.InternalServerError, null, LogLevel.Error); - - private ErrorResponse CreateErrorResponse(HttpStatusCode statusCode, Exception error, string errorId, string message, IEnumerable? details, Func)>? getSourceAndMessages) - { - var meta = Metadata.GetValueOrDefault(statusCode, Metadata[HttpStatusCode.InternalServerError]); - var (source, messages) = getSourceAndMessages?.Invoke(error) ?? (error.Source, Enumerable.Repeat(message, 1)); - - var messageMap = new Dictionary> { { source ?? "unknown", messages } }; - while (error.InnerException != null) - { - error = error.InnerException; - source = error.Source ?? "inner"; - - messageMap[source] = messageMap.TryGetValue(source, out messages) - ? messages.Append(GetErrorMessage(error)) - : Enumerable.Repeat(GetErrorMessage(error), 1); - } - - return new ErrorResponse( - meta.Url, - meta.Description, - (int)statusCode, - messageMap, - errorId, - details - ); - } - - private string GetErrorMessage(Exception exception) => - exception is DetailException { HasDetails: true } detail - ? detail.GetErrorMessage(errorMessageService) - : exception.Message; - - private IEnumerable GetErrorDetails(Exception exception) => - exception is DetailException { HasDetails: true } detail - ? detail.GetErrorDetails(errorMessageService) - : Enumerable.Empty(); - - private static void LogErrorInformation(string errorId, Exception exception) - { - LogContext.PushProperty("ErrorId", errorId); - LogContext.PushProperty("StackTrace", exception.StackTrace); - } - - private sealed record MetaData(string Url, string Description); -} diff --git a/src/hub/PolicyHub.Service/PolicyHub.Service.csproj b/src/hub/PolicyHub.Service/PolicyHub.Service.csproj index d2fc6a6..95a61ad 100644 --- a/src/hub/PolicyHub.Service/PolicyHub.Service.csproj +++ b/src/hub/PolicyHub.Service/PolicyHub.Service.csproj @@ -42,8 +42,8 @@ - - + + diff --git a/src/hub/PolicyHub.Service/Program.cs b/src/hub/PolicyHub.Service/Program.cs index 6c7b4ec..734aae3 100644 --- a/src/hub/PolicyHub.Service/Program.cs +++ b/src/hub/PolicyHub.Service/Program.cs @@ -23,6 +23,7 @@ using Org.Eclipse.TractusX.PolicyHub.Service.Controllers; using Org.Eclipse.TractusX.PolicyHub.Service.ErrorHandling; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling.Service; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling.Web; using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Extensions; using Org.Eclipse.TractusX.Portal.Backend.Framework.Web; using System.Text.Json.Serialization; @@ -34,6 +35,7 @@ await WebApplicationBuildRunner builder => { builder.Services + .AddTransient() .AddSingleton() .AddSingleton() .AddTransient() @@ -50,7 +52,7 @@ await WebApplicationBuildRunner }, (app, _) => { - app.UseMiddleware(); + app.UseMiddleware(); app.MapGroup("/api") .WithOpenApi() .MapPolicyHubApi();