errors

package
v0.0.0-...-128afb8 Latest Latest
Warning

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

Go to latest
Published: Nov 30, 2024 License: Apache-2.0 Imports: 15 Imported by: 834

Documentation

Overview

Package errors is an augmented replacement package for the stdlib "errors" package. It contains the same New method, but also has some handy methods and types for dealing with errors.

Index

Examples

Constants

View Source
const (

	// RenderedStackDumpRe is coupled with renderedStackDumpFormat to indicate its regex expression.
	RenderedStackDumpRe = `goroutine \d+:\n`
)

Variables

View Source
var ErrUnsupported = errors.ErrUnsupported

ErrUnsupported re-exports errors.ErrUnsupported from the standard library.

Functions

func Any

func Any(err error, fn func(error) bool) (any bool)

Any performs a Walk traversal of an error, returning true (and short-circuiting) if the supplied filter function returns true for any visited error.

If err is nil, Any will return false.

func Append

func Append(errs ...error) error

Append takes a list of errors, whether they are nil, MultiErrors, or regular errors, and combines them into a single error.

The resulting error is not a multierror unless it needs to be.

Sample usage shown below:

err := DoSomething()
err = errors.Append(err, DoSomethingElse())
err = errors.Append(err, DoAThirdThing())

if err != nil {
  // log an error or something, I don't know
}
// proceed as normal

func As

func As(e error, target any) bool

As re-exports errors.As from the standard library.

func Contains

func Contains(outer error, inner error) bool

Contains performs a Walk traversal of |outer|, returning true if any visited error is equal to |inner|.

func Filter

func Filter(err error, exclude error, others ...error) error

Filter examines a supplied error and removes instances of excluded errors from it. If the entire supplied error is excluded, Filter will return nil.

If a MultiError is supplied to Filter, it will be recursively traversed, and its child errors will be turned into nil if they match the supplied filter. If a MultiError has all of its children converted to nil as a result of the filter, it will itself be reduced to nil.

func FilterFunc

func FilterFunc(err error, shouldFilter func(error) bool) error

FilterFunc examines a supplied error and removes instances of errors that match the supplied filter function. If the entire supplied error is removed, FilterFunc will return nil.

If a MultiError is supplied to FilterFunc, it will be recursively traversed, and its child errors will be turned into nil if they match the supplied filter function. If a MultiError has all of its children converted to nil as a result of the filter, it will itself be reduced to nil.

Consqeuently, if err is a MultiError, shouldFilter will be called once with err as its value and once for every non-nil error that it contains.

func Flatten

func Flatten(err error) error

Flatten collapses a multi-dimensional MultiError space into a flat MultiError, removing "nil" errors.

If err is not an errors.MultiError, will return err directly.

As a special case, if merr contains no non-nil errors, nil will be returned.

func GetTags

func GetTags(err error) map[TagKey]any

GetTags returns a map of all TagKeys set in this error to their value.

A nil value means that the tag is present, but has a nil associated value.

This is done in a depth-first traversal of the error stack, with the most-recently-set value of the tag taking precedence.

func Is

func Is(e error, target error) bool

Is re-exports errors.Is from the standard library.

func IsPanicking

func IsPanicking(skip int) bool

IsPanicking returns true iff the current goroutine is panicking.

Always returns false when not invoked via a defer'd function.

This should only be used to indicate some best-effort error status, not to modify control flow of the program. Panics are still crashes!

HACK: Detection is implemented by looking up the stack at most skip+10 frames above IsPanicking to find if the golang panic handler is on the stack. This may break when the Go runtime changes!

`skip` indicates how many additional frames of the stack to skip (a value of 0 starts the stack at the caller of `IsPanicking`). Clamps to a minimum value of 0.

Does NOT invoke `recover()`. WILL detect `panic(nil)`.

Example
package main

import (
	"fmt"
	"runtime/debug"
	"strings"
)

func CrashingFunction() {
	panic("boom")
}

func main() {
	A := func(crash bool) {
		defer func() {
			if IsPanicking(0) {
				fmt.Println("PANIK!")
			} else {
				fmt.Println("kalm")
			}
		}()
		if crash {
			fmt.Println("about to boom")
			CrashingFunction()
		} else {
			fmt.Println("smooth sailing")
		}
	}

	defer func() {
		// Make sure IsPanicking didn't do a `recover()`, which would goof up the
		// stack.
		stack := string(debug.Stack())
		if !strings.Contains(stack, "CrashingFunction") {
			fmt.Println("stack trace doesn't originate from CrashingFunction")
		} else {
			fmt.Println("stack trace originates from CrashingFunction")
		}
		// But recover ourselves to make sure ExampleIsPanicking actually passes
		// instead of crashing with an unrecovered panic.
		recover()
	}()

	if IsPanicking(0) {
		fmt.Println("cannot be panicing when not in defer'd function.")
	}

	A(false)
	fmt.Println("first pass success")
	A(true)

}
Output:

smooth sailing
kalm
first pass success
about to boom
PANIK!
stack trace originates from CrashingFunction

func Join

func Join(errs ...error) error

Join re-exports errors.Join from the standard library.

func Log

func Log(ctx context.Context, err error, excludePkgs ...string)

Log logs the full error. If this is an Annotated error, it will log the full stack information as well.

This is a shortcut for logging the output of RenderStack(err).

If resulting log message is large, splits it into log entries of at most 64KiB.

func New

func New(msg string, tags ...TagValueGenerator) error

New is an API-compatible version of the standard errors.New function. Unlike the stdlib errors.New, this will capture the current stack information at the place this error was created.

func RenderStack

func RenderStack(err error, excludePkgs ...string) []string

RenderStack renders the error to a list of lines.

func SingleError

func SingleError(err error) error

SingleError provides a simple way to uwrap a MultiError if you know that it could only ever contain one element.

If err is a MultiError, return its first element. Otherwise, return err.

func TagValueIn

func TagValueIn(t TagKey, err error) (value any, ok bool)

TagValueIn will retrieve the tagged value from the error that's associated with this key, and a boolean indicating if the tag was present or not.

func Unwrap

func Unwrap(err error) error

Unwrap unwraps a wrapped error recursively, returning its inner error.

If the supplied error is not nil, Unwrap will never return nil. If a wrapped error reports that its Unwrap is nil, that error will be returned.

func Walk

func Walk(err error, fn func(error) bool)

Walk performs a depth-first traversal of the supplied error, unfolding it and invoke the supplied callback for each layered error recursively. If the callback returns true, Walk will continue its traversal.

  • If walk encounters a MultiError, the callback is called once for the outer MultiError, then once for each inner error.
  • If walk encounters a Wrapped error, the callback is called for the outer and inner error.
  • If an inner error is, itself, a container, Walk will recurse into it.

If err is nil, the callback will not be invoked.

func WalkLeaves

func WalkLeaves(err error, fn func(error) bool)

WalkLeaves is like Walk, but only calls fn on leaf nodes.

Types

type Annotator

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

Annotator is a builder for annotating errors. Obtain one by calling Annotate on an existing error or using Reason.

See the example test for Annotate to see how this is meant to be used.

func Annotate

func Annotate(err error, reason string, args ...any) *Annotator

Annotate captures the current stack frame and returns a new annotatable error, attaching the publicly readable `reason` format string to the error. You can add additional metadata to this error with the 'InternalReason' and 'Tag' methods, and then obtain a real `error` with the Err() function.

If this is passed nil, it will return a no-op Annotator whose .Err() function will also return nil.

The original error may be recovered by using Wrapped.Unwrap on the returned error.

Rendering the derived error with Error() will render a summary version of all the public `reason`s as well as the initial underlying error's Error() text. It is intended that the initial underlying error and all annotated reasons only contain user-visible information, so that the accumulated error may be returned to the user without worrying about leakage.

You should assume that end-users (including unauthenticated end users) may see the text in the `reason` field here. To only attach an internal reason, leave the `reason` argument blank and don't pass any additional formatting arguments.

The `reason` string is formatted with `args` and may contain Sprintf-style formatting directives.

Example
package main

import (
	"fmt"
)

func someProcessingFunction(val int) error {
	if val == 1 {
		// New and Reason automatically include stack information.
		return Reason("bad number: %d", val).Err()
	}
	if err := someProcessingFunction(val - 1); err != nil {
		// correctly handles recursion
		return Annotate(err, "").InternalReason("val(%d)", val).Err()
	}
	return nil
}

func someLibFunc(vals ...int) error {
	for i, v := range vals {
		if err := someProcessingFunction(v); err != nil {
			return Annotate(err, "processing %d", v).
				InternalReason("secret(%s)/i(%d)", "value", i).Err()
		}
	}
	return nil
}

type MiscWrappedError struct{ error }

func (e *MiscWrappedError) Error() string { return fmt.Sprintf("super wrapper(%s)", e.error.Error()) }
func (e *MiscWrappedError) Unwrap() error { return e.error }

func errorWrapper(err error) error {
	if err != nil {
		err = &MiscWrappedError{err}
	}
	return err
}

func someIntermediateFunc(vals ...int) error {
	errch := make(chan error)
	go func() {
		defer close(errch)
		errch <- Annotate(errorWrapper(someLibFunc(vals...)), "could not process").Err()
	}()
	me := MultiError(nil)
	for err := range errch {
		if err != nil {
			me = append(me, err)
		}
	}
	if me != nil {
		return Annotate(me, "while processing %v", vals).Err()
	}
	return nil
}

