-
Notifications
You must be signed in to change notification settings - Fork 4.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
status: Implement *statusError.Is #2868
Conversation
Thank you for your pull request. Before we can look at your contribution, we need to ensure all contributors are covered by a Contributor License Agreement. After the following items are addressed, please respond with a new comment here, and the automated system will re-verify.
Regards, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR, I agree this will be useful to have.
The CLAs still seem to be unhappy - can you take another look at that, please?
status/status.go
Outdated
return false | ||
} | ||
|
||
return se.Code == tse.Code && se.Message == tse.Message |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't check the Details
field -- to do so, I believe you can use proto.Equal(se, tse)
instead of comparing the fields individually.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dfawley I believe we don't actually want to check the Details
field, as we very well might have different details added onto the same error "type"
I imagine WithDetails
could be used for things like tagging something like the userId, or host source, which would break the equivalency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same types of arguments could be used for checking the Message field as well. It's common for users to put debugging details (username, raw request information, etc) into RPC error strings. Whether this is more or less likely is conjecture. I believe the spirit of "Is" is to perform an exact match, not a fuzzy one. But I'll have to look at the errors proposal again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking about this some more, I think since Details is just appended to the end of the message, equality would break if they were added in a different order, which doesn't seem great.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still of the opinion that it should be matching on just the code and message, but I'm happy to do a strict comparison instead if that will help push this through.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For As, I'm unsure about implementing it on &status.Status, as this isn't actually an error type, and it might be an anti-pattern to have As work on a non-error type? Maybe they intend for it to be usable this way but it seems a little weird.
I agree this seems a bit weird. As
would need to be implemented on *statusError
, but it would assign to a *status.Status
. I'm not sure if this would be considered an anti-pattern, but I will pose the question.
Alternatively, we could export a interface{ GRPCStatus() *status.Status } type to be used in As.
I considered this as well. I'm not sure this option would be intuitive or discoverable, and I'd rather just offer a status.Unwrap(error)
helper instead.
For the convenience wrapper, it seems like it would make sense to implement it in FromError(), although that seems like it would have to be in a separate go2 package.
We would have to move FromError
into its own file, and define two versions, one for pre-1.13 versions of Go and one for 1.13+. It might be undesirable/unexpected to unwrap in that function automatically, although I'm having trouble seeing where it would cause problems to do so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if this would be considered an anti-pattern, but I will pose the question.
Did you get a chance to think about this more?
Your plan for FromError
seems good.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I have not had a chance to ask yet. I'll see if I can find something out soon.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
errors.Is()
compares every single element in a chain of errors by Unwrap()
ing the next level.
WithDetails()
just adds such an element to the chain, so why should it be part of the comparison?
It just uses a different way for adding an element than the error
package, but the principle is the same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's an interesting thought, but the problem is that WithDetails
isn't necessarily wrapping a different error - it can also explain more about the error itself. We can't simply ignore it in a function intended to test strict equality.
@dfawley Working on getting a CCLA pushed through! |
@dfawley CLA got signed, thanks for your patience. |
@dfawley I updated this PR to use |
@dfawley Friendly bump :) |
status/status.go
Outdated
// Is implements future error.Is functionality. | ||
// A statusError is equivalent if the code and message are identical. | ||
func (se *statusError) Is(target error) bool { | ||
if target == nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor point: a nil
target
will also make the following type assertion fail, so this condition is unnecessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated
status/status_ext_test.go
Outdated
testErrWithDetails := statusWithDetails.Err() | ||
|
||
// Test cases. | ||
testCases := []testCaseErrorIs{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This type can be declared inline to avoid putting it in the global namespace:
testCases := []struct {
err1, err2 error
want bool
}{
...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was necessary for the appends, but I fixed that!
status/status_ext_test.go
Outdated
func TestErrorIs(t *testing.T) { | ||
// Test errors. | ||
testErr := status.Error(codes.Internal, "internal server error") | ||
statusWithDetails, _ := status.New(codes.Internal, "internal server error").WithDetails(&grpc_testing.Empty{}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd recommend a helper:
func errWithDetails(t *testing.T, s *Status, details ...proto.Message) error {
t.Helper()
res, err := s.WithDetails(details)
if err != nil {
t.Fatalf("(%v).WithDetails(%v) = %v, %v; want _, <nil>", s, details, res, err)
}
return res
}
...
testErrWithDetails := errWithDetails(status.New(codes.Internal, "internal server error"), &grpc_testing.Empty{})
This will also make it so you don't need to append
any test cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea
status/status.go
Outdated
return false | ||
} | ||
|
||
return se.Code == tse.Code && se.Message == tse.Message |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I have not had a chance to ask yet. I'll see if I can find something out soon.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please also add a grpc copyright notice to the top of status/status_ext_test.go
- or merge it into status_test.go. (This is what is causing travis to fail.)
@dfawley Updated! I need to keep |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, but please change the copyright year. Thanks!
|
||
is := isError.Is(tc.err2) | ||
if is != tc.want { | ||
t.Errorf("(%v).Is(%v) = %t; want %t", tc.err1, tc.err2, is, tc.want) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TIL %t
status/status_ext_test.go
Outdated
@@ -0,0 +1,72 @@ | |||
/* | |||
* | |||
* Copyright 2017 gRPC authors. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2019 please.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
In the go2 error proposal the Is function adds matching of error targets. On this line: https://github.com/golang/exp/blob/sumdb/v0.0.2/errors/wrap.go#L58 of the proposal implementation, there is an interface check for Is, to provide custom implementation of this matching. gRPC status errors that are defined as a var, currently don't successfully work with this function when crossing the gRPC boundary, as the pointer values will not be equivalent. Ideally, the following should work across gRPC boundaries: var ErrAuthInvalidToken = status.Error(codes.Unauthenticated, "Invalid token") _, err := grpcClient.Call(...) // Returns the above err.Is(ErrAuthInvalidToken) == true
🎉 Let me know if you'd like me to work with you on the |
Thanks for the offer. I filed an issue for this and CC'd you on it. Edit: #2934 |
### Release notes - https://github.com/grpc/grpc-go/releases/tag/v1.23.0 - much stuff! a bunch of HTTP2-related fixes -- we don't _expose_ go-grpc's listener, but we use it interally all over the place - there's `*satus.Is` now: grpc/grpc-go#2868 -- we could make some use of that, I suppose. - a bunch of perf improvements, always welcome :) - https://github.com/golang/protobuf/releases/tag/v1.3.2 - small fry, but nice stuff: golang/protobuf#785 adds an Unimplemented method to each server, allowing old servers to be used when there's proto changes. * deps: bump protobuf (1.3.2) and go-grpc (1.23.0) * deps: bump protobuf (1.3.2) and go-grpc (1.23.0) [revendor] * deps: bump protobuf (1.3.2) and go-grpc (1.23.0) [regenerate] Signed-off-by: Stephan Renatus <[email protected]>
In the go2 error proposal the
Is
function adds matching of error targets.On this line of the proposal implementation, there is an interface check for Is, to provide
custom implementation of this matching.
gRPC status errors that are defined as a var, currently don't successfully work
with this function when crossing the gRPC boundary, as the pointer values will
not be equivalent.
Ideally, the following should work across gRPC boundaries: