errors

package
v0.0.0-...-2dbaee0 Latest Latest
Warning

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

Go to latest
Published: Jan 22, 2025 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package errors provides enhanced error handling primitives that extend Go's standard errors package. It offers stack traces, error wrapping, and HTTP error handling while maintaining compatibility with the standard library.

This is a copy of github.com/pkg/errors slightly updated and extended to support more modern logging approaches.

Key Features:

  • Stack Traces: Automatically capture stack traces when errors are created
  • Error Wrapping: Add context to errors while preserving the original error
  • HTTP Errors: Structured error types for HTTP responses
  • Error Joining: Combine multiple errors into a single error value

Basic Usage:

// Create a new error with stack trace
err := errors.New("database connection failed")

// Add context to an existing error
if err != nil {
    return errors.Wrap(err, "failed to initialize storage")
}

// Format error with stack trace
fmt.Printf("%+v\n", err)

Error Types:

fundamental - Base error type with stack trace
withStack  - Adds stack trace to an existing error
withMessage - Adds a message to an existing error
withMultierr - Combines multiple errors into one
HTTPError - HTTP-specific error with status code

Error Wrapping:

The package provides several ways to wrap errors:

errors.Wrap(err, "message")     // Add message and stack trace
errors.Wrapf(err, "fmt", args)  // Add formatted message and stack trace
errors.WithStack(err)           // Add only stack trace
errors.WithMessage(err, "msg")  // Add only message

Error Inspection:

Use these methods to inspect errors:

errors.Is(err, target)     // Check if err matches target
errors.As(err, &target)    // Try to convert err to target type
errors.Cause(err)          // Get the root cause of the error

HTTP Error Handling:

For HTTP services, use the HTTP error types:

errors.NewHTTPError(http.StatusNotFound, "user not found", err)
errors.AsHTTPError(err)  // Convert any error to an HTTPError

Formatting:

All error types implement fmt.Formatter and support:

%s    - Print the error message
%v    - Same as %s
%q    - Print the error message with quotes
%+v   - Print detailed error with stack trace

Error Joining:

Combine multiple errors into one:

err1 := errors.New("first error")
err2 := errors.New("second error")
combined := errors.Join(err1, err2)

Best Practices:

  1. Always use errors.New() instead of errors.New() for new errors
  2. Use errors.Wrap() when adding context to returned errors
  3. Use %+v when logging errors to include stack traces
  4. Use errors.Is() and errors.As() for error inspection
  5. Use HTTPError types for HTTP API responses
Example (StackTrace)
package main

import (
	"fmt"

	"github.com/aexvir/skladka/internal/errors"
)

func fn() error {
	e1 := errors.New("error")
	e2 := errors.Wrap(e1, "inner")
	e3 := errors.Wrap(e2, "middle")
	return errors.Wrap(e3, "outer")
}

func main() {
	type stackTracer interface {
		StackTrace() errors.StackTrace
	}

	err, ok := errors.Cause(fn()).(stackTracer)
	if !ok {
		panic("oops, err does not implement stackTracer")
	}

	st := err.StackTrace()
	fmt.Printf("%+v", st[0:2]) // top two frames

	// Example output:
	// github.com/pkg/errors_test.fn
	//	/home/dfc/src/github.com/pkg/errors/example_test.go:47
	// github.com/pkg/errors_test.Example_stackTrace
	//	/home/dfc/src/github.com/pkg/errors/example_test.go:127
}
Output:

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func As

func As(err error, target any) bool

As finds the first error in err's chain that matches target, and if one is found, As returns true and sets target to that error value. Otherwise, As returns false and sets target to nil.

func Cause

func Cause(err error) error

Cause returns the underlying cause of the error, if possible. An error value has a cause if it implements the following interface:

type causer interface {
    Cause() error
}

If the error does not implement Cause, the original error will be returned. If the error is nil, nil will be returned without further investigation.

Example
package main

import (
	"fmt"

	"github.com/aexvir/skladka/internal/errors"
)

func fn() error {
	e1 := errors.New("error")
	e2 := errors.Wrap(e1, "inner")
	e3 := errors.Wrap(e2, "middle")
	return errors.Wrap(e3, "outer")
}

func main() {
	err := fn()
	fmt.Println(err)
	fmt.Println(errors.Cause(err))

}
Output:

outer: middle: inner: error
error
Example (Printf)
package main

import (
	"fmt"

	"github.com/aexvir/skladka/internal/errors"
)

