errors

package
v0.25.3 Latest Latest
Warning

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

Go to latest
Published: Aug 7, 2024 License: Unlicense Imports: 3 Imported by: 59

Documentation

Overview

Package errors is a drop-in replacement and extension of the Go standard library's package errors.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrUnsupported = stderrors.ErrUnsupported

ErrUnsupported indicates that a requested operation cannot be performed, because it is unsupported. For example, a call to os.Link when using a file system that does not support hard links.

Functions

func Annotate

func Annotate(err error, format string, args ...any) (annotated error)

Annotate annotates the error with the message, unless the error is nil. The last verb in format must be a verb compatible with errors, for example "%w".

In Defers

The primary use case for this function is to simplify code like this:

func (f *foo) doStuff(s string) (err error) {
	defer func() {
		if err != nil {
			err = fmt.Errorf("bad foo %q: %w", s, err)
		}
	}()

	// …
}

Instead, write:

func (f *foo) doStuff(s string) (err error) {
	defer func() { err = errors.Annotate(err, "bad foo %q: %w", s) }()

	// …
}

At The End Of Functions

Another possible use case is to simplify final checks like this:

func (f *foo) doStuff(s string) (err error) {
	// …

	if err != nil {
		return fmt.Errorf("doing stuff with %s: %w", s, err)
	}

	return nil
}

Instead, you could write:

func (f *foo) doStuff(s string) (err error) {
	// …

	return errors.Annotate(err, "doing stuff with %s: %w", s)
}

Warning

This function requires that there be only ONE error named "err" in the function and that it is always the one that is returned. Example (Bad) provides an example of the incorrect usage of WithDeferred.

Example
package main

import (
	"fmt"

	"github.com/AdguardTeam/golibs/errors"
)

func main() {
	f := func(fn string) (err error) {
		defer func() {
			err = errors.Annotate(err, "reading %q: %w", fn)
		}()

		return errors.Error("not found")
	}

	err := f("non-existing")
	fmt.Println("with err    :", err)

	f = func(fn string) (err error) {
		defer func() {
			err = errors.Annotate(err, "reading %q: %w", fn)
		}()

		return nil
	}

	err = f("non-existing")
	fmt.Println("without err :", err)

}
Output:


with err    : reading "non-existing": not found
without err : <nil>
Example (Bad)
package main

import (
	"fmt"

	"github.com/AdguardTeam/golibs/errors"
)

func main() {
	const errNotFound errors.Error = "not found"

	cond := true
	g := func() (err error) { return nil }
	f := func() error {
		if cond {
			err := g()
			if err != nil {
				return err
			}

			// BAD!  This err is not the same err as the one that is
			// returned from the top level.
			defer func() { err = errors.Annotate(err, "f") }()
		}

		// This error is returned without an annotation.
		return errNotFound
	}

	// Outputs the error without an annotation.
	fmt.Println(f())

}
Output:


not found
Example (End)
package main

import (
	"fmt"

	"github.com/AdguardTeam/golibs/errors"
)

func main() {
	f := func(fn string) (err error) {
		err = errors.Error("not found")

		// Some operations.

		return errors.Annotate(err, "reading %q: %w", fn)
	}

	err := f("non-existing")
	fmt.Println("with err    :", err)

	f = func(fn string) (err error) {
		// Some operations.

		return errors.Annotate(err, "reading %q: %w", fn)
	}

	err = f("non-existing")
	fmt.Println("without err :", err)

}
Output:


with err    : reading "non-existing": not found
without err : <nil>

func As

func As(err error, target any) (ok bool)

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

It calls errors.As from the Go standard library.

Example
package main

import (
	"fmt"
	"os"

	"github.com/AdguardTeam/golibs/errors"
)

func main() {
	if _, err := os.Open("non-existing"); err != nil {

		var pathError *os.PathError

		if errors.As(err, &pathError) {
			fmt.Println("Failed at path:", pathError.Path)
		} else {
			fmt.Println(err)
		}

	}

}
Output:


Failed at path: non-existing

func Is

func Is(err, target error) (ok bool)

Is reports whether any error in err's chain matches target.

It calls errors.Is from the Go standard library.

Example
package main

import (
	"fmt"
	"os"

	"github.com/AdguardTeam/golibs/errors"
)

func main() {
	if _, err := os.Open("non-existing"); err != nil {
		if errors.Is(err, os.ErrNotExist) {
			fmt.Println("file does not exist")
		} else {
			fmt.Println(err)
		}
	}

}
Output:


file does not exist

func Join added in v0.12.1

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.

It calls errors.Join from the Go standard library.

func List deprecated

func List(msg string, errs ...error) (err error)

