httperrors

package
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 16, 2026 License: MIT Imports: 4 Imported by: 0

README

httperrors

Structured HTTP error handling with status codes and metadata.

Installation

go get github.com/eriicafes/httpx

Features

  • Status Codes - Attach HTTP status codes to errors
  • Error Metadata - Include structured metadata using Code, Details, or custom Fields
  • Error Wrapping - Wrap existing errors while preserving the error chain
  • Error Unwrapping - Extract HTTPError from any error chain
  • Convenient Parsing - Parse any error into usable HTTP response values with sensible defaults

Usage

Creating Errors

Create new errors with custom messages and status codes.

import "github.com/eriicafes/httpx/httperrors"

// Simple error
err := httperrors.New("User not found", http.StatusNotFound)

// With validation details
err := httperrors.New("Invalid input", http.StatusBadRequest, httperrors.Details{
    "email":    "must be a valid email address",
    "password": "must be at least 8 characters",
})

// With error code
const CodeNotFound = httperrors.Code("NOT_FOUND")
err := httperrors.New("User not found", http.StatusNotFound, CodeNotFound)

// With custom fields
err := httperrors.New("Internal error", http.StatusInternalServerError, httperrors.Fields{
    "request_id": "abc123",
    "trace_id":   "xyz789",
})

// Mix multiple metadata types
err := httperrors.New("Validation failed", http.StatusBadRequest,
    httperrors.Code("VALIDATION_ERROR"),
    httperrors.Details{"email": "invalid format"},
    httperrors.Fields{"request_id": "req-123"},
)
Reporting Errors

Convert standard Go errors into HTTPErrors while preserving their original error messages. Use Report when you want to expose the error message to clients.

// Simple report
if err := db.QueryRow(...); err != nil {
    return httperrors.Report(err, http.StatusInternalServerError)
}

// With additional metadata
if err := validator.Validate(input); err != nil {
    return httperrors.Report(err, http.StatusBadRequest, httperrors.Details{
        "username": "required field",
    })
}
Wrapping Errors

Wrap existing errors with custom user-friendly messages while preserving the error chain for logging. Use Wrap when you want to hide internal error details from clients but still maintain them for debugging.

// Simple wrap
if err := processPayment(); err != nil {
    return httperrors.Wrap(err, "Payment processing failed", http.StatusBadGateway)
}

// With metadata
if err := validateUser(input); err != nil {
    return httperrors.Wrap(err, "Validation failed", http.StatusBadRequest, httperrors.Details{
        "email": "already in use",
    })
}

// Inherit from wrapped HTTPError
baseErr := httperrors.New("Not found", http.StatusNotFound)
err := httperrors.Wrap(baseErr, "Resource not found", 0) // Inherits status code 404
Unwrapping

Extract HTTPError from the error chain to access status codes and metadata. This works even if the HTTPError is wrapped in multiple layers of standard Go errors.

if httpErr, ok := httperrors.Unwrap(err); ok {
    log.Printf("HTTP %d: %s", httpErr.StatusCode(), httpErr.Message())
    data := httpErr.ErrorData()
}

// Works through multiple wrappers
baseHTTPErr := httperrors.New("Base error", http.StatusNotFound)
wrappedErr := fmt.Errorf("context: %w", baseHTTPErr)
httpErr, ok := httperrors.Unwrap(wrappedErr) // Returns base error
Accessing Error Information

Access individual fields from HTTPError or parse any error for use in HTTP responses.

err := httperrors.New("Invalid input", http.StatusBadRequest, httperrors.Details{
    "email": "invalid format",
})

// Access individual fields from HTTPError
message := err.Message()       // "Invalid input"
statusCode := err.StatusCode() // 400
data := err.ErrorData()        // map[string]any{"details": Details{"email": "invalid format"}}

// Parse any error for HTTP responses (works with HTTPError or standard errors)
message, statusCode, data := httperrors.Parse(err)
// Returns usable values with defaults:
// - message: "Something went wrong" if empty
// - statusCode: 500 if zero
// - data: always non-nil, includes "error" field with the message
//   (without overriding any existing "error" field)
Sending Error Responses