func main() {
	err := errors.Wrap(func() error {
		return func() error {
			return errors.New("hello world")
		}()
	}(), "failed")

	fmt.Printf("%v", err)

}
Output:

failed: hello world

func Errorf

func Errorf(format string, args ...interface{}) error

Errorf returns a new error with formatted message and a stack trace. The error implements fmt.Formatter for custom error formatting.

name := "db"
err := errors.Errorf("connection to %s failed", name)
Example (Extended)
package main

import (
	"fmt"

	"github.com/aexvir/skladka/internal/errors"
)

func main() {
	err := errors.Errorf("whoops: %s", "foo")
	fmt.Printf("%+v", err)

	// Example output:
	// whoops: foo
	// github.com/pkg/errors_test.ExampleErrorf
	//         /home/dfc/src/github.com/pkg/errors/example_test.go:101
	// testing.runExample
	//         /home/dfc/go/src/testing/example.go:114
	// testing.RunExamples
	//         /home/dfc/go/src/testing/example.go:38
	// testing.(*M).Run
	//         /home/dfc/go/src/testing/testing.go:744
	// main.main
	//         /github.com/pkg/errors/_test/_testmain.go:102
	// runtime.main
	//         /home/dfc/go/src/runtime/proc.go:183
	// runtime.goexit
	//         /home/dfc/go/src/runtime/asm_amd64.s:2059
}
Output:

func Is

func Is(err, target error) bool

Is reports whether any error in errs matches target. An error matches target if the error values are identical, as determined by ==.

func Join

func Join(errs ...error) error

Join returns an error that wraps the given errors. Any nil error values are discarded. Join returns nil if errs contains no non-nil values. The error formats as the concatenation of the strings obtained by calling the Error method of each element of errs, with a newline between each string.

err1 := errors.New("first error")
err2 := errors.New("second error")
err := errors.Join(err1, err2)
fmt.Printf("%v", err)
// Output:
// first error
// second error
Example
package main

import (
	"fmt"

	"github.com/aexvir/skladka/internal/errors"
)

func main() {
	err1 := errors.New("error 1")
	err2 := errors.Wrap(errors.New("inner"), "error 2")

	err := errors.Join(err1, err2)
	fmt.Printf("%v\n", err)
}
Output:

error 1
error 2: inner

func New

func New(message string) error

New returns a new error with a stack trace. The error implements fmt.Formatter for custom error formatting.

err := errors.New("connection failed")
fmt.Printf("%+v", err) // prints error with stack trace
Example
package main

import (
	"fmt"

	"github.com/aexvir/skladka/internal/errors"
)

func main() {
	err := errors.New("whoops")
	fmt.Println(err)

}
Output:

whoops
Example (Printf)
package main

import (
	"fmt"

	"github.com/aexvir/skladka/internal/errors"
)

func main() {
	err := errors.New("whoops")
	fmt.Printf("%+v", err)

	// Example output:
	// whoops
	// github.com/pkg/errors_test.ExampleNew_printf
	//         /home/dfc/src/github.com/pkg/errors/example_test.go:17
	// testing.runExample
	//         /home/dfc/go/src/testing/example.go:114
	// testing.RunExamples
	//         /home/dfc/go/src/testing/example.go:38
	// testing.(*M).Run
	//         /home/dfc/go/src/testing/testing.go:744
	// main.main
	//         /github.com/pkg/errors/_test/_testmain.go:106
	// runtime.main
	//         /home/dfc/go/src/runtime/proc.go:183
	// runtime.goexit
	//         /home/dfc/go/src/runtime/asm_amd64.s:2059
}
Output:

func NewHTTPError

func NewHTTPError(code int, message string, err error) error

NewHTTPError creates a new HTTPError with the given code and message. If err is not nil, it will be wrapped and accessible via Unwrap().

err := errors.NewHTTPError(http.StatusNotFound, "user not found", nil)
err = errors.NewHTTPError(http.StatusInternalServerError, "database error", dbErr)

func WithErrorHandler

func WithErrorHandler(handler ErrorHandlerFunc) http.HandlerFunc

WithErrorHandler wraps an ErrorHandlerFunc and returns an http.HandlerFunc. It handles errors returned by the ErrorHandlerFunc by:

  • For HTTPError: responding with the error's code and message
  • For other errors: setting the OpenTelemetry span status to Error and responding with 500 Internal Server Error

Example usage:

http.HandleFunc(
    "/users", errors.WithErrorHandler(
        func(w http.ResponseWriter, r *http.Request) error {
            user, err := getUser(r.Context())
            if err != nil {
                return errors.NewHTTPError(http.StatusNotFound, "user not found", err)
            }
            return json.NewEncoder(w).Encode(user)
        }
    )
)

func WithMessage

func WithMessage(err error, message string) error

WithMessage annotates err with a new message. If err is nil, WithMessage returns nil.

err := doSomething()
if err != nil {
    return errors.WithMessage(err, "failed to do something")
}
Example
package main

import (
	"fmt"

	"github.com/aexvir/skladka/internal/errors"
)

func main() {
	cause := errors.New("whoops")
	err := errors.WithMessage(cause, "oh noes")
	fmt.Println(err)

}
Output:

oh noes: whoops

func WithMessagef

func WithMessagef(err error, format string, args ...interface{}) error

WithMessagef annotates err with the format specifier. If err is nil, WithMessagef returns nil.

err := doSomething()
if err != nil {
    return errors.WithMessagef(err, "failed to do something: %s", details)
}

func WithStack

func WithStack(err error) error

WithStack annotates err with a stack trace at the point WithStack was called. If err is nil, WithStack returns nil.

if err != nil {
    return errors.WithStack(err)
}
Example
package main

import (
	"fmt"

	"github.com/aexvir/skladka/internal/errors"
)

func main() {
	cause := errors.New("whoops")
	err := errors.WithStack(cause)
	fmt.Println(err)

}
Output:

whoops
Example (Printf)
package main

import (
	"fmt"

	"github.com/aexvir/skladka/internal/errors"
)

func main() {
	cause := errors.New("whoops")
	err := errors.WithStack(cause)
	fmt.Printf("%+v", err)

	// Example Output:
	// whoops
	// github.com/pkg/errors_test.ExampleWithStack_printf
	//         /home/fabstu/go/src/github.com/pkg/errors/example_test.go:55
	// testing.runExample
	//         /usr/lib/go/src/testing/example.go:114
	// testing.RunExamples
	//         /usr/lib/go/src/testing/example.go:38
	// testing.(*M).Run
	//         /usr/lib/go/src/testing/testing.go:744
	// main.main
	//         github.com/pkg/errors/_test/_testmain.go:106
	// runtime.main
	//         /usr/lib/go/src/runtime/proc.go:183
	// runtime.goexit
	//         /usr/lib/go/src/runtime/asm_amd64.s:2086
	// github.com/pkg/errors_test.ExampleWithStack_printf
	//         /home/fabstu/go/src/github.com/pkg/errors/example_test.go:56
	// testing.runExample
	//         /usr/lib/go/src/testing/example.go:114
	// testing.RunExamples
	//         /usr/lib/go/src/testing/example.go:38
	// testing.(*M).Run
	//         /usr/lib/go/src/testing/testing.go:744
	// main.main
	//         github.com/pkg/errors/_test/_testmain.go:106
	// runtime.main
	//         /usr/lib/go/src/runtime/proc.go:183
	// runtime.goexit
	//         /usr/lib/go/src/runtime/asm_amd64.s:2086
}
Output:

func Wrap

func Wrap(err error, message string) error

Wrap returns an error annotating err with a stack trace at the point Wrap is called, and the supplied message. If err is nil, Wrap returns nil.

err := doSomething()
if err != nil {
    return errors.Wrap(err, "failed to do something")
}
Example
package main

import (
	"fmt"

	"github.com/aexvir/skladka/internal/errors"
)

func main() {
	cause := errors.New("whoops")
	err := errors.Wrap(cause, "oh noes")
	fmt.Println(err)

}
Output:

oh noes: whoops
Example (Extended)
package main

import (
	"fmt"

	"github.com/aexvir/skladka/internal/errors"
)

func fn() error {
	e1 := errors.New("error")
	e2 := errors.Wrap(e1, "inner")
	e3 := errors.Wrap(e2, "middle")
	return errors.Wrap(e3, "outer")
}

func main() {
	err := fn()
	fmt.Printf("%+v\n", err)

	// Example output:
	// error
	// github.com/pkg/errors_test.fn
	//         /home/dfc/src/github.com/pkg/errors/example_test.go:47
	// github.com/pkg/errors_test.ExampleCause_printf
	//         /home/dfc/src/github.com/pkg/errors/example_test.go:63
	// testing.runExample
	//         /home/dfc/go/src/testing/example.go:114
	// testing.RunExamples
	//         /home/dfc/go/src/testing/example.go:38
	// testing.(*M).Run
	//         /home/dfc/go/src/testing/testing.go:744
	// main.main
	//         /github.com/pkg/errors/_test/_testmain.go:104
	// runtime.main
	//         /home/dfc/go/src/runtime/proc.go:183
	// runtime.goexit
	//         /home/dfc/go/src/runtime/asm_amd64.s:2059
	// github.com/pkg/errors_test.fn
	// 	  /home/dfc/src/github.com/pkg/errors/example_test.go:48: inner
	// github.com/pkg/errors_test.fn
	//        /home/dfc/src/github.com/pkg/errors/example_test.go:49: middle
	// github.com/pkg/errors_test.fn
	//      /home/dfc/src/github.com/pkg/errors/example_test.go:50: outer
}
Output:

