errtag

package
v0.0.0-...-54bb4a8 Latest Latest
Warning

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

Go to latest
Published: Mar 4, 2025 License: Apache-2.0 Imports: 6 Imported by: 8

Documentation

Overview

Package errtag provides functionality to add metadata (tag+value pairs) to errors without affecting either their queryability (via errors.Is or errors.As) or their rendering.

Example:

type Flavor int

const (
	Bland Flavor = iota
	Sweet
	Salty
	Savory
	Sour
)

// FlavorTag has the default value Bland.
var FlavorTag = errtag.Make("somepkg.Flavor", Bland)

func something() error {
	var err error
	...
	return FlavorTag.ApplyValue(err, Savory)
}

func otherFunc() {
	err := something()
	if FlavorTag.ValueOrDefault(err) == Savory {
		...
	}
}

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type CollectedValues

type CollectedValues map[TagKey]any

CollectedValues maps Tag description to one or more values for an error, obtained via Collect.

func Collect

func Collect(err error, exclude ...TagType) CollectedValues

Collect scans the error for all known tag keys and returns a map of tag description to the current value for that tag.

func (CollectedValues) String

func (c CollectedValues) String() string

String renders the CollectedValues to a multi-line string.

NOTE: If multiple TagKey's were created with the same description text, it is possible to see the same descriptive string on the left hand side multiple times. This probably indicates that there is a bug in the program (possibly due to copy/pasting a [MakeTag] invocation, or using MakeTag in a way where the output is not correctly reused across the process).

type MergeFn

type MergeFn[T comparable] func(values []*T) *T

MergeFn is the type signature of the Merging function that Tag uses.

This function will only ever be called with len(values) > 1, and every item in values is non-nil.

It may return one of the items in `values`, or synthesize an entirely new value.

If this returns `nil`, it indicates "no value" (e.g. if your merge function allows sibling errors to 'cancel out').

This function MUST NOT modify the contents of the *T in `values`. Pointers are used here simply as a way to avoid copying many potentially large T objects. If Go provided a way to make these immutable non-pointers which weren't copied, I would do it, but unfortunately the best I can do is ask via this doc for your pinkie-promise :/.

This function MUST be left associative, i.e.

Merge(A, B, C, D)

Must be the same as:

Merge(A, Merge(B, Merge(C, D)))

type Tag

type Tag[T comparable] struct {
	// contains filtered or unexported fields
}

Tag holds everything necessary to associate values of type T with errors.

Construct this with Make.

As a compatibility feature, a Tag can currently be used with:

  • errors.Annotate(err, ...).Tag(<the tag>)
  • errors.New(err, <the tag>))

However, these usages are NOT recommended, and exactly equivalent to just doing `tag.Apply(err)`. At some point in the medium-term future, the errors.Annotate construct will be removed entirely and replaced with `fmt.Errorf("... %w ...", ...)`.

Tag keys

Every Tag[T] has a unique description, which is enforced by Make.

Default Value

Tags have a default value, which is the zero value of T, but can be changed with Tag.WithDefault.

The default value is used for Tag.Apply, Tag.In, Tag.Value and Tag.ValueOrDefault.

Merging

When extracting a Value from an error, Tags do a process called 'merging'. Merging happens when an error in the stack implements `Unwrap() []error`, and multiple of those wrapped errors contain a value for Tag[T]. To provide a cohesive understanding of what the current singular value is for such an error, there needs to be some defined way to merge or pick one value.

By default Tags include a Merge function which simply picks the top-left-most value encountered with a breadth-first search, which has historically been good-enough. However some tag value types like status codes may have a concept of a 'worst' value or such. The Merge function can be set with MakeWithMerge.

func Make

func Make[T comparable](description string, defaultValue T) Tag[T]

Make creates a new Tag with a default value.

This generates a new TagKey (which is a very cheap operation).

See Tag.WithDefault to change the default value.

Expected Use - Global Tags

The main use for errtag is to define package-level Tag[T] instances so that packages can tag their own errors, or for multiple packages to coordinate between them for common tags, such as go.chromium.org/luci/common/retry/transient.Tag.

