Skip to content
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

Follow recommended Go 1.13 error handling guidelines #1846

Closed
gmlewis opened this issue Apr 4, 2021 · 12 comments · Fixed by #1854
Closed

Follow recommended Go 1.13 error handling guidelines #1846

gmlewis opened this issue Apr 4, 2021 · 12 comments · Fixed by #1854

Comments

@gmlewis
Copy link
Collaborator

gmlewis commented Apr 4, 2021

In a recent code review, we felt that this repo should use the recommended Go 1.13 error handling guidelines.

Specifically, we should never use reflect.DeepEqual for checking errors (even in tests) and would ideally use the "new" error.Is functionality provided in Go 1.13.

Additionally, we should update the README.md to reflect that it is no longer Go 1.9 that we require, but instead we need a minimum of Go 1.13.

@mbalayil
Copy link
Contributor

mbalayil commented Apr 8, 2021

I would like to work on this issue.

@gmlewis
Copy link
Collaborator Author

gmlewis commented Apr 8, 2021

Thank you, @mbalayil ! It is yours.

@mbalayil
Copy link
Contributor

mbalayil commented Apr 9, 2021

There are 6 places in github/github_test.go where reflect.DeepEqual is used for error comparisons. Switching it to errors.Is in TestDo_nilContext is straightforward.

@@ -705,7 +708,7 @@ func TestDo_nilContext(t *testing.T) {
        req, _ := client.NewRequest("GET", ".", nil)
        _, err := client.Do(nil, req, nil)

-       if !reflect.DeepEqual(err, errors.New("context must be non-nil")) {
+       if !errors.Is(err, errNonNilContext) {
                t.Errorf("Expected context must be non-nil error")
        }
 }

where errNonNilContext is defined in github/github.go.

However for the rest of the 5 instances where reflect.DeepEqual is used, the errors being tested are not sentinel errors.
We can either implement Is(error) bool interface for ErrorResponse, RateLimitError and AbuseRateLimitError (possibly using github.com/google/go-cmp) and utilize errors.Is in the corresponding tests or use github.com/google/go-cmp directly to compare the expected and actual values as shown below.

@@ -1097,7 +1100,11 @@ func TestCheckResponse(t *testing.T) {
                        CreatedAt: &Timestamp{time.Date(2016, time.March, 17, 15, 39, 46, 0, time.UTC)},
                },
        }
-       if !reflect.DeepEqual(err, want) {
+       opts := []cmp.Option{
+               cmp.AllowUnexported(bytes.Buffer{}),
+               cmp.AllowUnexported(http.Request{}),
+       }
+       if !cmp.Equal(err, want, opts...) {
                t.Errorf("Error = %#v, want %#v", err, want)
        }
 }

Which method is preferred ? Or is there another alternative ?

@gmlewis
Copy link
Collaborator Author

gmlewis commented Apr 9, 2021

My opinion is to implement Is(target error) bool for each of those errors (and maybe also for AcceptedError).

@mbalayil
Copy link
Contributor

mbalayil commented Apr 9, 2021

Can I use github.com/google/go-cmp for the implementation of the Is function ?

@gmlewis
Copy link
Collaborator Author

gmlewis commented Apr 9, 2021

Can I use github.com/google/go-cmp for the implementation of the Is function ?

Do you have a link? I'm not seeing any Is method on this page: https://pkg.go.dev/github.com/google/go-cmp/cmp

Shouldn't it be as simple as this:

func (e *AcceptedError) Is(target error) bool {
    _, ok := target.(*AcceptedError)
    return ok
}

and then it would be used like this:

if errors.Is(err, &AcceptedError{}) {
    // ...
}

or am I missing something?

@willnorris
Copy link
Collaborator

Isn't errors.Is intended to be an equality check, not just a type check (with the added feature of checking the full error chain)?

If so, AcceptedError.Is() should probably consider the raw field as well... I worry about unintended consequences elsewhere if we ignore it. If all we care about in this test is the type, then we should just do a type check directly in the test.

@gmlewis
Copy link
Collaborator Author

gmlewis commented Apr 9, 2021

That makes sense. Sounds good, @willnorris .

@mbalayil
Copy link
Contributor

  1. Updated the README.md to reflect that it is no longer Go 1.9 that we require, but instead we need a minimum of Go 1.13.
  2. Replaced reflect.DeepEqual with errors.Is in TestDo_nilContext.
  3. Implemented Is function for AcceptedError.

Will update a PR soon with these partial fixes.
However, for ErrorResponse, RateLimitError and AbuseRateLimitError, I am still looking for ways to implement the Is function without using github.com/google/go-cmp. If you have any suggestions on comparing the individual fields of a struct with nested fields, let me know.

@gmlewis
Copy link
Collaborator Author

gmlewis commented Apr 12, 2021

Hi @mbalayil - go ahead and provide a PR with your proposed solution (using go-cmp) so we can take a look at it, please.

@mbalayil
Copy link
Contributor

provide a PR with your proposed solution (using go-cmp)

"It is intended to only be used in tests, as performance is not a goal and it may panic if it cannot compare the values. Its propensity towards panicking means that its unsuitable for production environments where a spurious panic may be fatal."

In https://pkg.go.dev/github.com/google/go-cmp/cmp#pkg-overview, it is mentioned that this package is better not used in production code. So I have created the PR by directly comparing the values. However, for the http.Response struct, only StatusCode is being compared. If the other fields of the http.Response struct need to be compared, please let me know.

Apologies for the delay. It took me some time to figure all this out.

@gmlewis
Copy link
Collaborator Author

gmlewis commented Apr 13, 2021

That makes sense to me. Let's try this out unless we hear other feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants