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:
Abhinav Gupta 2023-10-11 11:46:30 -07:00 committed by GitHub
parent de75ae527b
commit 616f4860ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 89 additions and 217 deletions

View File

@ -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:

View File

@ -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.

View File

@ -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()...)
}

View File

@ -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))
})
}

View File

@ -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()...)
}

View File

@ -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
View File

@ -1,6 +1,6 @@
module go.uber.org/multierr
go 1.19
go 1.20
require github.com/stretchr/testify v1.7.0

View File

@ -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