These can then either be exported (for multiple packages to write and/or read the tag values out of errors), or not (for just the package's own use passing data around).

This pattern is far and away the most common use of error tags.

Expected Use - Local Tags

Occasionally you need to tag an error within a very narrow context, for example if you have an outer function calling something and passing a callback. In this case, the 'something' may be doing `errors.Is` checks where the callback must return errors derived (or joined) with some specific errors. It would be OK to make a tag within the outer function, and use this in the callback to pass back some data which is only directly* observable by the outer function.

Each outer function call would generate a new, unique, Tag[T].

* However, note that if the outer function returns this error, the tag value could be observed indirectly via Collect.

Merging values

In the event of a 'multi error' (e.g. errors.Join, fmt.Errorf with multiple %w verbs, the LUCI errors.MultiError, etc.), when you read the tag value from an error, it may need to 'merge' multiple values for this tag.

The Tag returned by Make will merge multiple values by simply picking the first value (so - traverse the error breadth-first, picking the 'leftmost' value at the highest level encountered).

If you need to override this, use MakeWithMerge.

func MakeWithMerge

func MakeWithMerge[T comparable](description string, defaultValue T, merge MergeFn[T]) Tag[T]

MakeWithMerge creates a new Tag.

This acts the same as Make, but also allows you to set a merge function.

func (Tag[T]) Apply

func (t Tag[T]) Apply(err error) (wrapped error)

Apply is shorthand for ApplyValue(err, <defaultValue>).

func (Tag[T]) ApplyValue

func (t Tag[T]) ApplyValue(err error, value T) (wrapped error)

ApplyValue wraps `err` with an error containing the given value.

If `err` is nil, this returns nil.

func (Tag[T]) GenerateErrorTagValue deprecated

func (t Tag[T]) GenerateErrorTagValue() (key, value any)

GenerateErrorTagValue allows this tag to be compatible with errors.Annotate.Tag.

This is a no-op and just allows this to wrapper to be used in the errors.Annotate(...).Tag the function type signature. This should be removed when the Annotate pattern is removed from the errors package.

Deprecated: Do not use.

func (Tag[T]) In

func (t Tag[T]) In(err error) (existsWithDefault bool)

In returns true iff the Tag is present in the error AND currently has the defaultValue for the tag.

If you want to check the presence of a tag, regardless of value, use Tag.Value.

NOTE: This is almost only ever useful for Tags of type `bool`. However, it is so useful for those that it's worth keeping, even though other Tag types can't make good use of it.

It is an alias for:

val, found := t.Value(err)
return found && val == <defaultValue>

func (Tag[T]) Is

func (t Tag[T]) Is(other Tag[T]) bool

Is checks to see if this Tag[T] is the same key as `other`.

This does not compare default values of `t` and `other`.

func (Tag[T]) Key

func (t Tag[T]) Key() TagKey

Key returns the unique TagKey that corresponds with this tag.

func (Tag[T]) Value

func (t Tag[T]) Value(err error) (value T, found bool)

Value retrieves the current value associated with this Tag in the error, also indicating if the tag was found at all.

If the tag was not found, returns (<defaultValue>, false).

func (Tag[T]) ValueOrDefault

func (t Tag[T]) ValueOrDefault(err error) T

ValueOrDefault is the same as Tag.Value, but ignoring `found`.

func (Tag[T]) WithDefault

func (t Tag[T]) WithDefault(newDefault T) Tag[T]

WithDefault returns a variant of this Tag with a different default (for Tag.Apply, etc.).

The returned tag will have the SAME key as `t`. That is, you can probe the error with Tag.Value with either tag to see the current value. The only difference is that if the tag is entirely missing from the error, `t` will return the old default and the new tag will return the new default.

This is useful for cases where you want to have distinct Go symbols for different well know values of the tag (for example, if the tag has an enum type, you may want one tag variant per enum value so that they can be simply applied via Tag.Apply).

type TagKey

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

TagKey is a process-unique key associated with a family of Tag[T]'s derived from the same Make/MakeWithMerge.

These are `comparable`, so e.g.

SomeTag.Key() == SomeTag.WithDefault(...).Key()

Will always compile and be `true`.

func (TagKey) String

func (k TagKey) String() string

String returns the original description associated with this TagKey.

func (TagKey) Valid

func (k TagKey) Valid() bool

Valid returns `true` if this TagKey was obtained via Make/MakeWithMerge.

type TagType

type TagType interface {
	Key() TagKey
}

TagType is implemented by all Tag[T] from this package.

This allows Collect operate across all Tag[??].

Jump to

Keyboard shortcuts

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