package apperrors import ( "errors" "fmt" "net/http" "github.com/go-playground/validator/v10" "github.com/labstack/echo/v4" "github.com/rs/zerolog/log" "github.com/treytartt/casera-api/internal/dto/responses" "github.com/treytartt/casera-api/internal/i18n" customvalidator "github.com/treytartt/casera-api/internal/validator" ) // HTTPErrorHandler handles all errors returned from handlers in a consistent way. // It converts AppErrors, validation errors, and Echo HTTPErrors to JSON responses. // This is the base handler - additional service-level error handling can be added in router.go. func HTTPErrorHandler(err error, c echo.Context) { // Already committed? Skip if c.Response().Committed { return } // Handle AppError (our custom application errors) var appErr *AppError if errors.As(err, &appErr) { message := i18n.LocalizedMessage(c, appErr.MessageKey) // If i18n key not found (returns the key itself), use fallback message if message == appErr.MessageKey && appErr.Message != "" { message = appErr.Message } else if message == appErr.MessageKey { message = appErr.MessageKey // Use the key as last resort } // Log internal errors if appErr.Err != nil { log.Error().Err(appErr.Err).Str("message_key", appErr.MessageKey).Msg("Application error") } c.JSON(appErr.Code, responses.ErrorResponse{Error: message}) return } // Handle validation errors from go-playground/validator var validationErrs validator.ValidationErrors if errors.As(err, &validationErrs) { c.JSON(http.StatusBadRequest, customvalidator.FormatValidationErrors(err)) return } // Handle Echo's built-in HTTPError var httpErr *echo.HTTPError if errors.As(err, &httpErr) { msg := fmt.Sprintf("%v", httpErr.Message) c.JSON(httpErr.Code, responses.ErrorResponse{Error: msg}) return } // Default: Internal server error (don't expose error details to client) log.Error().Err(err).Msg("Unhandled error") c.JSON(http.StatusInternalServerError, responses.ErrorResponse{ Error: i18n.LocalizedMessage(c, "error.internal"), }) }