Documentation
¶
Overview ¶
Package errenvelope provides a tiny, framework-agnostic server-side HTTP error envelope for Go services. It standardizes code/message/details/trace_id/retryable and includes helpers for validation, auth, timeouts, and downstream errors.
Index ¶
- Constants
- func Is(err error, code Code) bool
- func TraceIDFromRequest(r *http.Request) string
- func TraceMiddleware(next http.Handler) http.Handler
- func WithTraceID(ctx context.Context, id string) context.Context
- func Write(w http.ResponseWriter, r *http.Request, err error)
- type Code
- type Error
- func BadRequest(msg string) *Error
- func BadRequestf(format string, args ...any) *Error
- func Conflict(msg string) *Error
- func Conflictf(format string, args ...any) *Error
- func Downstream(service string, cause error) *Error
- func DownstreamTimeout(service string, cause error) *Error
- func Forbidden(msg string) *Error
- func Forbiddenf(format string, args ...any) *Error
- func From(err error) *Error
- func Gone(msg string) *Error
- func Internal(msg string) *Error
- func Internalf(format string, args ...any) *Error
- func MethodNotAllowed(msg string) *Error
- func New(code Code, status int, msg string) *Error
- func Newf(code Code, status int, format string, args ...any) *Error
- func NotFound(msg string) *Error
- func NotFoundf(format string, args ...any) *Error
- func PayloadTooLarge(msg string) *Error
- func RateLimited(msg string) *Error
- func RequestTimeout(msg string) *Error
- func Timeout(msg string) *Error
- func Timeoutf(format string, args ...any) *Error
- func Unauthorized(msg string) *Error
- func Unauthorizedf(format string, args ...any) *Error
- func Unavailable(msg string) *Error
- func Unavailablef(format string, args ...any) *Error
- func UnprocessableEntity(msg string) *Error
- func Validation(fields FieldErrors) *Error
- func Wrap(code Code, status int, msg string, cause error) *Error
- func Wrapf(code Code, status int, format string, cause error, args ...any) *Error
- func (e *Error) Error() string
- func (e *Error) LogValue() slog.Value
- func (e *Error) MarshalJSON() ([]byte, error)
- func (e *Error) Unwrap() error
- func (e *Error) WithDetails(details any) *Error
- func (e *Error) WithRetryAfter(d time.Duration) *Error
- func (e *Error) WithRetryable(v bool) *Error
- func (e *Error) WithStatus(status int) *Error
- func (e *Error) WithTraceID(id string) *Error
- type FieldErrors
- type ValidationDetails
Examples ¶
Constants ¶
const (
// HeaderTraceID is the standard header name for trace/request IDs.
HeaderTraceID = "X-Request-Id"
)
Variables ¶
This section is empty.
Functions ¶
func TraceIDFromRequest ¶
TraceIDFromRequest extracts the trace ID from the request header or context.
func TraceMiddleware ¶
TraceMiddleware generates or propagates a trace ID for each request.
Example ¶
ExampleTraceMiddleware demonstrates adding trace ID middleware.
package main
import (
"fmt"
"net/http"
"net/http/httptest"
errenvelope "github.com/blackwell-systems/err-envelope"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/api/user", func(w http.ResponseWriter, r *http.Request) {
// Trace ID is available from request
traceID := errenvelope.TraceIDFromRequest(r)
fmt.Printf("Trace ID present: %v\n", traceID != "")
w.WriteHeader(http.StatusOK)
})
// Wrap with trace middleware
handler := errenvelope.TraceMiddleware(mux)
req := httptest.NewRequest("GET", "/api/user", nil)
w := httptest.NewRecorder()
handler.ServeHTTP(w, req)
}
Output: Trace ID present: true
func WithTraceID ¶
WithTraceID adds a trace ID to the context.
func Write ¶
func Write(w http.ResponseWriter, r *http.Request, err error)
Write writes a consistent JSON error envelope to the response. If TraceID is missing on the error, it tries to derive it from the request.
Example (Conflict) ¶
ExampleWrite_conflict demonstrates handling conflict errors (e.g., duplicate resources).
package main
import (
"fmt"
"net/http"
"net/http/httptest"
errenvelope "github.com/blackwell-systems/err-envelope"
)
func main() {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Simulate duplicate email
err := errenvelope.Conflict("User with this email already exists")
errenvelope.Write(w, r, err)
})
req := httptest.NewRequest("POST", "/signup", nil)
w := httptest.NewRecorder()
handler(w, req)
fmt.Printf("Status: %d\n", w.Code)
}
Output: Status: 409
Example (Downstream) ¶
ExampleWrite_downstream demonstrates handling downstream service errors.
package main
import (
"fmt"
"net/http"
"net/http/httptest"
errenvelope "github.com/blackwell-systems/err-envelope"
)
func main() {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Simulate payment service failure
paymentErr := fmt.Errorf("connection refused")
err := errenvelope.Downstream("payment-service", paymentErr)
errenvelope.Write(w, r, err)
})
req := httptest.NewRequest("POST", "/checkout", nil)
w := httptest.NewRecorder()
handler(w, req)
fmt.Printf("Status: %d\n", w.Code)
}
Output: Status: 502
Example (NotFound) ¶
ExampleWrite_notFound demonstrates handling 404 errors.
package main
import (
"fmt"
"net/http"
"net/http/httptest"
errenvelope "github.com/blackwell-systems/err-envelope"
)
func main() {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Simulate user not found
err := errenvelope.NotFound("User not found")
errenvelope.Write(w, r, err)
})
req := httptest.NewRequest("GET", "/users/999", nil)
w := httptest.NewRecorder()
handler(w, req)
fmt.Printf("Status: %d\n", w.Code)
}
Output: Status: 404
Example (Validation) ¶
ExampleWrite_validation demonstrates handling validation errors with field details.
package main
import (
"fmt"
"net/http"
"net/http/httptest"
errenvelope "github.com/blackwell-systems/err-envelope"
)
func main() {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
email := r.URL.Query().Get("email")
password := r.URL.Query().Get("password")
if email == "" || password == "" {
err := errenvelope.Validation(errenvelope.FieldErrors{
"email": "is required",
"password": "is required",
})
errenvelope.Write(w, r, err)
return
}
w.WriteHeader(http.StatusOK)
})
req := httptest.NewRequest("GET", "/signup", nil)
w := httptest.NewRecorder()
handler(w, req)
fmt.Printf("Status: %d\n", w.Code)
fmt.Printf("Content-Type: %s\n", w.Header().Get("Content-Type"))
}
Output: Status: 400 Content-Type: application/json
Types ¶
type Code ¶
type Code string
Code is a stable, machine-readable error identifier.
const ( // Generic CodeInternal Code = "INTERNAL" CodeBadRequest Code = "BAD_REQUEST" CodeNotFound Code = "NOT_FOUND" CodeMethodNotAllowed Code = "METHOD_NOT_ALLOWED" CodeGone Code = "GONE" CodeConflict Code = "CONFLICT" CodePayloadTooLarge Code = "PAYLOAD_TOO_LARGE" CodeRequestTimeout Code = "REQUEST_TIMEOUT" CodeRateLimited Code = "RATE_LIMITED" // Validation / auth CodeValidationFailed Code = "VALIDATION_FAILED" CodeForbidden Code = "FORBIDDEN" CodeUnprocessableEntity Code = "UNPROCESSABLE_ENTITY" // Timeouts / cancellations CodeTimeout Code = "TIMEOUT" CodeCanceled Code = "CANCELED" // Downstream CodeDownstream Code = "DOWNSTREAM_ERROR" CodeDownstreamTimeout Code = "DOWNSTREAM_TIMEOUT" )
type Error ¶
type Error struct {
Code Code `json:"code"`
Message string `json:"message"`
Details any `json:"details,omitempty"`
TraceID string `json:"trace_id,omitempty"`
Retryable bool `json:"retryable"`
// Not serialized:
Status int `json:"-"`
Cause error `json:"-"`
RetryAfter time.Duration `json:"-"` // Duration to wait before retrying
}
Error is a structured error envelope for HTTP APIs.
func BadRequest ¶
BadRequest creates a generic bad request error (400).
func BadRequestf ¶ added in v1.1.0
BadRequestf creates a generic bad request error (400) with a formatted message.
func Downstream ¶
Downstream creates an error for downstream service failures (502).
func DownstreamTimeout ¶
DownstreamTimeout creates a timeout error for downstream services (504).
func Forbiddenf ¶ added in v1.1.0
Forbiddenf creates a forbidden error (403) with a formatted message.
func From ¶
From maps arbitrary errors into an *Error. Handles context errors, network timeouts, and wraps unknown errors.
Example ¶
ExampleFrom demonstrates mapping arbitrary errors to envelopes.
package main
import (
"fmt"
errenvelope "github.com/blackwell-systems/err-envelope"
)
func main() {
// Standard library error
err := fmt.Errorf("something went wrong")
envelope := errenvelope.From(err)
fmt.Printf("Code: %s\n", envelope.Code)
fmt.Printf("Status: %d\n", envelope.Status)
}
Output: Code: INTERNAL Status: 500
func Internalf ¶ added in v1.1.0
Internalf creates an internal server error (500) with a formatted message.
func MethodNotAllowed ¶
MethodNotAllowed creates a method not allowed error (405).
func New ¶
New creates a new Error with the given code, HTTP status, and message. If status is 0, defaults to 500. If message is empty, uses a default.
Example ¶
ExampleNew demonstrates creating a custom error with details.
package main
import (
"fmt"
"net/http"
errenvelope "github.com/blackwell-systems/err-envelope"
)
func main() {
err := errenvelope.New(
errenvelope.CodeInternal,
http.StatusInternalServerError,
"Database connection failed",
)
err = err.WithDetails(map[string]any{
"database": "postgres",
"host": "db.example.com",
})
err = err.WithRetryable(true)
fmt.Printf("Code: %s\n", err.Code)
fmt.Printf("Message: %s\n", err.Message)
fmt.Printf("Retryable: %v\n", err.Retryable)
}
Output: Code: INTERNAL Message: Database connection failed Retryable: true
func Newf ¶ added in v1.1.0
Newf creates a new Error with a formatted message. Mirrors fmt.Errorf ergonomics for creating errors with interpolated values.
func NotFoundf ¶ added in v1.1.0
NotFoundf creates a not found error (404) with a formatted message.
func PayloadTooLarge ¶
PayloadTooLarge creates a payload too large error (413).
func RateLimited ¶
RateLimited creates a rate limit error (429).
func RequestTimeout ¶
RequestTimeout creates a request timeout error (408). This is for client-side timeouts, distinct from 504 Gateway Timeout.
func Unauthorized ¶
Unauthorized creates an unauthorized error (401).
func Unauthorizedf ¶ added in v1.1.0
Unauthorizedf creates an unauthorized error (401) with a formatted message.
func Unavailable ¶
Unavailable creates an unavailable error (503).
func Unavailablef ¶ added in v1.1.0
Unavailablef creates an unavailable error (503) with a formatted message.
func UnprocessableEntity ¶
UnprocessableEntity creates an unprocessable entity error (422). Useful for semantic validation errors that differ from 400.
func Validation ¶
func Validation(fields FieldErrors) *Error
Validation creates a validation error with field-level details.
Example ¶
ExampleValidation demonstrates field-level validation errors.
package main
import (
"fmt"
errenvelope "github.com/blackwell-systems/err-envelope"
)
func main() {
err := errenvelope.Validation(errenvelope.FieldErrors{
"email": "must be a valid email address",
"age": "must be at least 18",
"password": "must be at least 8 characters",
})
fmt.Printf("Code: %s\n", err.Code)
fmt.Printf("Status: %d\n", err.Status)
fmt.Printf("Retryable: %v\n", err.Retryable)
}
Output: Code: VALIDATION_FAILED Status: 400 Retryable: false
func Wrapf ¶ added in v1.1.0
Wrapf creates a new Error that wraps an underlying cause with a formatted message. Mirrors fmt.Errorf ergonomics while preserving error chain.
func (*Error) MarshalJSON ¶ added in v1.1.0
MarshalJSON implements custom JSON serialization to include retry_after as a human-readable string. When RetryAfter is set, it appears in the JSON response as "retry_after": "30s" or "5m0s".
func (*Error) WithDetails ¶
WithDetails adds structured details to the error. Returns a copy to avoid mutating shared error instances.
func (*Error) WithRetryAfter ¶
WithRetryAfter sets the retry-after duration for rate-limited responses. The duration will be sent as a Retry-After header (in seconds). Returns a copy to avoid mutating shared error instances.
func (*Error) WithRetryable ¶
WithRetryable sets whether the error is retryable. Returns a copy to avoid mutating shared error instances.
func (*Error) WithStatus ¶
WithStatus overrides the HTTP status code. Returns a copy to avoid mutating shared error instances.
func (*Error) WithTraceID ¶
WithTraceID adds a trace ID for distributed tracing. Returns a copy to avoid mutating shared error instances.
type FieldErrors ¶
FieldErrors is a simple, library-agnostic validation shape.
type ValidationDetails ¶
type ValidationDetails struct {
Fields FieldErrors `json:"fields"`
}
ValidationDetails holds field-level validation errors.
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
nethttp
command
|
|
|
integrations
|
|
|
chi
Package chi provides thin adapters for using err-envelope with chi router.
|
Package chi provides thin adapters for using err-envelope with chi router. |
|
echo
Package echo provides adapters for using err-envelope with Echo framework.
|
Package echo provides adapters for using err-envelope with Echo framework. |
|
gin
Package gin provides adapters for using err-envelope with Gin framework.
|
Package gin provides adapters for using err-envelope with Gin framework. |