Error Handling
This example demonstrates various error handling patterns in Fox, including simple errors, HTTP errors with status codes, and detailed error responses.
Features
Section titled “Features”- Simple error returns
- HTTP errors with status codes
- Custom error definitions
- Conditional error handling
- Error with additional details
- Panic recovery
- Consistent error responses
Complete Example
Section titled “Complete Example”package main
import ( "errors" "net/http"
"github.com/fox-gonic/fox" "github.com/fox-gonic/fox/httperrors")
// Define custom errorsvar ( ErrUserNotFound = &httperrors.Error{ HTTPCode: http.StatusNotFound, Code: "USER_NOT_FOUND", Err: errors.New("user not found"), }
ErrInsufficientBalance = &httperrors.Error{ HTTPCode: http.StatusPaymentRequired, Code: "INSUFFICIENT_BALANCE", Err: errors.New("insufficient account balance"), }
ErrDuplicateEmail = &httperrors.Error{ HTTPCode: http.StatusConflict, Code: "DUPLICATE_EMAIL", Err: errors.New("email already exists"), }
ErrInvalidCredentials = &httperrors.Error{ HTTPCode: http.StatusUnauthorized, Code: "INVALID_CREDENTIALS", Err: errors.New("invalid username or password"), })
type User struct { ID int `json:"id"` Name string `json:"name"` Email string `json:"email"` Balance float64 `json:"balance"`}
func main() { router := fox.Default()
// 1. Simple error router.GET("/error/simple", func() (string, error) { return "", errors.New("something went wrong") })
// 2. HTTP error with status code router.GET("/error/http", func() (string, error) { return "", &httperrors.Error{ HTTPCode: http.StatusBadRequest, Code: "INVALID_REQUEST", Err: errors.New("invalid request parameters"), } })
// 3. Conditional error (user not found) router.GET("/user/:id", func(ctx *fox.Context) (*User, error) { id := ctx.Param("id")
// Simulate database lookup if id != "1" { return nil, ErrUserNotFound }
return &User{ ID: 1, Name: "Alice", Email: "alice@example.com", Balance: 100.50, }, nil })
// 4. Business logic error (insufficient balance) type TransferRequest struct { From int `json:"from" binding:"required"` To int `json:"to" binding:"required"` Amount float64 `json:"amount" binding:"required,gt=0"` }
router.POST("/transfer", func(_ *fox.Context, req *TransferRequest) (map[string]any, error) { // Simulate checking balance currentBalance := 50.0
if req.Amount > currentBalance { return nil, ErrInsufficientBalance }
return map[string]any{ "message": "Transfer successful", "from": req.From, "to": req.To, "amount": req.Amount, }, nil })
// 5. Authentication error type LoginRequest struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` }
router.POST("/login", func(_ *fox.Context, req *LoginRequest) (map[string]any, error) { // Simulate credential check if req.Username != "admin" || req.Password != "password" { return nil, ErrInvalidCredentials }
return map[string]any{ "token": "jwt-token-here", }, nil })
// 6. Conflict error (duplicate email) type SignupRequest struct { Email string `json:"email" binding:"required,email"` Password string `json:"password" binding:"required,min=6"` }
router.POST("/signup", func(_ *fox.Context, req *SignupRequest) (map[string]any, error) { // Simulate checking if email exists existingEmails := []string{"alice@example.com", "bob@example.com"}
for _, email := range existingEmails { if email == req.Email { return nil, ErrDuplicateEmail } }
return map[string]any{ "message": "Account created successfully", "email": req.Email, }, nil })
// 7. Permission error router.DELETE("/user/:id", func(ctx *fox.Context) (string, error) { id := ctx.Param("id")
// Simulate permission check if id == "1" { return "", &httperrors.Error{ HTTPCode: http.StatusForbidden, Code: "CANNOT_DELETE_ADMIN", Err: errors.New("cannot delete admin user"), } }
return "User deleted successfully", nil })
// 8. Error with additional details router.GET("/detailed-error", func() (string, error) { return "", &httperrors.Error{ HTTPCode: http.StatusUnprocessableEntity, Code: "VALIDATION_FAILED", Err: errors.New("validation failed"), Fields: map[string]any{ "fields": []map[string]string{ {"field": "email", "error": "invalid format"}, {"field": "age", "error": "must be at least 18"}, }, }, } })
// 9. Panic recovery (handled by default recovery middleware) router.GET("/panic", func() string { panic("something went terribly wrong") })
if err := router.Run(":8080"); err != nil { panic(err) }}Running the Example
Section titled “Running the Example”go run main.goTesting
Section titled “Testing”Simple Error
Section titled “Simple Error”curl http://localhost:8080/error/simpleResponse: plain text with status 400 Bad Request.
something went wrongHTTP Error
Section titled “HTTP Error”curl http://localhost:8080/error/httpResponse (400):
{ "code": "INVALID_REQUEST", "error": "(400): invalid request parameters", "meta": "invalid request parameters"}User Not Found
Section titled “User Not Found”curl http://localhost:8080/user/999Response (404):
{ "code": "USER_NOT_FOUND", "error": "(404): user not found", "meta": "user not found"}Insufficient Balance
Section titled “Insufficient Balance”curl -X POST http://localhost:8080/transfer \ -H "Content-Type: application/json" \ -d '{ "from": 1, "to": 2, "amount": 100 }'Response (402):
{ "code": "INSUFFICIENT_BALANCE", "error": "(402): insufficient account balance", "meta": "insufficient account balance"}Invalid Credentials
Section titled “Invalid Credentials”curl -X POST http://localhost:8080/login \ -H "Content-Type: application/json" \ -d '{ "username": "wrong", "password": "wrong" }'Response (401):
{ "code": "INVALID_CREDENTIALS", "error": "(401): invalid username or password", "meta": "invalid username or password"}Duplicate Email
Section titled “Duplicate Email”curl -X POST http://localhost:8080/signup \ -H "Content-Type: application/json" \ -d '{ "email": "alice@example.com", "password": "password123" }'Response (409):
{ "code": "DUPLICATE_EMAIL", "error": "(409): email already exists", "meta": "email already exists"}Detailed Error
Section titled “Detailed Error”curl http://localhost:8080/detailed-errorResponse (422):
{ "code": "VALIDATION_FAILED", "error": "(422): validation failed", "fields": [ {"field": "email", "error": "invalid format"}, {"field": "age", "error": "must be at least 18"} ], "meta": "validation failed"}Panic Recovery
Section titled “Panic Recovery”curl http://localhost:8080/panicResponse (500):
{ "error": "Internal Server Error"}Error Types
Section titled “Error Types”1. Simple Error
Section titled “1. Simple Error”Returns plain text with Fox’s default error status code, 400 Bad Request:
return "", errors.New("something went wrong")2. HTTP Error with Status Code
Section titled “2. HTTP Error with Status Code”Returns error with custom status code:
return "", &httperrors.Error{ HTTPCode: http.StatusBadRequest, Code: "ERROR_CODE", Err: errors.New("error message"),}3. HTTP Error with Additional Details
Section titled “3. HTTP Error with Additional Details”Returns error with extra information:
return "", &httperrors.Error{ HTTPCode: http.StatusUnprocessableEntity, Code: "VALIDATION_FAILED", Err: errors.New("validation failed"), Fields: map[string]any{ "fields": []string{"email", "password"}, },}HTTP Status Codes
Section titled “HTTP Status Codes”Common status codes for different error scenarios:
400 Bad Request- Invalid request parameters401 Unauthorized- Authentication required or failed403 Forbidden- Permission denied404 Not Found- Resource not found409 Conflict- Resource conflict (e.g., duplicate email)422 Unprocessable Entity- Validation failed429 Too Many Requests- Rate limit exceeded500 Internal Server Error- Server error
Best Practices
Section titled “Best Practices”- Use Consistent Error Codes: Define error codes in UPPER_SNAKE_CASE
- Provide User-Friendly Messages: Don’t expose internal implementation details
- Use Appropriate Status Codes: Match HTTP semantics
- Log Errors: Log errors with context for debugging
- Avoid Sensitive Data: Never expose sensitive information in error messages
- Handle Errors Early: Return errors as soon as they occur
- Use Custom Error Types: Define reusable error types for common scenarios
- Add Context: Wrap errors with additional context when needed
Next Steps
Section titled “Next Steps”- Custom Validator - Custom validation with error handling
- Middleware - Error handling in middleware
- Error Handling Documentation - Learn more about error handling