tailscale/util/multierr/multierr.go
Joe Tsai c47578b528
util/multierr: add Range (#6643)
Errors in Go are no longer viewed as a linear chain, but a tree.
See golang/go#53435.

Add a Range function that iterates through an error
in a pre-order, depth-first order.
This matches the iteration order of errors.As in Go 1.20.

This adds the logic (but currently commented out) for having
Error implement the multi-error version of Unwrap in Go 1.20.
It is commented out currently since it causes "go vet"
to complain about having the "wrong" signature.

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-12-12 16:48:11 -08:00

137 lines
3.2 KiB
Go

// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package multierr provides a simple multiple-error type.
// It was inspired by github.com/go-multierror/multierror.
package multierr
import (
"errors"
"strings"
"golang.org/x/exp/slices"
)
// An Error represents multiple errors.
type Error struct {
errs []error
}
// Error implements the error interface.
func (e Error) Error() string {
s := new(strings.Builder)
s.WriteString("multiple errors:")
for _, err := range e.errs {
s.WriteString("\n\t")
s.WriteString(err.Error())
}
return s.String()
}
// Errors returns a slice containing all errors in e.
func (e Error) Errors() []error {
return slices.Clone(e.errs)
}
// TODO(https://go.dev/cl/53435): Implement Unwrap when Go 1.20 is released.
/*
// Unwrap returns the underlying errors as is.
func (e Error) Unwrap() []error {
// Do not clone since Unwrap requires callers to not mutate the slice.
// See the documentation in the Go "errors" package.
return e.errs
}
*/
// New returns an error composed from errs.
// Some errors in errs get special treatment:
// - nil errors are discarded
// - errors of type Error are expanded into the top level
//
// If the resulting slice has length 0, New returns nil.
// If the resulting slice has length 1, New returns that error.
// If the resulting slice has length > 1, New returns that slice as an Error.
func New(errs ...error) error {
dst := make([]error, 0, len(errs))
for _, e := range errs {
switch e := e.(type) {
case nil:
continue
case Error:
dst = append(dst, e.errs...)
default:
dst = append(dst, e)
}
}
// dst has been filtered and splatted.
switch len(dst) {
case 0:
return nil
case 1:
return dst[0]
}
// Zero any remaining elements of dst left over from errs, for GC.
tail := dst[len(dst):cap(dst)]
for i := range tail {
tail[i] = nil
}
return Error{errs: dst}
}
// Is reports whether any error in e matches target.
func (e Error) Is(target error) bool {
for _, err := range e.errs {
if errors.Is(err, target) {
return true
}
}
return false
}
// As finds the first error in e that matches target, and if any is found,
// sets target to that error value and returns true. Otherwise, it returns false.
func (e Error) As(target any) bool {
for _, err := range e.errs {
if ok := errors.As(err, target); ok {
return true
}
}
return false
}
// Range performs a pre-order, depth-first iteration of the error tree
// by successively unwrapping all error values.
// For each iteration it calls fn with the current error value and
// stops iteration if it ever reports false.
func Range(err error, fn func(error) bool) bool {
if err == nil {
return true
}
if !fn(err) {
return false
}
switch err := err.(type) {
case interface{ Unwrap() error }:
if err := err.Unwrap(); err != nil {
if !Range(err, fn) {
return false
}
}
case interface{ Unwrap() []error }:
for _, err := range err.Unwrap() {
if !Range(err, fn) {
return false
}
}
// TODO(https://go.dev/cl/53435): Delete this when Error implements Unwrap.
case Error:
for _, err := range err.errs {
if !Range(err, fn) {
return false
}
}
}
return true
}