Files
bookhoard/internal/middleware/error_handler.go
T
john-okeefe 48725544f5 refactor: use errors.Is()/errors.AsType() for error comparison and rename shadowed variables
Replace direct error equality checks (err == pgx.ErrNoRows, err != http.ErrServerClosed)
with the idiomatic errors.Is() function throughout handlers, services, middleware, and app
startup. This correctly handles wrapped error chains.

Also replace a raw type assertion (*HTTPError) with errors.AsType[*HTTPError]() in the
error handler middleware for consistency.

Additionally, rename shadowed variables for clarity:
- sidecar.go: config -> sidecarConfig, systemConfig (shadowed package-level vars)
- media_scanner.go: uuid -> uuidString (shadowed the uuid package import)
2026-04-20 21:20:22 -04:00

95 lines
2.5 KiB
Go

package middleware
import (
"errors"
"fmt"
"net/http"
"github.com/labstack/echo/v5"
)
// ErrorResponse represents a standardized error response
type ErrorResponse struct {
Error string `json:"error"`
Message string `json:"message,omitempty"`
Code string `json:"code,omitempty"`
}
// HTTPError represents an HTTP error with additional context
type HTTPError struct {
Code int
Message string
Err error
}
// Error implements the error interface
func (e *HTTPError) Error() string {
if e.Err != nil {
return fmt.Sprintf("%s: %v", e.Message, e.Err)
}
return e.Message
}
// Common error types
var (
ErrBadRequest = &HTTPError{Code: http.StatusBadRequest, Message: "Bad request"}
ErrUnauthorized = &HTTPError{Code: http.StatusUnauthorized, Message: "Unauthorized"}
ErrForbidden = &HTTPError{Code: http.StatusForbidden, Message: "Forbidden"}
ErrNotFound = &HTTPError{Code: http.StatusNotFound, Message: "Resource not found"}
ErrConflict = &HTTPError{Code: http.StatusConflict, Message: "Resource conflict"}
ErrTooManyRequest = &HTTPError{Code: http.StatusTooManyRequests, Message: "Too many requests"}
ErrInternal = &HTTPError{Code: http.StatusInternalServerError, Message: "Internal server error"}
)
// NewHTTPError creates a new HTTP error
func NewHTTPError(code int, message string, err error) *HTTPError {
return &HTTPError{
Code: code,
Message: message,
Err: err,
}
}
// RespondWithError sends a standardized error response
func RespondWithError(c *echo.Context, code int, message string, err error) error {
response := ErrorResponse{
Error: message,
Message: "",
Code: "",
}
// Include original error message in development mode if available
if err != nil {
response.Message = err.Error()
}
return c.JSON(code, response)
}
// RespondWithHTTPError sends an HTTPError as JSON
func RespondWithHTTPError(c *echo.Context, httpErr *HTTPError) error {
response := ErrorResponse{
Error: httpErr.Message,
}
if httpErr.Err != nil {
response.Message = httpErr.Err.Error()
}
return c.JSON(httpErr.Code, response)
}
// WrapHandler wraps an echo.HandlerFunc to return standardized errors
func WrapHandler(fn func(*echo.Context) error) echo.HandlerFunc {
return func(c *echo.Context) error {
err := fn(c)
if err != nil {
if httpErr, ok := errors.AsType[*HTTPError](err); ok {
return RespondWithHTTPError(c, httpErr)
}
return RespondWithError(c, http.StatusInternalServerError, "Internal server error", err)
}
return nil
}
}