// 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 }