Skip to content

Commit f198c38

Browse files
authored
Merge pull request #5905 from fiam/alberto/grpc-preserve-unknown-types
feat(grpc): preserve unknown detail types when decoding gRPC errors
2 parents 2e531e7 + 5848d95 commit f198c38

File tree

2 files changed

+107
-1
lines changed

2 files changed

+107
-1
lines changed

util/grpcerrors/grpcerrors.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func ToGRPC(ctx context.Context, err error) error {
4545

4646
// If the original error was wrapped with more context than the GRPCStatus error,
4747
// copy the original message to the GRPCStatus error
48-
if err.Error() != st.Message() {
48+
if errorHasMoreContext(err, st) {
4949
pb := st.Proto()
5050
pb.Message = err.Error()
5151
st = status.FromProto(pb)
@@ -72,6 +72,21 @@ func ToGRPC(ctx context.Context, err error) error {
7272
return st.Err()
7373
}
7474

75+
// errorHasMoreContext checks if the original error provides more context by having
76+
// a different message or additional details than the Status.
77+
func errorHasMoreContext(err error, st *status.Status) bool {
78+
if errMessage := err.Error(); len(errMessage) > len(st.Message()) {
79+
// check if the longer message in errMessage is only due to
80+
// prepending with the status code
81+
var grpcStatusError *grpcStatusError
82+
if errors.As(err, &grpcStatusError) {
83+
return st.Code() != grpcStatusError.st.Code() || st.Message() != grpcStatusError.st.Message()
84+
}
85+
return true
86+
}
87+
return false
88+
}
89+
7590
func withDetails(ctx context.Context, s *status.Status, details ...proto.Message) (*status.Status, error) {
7691
if s.Code() == codes.OK {
7792
return nil, errors.New("no error details for status with code OK")
@@ -172,6 +187,8 @@ func FromGRPC(err error) error {
172187
for _, d := range pb.Details {
173188
m, err := typeurl.UnmarshalAny(d)
174189
if err != nil {
190+
bklog.L.Debugf("failed to unmarshal error detail with type %q: %v", d.GetTypeUrl(), err)
191+
n.Details = append(n.Details, d)
175192
continue
176193
}
177194

@@ -181,6 +198,7 @@ func FromGRPC(err error) error {
181198
case TypedErrorProto:
182199
details = append(details, v)
183200
default:
201+
bklog.L.Debugf("unknown detail with type %T", v)
184202
n.Details = append(n.Details, d)
185203
}
186204
}

util/grpcerrors/grpcerrors_test.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package grpcerrors_test
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
"testing"
8+
9+
"github.com/moby/buildkit/util/grpcerrors"
10+
"github.com/pkg/errors"
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
spb "google.golang.org/genproto/googleapis/rpc/status"
14+
"google.golang.org/grpc/codes"
15+
"google.golang.org/grpc/status"
16+
"google.golang.org/protobuf/types/known/anypb"
17+
)
18+
19+
func TestFromGRPCPreserveUnknownTypes(t *testing.T) {
20+
t.Parallel()
21+
const (
22+
unknownType = "type.googleapis.com/unknown.Type"
23+
unknownValue = "unknown value"
24+
25+
errMessage = "something failed"
26+
errCode = codes.Internal
27+
)
28+
raw := &anypb.Any{
29+
TypeUrl: unknownType,
30+
Value: []byte(unknownValue),
31+
}
32+
33+
pb := &spb.Status{
34+
Code: int32(errCode),
35+
Message: errMessage,
36+
Details: []*anypb.Any{raw},
37+
}
38+
39+
assertErrorProperties := func(t *testing.T, err error) {
40+
require.Error(t, err)
41+
assert.Equal(t, fmt.Sprintf("%s: %s", codes.Code(errCode), errMessage), err.Error())
42+
43+
st, ok := status.FromError(err)
44+
require.True(t, ok)
45+
46+
details := st.Proto().Details
47+
require.Len(t, details, 1)
48+
assert.Equal(t, unknownType, details[0].TypeUrl)
49+
assert.Equal(t, []byte(unknownValue), details[0].Value)
50+
}
51+
52+
t.Run("encode", func(t *testing.T) {
53+
t.Parallel()
54+
err := grpcerrors.FromGRPC(status.FromProto(pb).Err())
55+
assertErrorProperties(t, err)
56+
})
57+
58+
t.Run("roundtrip", func(t *testing.T) {
59+
t.Parallel()
60+
61+
decodedErr := grpcerrors.FromGRPC(status.FromProto(pb).Err())
62+
63+
reEncodedErr := grpcerrors.ToGRPC(context.TODO(), decodedErr)
64+
65+
reDecodedErr := grpcerrors.FromGRPC(reEncodedErr)
66+
assertErrorProperties(t, reDecodedErr)
67+
})
68+
}
69+
70+
func TestToGRPCMessage(t *testing.T) {
71+
t.Parallel()
72+
t.Run("avoid prepending grpc status code", func(t *testing.T) {
73+
t.Parallel()
74+
err := errors.New("something")
75+
decoded := grpcerrors.FromGRPC(grpcerrors.ToGRPC(context.TODO(), err))
76+
assert.Equal(t, err.Error(), decoded.Error())
77+
})
78+
t.Run("keep extra context", func(t *testing.T) {
79+
t.Parallel()
80+
err := errors.New("something")
81+
wrapped := errors.Wrap(grpcerrors.ToGRPC(context.TODO(), err), "extra context")
82+
// 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)
85+
decoded := grpcerrors.FromGRPC(encoded)
86+
assert.Equal(t, wrapped.Error(), decoded.Error())
87+
})
88+
}

0 commit comments

Comments
 (0)