func main() {
	if err := someIntermediateFunc(3); err != nil {
		err = Annotate(err, "top level").Err()
		fmt.Println("Public-facing error:\n ", err)
		fmt.Println("\nfull error:")
		for _, l := range FixForTest(RenderStack(err, "runtime", "_test")) {
			fmt.Println(l)
		}
	}

}
Output:

Public-facing error:
  top level: while processing [3]: could not process: super wrapper(processing 3: bad number: 1)

full error:
original error: bad number: 1

GOROUTINE LINE
#? go.chromium.org/luci/common/errors/annotate_example_test.go:24 - errors.someProcessingFunction()
  reason: bad number: 1

#? go.chromium.org/luci/common/errors/annotate_example_test.go:26 - errors.someProcessingFunction()
  internal reason: val(2)

#? go.chromium.org/luci/common/errors/annotate_example_test.go:26 - errors.someProcessingFunction()
  internal reason: val(3)

From frame 2 to 3, the following wrappers were found:
  unknown wrapper *errors.MiscWrappedError

#? go.chromium.org/luci/common/errors/annotate_example_test.go:35 - errors.someLibFunc()
  reason: processing 3
  internal reason: secret(value)/i(0)

From frame 3 to 4, the following wrappers were found:
  internal reason: MultiError 1/1: following first non-nil error.

#? go.chromium.org/luci/common/errors/annotate_example_test.go:59 - errors.someIntermediateFunc.func1()
  reason: could not process

... skipped SOME frames in pkg "runtime"...

GOROUTINE LINE
#? go.chromium.org/luci/common/errors/annotate_example_test.go:68 - errors.someIntermediateFunc()
  reason: while processing [3]

#? go.chromium.org/luci/common/errors/annotate_example_test.go:74 - errors.ExampleAnnotate()
  reason: top level

#? testing/run_example.go:XXX - testing.runExample()
#? testing/example.go:XXX - testing.runExamples()
#? testing/testing.go:XXX - testing.(*M).Run()
#? ./_testmain.go:XXX - main.main()
... skipped SOME frames in pkg "runtime"...

func Reason

func Reason(reason string, args ...any) *Annotator

Reason builds a new Annotator starting with reason. This allows you to use all the formatting directives you would normally use with Annotate, in case your originating error needs tags or an internal reason.

errors.Reason("something bad: %d", value).Tag(transient.Tag).Err()

Prefer this form to errors.New(fmt.Sprintf("...")) or fmt.Errorf("...")

func (*Annotator) Err

func (a *Annotator) Err() error

Err returns the finalized annotated error.

func (*Annotator) InternalReason

func (a *Annotator) InternalReason(reason string, args ...any) *Annotator

InternalReason adds a stack-trace-only internal reason string (for humans) to this error.

The text here will only be visible when using `errors.Log` or `errors.RenderStack`, not when calling the .Error() method of the resulting error.

The `reason` string is formatted with `args` and may contain Sprintf-style formatting directives.

func (*Annotator) Tag

func (a *Annotator) Tag(tags ...TagValueGenerator) *Annotator

Tag adds a tag with an optional value to this error.

`value` is a unary optional argument, and must be a simple type (i.e. has a reflect.Kind which is a base data type like bool, string, or int).

type BoolTag

type BoolTag struct{ Key TagKey }

BoolTag is an error tag implementation which holds a boolean value.

It should be constructed like:

var myTag = errors.BoolTag{Key: errors.NewTagKey("some description")}

func (BoolTag) Apply

func (b BoolTag) Apply(err error) error

Apply is a shortcut for With(true).Apply(err)

func (BoolTag) GenerateErrorTagValue

func (b BoolTag) GenerateErrorTagValue() TagValue

GenerateErrorTagValue implements TagValueGenerator, and returns a default value for the tag of `true`. If you want to set this BoolTag value to false, use BoolTag.Off().

func (BoolTag) In

func (b BoolTag) In(err error) bool

In returns true iff this tag value has been set to true on this error.

func (BoolTag) Off

func (b BoolTag) Off() TagValue

Off allows you to "remove" this boolean tag from an error (by setting it to false).

type LazyMultiError

type LazyMultiError interface {
	// Assign semantically assigns the error to the given index in the MultiError.
	// If the error is nil, no action is taken. Otherwise the MultiError is
	// allocated to its full size (if not already), and the error assigned into
	// it.
	//
	// Returns true iff err != nil (i.e. "was it assigned?"), so you can use this
	// like:
	//   if !lme.Assign(i, err) {
	//     // stuff requiring err == nil
	//   }
	Assign(int, error) bool

	// GetOne returns the error at the given index (which may be nil)
	GetOne(int) error

	// Get returns the MultiError, or nil, if no non-nil error was Assign'd.
	Get() error
}

