sourcegraph/lib/errors/multi_error.go
Robert Lin d75a3733d4
lib/errors: add interfaces for typed errors (#39256)
Introducing custom error types can be tricky - I realized there's no code-level assertions or docs on Is and As implementations, so this PR introduces some interfaces to guide implementers. We write them ourselves because the standard library does not provide them, and it allows us to include docstrings better.
2022-07-25 19:59:14 -04:00

109 lines
2.5 KiB
Go

package errors
import (
"fmt"
)
// MultiError is a container for groups of errors.
type MultiError interface {
error
// Errors returns all errors carried by this MultiError, or an empty slice otherwise.
Errors() []error
}
// multiError is our default underlying implementation for MultiError. It is compatible
// with cockroachdb.Error's formatting, printing, etc. and supports introspecting via
// As, Is, and friends.
//
// Implementation is based on https://github.com/knz/shakespeare/blob/master/pkg/cmd/errors.go
type multiError struct {
errs []error
}
var _ MultiError = (*multiError)(nil)
var _ Typed = (*multiError)(nil)
func combineNonNilErrors(err1 error, err2 error) MultiError {
multi1, ok1 := err1.(MultiError)
multi2, ok2 := err2.(MultiError)
// flatten
var errs []error
if ok1 && ok2 {
errs = append(multi1.Errors(), multi2.Errors()...)
} else if ok1 {
errs = append(multi1.Errors(), err2)
} else if ok2 {
errs = append([]error{err1}, multi2.Errors()...)
} else {
errs = []error{err1, err2}
}
return &multiError{errs: errs}
}
// CombineErrors returns a MultiError from err1 and err2. If both are nil, nil is returned.
func CombineErrors(err1, err2 error) MultiError {
if err1 == nil && err2 == nil {
return nil
}
if err1 == nil {
if multi, ok := err2.(MultiError); ok {
return multi
}
return &multiError{errs: []error{err2}}
} else if err2 == nil {
if multi, ok := err1.(MultiError); ok {
return multi
}
return &multiError{errs: []error{err1}}
}
return combineNonNilErrors(err1, err2)
}
// Append returns a MultiError created from all given errors, skipping errs that are nil.
// If no non-nil errors are provided, nil is returned.
func Append(err error, errs ...error) MultiError {
multi := CombineErrors(err, nil)
for _, e := range errs {
if e != nil {
multi = CombineErrors(multi, e)
}
}
return multi
}
func (e *multiError) Error() string { return fmt.Sprintf("%v", e) }
func (e *multiError) Errors() []error {
if e == nil || e.errs == nil {
return nil
}
return e.errs
}
func (e *multiError) Cause() error { return e.errs[len(e.errs)-1] }
func (e *multiError) Unwrap() error { return e.errs[len(e.errs)-1] }
func (e *multiError) Is(refError error) bool {
if e == refError {
return true
}
for _, err := range e.errs {
if Is(err, refError) {
return true
}
}
return false
}
func (e *multiError) As(target any) bool {
if m, ok := target.(*multiError); ok {
*m = *e
return true
}
for _, err := range e.errs {
if As(err, target) {
return true
}
}
return false
}