Use Parse to convert any error into a usable HTTP response. This is the recommended approach in application code.

func handleRequest(w http.ResponseWriter, r *http.Request) {
    if err := someOperation(); err != nil {
        message, status, data := httperrors.Parse(err)
        w.WriteHeader(status)
        json.NewEncoder(w).Encode(data)
        log.Printf("Error: %s (status %d)", message, status)
        return
    }
    // ... success response
}

// Response for HTTPError:
// {
//   "error": "Invalid input",
//   "details": {"email": "invalid format"}
// }

// Response for standard error:
// {
//   "error": "Something went wrong"
// }
Custom Error Field

If you need a custom error format, provide an "error" field in your data. Parse will preserve it and not override it.

err := httperrors.New("Operation failed", http.StatusBadRequest, httperrors.Fields{
    "error": map[string]any{
        "code":    "CUSTOM_ERROR",
        "message": "Custom error format",
    },
})

_, _, data := httperrors.Parse(err)
// data: {"error": {"code": "CUSTOM_ERROR", "message": "Custom error format"}}

Error Data

Errors can include additional structured metadata beyond the message and status code. This metadata is accessible via the ErrorData() method and can be used to provide extra context like error codes, validation details.

Code

Error codes for standardized error classification. Usually defined as constants.

const (
    CodeNotFound     = httperrors.Code("NOT_FOUND")
    CodeValidation   = httperrors.Code("VALIDATION_ERROR")
    CodeUnauthorized = httperrors.Code("UNAUTHORIZED")
)

err := httperrors.New("User not found", http.StatusNotFound, CodeNotFound)
// err.ErrorData() returns: {"code": "NOT_FOUND"}
Details

Field-level validation errors where specific fields have specific error messages.

details := httperrors.Details{
    "email":    "must be a valid email address",
    "password": "must be at least 8 characters",
}
err := httperrors.New("Invalid input", http.StatusBadRequest, details)
// err.ErrorData() returns: {"details": {"email": "...", "password": "..."}}
Fields

Arbitrary custom fields for any top-level metadata.

fields := httperrors.Fields{
    "request_id": "abc123",
    "trace_id":   "xyz789",
}
err := httperrors.New("Error", http.StatusInternalServerError, fields)
// err.ErrorData() returns: {"request_id": "abc123", "trace_id": "xyz789"}
Combining Multiple Error Data

When multiple ErrorData types are provided, they are merged together. Later values override earlier ones for the same keys.

code := httperrors.Code("VALIDATION_ERROR")
details := httperrors.Details{"email": "invalid format"}
fields := httperrors.Fields{"request_id": "req-123"}

err := httperrors.New("Validation failed", http.StatusBadRequest, code, details, fields)
// err.ErrorData() returns merged data:
// {
//   "code": "VALIDATION_ERROR",
//   "details": {"email": "invalid format"},
//   "request_id": "req-123"
// }

API

type HTTPError interface {
    error
    ErrorData
    Message() string
    StatusCode() int
}

type ErrorData interface {
    ErrorData() map[string]any
}

type Details = map[string]string
type Code = string
type Fields = map[string]any

func New(message string, statusCode int, data ...ErrorData) HTTPError
func Report(err error, statusCode int, data ...ErrorData) HTTPError
func Wrap(err error, message string, statusCode int, data ...ErrorData) HTTPError
func Unwrap(err error) (HTTPError, bool)
func Parse(err error) (message string, statusCode int, data map[string]any)

Documentation

Overview

Package httperrors provides a structured way to handle HTTP errors with status codes and details.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Parse added in v0.3.0

func Parse(e error) (message string, statusCode int, data map[string]any)

Parse returns the message, status code, and data with defaults for empty values. The returned data map is always non-nil and includes an "error" field containing the message, without overriding any existing "error" field.

This function is intended for use in application code where an error response needs to be sent. It accepts any error (HTTPError or standard error) and returns usable values for an HTTP response.

Examples:

Using with an HTTPError:

err := httperrors.New("Invalid email", http.StatusBadRequest, httperrors.Details{
	"email": "must be a valid email address",
})
message, status, data := httperrors.Parse(err)
// message: "Invalid email"
// status: 400
// data: {"error": "Invalid email", "details": {"email": "must be a valid email address"}}