LazyMultiError is a lazily-constructed MultiError.

LazyMultiError is like MultiError, except that you know the ultimate size up front, and then you call Assign for each error encountered, and it's potential index. The underlying MultiError will only be allocated if one of the Assign'd errors is non-nil. Similarly, Get will retrieve either the allocated MultiError, or nil if no error was encountered. Build one with NewLazyMultiError.

func NewLazyMultiError

func NewLazyMultiError(size int) LazyMultiError

NewLazyMultiError makes a new LazyMultiError of the provided size.

type MultiError

type MultiError []error

MultiError is a simple `error` implementation which represents multiple `error` objects in one.

func NewMultiError

func NewMultiError(errors ...error) MultiError

NewMultiError create new multi error from given errors.

Can be used to workaround 'go vet' confusion "composite literal uses unkeyed fields" or if you do not want to remember that MultiError is in fact []error.

func (MultiError) AsError

func (m MultiError) AsError() error

AsError returns an `error` interface for this MultiError only if it has >0 length.

func (MultiError) Error

func (m MultiError) Error() string

func (MultiError) First

func (m MultiError) First() error

First returns the first non-nil error.

func (MultiError) Is

func (m MultiError) Is(other error) bool

Is implements errors.Is.

This implementation does not interfere with errors.Is, but DOES allow matching, especially in tests, when matching a MultiError pattern against another MultiError whose size matches exactly and whose contents recursively match with `errors.Is`.

This is necessary because the stdlib `errors.Is` will call `Unwrap() []error` on the source `err`, but NOT the target `err`, meaning that without this method, MultiError can NEVER be the right hand side of a successful `errors.Is` call.

If this returns false, `errors.Is` will continue its typical algorithm.

func (*MultiError) MaybeAdd

func (m *MultiError) MaybeAdd(err error)

MaybeAdd will add `err` to `m` if `err` is not nil.

func (MultiError) Summary

func (m MultiError) Summary() (n int, first error)

Summary gets the total count of non-nil errors and returns the first one.

func (MultiError) Unwrap

func (m MultiError) Unwrap() []error

Unwrap turns MultiError to a slices of errors.

This will make MultiError works with errors.Is or errors.As from stdlib.

type TagKey

type TagKey *tagDescription

TagKey objects are used for applying tags and finding tags/values in errors. See NewTag for details.

func NewTagKey

func NewTagKey(description string) TagKey

NewTagKey creates a new TagKey.

Use this with a BoolTag or your own custom tag implementation.

Example (bool tag):

var myTag = errors.BoolTag{Key: errors.NewTagKey("this error is a user error")}

err = myTag.Apply(err)
myTag.In(err) // == true

err2 := myTag.Off().Apply(err)
myTag.In(err2) // == false

Example (custom tag)

type SomeType int
type myTag struct { Key errors.TagKey }
func (m myTag) With(value SomeType) errors.TagValue {
  return errors.TagValue{Key: m.Key, Value: value}
}
func (m myTag) In(err error) (v SomeType, ok bool) {
  d, ok := errors.TagValueIn(m.Key, err)
  if ok {
    v = d.(SomeType)
  }
  return
}
var MyTag = myTag{errors.NewTagKey("has a SomeType")}

You could then use it like:

err = MyTag.With(100).Apply(err)
MyTag.In(err) // == true
errors.ValueIn(err) // == (SomeType(100), true)

type TagValue

type TagValue struct {
	Key   TagKey
	Value any
}

TagValue represents a (tag, value) to be used with Annotate.Tag, or may be applied to an error directly with the Apply method.

Usually tag implementations will have a typesafe With method that generates these. Avoid constructing these ad-hoc so that a given tag definition can control the type safety around these.

func (TagValue) Apply

func (t TagValue) Apply(err error) error

Apply applies this tag value (key+value) directly to the error. This is a shortcut for `errors.Annotate(err, "").Tag(t).Err()`.

func (TagValue) GenerateErrorTagValue

func (t TagValue) GenerateErrorTagValue() TagValue

GenerateErrorTagValue implements TagValueGenerator

type TagValueGenerator

type TagValueGenerator interface {
	GenerateErrorTagValue() TagValue
}

TagValueGenerator generates (TagKey, value) pairs, for use with Annoatator.Tag and New().

type Wrapped

type Wrapped interface {
	// Unwrap returns the wrapped error.
	Unwrap() error
}

Wrapped indicates an error that wraps another error.

Jump to

Keyboard shortcuts

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