using DocumentOperator.Domain.Common.Exceptions; using FluentValidation; using Microsoft.AspNetCore.Mvc; using System.Net; using System.Text.Json; namespace DocumentOperator.API.Middleware; /// /// Central exception handling middleware /// Maps exceptions to HTTP status codes and RFC 7807 Problem Details /// /// /// Initializes a new instance of the class. /// /// The next middleware in the pipeline. public class ExceptionHandlingMiddleware(RequestDelegate Next) { private static readonly JsonSerializerOptions ProbDetailsJsonOpt = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; /// /// Invokes the middleware to handle incoming HTTP requests and catch exceptions. /// /// The HTTP context for the current request. public async Task InvokeAsync(HttpContext context) { try { await Next(context); } catch (Exception ex) { await HandleExceptionAsync(context, ex); } } private static async Task HandleExceptionAsync(HttpContext context, Exception exception) { var (statusCode, problemDetails) = MapExceptionToProblemDetails(exception, context); context.Response.StatusCode = (int)statusCode; context.Response.ContentType = "application/problem+json"; await context.Response.WriteAsync(JsonSerializer.Serialize(problemDetails, ProbDetailsJsonOpt)); } private static (HttpStatusCode StatusCode, ProblemDetails ProblemDetails) MapExceptionToProblemDetails( Exception exception, HttpContext context) { return exception switch { // FluentValidation (400 Bad Request) ValidationException validationEx => ( HttpStatusCode.BadRequest, new ProblemDetails { Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.1", Title = "Validation Error", Status = (int)HttpStatusCode.BadRequest, Detail = string.Join("; ", validationEx.Errors.Select(e => e.ErrorMessage)), Instance = context.Request.Path } ), // Bad Request Exception (400 Bad Request) BadRequestException badReqEx => ( HttpStatusCode.BadRequest, new ProblemDetails { Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.4", Title = "Bad Request", Status = (int)HttpStatusCode.BadRequest, Detail = badReqEx.Message, Instance = context.Request.Path } ), // Not Found Exception (404 Not Found) NotFoundException notFoundEx => ( HttpStatusCode.NotFound, new ProblemDetails { Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.4", Title = "Resource Not Found", Status = (int)HttpStatusCode.NotFound, Detail = notFoundEx.Message, Instance = context.Request.Path } ), // Generic Exception (500 Internal Server Error) _ => ( HttpStatusCode.InternalServerError, new ProblemDetails { Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.6.1", Title = "Internal Server Error", Status = (int)HttpStatusCode.InternalServerError, Detail = "An unexpected error occurred. Please contact support.", Instance = context.Request.Path } ) }; } }