Skip to content

Commit a28de7b

Browse files
committed
grpcerrors: Ensure errors from errors.Join are preserved
Adds support for the `Unwrap() []error` interface used by `errors.Join` so that roundtripping through grpcerrors preserves types. Signed-off-by: Brian Goff <[email protected]>
1 parent 7946fd9 commit a28de7b

File tree

2 files changed

+69
-7
lines changed

2 files changed

+69
-7
lines changed

util/grpcerrors/grpcerrors.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -252,9 +252,21 @@ func (e *withCodeError) Unwrap() error {
252252

253253
func each(err error, fn func(error)) {
254254
fn(err)
255-
if wrapped, ok := err.(interface {
255+
256+
type singleUnwrapper interface {
256257
Unwrap() error
257-
}); ok {
258-
each(wrapped.Unwrap(), fn)
258+
}
259+
260+
type multiUnwrapper interface {
261+
Unwrap() []error
262+
}
263+
264+
switch e := err.(type) {
265+
case singleUnwrapper:
266+
each(e.Unwrap(), fn)
267+
case multiUnwrapper:
268+
for _, err := range e.Unwrap() {
269+
each(err, fn)
270+
}
259271
}
260272
}

util/grpcerrors/grpcerrors_test.go

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ package grpcerrors_test
22

33
import (
44
"context"
5+
stderrors "errors"
56
"fmt"
6-
"strings"
77
"testing"
88

9+
"github.com/moby/buildkit/solver/errdefs"
910
"github.com/moby/buildkit/util/grpcerrors"
1011
"github.com/pkg/errors"
1112
"github.com/stretchr/testify/assert"
@@ -65,6 +66,50 @@ func TestFromGRPCPreserveUnknownTypes(t *testing.T) {
6566
reDecodedErr := grpcerrors.FromGRPC(reEncodedErr)
6667
assertErrorProperties(t, reDecodedErr)
6768
})
69+
70+
}
71+
72+
func TestFromGRPCPreserveUnknownTypes_WithJoin(t *testing.T) {
73+
t.Parallel()
74+
const (
75+
unknownType = "type.googleapis.com/unknown.Type"
76+
unknownValue = "unknown value"
77+
78+
errMessage = "something failed"
79+
errCode = codes.Internal
80+
)
81+
raw := &anypb.Any{
82+
TypeUrl: unknownType,
83+
Value: []byte(unknownValue),
84+
}
85+
86+
pb := &spb.Status{
87+
Code: int32(errCode),
88+
Message: errMessage,
89+
Details: []*anypb.Any{raw},
90+
}
91+
92+
// Decode the original status into a Go error, then join it with a typed error
93+
decodedErr := grpcerrors.FromGRPC(status.FromProto(pb).Err())
94+
// Create a typed error (errdefs.Solve) and wrap a base error with it
95+
typed := &errdefs.Solve{InputIDs: []string{"typed-input"}}
96+
typedErr := typed.WrapError(stderrors.New("typed-base"))
97+
joined := stderrors.Join(decodedErr, typedErr)
98+
99+
// Re-encode and decode the joined error and ensure the unknown detail is preserved
100+
reEncoded := grpcerrors.ToGRPC(context.TODO(), joined)
101+
reDecoded := grpcerrors.FromGRPC(reEncoded)
102+
103+
require.Error(t, reDecoded)
104+
105+
// Ensure the typed error was preserved by finding the SolveError in the decoded error
106+
var se *errdefs.SolveError
107+
require.True(t, stderrors.As(reDecoded, &se))
108+
require.NotNil(t, se)
109+
assert.Equal(t, []string{"typed-input"}, se.Solve.InputIDs)
110+
111+
// And ensure the original unknown detail is still present
112+
assert.ErrorContains(t, reDecoded, errMessage)
68113
}
69114

70115
func TestToGRPCMessage(t *testing.T) {
@@ -79,10 +124,15 @@ func TestToGRPCMessage(t *testing.T) {
79124
t.Parallel()
80125
err := errors.New("something")
81126
wrapped := errors.Wrap(grpcerrors.ToGRPC(context.TODO(), err), "extra context")
127+
128+
anotherErr := errors.New("another error")
129+
joined := stderrors.Join(wrapped, anotherErr)
130+
82131
// Check that wrapped.Error() starts with "extra context"
83-
assert.True(t, strings.HasPrefix(wrapped.Error(), "extra context"), "expected wrapped error to start with 'extra context'")
84-
encoded := grpcerrors.ToGRPC(context.TODO(), wrapped)
132+
encoded := grpcerrors.ToGRPC(context.TODO(), joined)
85133
decoded := grpcerrors.FromGRPC(encoded)
86-
assert.Equal(t, wrapped.Error(), decoded.Error())
134+
135+
assert.ErrorContains(t, decoded, wrapped.Error())
136+
assert.ErrorContains(t, decoded, anotherErr.Error())
87137
})
88138
}

0 commit comments

Comments
 (0)