Skip to content

Commit

Permalink
Enable Errors support for any multi-error (#75)
Browse files Browse the repository at this point in the history
Starting Go 1.20, any multi-error should conform to the standard unwrap
method: Unwrap() []error.

This changes multierr.Errors() method to support any error that complies
to that interface.

Fix #70 / GO-1883
  • Loading branch information
sywhang committed Mar 16, 2023
1 parent 8767aa9 commit faff69d
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 20 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
Releases
========

Unreleased
====================
- `Errors` now supports any error that implements multiple-error
interface.

v1.10.0 (2023-03-08)
====================

Expand Down
20 changes: 1 addition & 19 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,23 +194,7 @@ type errorGroup interface {
//
// Callers of this function are free to modify the returned slice.
func Errors(err error) []error {
if err == nil {
return nil
}

// Note that we're casting to multiError, not errorGroup. Our contract is
// that returned errors MAY implement errorGroup. Errors, however, only
// has special behavior for multierr-specific error objects.
//
// This behavior can be expanded in the future but I think it's prudent to
// start with as little as possible in terms of contract and possibility
// of misuse.
eg, ok := err.(*multiError)
if !ok {
return []error{err}
}

return append(([]error)(nil), eg.Errors()...)
return extractErrors(err)
}

// multiError is an error that holds one or more errors.
Expand All @@ -225,8 +209,6 @@ type multiError struct {
errors []error
}

var _ errorGroup = (*multiError)(nil)

// Errors returns the list of underlying errors.
//
// This slice MUST NOT be modified.
Expand Down
19 changes: 19 additions & 0 deletions error_post_go120.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,22 @@ package multierr
func (merr *multiError) Unwrap() []error {
return merr.Errors()
}

type multipleErrors interface {
Unwrap() []error
}

func extractErrors(err error) []error {
if err == nil {
return nil
}

// check if the given err is an Unwrapable error that
// implements multipleErrors interface.
eg, ok := err.(multipleErrors)
if !ok {
return []error{err}
}

return append(([]error)(nil), eg.Unwrap()...)
}
42 changes: 42 additions & 0 deletions error_post_go120_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) 2023 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

//go:build go1.20
// +build go1.20

package multierr

import (
"errors"
"testing"

"github.com/stretchr/testify/assert"
)

func TestErrorsOnErrorsJoin(t *testing.T) {
err1 := errors.New("err1")
err2 := errors.New("err2")
err := errors.Join(err1, err2)

errs := Errors(err)
assert.Equal(t, 2, len(errs))
assert.Equal(t, err1, errs[0])
assert.Equal(t, err2, errs[1])
}
20 changes: 20 additions & 0 deletions error_pre_go120.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,23 @@ func (merr *multiError) Is(target error) bool {
}
return false
}

func extractErrors(err error) []error {
if err == nil {
return nil
}

// Note that we're casting to multiError, not errorGroup. Our contract is
// that returned errors MAY implement errorGroup. Errors, however, only
// has special behavior for multierr-specific error objects.
//
// This behavior can be expanded in the future but I think it's prudent to
// start with as little as possible in terms of contract and possibility
// of misuse.
eg, ok := err.(*multiError)
if !ok {
return []error{err}
}

return append(([]error)(nil), eg.Errors()...)
}
3 changes: 2 additions & 1 deletion error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,8 @@ func TestErrors(t *testing.T) {
dontCast: true,
},
{
// We don't yet support non-multierr errors.
// We don't yet support non-multierr errors that do
// not implement Unwrap() []error.
give: notMultiErr{},
want: []error{notMultiErr{}},
dontCast: true,
Expand Down

0 comments on commit faff69d

Please sign in to comment.