// API.Core/Filters/GlobalExceptionFilter.cs using Domain.Exceptions; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Data.SqlClient; using Shared.Contracts; using ValidationException = FluentValidation.ValidationException; namespace API.Core; /// /// MVC exception filter that converts unhandled exceptions raised by controller actions into /// consistent JSON error responses with appropriate HTTP status codes. /// public class GlobalExceptionFilter(ILogger logger) : IExceptionFilter { /// /// Logs the exception and sets to an appropriate error response. /// /// /// Maps to 400, to 409, /// to 404, to 401, /// to 403, to 503, /// to 400, and anything else to 500. /// public void OnException(ExceptionContext context) { logger.LogError(context.Exception, "Unhandled exception occurred"); switch (context.Exception) { case ValidationException fluentValidationException: Dictionary errors = fluentValidationException .Errors.GroupBy(e => e.PropertyName) .ToDictionary(g => g.Key, g => g.Select(e => e.ErrorMessage).ToArray()); context.Result = new BadRequestObjectResult( new { message = "Validation failed", errors } ); context.ExceptionHandled = true; break; case ConflictException ex: context.Result = new ObjectResult(new ResponseBody { Message = ex.Message }) { StatusCode = 409, }; context.ExceptionHandled = true; break; case NotFoundException ex: context.Result = new ObjectResult(new ResponseBody { Message = ex.Message }) { StatusCode = 404, }; context.ExceptionHandled = true; break; case UnauthorizedException ex: context.Result = new ObjectResult(new ResponseBody { Message = ex.Message }) { StatusCode = 401, }; context.ExceptionHandled = true; break; case ForbiddenException ex: context.Result = new ObjectResult(new ResponseBody { Message = ex.Message }) { StatusCode = 403, }; context.ExceptionHandled = true; break; case SqlException ex: context.Result = new ObjectResult( new ResponseBody { Message = "A database error occurred." } ) { StatusCode = 503, }; context.ExceptionHandled = true; break; case Domain.Exceptions.ValidationException ex: context.Result = new ObjectResult(new ResponseBody { Message = ex.Message }) { StatusCode = 400, }; context.ExceptionHandled = true; break; default: context.Result = new ObjectResult( new ResponseBody { Message = "An unexpected error occurred" } ) { StatusCode = 500, }; context.ExceptionHandled = true; break; } } }