Using with a standard error:

err := errors.New("database connection failed")
message, status, data := httperrors.Parse(err)
// message: "Something went wrong"
// status: 500
// data: {"error": "Something went wrong"}

Sending a JSON error response:

if err != nil {
	message, status, data := httperrors.Parse(err)
	w.WriteHeader(status)
	json.NewEncoder(w).Encode(data)
	return
}

Custom error field preservation:

err := httperrors.New("Operation failed", http.StatusBadRequest, httperrors.Fields{
	"error": map[string]any{"code": "CUSTOM_ERROR", "message": "Custom error format"},
})
_, _, data := httperrors.Parse(err)
// data: {"error": {"code": "CUSTOM_ERROR", "message": "Custom error format"}}
// The existing "error" field is preserved, not overridden

Types

type Code

type Code string

Code represents an error code that can be attached to an HTTPError. Error codes are usually a limited set defined as constants.

Example:

const (
	CodeNotFound     = httperrors.Code("NOT_FOUND")
	CodeValidation   = httperrors.Code("VALIDATION_ERROR")
	CodeUnauthorized = httperrors.Code("UNAUTHORIZED")
)

err := httperrors.New("User not found", http.StatusNotFound, CodeNotFound)

func (Code) ErrorData

func (c Code) ErrorData() map[string]any

type Details

type Details map[string]string

Details represents field-level validation error metadata that can be attached to an HTTPError. Useful for validation errors where specific fields have specific error messages.

Example:

details := httperrors.Details{
	"email":    "must be a valid email address",
	"password": "must be at least 8 characters",
}
err := httperrors.New("Invalid input", http.StatusBadRequest, details)

func (Details) ErrorData

func (d Details) ErrorData() map[string]any

type ErrorData

type ErrorData interface {
	ErrorData() map[string]any
}

ErrorData provides a method to retrieve additional metadata about an error.

type Fields

type Fields map[string]any

Fields represents arbitrary custom fields that can be attached to an HTTPError. Unlike Details and Code which have fixed keys, Field allows you to add any top-level keys.

Example:

fields := httperrors.Fields{
	"request_id": "abc123",
	"trace_id":   "xyz789",
}
err := httperrors.New("Internal error", http.StatusInternalServerError, fields)

func (Fields) ErrorData

func (f Fields) ErrorData() map[string]any

type HTTPError

type HTTPError interface {
	error

	// Message returns the user-facing error message.
	Message() string

	// StatusCode returns the HTTP status code for this error.
	StatusCode() int

	ErrorData
}

HTTPError represents an error that can be returned as an HTTP response.

func New

func New(message string, statusCode int, data ...ErrorData) HTTPError

New creates a new HTTPError with the given message, status code, and additional data.

Example:

err := httperrors.New("Invalid input", http.StatusBadRequest, httperrors.Details{
	"email": "must be a valid email address",
	"password": "must be at least 8 characters",
})

func Report

func Report(err error, statusCode int, data ...ErrorData) HTTPError

Report converts a standard error into an HTTPError with status code, and additional data. The error's Error() message is used as the HTTP error message.

Example:

if err := validator.Validate(input); err != nil {
	return httperrors.Report(err, http.StatusBadRequest, httperrors.Details{
		"username": "required field",
	})
}

func Unwrap

func Unwrap(err error) (HTTPError, bool)

Unwrap checks if an error is or wraps an HTTPError. It returns the HTTPError and true if found, otherwise nil and false.

Example:

if httpErr, ok := httperrors.Unwrap(err); ok {
	log.Printf("HTTP error: %d - %s", httpErr.StatusCode(), httpErr.Message())
}

func Wrap

func Wrap(err error, message string, statusCode int, data ...ErrorData) HTTPError

Wrap wraps an error into an HTTPError with a custom message, status code, and additional data. If err is already an HTTPError, its message, statusCode, and data will be used as fallbacks when the provided values are empty.

Example:

if err := validateUser(input); err != nil {
	return httperrors.Wrap(err, "Validation failed", http.StatusBadRequest, httperrors.Details{
		"email": "already in use",
	})
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL