using System.Text.Json; namespace MealPlan.Api.Middleware; /// /// Converts unhandled exceptions into RFC-7807-style JSON problem responses and logs them. /// Internal details are never leaked to the client. /// public class ExceptionMiddleware { private readonly RequestDelegate _next; private readonly ILogger _logger; public ExceptionMiddleware(RequestDelegate next, ILogger logger) { _next = next; _logger = logger; } public async Task InvokeAsync(HttpContext context) { try { await _next(context); } catch (OperationCanceledException) when (context.RequestAborted.IsCancellationRequested) { // Client disconnected; nothing actionable to report. _logger.LogDebug("Request {Path} was cancelled by the client", context.Request.Path); } catch (Exception ex) { _logger.LogError(ex, "Unhandled exception for {Method} {Path}", context.Request.Method, context.Request.Path); if (context.Response.HasStarted) { throw; } context.Response.Clear(); context.Response.StatusCode = StatusCodes.Status500InternalServerError; context.Response.ContentType = "application/problem+json"; var payload = new { type = "https://httpstatuses.io/500", title = "An unexpected error occurred.", status = StatusCodes.Status500InternalServerError, traceId = context.TraceIdentifier, }; await context.Response.WriteAsync(JsonSerializer.Serialize(payload)); } } } public static class ExceptionMiddlewareExtensions { public static IApplicationBuilder UseExceptionMiddleware(this IApplicationBuilder app) => app.UseMiddleware(); }