func Wrapf

func Wrapf(err error, format string, args ...interface{}) error

Wrapf returns an error annotating err with a stack trace at the point Wrapf is called, and the format specifier. If err is nil, Wrapf returns nil.

err := doSomething()
if err != nil {
    return errors.Wrapf(err, "failed to do something: %s", details)
}
Example
package main

import (
	"fmt"

	"github.com/aexvir/skladka/internal/errors"
)

func main() {
	cause := errors.New("whoops")
	err := errors.Wrapf(cause, "oh noes #%d", 2)
	fmt.Println(err)

}
Output:

oh noes #2: whoops

Types

type ErrorHandlerFunc

type ErrorHandlerFunc func(http.ResponseWriter, *http.Request) error

type Frame

type Frame uintptr

Frame represents a program counter inside a stack frame. For historical reasons if Frame is interpreted as a uintptr its value represents the program counter + 1.

func (Frame) Format

func (f Frame) Format(state fmt.State, verb rune)

Format formats the frame according to the fmt.Formatter interface.

%s    source file
%d    source line
%n    function name
%v    equivalent to %s:%d

Format accepts flags that alter the printing of some verbs, as follows:

%+s   function name and path of source file relative to the compile time
      GOPATH separated by \n\t (<funcname>\n\t<path>)
%+v   equivalent to %+s:%d

func (Frame) MarshalText

func (f Frame) MarshalText() ([]byte, error)

MarshalText formats a stacktrace Frame as a text string. The output is the same as that of fmt.Sprintf("%+v", f), but without newlines or tabs.

type HTTPError

type HTTPError struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
	// contains filtered or unexported fields
}

HTTPError represents an error that includes an HTTP status code. It implements error, fmt.Formatter, and errors.Unwrap interfaces. The error can be formatted with %s, %q, %v, and %+v verbs.

Example usage:

err := errors.NewHTTPError(http.StatusNotFound, "user not found", nil)
fmt.Printf("%+v\n", err) // prints detailed error with stack trace
fmt.Printf("%v\n", err)  // prints "user not found"
fmt.Printf("%q\n", err)  // prints "\"user not found\""

func AsHTTPError

func AsHTTPError(err error) *HTTPError

AsHTTPError attempts to convert an error to an HTTPError. If the error is already an HTTPError, it is returned as is. If the error is not an HTTPError, it is wrapped as an internal server error.

err := someFunction()
httpErr := errors.AsHTTPError(err)
fmt.Printf("Status code: %d\n", httpErr.Code)

func (*HTTPError) Error

func (e *HTTPError) Error() string

func (*HTTPError) Format

func (e *HTTPError) Format(s fmt.State, verb rune)

Format implements the fmt.Formatter interface to customize how the error is formatted. It supports the following format verbs:

%s    prints just the error message
%q    wraps the error message in quotes
%v    same as %s
%+v   prints error message, wrapped error if any, and stack trace

func (*HTTPError) Is

func (e *HTTPError) Is(target error) bool

Is reports whether this error matches target. An error matches if both the Code and Message are equal.

func (HTTPError) StackTrace

func (s HTTPError) StackTrace() StackTrace

func (*HTTPError) Unwrap

func (e *HTTPError) Unwrap() error

Unwrap implements the errors.Unwrap interface. This allows the error to work with errors.Is, errors.As and errors.Unwrap.

type StackTrace

type StackTrace []Frame

StackTrace is stack of Frames from innermost (newest) to outermost (oldest).

func (StackTrace) Format

func (st StackTrace) Format(state fmt.State, verb rune)

Format formats the stack of Frames according to the fmt.Formatter interface.

%s	lists source files for each Frame in the stack
%v	lists the source file and line number for each Frame in the stack

Format accepts flags that alter the printing of some verbs, as follows:

%+v   Prints filename, function, and line number for each Frame in the stack.

Jump to

Keyboard shortcuts

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