Skip to content

Instantly share code, notes, and snippets.

@felixge
Last active March 31, 2020 14:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save felixge/3ca60f00530146b5b5d515e4c8604bdc to your computer and use it in GitHub Desktop.
Save felixge/3ca60f00530146b5b5d515e4c8604bdc to your computer and use it in GitHub Desktop.
// Package multiwrap implements support for wrapping multiple errors into a
// single error value that supports the Go 1.13 Unwrap(), Is() and As()
// interface. It's similar to go-multierror [1].
//
// This is a proof of concept and I'm looking for feedback! Maybe somebody has
// already done a nicer version of this idea?
//
// [1] https://godoc.org/github.com/hashicorp/go-multierror
package multiwrap
import (
"errors"
"strings"
)
func Multiwrap(errs ...error) error {
var top *multiwrap
var current *multiwrap
for _, err := range errs {
if current == nil {
top = &multiwrap{error: err}
current = top
} else {
current.next = &multiwrap{error: err}
current = current.next
}
}
if top == nil {
return nil
}
return top
}
type multiwrap struct {
error
next *multiwrap
}
func (m *multiwrap) Error() string {
var msgs []string
for m != nil {
msgs = append(msgs, m.error.Error())
m = m.next
}
return strings.Join(msgs, ": ")
}
func (m *multiwrap) Unwrap() error {
if m == nil {
return nil
}
return m.next
}
func (m *multiwrap) Is(target error) bool {
if m == nil {
return false
}
return errors.Is(m.error, target)
}
func (m *multiwrap) As(target interface{}) bool {
if m == nil {
return false
}
return errors.As(m.error, target)
}
package multiwrap
import (
"errors"
"testing"
)
func TestMultiwrap(t *testing.T) {
tests := []struct {
Wrap []error
Is []error
IsNot []error
Msg string
Nil bool
}{
{
Wrap: []error{},
Is: []error{},
IsNot: []error{A{}, B{}, C{}},
Nil: true,
},
{
Wrap: []error{A{}},
Is: []error{A{}},
IsNot: []error{B{}, C{}},
Msg: "A",
},
{
Wrap: []error{A{}, B{}},
Is: []error{A{}, B{}},
IsNot: []error{C{}},
Msg: "A: B",
},
{
Wrap: []error{B{}, A{}},
Is: []error{A{}, B{}},
IsNot: []error{C{}},
Msg: "B: A",
},
{
Wrap: []error{A{}, B{}, C{}},
Is: []error{A{}, B{}, C{}},
IsNot: []error{},
Msg: "A: B: C",
},
}
for i, test := range tests {
err := Multiwrap(test.Wrap...)
if test.Nil {
if err != nil {
t.Errorf("test=%d want nil, got=%#v", i, err)
}
continue
}
for _, want := range test.Is {
if !errors.Is(err, want) {
t.Errorf("test=%d want %T", i, want)
}
}
for _, want := range test.IsNot {
if errors.Is(err, want) {
t.Errorf("test=%d want not %T", i, want)
}
}
if err.Error() != test.Msg {
t.Errorf("test=%d got=%q want=%q", i, err.Error(), test.Msg)
}
}
}
type A struct{}
func (a A) Error() string { return "A" }
type B struct{}
func (a B) Error() string { return "B" }
type C struct{}
func (a C) Error() string { return "C" }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment