Documentation
¶
Overview ¶
Package cause provides structured error handling with support for error codes, attributes, details, stack traces, and validation. It extends Go's standard error interface with additional context and structured logging capabilities.
Index ¶
- Variables
- type Error
- func (e *Error) Clone() *Error
- func (e *Error) Error() string
- func (e *Error) Is(target error) bool
- func (e Error) LogValue() slog.Value
- func (e *Error) MarshalJSON() ([]byte, error)
- func (e *Error) UnmarshalJSON(b []byte) error
- func (e *Error) Unwrap() error
- func (e *Error) WithAttrs(attrs ...slog.Attr) *Error
- func (e *Error) WithCause(cause error) *Error
- func (e *Error) WithDetails(details map[string]any) *Error
- func (e *Error) WithMessage(message string, args ...any) *Error
- func (e *Error) WithStack() *Error
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( ErrAborted = New(codes.Aborted, "ABORTED", "The operation was aborted") ErrBadRequest = New(codes.BadRequest, "BAD_REQUEST", "The request is invalid") ErrCanceled = New(codes.Canceled, "CANCELED", "The operation was canceled") ErrConflict = New(codes.Conflict, "CONFLICT", "The request could not be completed due to a conflict with the current state of the target resource") ErrDataLoss = New(codes.DataLoss, "DATA_LOSS", "Unrecoverable data loss or corruption") ErrDeadlineExceeded = New(codes.DeadlineExceeded, "DEADLINE_EXCEEDED", "The deadline expired before the operation could complete") ErrExists = New(codes.Exists, "EXISTS", "The resource that a client tried to create already exists") ErrForbidden = New(codes.Forbidden, "FORBIDDEN", "The caller does not have permission to execute the specified operation") ErrInternal = New(codes.Internal, "INTERNAL", "Internal server error") ErrNotFound = New(codes.NotFound, "NOT_FOUND", "The specified resource was not found") ErrNotImplemented = New(codes.NotImplemented, "NOT_IMPLEMENTED", "The operation is not implemented or not supported") ErrOutOfRange = New(codes.OutOfRange, "OUT_OF_RANGE", "The operation was attempted past the valid range") ErrPreconditionFailed = New(codes.PreconditionFailed, "PRECONDITION_FAILED", "The operation was rejected because the system is not in a state required for the operation's execution") ErrTooManyRequests = New(codes.TooManyRequests, "TOO_MANY_REQUESTS", "The caller has sent too many requests in a given amount of time") ErrUnknown = New(codes.Unknown, "UNKNOWN", "An unknown error occurred") )
Functions ¶
This section is empty.
Types ¶
type Error ¶
type Error struct {
// Attrs contains structured logging attributes
Attrs []slog.Attr
// Cause is the underlying wrapped error
Cause error
// Code represents the error classification
Code codes.Code
// Details contains additional context as key-value pairs
Details map[string]any
// Message is the human-readable error message
Message string
// Name is a unique identifier for this error type
Name string
// Stack contains the stack trace when the error was created
Stack string
}
Error represents a structured error with additional context including error codes, attributes, details, stack traces, and nested causes. It implements the standard error interface and provides enhanced logging capabilities.
Example (Log_attr) ¶
package main
import (
"bytes"
"encoding/json"
"fmt"
"log/slog"
"time"
"github.com/alextanhongpin/errors/cause"
"github.com/alextanhongpin/errors/codes"
)
func main() {
var err error = cause.New(codes.BadRequest, "BadRequest", "email=%s is invalid", "[email protected]").
WithAttrs(slog.String("email", "[email protected]"))
replacer := func(groups []string, a slog.Attr) slog.Attr {
if a.Key == "time" {
a.Value = slog.TimeValue(time.Date(2025, 6, 7, 0, 52, 24, 115438000, time.UTC))
}
return a
}
b := new(bytes.Buffer)
logger := slog.New(slog.NewJSONHandler(b, &slog.HandlerOptions{AddSource: true, ReplaceAttr: replacer}))
logger.Error("payment failed", slog.Any("error", err))
data := b.Bytes()
b.Reset()
if err := json.Indent(b, data, "", " "); err != nil {
panic(err)
}
fmt.Println(b.String())
}
Output: { "time": "2025-06-07T00:52:24.115438Z", "level": "ERROR", "source": { "function": "github.com/alextanhongpin/errors/cause_test.ExampleError_log_attr", "file": "/Users/alextanhongpin/Documents/go/errors/cause/examples_log_attr_test.go", "line": 27 }, "msg": "payment failed", "error": { "message": "[email protected] is invalid", "code": "bad_request", "name": "BadRequest", "data": { "email": "[email protected]" } } }
Example (Marshal) ¶
package main
import (
"database/sql"
"encoding/json"
"errors"
"fmt"
"log"
"github.com/alextanhongpin/errors/cause"
"github.com/alextanhongpin/errors/codes"
)
var ErrUnknown = cause.New(codes.Unknown, "Unknown error", "This needs to be fixed")
func main() {
var err error = ErrUnknown.WithCause(sql.ErrNoRows)
b, err := json.Marshal(err)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(b))
var causeErr *cause.Error
if err := json.Unmarshal(b, &causeErr); err != nil {
log.Fatal(err)
}
fmt.Println("is sql.ErrNoRows?:", errors.Is(causeErr, sql.ErrNoRows))
fmt.Println("is ErrUnknown?:", errors.Is(causeErr, ErrUnknown))
}
Output: {"cause":{"code":17,"message":"sql: no rows in result set","name":"Unknown"},"code":17,"message":"This needs to be fixed","name":"Unknown error"} is sql.ErrNoRows?: true is ErrUnknown?: true
Example (Marshal_nested) ¶
package main
import (
"database/sql"
"encoding/json"
"errors"
"fmt"
"log"
"github.com/alextanhongpin/errors/cause"
"github.com/alextanhongpin/errors/codes"
)
var (
ErrUnknown = cause.New(codes.Unknown, "Unknown error", "This needs to be fixed")
ErrNested = cause.New(codes.Unknown, "Nested error", "One level of nesting")
)
func main() {
var err error = ErrUnknown.WithCause(ErrNested.WithCause(sql.ErrNoRows))
b, err := json.Marshal(err)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(b))
var causeErr *cause.Error
if err := json.Unmarshal(b, &causeErr); err != nil {
log.Fatal(err)
}
fmt.Println("is sql.ErrNoRows?:", errors.Is(causeErr, sql.ErrNoRows))
fmt.Println("is ErrUnknown?:", errors.Is(causeErr, ErrUnknown))
fmt.Println("is ErrNested?:", errors.Is(causeErr, ErrNested))
}
Output: {"cause":{"cause":{"code":17,"message":"sql: no rows in result set","name":"Unknown"},"code":17,"message":"One level of nesting","name":"Nested error"},"code":17,"message":"This needs to be fixed","name":"Unknown error"} is sql.ErrNoRows?: true is ErrUnknown?: true is ErrNested?: true
func New ¶
New creates a new Error with the specified code, name, and message. Additional arguments can include slog.Attr for structured logging attributes. The message supports fmt.Sprintf formatting with the provided args.
Example:
err := New(codes.NotFound, "UserNotFound", "User %s not found", userID)
Example ¶
package main
import (
"errors"
"fmt"
"github.com/alextanhongpin/errors/cause"
"github.com/alextanhongpin/errors/codes"
)
var ErrUserNotFound = cause.New(codes.NotFound, "UserNotFoundError", "User not found")
func main() {
var err error = ErrUserNotFound
fmt.Println("err:", err)
fmt.Println("is:", errors.Is(err, ErrUserNotFound))
var causeErr *cause.Error
if errors.As(err, &causeErr) {
fmt.Println("code:", causeErr.Code)
fmt.Println("details:", causeErr.Details)
fmt.Println("message:", causeErr.Message)
fmt.Println("name:", causeErr.Name)
}
}
Output: err: User not found is: true code: not_found details: map[] message: User not found name: UserNotFoundError
func (*Error) Clone ¶
Clone creates a deep copy of the error, allowing safe modification without affecting the original error.
func (*Error) Is ¶
Is reports whether this error matches the target error. Two errors match if they have the same Code and Name.
func (Error) LogValue ¶
LogValue implements slog.LogValuer for structured logging. It returns a grouped slog.Value containing all error context including message, code, name, attributes, details, and cause.
Example ¶
package main
import (
"bytes"
"database/sql"
"encoding/json"
"fmt"
"log/slog"
"time"
"github.com/alextanhongpin/errors/cause"
"github.com/alextanhongpin/errors/codes"
)
var ErrDuplicateRow = cause.New(codes.Exists, "DuplicateRowError", "Duplicate row")
var ErrPaymentFailed = cause.New(codes.Conflict, "PaymentFailedError", "Duplicate payment attempt")
func main() {
var err error = ErrPaymentFailed.WithDetails(map[string]any{
"order_id": "12345",
}).WithCause(ErrDuplicateRow.WithCause(sql.ErrNoRows))
fmt.Println(err)
replacer := func(groups []string, a slog.Attr) slog.Attr {
if a.Key == "time" {
a.Value = slog.TimeValue(time.Date(2025, 6, 7, 0, 52, 24, 115438000, time.UTC))
}
return a
}
b := new(bytes.Buffer)
logger := slog.New(slog.NewJSONHandler(b, &slog.HandlerOptions{AddSource: true, ReplaceAttr: replacer}))
logger.Error("payment failed", slog.Any("error", err))
data := b.Bytes()
b.Reset()
if err := json.Indent(b, data, "", " "); err != nil {
panic(err)
}
fmt.Println(b.String())
}
Output: Duplicate payment attempt Caused by: Duplicate row Caused by: sql: no rows in result set { "time": "2025-06-07T00:52:24.115438Z", "level": "ERROR", "source": { "function": "github.com/alextanhongpin/errors/cause_test.ExampleError_LogValue", "file": "/Users/alextanhongpin/Documents/go/errors/cause/examples_log_value_test.go", "line": 33 }, "msg": "payment failed", "error": { "message": "Duplicate payment attempt", "code": "conflict", "name": "PaymentFailedError", "cause": { "message": "Duplicate row", "code": "exists", "name": "DuplicateRowError", "cause": "sql: no rows in result set" }, "details": { "order_id": "12345" } } }
func (*Error) MarshalJSON ¶ added in v0.3.2
func (*Error) UnmarshalJSON ¶ added in v0.3.2
func (*Error) WithAttrs ¶
WithAttrs returns a new error with additional structured logging attributes.
func (*Error) WithCause ¶ added in v0.3.0
WithCause returns a new error with the specified cause error.
Example ¶
package main
import (
"database/sql"
"errors"
"fmt"
"github.com/alextanhongpin/errors/cause"
"github.com/alextanhongpin/errors/codes"
)
var ErrStorage = cause.New(codes.Internal, "StorageError", "Storage error")
func main() {
var err error = ErrStorage.WithCause(sql.ErrNoRows)
fmt.Println("is sql.ErrNoRows?:", errors.Is(err, sql.ErrNoRows))
fmt.Println("is ErrStorage?:", errors.Is(err, ErrStorage))
var causeErr *cause.Error
if errors.As(err, &causeErr) {
fmt.Println("cause:", causeErr.Unwrap())
}
fmt.Println(ErrStorage.Unwrap())
}
Output: is sql.ErrNoRows?: true is ErrStorage?: true cause: sql: no rows in result set <nil>
func (*Error) WithDetails ¶
WithDetails returns a new error with additional details merged in. Existing details are preserved, new details override existing keys.
func (*Error) WithMessage ¶ added in v0.3.0
WithMessage returns a new error with the specified message formatted with args.
func (*Error) WithStack ¶
WithStack returns a new error with the current stack trace captured.
Example ¶
package main
import (
"database/sql"
"fmt"
"log/slog"
"os"
"github.com/alextanhongpin/errors/cause"
"github.com/alextanhongpin/errors/codes"
)
func main() {
err := bar()
fmt.Println(err)
fmt.Println()
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
AddSource: true,
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
if a.Key == slog.TimeKey && len(groups) == 0 {
return slog.Attr{}
}
return a
}}))
logger.Error("An error occurred", "error", err)
}
func foo() error {
return cause.New(codes.Internal, "StackError", "An error with stack trace").
WithCause(sql.ErrNoRows).
WithStack()
}
func bar() error {
return cause.New(codes.Internal, "BarError", "An error in bar").
WithStack().
WithCause(foo())
}
Output: An error in bar at /Users/alextanhongpin/Documents/go/errors/cause/examples_stack_trace_test.go.46 Caused by: An error with stack trace at /Users/alextanhongpin/Documents/go/errors/cause/examples_stack_trace_test.go.41 Caused by: sql: no rows in result set {"level":"ERROR","source":{"function":"github.com/alextanhongpin/errors/cause_test.ExampleError_WithStack","file":"/Users/alextanhongpin/Documents/go/errors/cause/examples_stack_trace_test.go","line":26},"msg":"An error occurred","error":{"message":"An error in bar","code":"internal","name":"BarError","cause":{"message":"An error with stack trace","code":"internal","name":"StackError","cause":"sql: no rows in result set"}}}