List wraps several errors into a single error with an additional message.

Deprecated: Use errors.Join instead.

Example
package main

import (
	"fmt"

	"github.com/AdguardTeam/golibs/errors"
)

func main() {
	const (
		err1 errors.Error = "stage 1"
		err2 errors.Error = "stage 2"
	)

	err := errors.List("fail")
	fmt.Printf("msg only     : %q %v\n", err, errors.Unwrap(err))

	err = errors.List("fail", err1)
	fmt.Printf("msg and err  : %q %q\n", err, errors.Unwrap(err))

	err = errors.List("fail", err1, err2)
	fmt.Printf("msg and errs : %q %q\n", err, errors.Unwrap(err))

}
Output:


msg only     : "fail" <nil>
msg and err  : "fail: stage 1" "stage 1"
msg and errs : "fail: 2 errors: \"stage 1\", \"stage 2\"" "stage 1"

func New deprecated

func New(msg string) (err error)

New returns an error that formats as the given msg. Each call to New returns a distinct error value even if the text is identical.

It calls errors.New from the Go standard library.

Deprecated: Use type Error and constant errors instead.

Example
package main

import (
	"fmt"

	"github.com/AdguardTeam/golibs/errors"
)

func main() {
	err := errors.New("emit macho dwarf: elf header corrupted")
	if err != nil {
		fmt.Print(err)
	}

}
Output:


emit macho dwarf: elf header corrupted

func Unwrap

func Unwrap(err error) (unwrapped error)

Unwrap returns the result of calling the Unwrap method on err, if err's type contains an Unwrap method returning error. Otherwise, Unwrap returns nil.

It calls errors.Unwrap from the Go standard library.

func WithDeferred

func WithDeferred(returned, deferred error) (result error)

WithDeferred is a helper function for deferred errors. For example, to preserve errors from the Close method, replace this:

defer f.Close()

With this:

defer func() { err = errors.WithDeferred(err, f.Close()) }

If returned is nil and deferred is non-nil, the returned error implements the Deferred interface. If both returned and deferred are non-nil, result has the underlying type of *Pair.

Warning

This function requires that there be only ONE error named "err" in the function and that it is always the one that is returned. Example (Bad) provides an example of the incorrect usage of WithDeferred.

Example
package main

import (
	"fmt"

	"github.com/AdguardTeam/golibs/errors"
)

func main() {
	const (
		errClose    errors.Error = "close fail"
		errNotFound errors.Error = "not found"
	)

	close := func(fn string) (err error) { return nil }
	f := func(fn string) (err error) {
		defer func() { err = errors.WithDeferred(err, close(fn)) }()

		return nil
	}

	err := f("non-existing")
	fmt.Println("without errs      :", err)

	close = func(fn string) (err error) { return nil }
	f = func(fn string) (err error) {
		defer func() { err = errors.WithDeferred(err, close(fn)) }()

		return errNotFound
	}

	err = f("non-existing")
	fmt.Println("with returned err :", err)

	close = func(fn string) (err error) { return errClose }
	f = func(fn string) (err error) {
		defer func() { err = errors.WithDeferred(err, close(fn)) }()

		return nil
	}

	err = f("non-existing")
	fmt.Println("with deferred err :", err)

	close = func(fn string) (err error) { return errClose }
	f = func(fn string) (err error) {
		defer func() { err = errors.WithDeferred(err, close(fn)) }()

		return errNotFound
	}

	err = f("non-existing")
	fmt.Println("with both errs    :", err)

}
Output:


without errs      : <nil>
with returned err : not found
with deferred err : deferred: close fail
with both errs    : returned: "not found", deferred: "close fail"
Example (Bad)
package main

import (
	"fmt"

	"github.com/AdguardTeam/golibs/errors"
)

func main() {
	const (
		errClose    errors.Error = "close fail"
		errNotFound errors.Error = "not found"
	)

	cond := true
	close := func(fn string) (err error) { return errClose }
	g := func() (err error) { return nil }
	f := func(fn string) error {
		if cond {
			err := g()
			if err != nil {
				return err
			}

			// BAD!  This err is not the same err as the one that is
			// returned from the top level.
			defer func() { err = errors.WithDeferred(err, close(fn)) }()
		}

		// This error is the one that is actually returned.
		return errNotFound
	}

	// Only outputs the returned error and ignores the deferred one.
	fmt.Println(f("non-existing"))

}
Output:


not found

Types

type Aser

type Aser interface {
	As(target any) (ok bool)
}

Aser is a copy of the hidden aser interface from the Go standard library. It is added here for tests, linting, etc.

type Deferred

type Deferred interface {
	error
	Deferred() (ok bool)
}

Deferred is the interface for errors that were returned by cleanup functions, such as Close. This is useful in APIs which desire to handle such errors differently, for example to log them as warnings.

Method Deferred returns a bool to mirror the behavior of types like net.Error and allow implementations to decide if the error is a deferred one dynamically. Users of this API must check it's return value as well as the result errors.As.

if derr := errors.Deferred(nil); errors.As(err, &derr) && derr.Deferred() {
        // …
}

See https://dave.cheney.net/2014/12/24/inspecting-errors.

Example
package main

import (
	"fmt"

	"github.com/AdguardTeam/golibs/errors"
)

func main() {
	const (
		errClose    errors.Error = "close fail"
		errNotFound errors.Error = "not found"
	)

	// logErr logs the error according to its severity level.
	logErr := func(err error) {
		if defErr := errors.Deferred(nil); errors.As(err, &defErr) && defErr.Deferred() {
			// Log deferred errors as warnings.
			fmt.Printf("warning: %s\n", errors.Unwrap(defErr))
		} else {
			fmt.Printf("ERROR: %s\n", err)
		}
	}

	// Case 1: the function fails, but the cleanup succeeds.
	close := func(fn string) (err error) { return nil }
	f := func(fn string) (err error) {
		defer func() { err = errors.WithDeferred(err, close(fn)) }()

		return errNotFound
	}

	err := f("non-existing")
	logErr(err)

	// Case 2: the function succeeds, but the cleanup fails.
	close = func(fn string) (err error) { return errClose }
	f = func(fn string) (err error) {
		defer func() { err = errors.WithDeferred(err, close(fn)) }()

		return nil
	}

	err = f("non-existing")
	logErr(err)

}
Output:


ERROR: not found
warning: close fail

type Error

type Error string

Error is the constant error type.

See https://dave.cheney.net/2016/04/07/constant-errors.

Example
package main

import (
	"fmt"

	"github.com/AdguardTeam/golibs/errors"
)

func main() {
	const errNotFound errors.Error = "not found"

	f := func(fn string) (err error) {
		return fmt.Errorf("opening %q: %w", fn, errNotFound)
	}

	err := f("non-existing")
	fmt.Println("err       :", err)
	fmt.Println("unwrapped :", errors.Unwrap(err))

}
Output:


err       : opening "non-existing": not found
unwrapped : not found

func (Error) Error

func (err Error) Error() (msg string)

Error implements the error interface for Error.

type Iser

type Iser interface {
	Is(target error) (ok bool)
}

Iser is a copy of the hidden iser interface from the Go standard library. It is added here for tests, linting, etc.

type Pair

type Pair struct {
	Returned error
	Deferred error
}

Pair is a pair of errors. The Returned error is the main error that has been returned by a function. The Deferred error is the error returned by the cleanup function, such as Close.

In pairs returned from WithDeferred, the Deferred error always implements the Deferred interface.

Example
package main

import (
	"fmt"

	"github.com/AdguardTeam/golibs/errors"
)

func main() {
	close := func(fn string) (err error) { return errors.Error("close fail") }
	f := func(fn string) (err error) {
		defer func() { err = errors.WithDeferred(err, close(fn)) }()

		return errors.Error("not found")
	}

	err := f("non-existing")
	fmt.Println("err       :", err)
	fmt.Println("unwrapped :", errors.Unwrap(err))

	fmt.Println()

	errPair := &errors.Pair{}
	if !errors.As(err, &errPair) {
		panic("err is not an *error.Pair")
	}

	defErr := errPair.Deferred.(errors.Deferred)
	fmt.Println("deferred           :", defErr)
	fmt.Println("deferred unwrapped :", errors.Unwrap(defErr))
	fmt.Println("deferred check     :", defErr.Deferred())

}
Output:


err       : returned: "not found", deferred: "close fail"
unwrapped : not found

deferred           : deferred: close fail
deferred unwrapped : close fail
deferred check     : true

func (*Pair) Error

func (err *Pair) Error() string

Error implements the error interface for *Pair.

func (*Pair) Unwrap

func (err *Pair) Unwrap() (unwrapped error)

Unwrap implements the Wrapper interface for *Pair. It returns the Returned error.

type Wrapper

type Wrapper interface {
	Unwrap() error
}

Wrapper is a copy of the hidden wrapper interface from the Go standard library. It is added here for tests, linting, etc.

type WrapperSlice added in v0.12.1

type WrapperSlice interface {
	Unwrap() []error
}

WrapperSlice is a copy of the hidden wrapper interface added to the Go standard library in Go 1.20. It is added here for tests, linting, etc.

Jump to

Keyboard shortcuts

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