knownerror

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Dec 27, 2025 License: MIT Imports: 2 Imported by: 0

README

knownerror

CI Go Report Card codecov Go Reference

A Go library for creating "known" errors that can extend other errors while maintaining compatibility with errors.Is() and errors.As().

When to use

This library is not needed for most Go projects. Standard errors.New(), fmt.Errorf(), and error wrapping cover the majority of use cases.

Consider this library when:

  • You have many sentinel errors across different packages
  • You want to categorize errors for mapping in the presentation layer (e.g., HTTP status codes, gRPC codes) without checking each specific error
  • You need an error to match multiple categories via errors.Is()
  • You want to add categories without creating custom error types
  • You want to attach a root cause to sentinel errors while preserving errors.Is() matching

Example: instead of mapping each error individually:

switch {
case errors.Is(err, user.ErrNotFound):
    return http.StatusNotFound
case errors.Is(err, order.ErrNotFound):
    return http.StatusNotFound
case errors.Is(err, product.ErrNotFound):
    return http.StatusNotFound
// ... many more
}

You can define error categories and check once:

var ErrNotFound = errors.New("not found")

// In each package:
var ErrUserNotFound = knownerror.New("user not found").Extends(ErrNotFound)
var ErrOrderNotFound = knownerror.New("order not found").Extends(ErrNotFound)

// In presentation layer:
if errors.Is(err, ErrNotFound) {
    return http.StatusNotFound
}

Errors can belong to multiple categories:

var (
    ErrNotFound  = errors.New("not found")
    ErrForbidden = errors.New("forbidden")
)

// User tried to access another user's deleted resource
var ErrNotFoundOrForbidden = knownerror.New("resource not available").Extends(ErrNotFound, ErrForbidden)

errors.Is(ErrNotFoundOrForbidden, ErrNotFound)  // true
errors.Is(ErrNotFoundOrForbidden, ErrForbidden) // true

Installation

go get github.com/pprishchepa/knownerror

Usage

Creating a known error
var ErrNotFound = knownerror.New("not found")
var ErrValidation = knownerror.Newf("validation failed: %s", "invalid input")
Wrapping an existing error
baseErr := errors.New("database connection failed")
wrapped := knownerror.Wrap(baseErr)
Adding a cause error

Use WithCause to attach a root cause error while preserving the original error identity:

var ErrUserNotFound = knownerror.New("user not found")

func GetUser(id string) (*User, error) {
    user, err := db.FindUser(id)
    if err != nil {
        return nil, ErrUserNotFound.WithCause(err)
    }
    return user, nil
}

// Later, check the error type:
if errors.Is(err, ErrUserNotFound) {
    // Handle user not found
}

// Access the root cause:
type Causer interface { Cause() error }
var causer Causer
if errors.As(err, &causer) {
    rootCause := causer.Cause()
}
Extending with other errors

Use Extends to make an error match multiple sentinel errors:

var (
    ErrNotFound   = knownerror.New("not found")
    ErrBadRequest = knownerror.New("bad request")
)

var ErrUserNotFound = knownerror.New("user not found").Extends(ErrNotFound, ErrBadRequest)

// Now both checks return true:
errors.Is(ErrUserNotFound, ErrNotFound)   // true
errors.Is(ErrUserNotFound, ErrBadRequest) // true
Formatting with %+v

When using %+v, the error prints both the message and the cause:

cause := errors.New("connection refused")
err := knownerror.New("database error").WithCause(cause)

fmt.Printf("%v\n", err)  // database error
fmt.Printf("%+v\n", err) // database error (cause: connection refused)

API

Functions
  • New(text string) *Proxy - creates a new error with the given message
  • Newf(format string, args ...any) *Proxy - creates a new formatted error
  • Wrap(err error) *Proxy - wraps an existing error (returns nil if err is nil)
Methods
  • WithCause(cause error) *Proxy - returns a copy with a root cause error attached
  • Extends(errs ...error) *Proxy - returns a copy that matches additional errors via Is/As
  • Error() string - returns the error message
  • Unwrap() error - returns the base error
  • Cause() error - returns the root cause error (set via WithCause)
  • Is(target error) bool - checks if any extended error matches the target
  • As(target any) bool - extracts a matching extended error into the target
  • Format(s fmt.State, verb rune) - implements fmt.Formatter for custom formatting

License

MIT License - see LICENSE for details.

Documentation

Overview

Package knownerror provides error proxies that can extend multiple errors while maintaining compatibility with errors.Is and errors.As.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Proxy

type Proxy struct {
	// contains filtered or unexported fields
}

Proxy wraps an error, allows it to match multiple sentinel errors via Is/As, and can hold a root cause error.

func New

func New(text string) *Proxy

New creates a Proxy with a simple text message.

func Newf

func Newf(format string, args ...any) *Proxy

Newf creates a Proxy with a formatted message.

func Wrap

func Wrap(err error) *Proxy

Wrap converts an existing error into a Proxy. Returns nil if err is nil.

func (*Proxy) As

func (e *Proxy) As(target any) bool

As is a hook for errors.As. Finds the first extended error that matches target.

func (*Proxy) Cause

func (e *Proxy) Cause() error

Cause returns the root cause error attached via WithCause.

func (*Proxy) Error

func (e *Proxy) Error() string

Error returns the error message.

func (*Proxy) Extends

func (e *Proxy) Extends(errs ...error) *Proxy

Extends adds error categories. The Proxy will match all extended errors via errors.Is:

var ErrNotFound = errors.New("not found")
var ErrUserNotFound = knownerror.New("user not found").Extends(ErrNotFound)
errors.Is(ErrUserNotFound, ErrNotFound) // true

func (*Proxy) Format

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

Format implements fmt.Formatter. With %+v, prints the error and cause:

err := knownerror.New("db error").WithCause(errors.New("connection refused"))
fmt.Printf("%+v", err) // db error (cause: connection refused)

func (*Proxy) Is

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

Is is a hook for errors.Is. Reports whether any extended error matches target.

func (*Proxy) Unwrap

func (e *Proxy) Unwrap() error

Unwrap is a hook for errors.Unwrap. Returns the base error.

func (*Proxy) WithCause

func (e *Proxy) WithCause(cause error) *Proxy

WithCause attaches a root cause error and preserves the original error identity:

var ErrUserNotFound = knownerror.New("user not found")
err := ErrUserNotFound.WithCause(sql.ErrNoRows)
errors.Is(err, ErrUserNotFound) // true
err.Cause()                     // sql.ErrNoRows

Jump to

Keyboard shortcuts

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