mirror of https://github.com/uber-go/multierr.git
chore: Drop support for Go < 1.20 (#80)
Drops support for versions of Go older than 1.20. With 1.20 being the minimum supported Go version, we can remove the pre_go120 code, and merge the post_go120 code. With Go 1.20's multi-error interface, our errorGroup interface is not necessary, but we need it for backwards compatibility. Also updates the documentation to suggest Go 1.20's interface instead of ours. The `Errors() []error` function is now an implementation detail.
This commit is contained in:
parent
de75ae527b
commit
616f4860ca
|
|
@ -16,9 +16,9 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
go: ["1.19.x", "1.20.x"]
|
||||
go: ["1.20.x", "1.21.x"]
|
||||
include:
|
||||
- go: 1.20.x
|
||||
- go: 1.21.x
|
||||
latest: true
|
||||
|
||||
steps:
|
||||
|
|
|
|||
70
error.go
70
error.go
|
|
@ -115,24 +115,22 @@
|
|||
// # Advanced Usage
|
||||
//
|
||||
// Errors returned by Combine and Append MAY implement the following
|
||||
// interface.
|
||||
// method.
|
||||
//
|
||||
// type errorGroup interface {
|
||||
// // Returns a slice containing the underlying list of errors.
|
||||
// //
|
||||
// // This slice MUST NOT be modified by the caller.
|
||||
// Errors() []error
|
||||
// }
|
||||
// // Returns a slice containing the underlying list of errors.
|
||||
// //
|
||||
// // This slice MUST NOT be modified by the caller.
|
||||
// Unwrap() []error
|
||||
//
|
||||
// Note that if you need access to list of errors behind a multierr error, you
|
||||
// should prefer using the Errors function. That said, if you need cheap
|
||||
// should prefer using the [Errors] function. That said, if you need cheap
|
||||
// read-only access to the underlying errors slice, you can attempt to cast
|
||||
// the error to this interface. You MUST handle the failure case gracefully
|
||||
// because errors returned by Combine and Append are not guaranteed to
|
||||
// implement this interface.
|
||||
//
|
||||
// var errors []error
|
||||
// group, ok := err.(errorGroup)
|
||||
// group, ok := err.(interface{ Unwrap() []error })
|
||||
// if ok {
|
||||
// errors = group.Errors()
|
||||
// } else {
|
||||
|
|
@ -180,10 +178,20 @@ var _bufferPool = sync.Pool{
|
|||
},
|
||||
}
|
||||
|
||||
// errorGroup is the old interface defined by multierr for combined errors.
|
||||
//
|
||||
// It is deprecated in favor of the Go 1.20 multi-error interface.
|
||||
// However, we still need to implement and support it
|
||||
// for backward compatibility.
|
||||
type errorGroup interface {
|
||||
Errors() []error
|
||||
}
|
||||
|
||||
// multipleErrors matches the Go 1.20 multi-error interface.
|
||||
type multipleErrors interface {
|
||||
Unwrap() []error
|
||||
}
|
||||
|
||||
// Errors returns a slice containing zero or more errors that the supplied
|
||||
// error is composed of. If the error is nil, a nil slice is returned.
|
||||
//
|
||||
|
|
@ -210,6 +218,13 @@ type multiError struct {
|
|||
errors []error
|
||||
}
|
||||
|
||||
// Unwrap returns a list of errors wrapped by this multierr.
|
||||
//
|
||||
// This satisfies the Go 1.20 multi-error interface.
|
||||
func (merr *multiError) Unwrap() []error {
|
||||
return merr.Errors()
|
||||
}
|
||||
|
||||
// Errors returns the list of underlying errors.
|
||||
//
|
||||
// This slice MUST NOT be modified.
|
||||
|
|
@ -235,17 +250,6 @@ func (merr *multiError) Error() string {
|
|||
return result
|
||||
}
|
||||
|
||||
// Every compares every error in the given err against the given target error
|
||||
// using [errors.Is], and returns true only if every comparison returned true.
|
||||
func Every(err error, target error) bool {
|
||||
for _, e := range extractErrors(err) {
|
||||
if !errors.Is(e, target) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (merr *multiError) Format(f fmt.State, c rune) {
|
||||
if c == 'v' && f.Flag('+') {
|
||||
merr.writeMultiline(f)
|
||||
|
|
@ -508,6 +512,32 @@ func AppendInto(into *error, err error) (errored bool) {
|
|||
return true
|
||||
}
|
||||
|
||||
// Every compares every error in the given err against the given target error
|
||||
// using [errors.Is], and returns true only if every comparison returned true.
|
||||
func Every(err error, target error) bool {
|
||||
for _, e := range extractErrors(err) {
|
||||
if !errors.Is(e, target) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
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()...)
|
||||
}
|
||||
|
||||
// Invoker is an operation that may fail with an error. Use it with
|
||||
// AppendInvoke to append the result of calling the function into an error.
|
||||
// This allows you to conveniently defer capture of failing operations.
|
||||
|
|
|
|||
|
|
@ -1,48 +0,0 @@
|
|||
// Copyright (c) 2017-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
|
||||
|
||||
// Unwrap returns a list of errors wrapped by this 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()...)
|
||||
}
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
// 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])
|
||||
}
|
||||
|
||||
func TestEveryWithErrorsJoin(t *testing.T) {
|
||||
myError1 := errors.New("woeful misfortune")
|
||||
myError2 := errors.New("worrisome travesty")
|
||||
|
||||
t.Run("all match", func(t *testing.T) {
|
||||
err := errors.Join(myError1, myError1, myError1)
|
||||
|
||||
assert.True(t, errors.Is(err, myError1))
|
||||
assert.True(t, Every(err, myError1))
|
||||
assert.False(t, errors.Is(err, myError2))
|
||||
assert.False(t, Every(err, myError2))
|
||||
})
|
||||
|
||||
t.Run("one matches", func(t *testing.T) {
|
||||
err := errors.Join(myError1, myError2)
|
||||
|
||||
assert.True(t, errors.Is(err, myError1))
|
||||
assert.False(t, Every(err, myError1))
|
||||
assert.True(t, errors.Is(err, myError2))
|
||||
assert.False(t, Every(err, myError2))
|
||||
})
|
||||
}
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
// Copyright (c) 2017-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"
|
||||
|
||||
// Versions of Go before 1.20 did not support the Unwrap() []error method.
|
||||
// This provides a similar behavior by implementing the Is(..) and As(..)
|
||||
// methods.
|
||||
// See the errors.Join proposal for details:
|
||||
// https://github.com/golang/go/issues/53435
|
||||
|
||||
// As attempts to find the first error in the error list that matches the type
|
||||
// of the value that target points to.
|
||||
//
|
||||
// This function allows errors.As to traverse the values stored on the
|
||||
// multierr error.
|
||||
func (merr *multiError) As(target interface{}) bool {
|
||||
for _, err := range merr.Errors() {
|
||||
if errors.As(err, target) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Is attempts to match the provided error against errors in the error list.
|
||||
//
|
||||
// This function allows errors.Is to traverse the values stored on the
|
||||
// multierr error.
|
||||
func (merr *multiError) Is(target error) bool {
|
||||
for _, err := range merr.Errors() {
|
||||
if errors.Is(err, target) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
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()...)
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) 2017-2021 Uber Technologies, Inc.
|
||||
// Copyright (c) 2017-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
|
||||
|
|
@ -777,3 +777,37 @@ func newCloserMock(tb testing.TB, err error) io.Closer {
|
|||
return err
|
||||
})
|
||||
}
|
||||
|
||||
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])
|
||||
}
|
||||
|
||||
func TestEveryWithErrorsJoin(t *testing.T) {
|
||||
myError1 := errors.New("woeful misfortune")
|
||||
myError2 := errors.New("worrisome travesty")
|
||||
|
||||
t.Run("all match", func(t *testing.T) {
|
||||
err := errors.Join(myError1, myError1, myError1)
|
||||
|
||||
assert.True(t, errors.Is(err, myError1))
|
||||
assert.True(t, Every(err, myError1))
|
||||
assert.False(t, errors.Is(err, myError2))
|
||||
assert.False(t, Every(err, myError2))
|
||||
})
|
||||
|
||||
t.Run("one matches", func(t *testing.T) {
|
||||
err := errors.Join(myError1, myError2)
|
||||
|
||||
assert.True(t, errors.Is(err, myError1))
|
||||
assert.False(t, Every(err, myError1))
|
||||
assert.True(t, errors.Is(err, myError2))
|
||||
assert.False(t, Every(err, myError2))
|
||||
})
|
||||
}
|
||||
|
|
|
|||
2
go.mod
2
go.mod
|
|
@ -1,6 +1,6 @@
|
|||
module go.uber.org/multierr
|
||||
|
||||
go 1.19
|
||||
go 1.20
|
||||
|
||||
require github.com/stretchr/testify v1.7.0
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
module go.uber.org/multierr/tools
|
||||
|
||||
go 1.18
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616
|
||||
|
|
|
|||
Loading…
Reference in New Issue