This repository has been archived by the owner on Apr 18, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 164
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #84 from FUSAKLA/fus-error-unwrapping
feat: added error unwrapping
- Loading branch information
Showing
8 changed files
with
152 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,3 @@ | ||
SHELL="/bin/bash" | ||
|
||
GOFILES_NOVENDOR = $(shell go list ./... | grep -v /vendor/) | ||
|
||
all: vet fmt test | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package grpcstatus | ||
|
||
import ( | ||
"google.golang.org/grpc/codes" | ||
"google.golang.org/grpc/status" | ||
) | ||
|
||
type gRPCStatus interface { | ||
GRPCStatus() *status.Status | ||
} | ||
|
||
func unwrapPkgErrorsGRPCStatus(err error) (*status.Status, bool) { | ||
type causer interface { | ||
Cause() error | ||
} | ||
|
||
// Unwrapping the github.com/pkg/errors causer interface, using `Cause` directly could miss some error implementing | ||
// the `GRPCStatus` function so we have to check it on our selves. | ||
unwrappedCauser := err | ||
for unwrappedCauser != nil { | ||
if s, ok := unwrappedCauser.(gRPCStatus); ok { | ||
return s.GRPCStatus(), true | ||
} | ||
cause, ok := unwrappedCauser.(causer) | ||
if !ok { | ||
break | ||
} | ||
unwrappedCauser = cause.Cause() | ||
} | ||
return nil, false | ||
} | ||
|
||
// Since error can be wrapped and the `FromError` function only checks for `GRPCStatus` function | ||
// and as a fallback uses the `Unknown` gRPC status we need to unwrap the error if possible to get the original status. | ||
// pkg/errors and Go native errors packages have two different approaches so we try to unwrap both types. | ||
// Eventually should be implemented in the go-grpc status function `FromError`. See https://github.com/grpc/grpc-go/issues/2934 | ||
func FromError(err error) (s *status.Status, ok bool) { | ||
s, ok = status.FromError(err) | ||
if ok { | ||
return s, true | ||
} | ||
|
||
// Try to unwrap `github.com/pkg/errors` wrapped error | ||
s, ok = unwrapPkgErrorsGRPCStatus(err) | ||
if ok { | ||
return s, true | ||
} | ||
|
||
// Try to unwrap native wrapped errors using `fmt.Errorf` and `%w` | ||
s, ok = unwrapNativeWrappedGRPCStatus(err) | ||
if ok { | ||
return s, true | ||
} | ||
|
||
// We failed to unwrap any GRPSStatus so return default `Unknown` | ||
return status.New(codes.Unknown, err.Error()), false | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// +build go1.13 | ||
|
||
package grpcstatus | ||
|
||
import ( | ||
"fmt" | ||
"github.com/stretchr/testify/require" | ||
"google.golang.org/grpc/codes" | ||
"google.golang.org/grpc/status" | ||
"testing" | ||
) | ||
|
||
func TestNativeErrorUnwrapping(t *testing.T) { | ||
gRPCCode := codes.FailedPrecondition | ||
gRPCError := status.Errorf(gRPCCode, "Userspace error.") | ||
expectedGRPCStatus, _ := status.FromError(gRPCError) | ||
testedErrors := []error{ | ||
fmt.Errorf("go native wrapped error: %w", gRPCError), | ||
} | ||
|
||
for _, e := range testedErrors { | ||
resultingStatus, ok := FromError(e) | ||
require.True(t, ok) | ||
require.Equal(t, expectedGRPCStatus, resultingStatus) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package grpcstatus | ||
|
||
import ( | ||
"github.com/stretchr/testify/require" | ||
"google.golang.org/grpc/codes" | ||
"google.golang.org/grpc/status" | ||
"testing" | ||
) | ||
|
||
// Own implementation of pkg/errors withStack to avoid additional dependency | ||
type wrappedError struct { | ||
cause error | ||
msg string | ||
} | ||
|
||
func (w *wrappedError) Error() string { return w.msg + ": " + w.cause.Error() } | ||
|
||
func (w *wrappedError) Cause() error { return w.cause } | ||
|
||
func TestErrorUnwrapping(t *testing.T) { | ||
gRPCCode := codes.FailedPrecondition | ||
gRPCError := status.Errorf(gRPCCode, "Userspace error.") | ||
expectedGRPCStatus, _ := status.FromError(gRPCError) | ||
testedErrors := []error{ | ||
gRPCError, | ||
&wrappedError{cause: gRPCError, msg: "pkg/errors wrapped error: "}, | ||
} | ||
|
||
for _, e := range testedErrors { | ||
resultingStatus, ok := FromError(e) | ||
require.True(t, ok) | ||
require.Equal(t, expectedGRPCStatus, resultingStatus) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// +build !go1.13 | ||
|
||
package grpcstatus | ||
|
||
import ( | ||
"google.golang.org/grpc/status" | ||
) | ||
|
||
func unwrapNativeWrappedGRPCStatus(err error) (*status.Status, bool) { | ||
return nil, false | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// +build go1.13 | ||
|
||
package grpcstatus | ||
|
||
import ( | ||
"errors" | ||
"google.golang.org/grpc/status" | ||
) | ||
|
||
func unwrapNativeWrappedGRPCStatus(err error) (*status.Status, bool) { | ||
// Unwrapping the native Go unwrap interface | ||
var unwrappedStatus gRPCStatus | ||
if ok := errors.As(err, &unwrappedStatus); ok { | ||
return unwrappedStatus.GRPCStatus(), true | ||
} | ||
return nil, false | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters