errortree

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Jan 31, 2023 License: MIT Imports: 1 Imported by: 0

README

errortree

Go Reference Go Report Card MIT License

Package errortree provides multiple-error matching considering the tree structure of errors in Go1.20 and later.

This package also provides the ability to retrieve errors from the tree, using generics to keep the type info from the caller, and returns the search result with an arbitrary concrete type.

Why created the std errors extension?

Standard errors will support tree structures starting with Go1.20. Its design is as follows:

  • Depth-first tree traversal
  • If the target matches, the traversal ends and the result is returned
  • Not all branches in the tree have to match

So it is difficult to satisfying some of the requirements!

Requirement A: Check all branches in the tree whether err and target are equal

For example, sometimes users want to ignore err or only logging for target errors, like:

switch err := ExecuteSomeFunc(); {
case errors.Is(err, TargetErr):
    // ignore or logging...
case err != nil:
    return err
}

This code checks for errors that can be ignored. In this case, if multiErr{targetErr, DifferentErr} or similar is passed as err, errors.Is(err, target) will return true even though it contains DifferentErr.

In some contexts, there will be use cases where user want to make sure that all branches match more strictly to report whether the err tree is equal to target. std error.Is() cannot be used for such a purpose.

So, created following functionlity for a more exact comparison.

ExactlyIs(err error, target error) bool

Requirement B: Extract all matching targets in the tree

The std errors.As() retrieves only the first error that matches the target, but there are some cases where the user may want to retrieve all matching errors for handling purposes.

For this reason, this package provides the following functionality to retrieve errors of a concrete type using generics.

Scan[T any](err error, targetT) []T

Example

ExactlyIs

erra := errors.New("error A")
err := multiErr{
	wrapErr{
		multiErr{
			erra,
			erra,
		},
	},
	wrapErr{erra},
	multiErr{
		erra,
		erra,
		errors.New("wrong error"),
	},
}
result := errortree.ExactlyIs(err, erra)
fmt.Println(result)

// Output:
// false

Scan

err := multiErr{
	multiErr{
		errors.New("error"),
		&fs.PathError{Op: "poser A"},
	},
	wrapErr{&fs.PathError{Op: "poser B"}},
	multiErr{
		errors.New("error"),
		errors.New("error"),
		wrapErr{&fs.PathError{Op: "poser C"}},
	},
}
var p *fs.PathError
matched := errortree.Scan(err, p)
for _, v := range matched {
	fmt.Printf("%s\n", v.Op)
}

// Output:
// poser A
// poser B
// poser C

Documentation

Overview

Package errortree provides multiple-error matching considering the tree structure of errors in Go1.20 and later.

This package uses generics to keep the type info from the caller, and returns the search result with an arbitrary concrete type.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func ExactlyIs added in v0.1.0

func ExactlyIs(err error, target error) bool

ExactlyIs reports whether all branches in the err's tree matches the target.

The tree consists of err itself, followed by the errors obtained by repeatedly calling Unwrap. When err wraps multiple errors, Is examines err followed by a depth-first traversal of its children.

For example, ExactlyIs(err, target) will return true on the following tree because all branches match target when viewed from the root.

err
├── target
├── wrapErr
│   └── multiErr
│       ├── target
│       └── target
└── multiErr
    ├── wrapErr
    │   └── target
    └── target

An error is considered to match a target if it is equal to that target or if it implements a method Is(error) bool such that Is(target) returns true.

Example
package main

import (
	"errors"
	"fmt"

	"github.com/convto/errortree"
)

type wrapErr struct {
	err error
}

func (e wrapErr) Error() string { return "wrapErr" }
func (e wrapErr) Unwrap() error { return e.err }

type multiErr []error

func (m multiErr) Error() string   { return "multiError" }
func (m multiErr) Unwrap() []error { return []error(m) }

func main() {
	erra := errors.New("error A")
	err := multiErr{
		wrapErr{
			multiErr{
				erra,
				erra,
			},
		},
		wrapErr{erra},
		multiErr{
			erra,
			erra,
			errors.New("wrong error"),
		},
	}
	result := errortree.ExactlyIs(err, erra)
	fmt.Println(result)

}
Output:

false

func Scan

func Scan[T any](err error, target T) (matched []T)

Scan finds all matches to target in err's tree, always traverses all trees even if target is found during the search, if no matches are found, it returns nil.

For example, execute Scan(err, target) on the following tree

err
├── targetA(assignable to `target`)
├── wrapErr
│   └── multiErr
│       ├── targetB(assignable to `target`)
│       └── targetC(assignable to `target`)
└── multiErr
    ├── wrapErr
    │   └── targetD(assignable to `target`)
    └── targetE(assignable to `target`)

It returns `[]target{targetA, targetB, targetC, targetD, targetE}`.

The tree consists of err itself, followed by the errors obtained by repeatedly calling Unwrap. When err wraps multiple errors, Scan examines err followed by a depth-first traversal of its children.

An error matches target if the err's concrete value is assignable to the value pointed to by target.

Scan panics if target is not implements error, or is not any interface type.

Note target parameter accepts an interface, so setting `interface{}` or `any` to target will match all nodes!

Example
package main

import (
	"errors"
	"fmt"
	"io/fs"

	"github.com/convto/errortree"
)

type wrapErr struct {
	err error
}

func (e wrapErr) Error() string { return "wrapErr" }
func (e wrapErr) Unwrap() error { return e.err }

type multiErr []error

func (m multiErr) Error() string   { return "multiError" }
func (m multiErr) Unwrap() []error { return []error(m) }

func main() {
	err := multiErr{
		multiErr{
			errors.New("error"),
			&fs.PathError{Op: "poser A"},
		},
		wrapErr{&fs.PathError{Op: "poser B"}},
		multiErr{
			errors.New("error"),
			errors.New("error"),
			wrapErr{&fs.PathError{Op: "poser C"}},
		},
	}
	var p *fs.PathError
	matched := errortree.Scan(err, p)
	for _, v := range matched {
		fmt.Printf("%s\n", v.Op)
	}

}
Output:

poser A
poser B
poser C

Types

This section is empty.

Jump to

Keyboard shortcuts

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