Skip to content

Commit

Permalink
chore: Drop support for Go < 1.20 (#80)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
abhinav authored Oct 11, 2023
1 parent de75ae5 commit 616f486
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 217 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
70 changes: 50 additions & 20 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
//
Expand All @@ -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.
Expand All @@ -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)
Expand Down Expand Up @@ -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.
Expand Down
48 changes: 0 additions & 48 deletions error_post_go120.go

This file was deleted.

65 changes: 0 additions & 65 deletions error_post_go120_test.go

This file was deleted.

79 changes: 0 additions & 79 deletions error_pre_go120.go

This file was deleted.

36 changes: 35 additions & 1 deletion error_test.go
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module go.uber.org/multierr

go 1.19
go 1.20

require github.com/stretchr/testify v1.7.0

Expand Down
2 changes: 1 addition & 1 deletion tools/go.mod
Original file line number Diff line number Diff line change
@@ -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
Expand Down

0 comments on commit 616f486

Please sign in to comment.