I was trying to figure out if an error was a wrapped grpc error with go 1.13 style errors (currently using the golang.org/x/xerrors package to fill that functionality).
Finding the underlying error will allow me to report it or not depending on the status. A NotFound
error doesn't need to appear in our error reporting system.
However you can't access the actual error type for grpc errors since it is not a type the grpc/status package exports.
Basically I want to do:
if grpcCauseError := new(status.statusError); xerrors.As(err, &grpcCauseError) {
errorCode := status.Code(grpcCauseError)
switch errorCode {
case codes.InvalidArgument, codes.NotFound, codes.AlreadyExists, codes.PermissionDenied, codes.Unimplemented, codes.Unauthenticated:
// Don't report it
default:
// Report it
}
}
This won't work since I cannot access the status.statusError
type. I tried making a new grpc error to match against:
if grpcCauseError := grpc.Errorf(codes.Unknown, "error"); xerrors.As(err, &grpcCauseError) {
errorCode := status.Code(grpcCauseError)
switch errorCode {
case codes.InvalidArgument, codes.NotFound, codes.AlreadyExists, codes.PermissionDenied, codes.Unimplemented, codes.Unauthenticated:
// Don't report it
default:
// Report it
}
}
But that also doesn't work since the grpcCauseError
only ends up having the type error
and all errors will match that interface so that if
statement will always be successful.
The status.statusError
type exports 2 methods though: Error()
and GRPCStatus()
. So we can make an interface that fits this:
type grpcError interface {
Error() string
GRPCStatus() *status.Status
}
To be able to use it with xerrors.As()
though, we'll need more than a pointer to an interface, we'll need an actual instance of that interface. This can be achieved by either converting an existing grpc error or creating a new type that matches that interface:
baseGrpcError := grpc.Errorf(codes.Unknown, "oops")
grpcCauseError := e.(grpcError)
if xerrors.As(err, &grpcCauseError) {
errorCode := status.Code(grpcCauseError)
switch errorCode {
case codes.InvalidArgument, codes.NotFound, codes.AlreadyExists, codes.PermissionDenied, codes.Unimplemented, codes.Unauthenticated:
// Don't report it
default:
// Report it
}
}
// OR
type mockGRPCError struct{}
func (*mockGRPCError) Error() string { return "" }
func (*mockGRPCError) GRPCStatus() *status.Status { return &status.Status{} }
var grpcCauseError grpcError
grpcCauseError = mockGRPCError{}
if xerrors.As(err, &grpcCauseError) {
errorCode := status.Code(grpcCauseError)
switch errorCode {
case codes.InvalidArgument, codes.NotFound, codes.AlreadyExists, codes.PermissionDenied, codes.Unimplemented, codes.Unauthenticated:
// Don't report it
default:
// Report it
}
}
In both cases, getting the base error can be abstracted in a function so that we get a bit cleaner code:
if grpcCauseError := getGrpcError(); xerrors.As(err, &grpcCauseError) {
errorCode := status.Code(grpcCauseError)
switch errorCode {
case codes.InvalidArgument, codes.NotFound, codes.AlreadyExists, codes.PermissionDenied, codes.Unimplemented, codes.Unauthenticated:
// Don't report it
default:
// Report it
}
}
func getGrpcError() *grpcError {
baseGrpcError := grpc.Errorf(codes.Unknown, "oops")
grpcCauseError := e.(grpcError)
return grpcCauseError
}
Top comments (0)