Repository: google/go-github
Pull request #1854 (2021-04-13 10:41:05)

Update error handling as per Go 1.13 guidelines
Fixes [1846](https://github.com/google/go-github/issues/1846)

1. Updated `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 all the affected tests.
3. Implemented `Is(error) bool` interface for all the required errors (`ErrorResponse`, `RateLimitError`, `AcceptedError` and `AbuseRateLimitError`). All the individual fields are directly compared. However, for the `*http Response` field, only `StatusCode` is being compared. If the other fields of the `http.Response` struct need to be compared, please let me know.

codecov[bot]: # [Codecov](https://codecov.io/gh/google/go-github/pull/1854?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) Report
> Merging [#1854](https://codecov.io/gh/google/go-github/pull/1854?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) (0c21cda) into [master](https://codecov.io/gh/google/go-github/commit/d7115423bfa791b995679facc78cc86d0a546195?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) (d711542) will **decrease** coverage by `0.40%`.
> The diff coverage is `30.00%`.

[![Impacted file tree graph](https://codecov.io/gh/google/go-github/pull/1854/graphs/tree.svg?width=650&height=150&src=pr&token=EgYjGjZlI0&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google)](https://codecov.io/gh/google/go-github/pull/1854?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google)

```diff
@@            Coverage Diff             @@
##           master    #1854      +/-   ##
==========================================
- Coverage   97.63%   97.23%   -0.41%     
==========================================
  Files         105      105              
  Lines        6720     6759      +39     
==========================================
+ Hits         6561     6572      +11     
- Misses         86      102      +16     
- Partials       73       85      +12     
```


| [Impacted Files](https://codecov.io/gh/google/go-github/pull/1854?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) | Coverage Δ | |
|---|---|---|
| [github/github.go](https://codecov.io/gh/google/go-github/pull/1854/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google#diff-Z2l0aHViL2dpdGh1Yi5nbw==) | `89.31% <30.00%> (-7.32%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/google/go-github/pull/1854?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/google/go-github/pull/1854?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google). Last update [d711542...0c21cda](https://codecov.io/gh/google/go-github/pull/1854?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google).

mbalayil: Have updated the PR with more tests to improve coverage. Please take a look.

Patch:
diff --git a/README.md b/README.md
index df43395d..7d865d89 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 # go-github #
 
 [![go-github release (latest SemVer)](https://img.shields.io/github/v/release/google/go-github?sort=semver)](https://github.com/google/go-github/releases)
-[![GoDoc](https://img.shields.io/static/v1?label=godoc&message=reference&color=blue)](https://pkg.go.dev/github.com/google/go-github/v34/github)
+[![GoDoc](https://img.shields.io/static/v1?label=godoc&message=reference&color=blue)](https://pkg.go.dev/github.com/google/go-github/v35/github)
 [![Test Status](https://github.com/google/go-github/workflows/tests/badge.svg)](https://github.com/google/go-github/actions?query=workflow%3Atests)
 [![Test Coverage](https://codecov.io/gh/google/go-github/branch/master/graph/badge.svg)](https://codecov.io/gh/google/go-github)
 [![Discuss at go-github@googlegroups.com](https://img.shields.io/badge/discuss-go--github%40googlegroups.com-blue.svg)](https://groups.google.com/group/go-github)
@@ -9,7 +9,7 @@
 
 go-github is a Go client library for accessing the [GitHub API v3][].
 
-Currently, **go-github requires Go version 1.9 or greater**.  go-github tracks
+Currently, **go-github requires Go version 1.13 or greater**.  go-github tracks
 [Go's version support policy][support-policy].  We do our best not to break
 older versions of Go if we don't have to, but due to tooling constraints, we
 don't always test older versions.
@@ -24,7 +24,7 @@ If you're interested in using the [GraphQL API v4][], the recommended library is
 go-github is compatible with modern Go releases in module mode, with Go installed:
 
 ```bash
-go get github.com/google/go-github/v34
+go get github.com/google/go-github/v35
 ```
 
 will resolve and add the package to the current development module, along with its dependencies.
@@ -32,7 +32,7 @@ will resolve and add the package to the current development module, along with i
 Alternatively the same can be achieved if you use import in a package:
 
 ```go
-import "github.com/google/go-github/v34/github"
+import "github.com/google/go-github/v35/github"
 ```
 
 and run `go get` without paramters.
@@ -40,13 +40,13 @@ and run `go get` without paramters.
 Finally, to use the top-of-trunk version of this repo, use the following command:
 
 ```bash
-go get github.com/google/go-github/v34@master
+go get github.com/google/go-github/v35@master
 ```
 
 ## Usage ##
 
 ```go
-import "github.com/google/go-github/v34/github"	// with go modules enabled (GO111MODULE=on or outside GOPATH)
+import "github.com/google/go-github/v35/github"	// with go modules enabled (GO111MODULE=on or outside GOPATH)
 import "github.com/google/go-github/github" // with go modules disabled
 ```
 
@@ -246,7 +246,7 @@ For complete usage of go-github, see the full [package docs][].
 [oauth2]: https://github.com/golang/oauth2
 [oauth2 docs]: https://godoc.org/golang.org/x/oauth2
 [personal API token]: https://github.com/blog/1509-personal-api-tokens
-[package docs]: https://pkg.go.dev/github.com/google/go-github/v34/github
+[package docs]: https://pkg.go.dev/github.com/google/go-github/v35/github
 [GraphQL API v4]: https://developer.github.com/v4/
 [shurcooL/githubv4]: https://github.com/shurcooL/githubv4
 
diff --git a/example/appengine/app.go b/example/appengine/app.go
index 3c489fd9..588a2a07 100644
--- a/example/appengine/app.go
+++ b/example/appengine/app.go
@@ -12,7 +12,7 @@ import (
 	"net/http"
 	"os"
 
-	"github.com/google/go-github/v34/github"
+	"github.com/google/go-github/v35/github"
 	"golang.org/x/oauth2"
 	"google.golang.org/appengine"
 	"google.golang.org/appengine/log"
diff --git a/example/basicauth/main.go b/example/basicauth/main.go
index 46707c13..3c7efc82 100644
--- a/example/basicauth/main.go
+++ b/example/basicauth/main.go
@@ -22,7 +22,7 @@ import (
 	"strings"
 	"syscall"
 
-	"github.com/google/go-github/v34/github"
+	"github.com/google/go-github/v35/github"
 	"golang.org/x/crypto/ssh/terminal"
 )
 
diff --git a/example/commitpr/main.go b/example/commitpr/main.go
index 928f9658..604f4f58 100644
--- a/example/commitpr/main.go
+++ b/example/commitpr/main.go
@@ -31,7 +31,7 @@ import (
 	"strings"
 	"time"
 
-	"github.com/google/go-github/v34/github"
+	"github.com/google/go-github/v35/github"
 	"golang.org/x/oauth2"
 )
 
diff --git a/example/migrations/main.go b/example/migrations/main.go
index c423d652..230cff25 100644
--- a/example/migrations/main.go
+++ b/example/migrations/main.go
@@ -12,7 +12,7 @@ import (
 	"context"
 	"fmt"
 
-	"github.com/google/go-github/v34/github"
+	"github.com/google/go-github/v35/github"
 	"golang.org/x/oauth2"
 )
 
diff --git a/example/newrepo/main.go b/example/newrepo/main.go
index ea592a16..239ccfb7 100644
--- a/example/newrepo/main.go
+++ b/example/newrepo/main.go
@@ -16,7 +16,7 @@ import (
 	"log"
 	"os"
 
-	"github.com/google/go-github/v34/github"
+	"github.com/google/go-github/v35/github"
 	"golang.org/x/oauth2"
 )
 
diff --git a/example/newreposecret/go.mod b/example/newreposecret/go.mod
index 38e93690..50d854c6 100644
--- a/example/newreposecret/go.mod
+++ b/example/newreposecret/go.mod
@@ -4,7 +4,7 @@ go 1.15
 
 require (
 	github.com/GoKillers/libsodium-go v0.0.0-20171022220152-dd733721c3cb
-	github.com/google/go-github/v34 v34.0.0
+	github.com/google/go-github/v35 v35.0.0
 	golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43
 	gopkg.in/yaml.v2 v2.3.0 // indirect
 )
diff --git a/example/newreposecret/main.go b/example/newreposecret/main.go
index a2345924..928e4532 100644
--- a/example/newreposecret/main.go
+++ b/example/newreposecret/main.go
@@ -34,7 +34,7 @@ import (
 	"os"
 
 	sodium "github.com/GoKillers/libsodium-go/cryptobox"
-	"github.com/google/go-github/v34/github"
+	"github.com/google/go-github/v35/github"
 	"golang.org/x/oauth2"
 )
 
diff --git a/example/simple/main.go b/example/simple/main.go
index dac01078..ee0d5751 100644
--- a/example/simple/main.go
+++ b/example/simple/main.go
@@ -12,7 +12,7 @@ import (
 	"context"
 	"fmt"
 
-	"github.com/google/go-github/v34/github"
+	"github.com/google/go-github/v35/github"
 )
 
 // Fetch all the public organizations' membership of a user.
diff --git a/example/tokenauth/main.go b/example/tokenauth/main.go
index ff92db2a..922707c5 100644
--- a/example/tokenauth/main.go
+++ b/example/tokenauth/main.go
@@ -4,14 +4,17 @@
 // license that can be found in the LICENSE file.
 
 // The tokenauth command demonstrates using the oauth2.StaticTokenSource.
+// You can test out a GitHub Personal Access Token using this simple example.
+// You can generate them here: https://github.com/settings/tokens
 package main
 
 import (
 	"context"
 	"fmt"
+	"log"
 	"syscall"
 
-	"github.com/google/go-github/v34/github"
+	"github.com/google/go-github/v35/github"
 	"golang.org/x/crypto/ssh/terminal"
 	"golang.org/x/oauth2"
 )
@@ -29,12 +32,14 @@ func main() {
 
 	client := github.NewClient(tc)
 
-	user, _, err := client.Users.Get(ctx, "")
-
+	user, resp, err := client.Users.Get(ctx, "")
 	if err != nil {
 		fmt.Printf("\nerror: %v\n", err)
 		return
 	}
 
+	// Rate.Limit should most likely be 5000 when authorized.
+	log.Printf("Rate: %#v", resp.Rate)
+
 	fmt.Printf("\n%v\n", github.Stringify(user))
 }
diff --git a/example/topics/main.go b/example/topics/main.go
index 82f835fa..7011b6e3 100644
--- a/example/topics/main.go
+++ b/example/topics/main.go
@@ -12,7 +12,7 @@ import (
 	"context"
 	"fmt"
 
-	"github.com/google/go-github/v34/github"
+	"github.com/google/go-github/v35/github"
 )
 
 // Fetch and lists all the public topics associated with the specified GitHub topic
diff --git a/github/doc.go b/github/doc.go
index e7d0c424..def690b8 100644
--- a/github/doc.go
+++ b/github/doc.go
@@ -8,7 +8,7 @@ Package github provides a client for using the GitHub API.
 
 Usage:
 
-	import "github.com/google/go-github/v34/github"	// with go modules enabled (GO111MODULE=on or outside GOPATH)
+	import "github.com/google/go-github/v35/github"	// with go modules enabled (GO111MODULE=on or outside GOPATH)
 	import "github.com/google/go-github/github"     // with go modules disabled
 
 Construct a new GitHub client, then use the various services on the client to
diff --git a/github/github.go b/github/github.go
index 4489dab2..efde8f8d 100644
--- a/github/github.go
+++ b/github/github.go
@@ -132,6 +132,8 @@ const (
 	mediaTypeContentAttachmentsPreview = "application/vnd.github.corsair-preview+json"
 )
 
+var errNonNilContext = errors.New("context must be non-nil")
+
 // A Client manages communication with the GitHub API.
 type Client struct {
 	clientMu sync.Mutex   // clientMu protects the client during calls that modify the CheckRedirect func.
@@ -530,7 +532,7 @@ func parseRate(r *http.Response) Rate {
 // canceled or times out, ctx.Err() will be returned.
 func (c *Client) BareDo(ctx context.Context, req *http.Request) (*Response, error) {
 	if ctx == nil {
-		return nil, errors.New("context must be non-nil")
+		return nil, errNonNilContext
 	}
 	req = withContext(ctx, req)
 
@@ -653,6 +655,20 @@ func (c *Client) checkRateLimitBeforeDo(req *http.Request, rateLimitCategory rat
 	return nil
 }
 
+// compareHttpResponse returns whether two http.Response objects are equal or not.
+// Currently, only StatusCode is checked. This function is used when implementing the
+// Is(error) bool interface for the custom error types in this package.
+func compareHttpResponse(r1, r2 *http.Response) bool {
+	if r1 == nil && r2 == nil {
+		return true
+	}
+
+	if r1 != nil && r2 != nil {
+		return r1.StatusCode == r2.StatusCode
+	}
+	return false
+}
+
 /*
 An ErrorResponse reports one or more errors caused by an API request.
 
@@ -681,6 +697,50 @@ func (r *ErrorResponse) Error() string {
 		r.Response.StatusCode, r.Message, r.Errors)
 }
 
+// Is returns whether the provided error equals this error.
+func (r *ErrorResponse) Is(target error) bool {
+	v, ok := target.(*ErrorResponse)
+	if !ok {
+		return false
+	}
+
+	if r.Message != v.Message || (r.DocumentationURL != v.DocumentationURL) ||
+		!compareHttpResponse(r.Response, v.Response) {
+		return false
+	}
+
+	// Compare Errors.
+	if len(r.Errors) != len(v.Errors) {
+		return false
+	}
+	for idx := range r.Errors {
+		if r.Errors[idx] != v.Errors[idx] {
+			return false
+		}
+	}
+
+	// Compare Block.
+	if (r.Block != nil && v.Block == nil) || (r.Block == nil && v.Block != nil) {
+		return false
+	}
+	if r.Block != nil && v.Block != nil {
+		if r.Block.Reason != v.Block.Reason {
+			return false
+		}
+		if (r.Block.CreatedAt != nil && v.Block.CreatedAt == nil) || (r.Block.CreatedAt ==
+			nil && v.Block.CreatedAt != nil) {
+			return false
+		}
+		if r.Block.CreatedAt != nil && v.Block.CreatedAt != nil {
+			if *(r.Block.CreatedAt) != *(v.Block.CreatedAt) {
+				return false
+			}
+		}
+	}
+
+	return true
+}
+
 // TwoFactorAuthError occurs when using HTTP Basic Authentication for a user
 // that has two-factor authentication enabled. The request can be reattempted
 // by providing a one-time password in the request.
@@ -702,6 +762,18 @@ func (r *RateLimitError) Error() string {
 		r.Response.StatusCode, r.Message, formatRateReset(time.Until(r.Rate.Reset.Time)))
 }
 
+// Is returns whether the provided error equals this error.
+func (r *RateLimitError) Is(target error) bool {
+	v, ok := target.(*RateLimitError)
+	if !ok {
+		return false
+	}
+
+	return r.Rate == v.Rate &&
+		r.Message == v.Message &&
+		compareHttpResponse(r.Response, v.Response)
+}
+
 // AcceptedError occurs when GitHub returns 202 Accepted response with an
 // empty body, which means a job was scheduled on the GitHub side to process
 // the information needed and cache it.
@@ -717,6 +789,15 @@ func (*AcceptedError) Error() string {
 	return "job scheduled on GitHub side; try again later"
 }
 
+// Is returns whether the provided error equals this error.
+func (ae *AcceptedError) Is(target error) bool {
+	v, ok := target.(*AcceptedError)
+	if !ok {
+		return false
+	}
+	return bytes.Compare(ae.Raw, v.Raw) == 0
+}
+
 // AbuseRateLimitError occurs when GitHub returns 403 Forbidden response with the
 // "documentation_url" field value equal to "https://docs.github.com/en/free-pro-team@latest/rest/reference/#abuse-rate-limits".
 type AbuseRateLimitError struct {
@@ -735,6 +816,18 @@ func (r *AbuseRateLimitError) Error() string {
 		r.Response.StatusCode, r.Message)
 }
 
+// Is returns whether the provided error equals this error.
+func (r *AbuseRateLimitError) Is(target error) bool {
+	v, ok := target.(*AbuseRateLimitError)
+	if !ok {
+		return false
+	}
+
+	return r.Message == v.Message &&
+		r.RetryAfter == v.RetryAfter &&
+		compareHttpResponse(r.Response, v.Response)
+}
+
 // sanitizeURL redacts the client_secret parameter from the URL which may be
 // exposed to the user.
 func sanitizeURL(uri *url.URL) *url.URL {
diff --git a/go.mod b/go.mod
index e86ec117..18f16fcf 100644
--- a/go.mod
+++ b/go.mod
@@ -1,4 +1,4 @@
-module github.com/google/go-github/v34
+module github.com/google/go-github/v35
 
 require (
 	github.com/golang/protobuf v1.3.2 // indirect


Test patch:
diff --git a/github/examples_test.go b/github/examples_test.go
index aa85aff6..33851fac 100644
--- a/github/examples_test.go
+++ b/github/examples_test.go
@@ -12,7 +12,7 @@ import (
 	"fmt"
 	"log"
 
-	"github.com/google/go-github/v34/github"
+	"github.com/google/go-github/v35/github"
 )
 
 func ExampleClient_Markdown() {
diff --git a/github/github_test.go b/github/github_test.go
index db50b465..858b2043 100644
--- a/github/github_test.go
+++ b/github/github_test.go
@@ -705,7 +705,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")
 	}
 }
@@ -1097,7 +1097,7 @@ func TestCheckResponse(t *testing.T) {
 			CreatedAt: &Timestamp{time.Date(2016, time.March, 17, 15, 39, 46, 0, time.UTC)},
 		},
 	}
-	if !reflect.DeepEqual(err, want) {
+	if !errors.Is(err, want) {
 		t.Errorf("Error = %#v, want %#v", err, want)
 	}
 }
@@ -1125,7 +1125,7 @@ func TestCheckResponse_RateLimit(t *testing.T) {
 		Response: res,
 		Message:  "m",
 	}
-	if !reflect.DeepEqual(err, want) {
+	if !errors.Is(err, want) {
 		t.Errorf("Error = %#v, want %#v", err, want)
 	}
 }
@@ -1147,11 +1147,411 @@ func TestCheckResponse_AbuseRateLimit(t *testing.T) {
 		Response: res,
 		Message:  "m",
 	}
-	if !reflect.DeepEqual(err, want) {
+	if !errors.Is(err, want) {
 		t.Errorf("Error = %#v, want %#v", err, want)
 	}
 }
 
+func TestCompareHttpResponse(t *testing.T) {
+	testcases := map[string]struct {
+		h1       *http.Response
+		h2       *http.Response
+		expected bool
+	}{
+		"both are nil": {
+			expected: true,
+		},
+		"both are non nil - same StatusCode": {
+			expected: true,
+			h1:       &http.Response{StatusCode: 200},
+			h2:       &http.Response{StatusCode: 200},
+		},
+		"both are non nil - different StatusCode": {
+			expected: false,
+			h1:       &http.Response{StatusCode: 200},
+			h2:       &http.Response{StatusCode: 404},
+		},
+		"one is nil, other is not": {
+			expected: false,
+			h2:       &http.Response{},
+		},
+	}
+
+	for name, tc := range testcases {
+		t.Run(name, func(t *testing.T) {
+			v := compareHttpResponse(tc.h1, tc.h2)
+			if tc.expected != v {
+				t.Errorf("Expected %t, got %t for (%#v, %#v)", tc.expected, v, tc.h1, tc.h2)
+			}
+		})
+	}
+}
+
+func TestErrorResponse_Is(t *testing.T) {
+	err := &ErrorResponse{
+		Response: &http.Response{},
+		Message:  "m",
+		Errors:   []Error{{Resource: "r", Field: "f", Code: "c"}},
+		Block: &struct {
+			Reason    string     `json:"reason,omitempty"`
+			CreatedAt *Timestamp `json:"created_at,omitempty"`
+		}{
+			Reason:    "r",
+			CreatedAt: &Timestamp{time.Date(2016, time.March, 17, 15, 39, 46, 0, time.UTC)},
+		},
+		DocumentationURL: "https://github.com",
+	}
+	testcases := map[string]struct {
+		wantSame   bool
+		otherError error
+	}{
+		"errors are same": {
+			wantSame: true,
+			otherError: &ErrorResponse{
+				Response: &http.Response{},
+				Errors:   []Error{{Resource: "r", Field: "f", Code: "c"}},
+				Message:  "m",
+				Block: &struct {
+					Reason    string     `json:"reason,omitempty"`
+					CreatedAt *Timestamp `json:"created_at,omitempty"`
+				}{
+					Reason:    "r",
+					CreatedAt: &Timestamp{time.Date(2016, time.March, 17, 15, 39, 46, 0, time.UTC)},
+				},
+				DocumentationURL: "https://github.com",
+			},
+		},
+		"errors have different values - Message": {
+			wantSame: false,
+			otherError: &ErrorResponse{
+				Response: &http.Response{},
+				Errors:   []Error{{Resource: "r", Field: "f", Code: "c"}},
+				Message:  "m1",
+				Block: &struct {
+					Reason    string     `json:"reason,omitempty"`
+					CreatedAt *Timestamp `json:"created_at,omitempty"`
+				}{
+					Reason:    "r",
+					CreatedAt: &Timestamp{time.Date(2016, time.March, 17, 15, 39, 46, 0, time.UTC)},
+				},
+				DocumentationURL: "https://github.com",
+			},
+		},
+		"errors have different values - DocumentationURL": {
+			wantSame: false,
+			otherError: &ErrorResponse{
+				Response: &http.Response{},
+				Errors:   []Error{{Resource: "r", Field: "f", Code: "c"}},
+				Message:  "m",
+				Block: &struct {
+					Reason    string     `json:"reason,omitempty"`
+					CreatedAt *Timestamp `json:"created_at,omitempty"`
+				}{
+					Reason:    "r",
+					CreatedAt: &Timestamp{time.Date(2016, time.March, 17, 15, 39, 46, 0, time.UTC)},
+				},
+				DocumentationURL: "https://google.com",
+			},
+		},
+		"errors have different values - Response is nil": {
+			wantSame: false,
+			otherError: &ErrorResponse{
+				Errors:  []Error{{Resource: "r", Field: "f", Code: "c"}},
+				Message: "m",
+				Block: &struct {
+					Reason    string     `json:"reason,omitempty"`
+					CreatedAt *Timestamp `json:"created_at,omitempty"`
+				}{
+					Reason:    "r",
+					CreatedAt: &Timestamp{time.Date(2016, time.March, 17, 15, 39, 46, 0, time.UTC)},
+				},
+				DocumentationURL: "https://github.com",
+			},
+		},
+		"errors have different values - Errors": {
+			wantSame: false,
+			otherError: &ErrorResponse{
+				Response: &http.Response{},
+				Errors:   []Error{{Resource: "r1", Field: "f1", Code: "c1"}},
+				Message:  "m",
+				Block: &struct {
+					Reason    string     `json:"reason,omitempty"`
+					CreatedAt *Timestamp `json:"created_at,omitempty"`
+				}{
+					Reason:    "r",
+					CreatedAt: &Timestamp{time.Date(2016, time.March, 17, 15, 39, 46, 0, time.UTC)},
+				},
+				DocumentationURL: "https://github.com",
+			},
+		},
+		"errors have different values - Errors have different length": {
+			wantSame: false,
+			otherError: &ErrorResponse{
+				Response: &http.Response{},
+				Errors:   []Error{},
+				Message:  "m",
+				Block: &struct {
+					Reason    string     `json:"reason,omitempty"`
+					CreatedAt *Timestamp `json:"created_at,omitempty"`
+				}{
+					Reason:    "r",
+					CreatedAt: &Timestamp{time.Date(2016, time.March, 17, 15, 39, 46, 0, time.UTC)},
+				},
+				DocumentationURL: "https://github.com",
+			},
+		},
+		"errors have different values - Block - one is nil, other is not": {
+			wantSame: false,
+			otherError: &ErrorResponse{
+				Response:         &http.Response{},
+				Errors:           []Error{{Resource: "r", Field: "f", Code: "c"}},
+				Message:          "m",
+				DocumentationURL: "https://github.com",
+			},
+		},
+		"errors have different values - Block - different Reason": {
+			wantSame: false,
+			otherError: &ErrorResponse{
+				Response: &http.Response{},
+				Errors:   []Error{{Resource: "r", Field: "f", Code: "c"}},
+				Message:  "m",
+				Block: &struct {
+					Reason    string     `json:"reason,omitempty"`
+					CreatedAt *Timestamp `json:"created_at,omitempty"`
+				}{
+					Reason:    "r1",
+					CreatedAt: &Timestamp{time.Date(2016, time.March, 17, 15, 39, 46, 0, time.UTC)},
+				},
+				DocumentationURL: "https://github.com",
+			},
+		},
+		"errors have different values - Block - different CreatedAt #1": {
+			wantSame: false,
+			otherError: &ErrorResponse{
+				Response: &http.Response{},
+				Errors:   []Error{{Resource: "r", Field: "f", Code: "c"}},
+				Message:  "m",
+				Block: &struct {
+					Reason    string     `json:"reason,omitempty"`
+					CreatedAt *Timestamp `json:"created_at,omitempty"`
+				}{
+					Reason:    "r",
+					CreatedAt: nil,
+				},
+				DocumentationURL: "https://github.com",
+			},
+		},
+		"errors have different values - Block - different CreatedAt #2": {
+			wantSame: false,
+			otherError: &ErrorResponse{
+				Response: &http.Response{},
+				Errors:   []Error{{Resource: "r", Field: "f", Code: "c"}},
+				Message:  "m",
+				Block: &struct {
+					Reason    string     `json:"reason,omitempty"`
+					CreatedAt *Timestamp `json:"created_at,omitempty"`
+				}{
+					Reason:    "r",
+					CreatedAt: &Timestamp{time.Date(2017, time.March, 17, 15, 39, 46, 0, time.UTC)},
+				},
+				DocumentationURL: "https://github.com",
+			},
+		},
+		"errors have different types": {
+			wantSame:   false,
+			otherError: errors.New("Github"),
+		},
+	}
+
+	for name, tc := range testcases {
+		t.Run(name, func(t *testing.T) {
+			if tc.wantSame != err.Is(tc.otherError) {
+				t.Errorf("Error = %#v, want %#v", err, tc.otherError)
+			}
+		})
+	}
+}
+
+func TestRateLimitError_Is(t *testing.T) {
+	err := &RateLimitError{
+		Response: &http.Response{},
+		Message:  "Github",
+	}
+	testcases := map[string]struct {
+		wantSame   bool
+		err        *RateLimitError
+		otherError error
+	}{
+		"errors are same": {
+			wantSame: true,
+			err:      err,
+			otherError: &RateLimitError{
+				Response: &http.Response{},
+				Message:  "Github",
+			},
+		},
+		"errors are same - Response is nil": {
+			wantSame: true,
+			err: &RateLimitError{
+				Message: "Github",
+			},
+			otherError: &RateLimitError{
+				Message: "Github",
+			},
+		},
+		"errors have different values - Rate": {
+			wantSame: false,
+			err:      err,
+			otherError: &RateLimitError{
+				Rate:     Rate{Limit: 10},
+				Response: &http.Response{},
+				Message:  "Gitlab",
+			},
+		},
+		"errors have different values - Response is nil": {
+			wantSame: false,
+			err:      err,
+			otherError: &RateLimitError{
+				Message: "Github",
+			},
+		},
+		"errors have different values - StatusCode": {
+			wantSame: false,
+			err:      err,
+			otherError: &RateLimitError{
+				Response: &http.Response{StatusCode: 200},
+				Message:  "Github",
+			},
+		},
+		"errors have different types": {
+			wantSame:   false,
+			err:        err,
+			otherError: errors.New("Github"),
+		},
+	}
+
+	for name, tc := range testcases {
+		t.Run(name, func(t *testing.T) {
+			if tc.wantSame != tc.err.Is(tc.otherError) {
+				t.Errorf("Error = %#v, want %#v", tc.err, tc.otherError)
+			}
+		})
+	}
+}
+
+func TestAbuseRateLimitError_Is(t *testing.T) {
+	t1 := 1 * time.Second
+	t2 := 2 * time.Second
+	err := &AbuseRateLimitError{
+		Response:   &http.Response{},
+		Message:    "Github",
+		RetryAfter: &t1,
+	}
+	testcases := map[string]struct {
+		wantSame   bool
+		err        *AbuseRateLimitError
+		otherError error
+	}{
+		"errors are same": {
+			wantSame: true,
+			err:      err,
+			otherError: &AbuseRateLimitError{
+				Response:   &http.Response{},
+				Message:    "Github",
+				RetryAfter: &t1,
+			},
+		},
+		"errors are same - Response is nil": {
+			wantSame: true,
+			err: &AbuseRateLimitError{
+				Message:    "Github",
+				RetryAfter: &t1,
+			},
+			otherError: &AbuseRateLimitError{
+				Message:    "Github",
+				RetryAfter: &t1,
+			},
+		},
+		"errors have different values - Message": {
+			wantSame: false,
+			err:      err,
+			otherError: &AbuseRateLimitError{
+				Response:   &http.Response{},
+				Message:    "Gitlab",
+				RetryAfter: nil,
+			},
+		},
+		"errors have different values - RetryAfter": {
+			wantSame: false,
+			err:      err,
+			otherError: &AbuseRateLimitError{
+				Response:   &http.Response{},
+				Message:    "Github",
+				RetryAfter: &t2,
+			},
+		},
+		"errors have different values - Response is nil": {
+			wantSame: false,
+			err:      err,
+			otherError: &AbuseRateLimitError{
+				Message:    "Github",
+				RetryAfter: &t1,
+			},
+		},
+		"errors have different values - StatusCode": {
+			wantSame: false,
+			err:      err,
+			otherError: &AbuseRateLimitError{
+				Response:   &http.Response{StatusCode: 200},
+				Message:    "Github",
+				RetryAfter: &t1,
+			},
+		},
+		"errors have different types": {
+			wantSame:   false,
+			err:        err,
+			otherError: errors.New("Github"),
+		},
+	}
+
+	for name, tc := range testcases {
+		t.Run(name, func(t *testing.T) {
+			if tc.wantSame != tc.err.Is(tc.otherError) {
+				t.Errorf("Error = %#v, want %#v", tc.err, tc.otherError)
+			}
+		})
+	}
+}
+
+func TestAcceptedError_Is(t *testing.T) {
+	err := &AcceptedError{Raw: []byte("Github")}
+	testcases := map[string]struct {
+		wantSame   bool
+		otherError error
+	}{
+		"errors are same": {
+			wantSame:   true,
+			otherError: &AcceptedError{Raw: []byte("Github")},
+		},
+		"errors have different values": {
+			wantSame:   false,
+			otherError: &AcceptedError{Raw: []byte("Gitlab")},
+		},
+		"errors have different types": {
+			wantSame:   false,
+			otherError: errors.New("Github"),
+		},
+	}
+
+	for name, tc := range testcases {
+		t.Run(name, func(t *testing.T) {
+			if tc.wantSame != err.Is(tc.otherError) {
+				t.Errorf("Error = %#v, want %#v", err, tc.otherError)
+			}
+		})
+	}
+}
+
 // ensure that we properly handle API errors that do not contain a response body
 func TestCheckResponse_noBody(t *testing.T) {
 	res := &http.Response{
@@ -1168,7 +1568,7 @@ func TestCheckResponse_noBody(t *testing.T) {
 	want := &ErrorResponse{
 		Response: res,
 	}
-	if !reflect.DeepEqual(err, want) {
+	if !errors.Is(err, want) {
 		t.Errorf("Error = %#v, want %#v", err, want)
 	}
 }
@@ -1191,7 +1591,7 @@ func TestCheckResponse_unexpectedErrorStructure(t *testing.T) {
 		Message:  "m",
 		Errors:   []Error{{Message: "error 1"}},
 	}
-	if !reflect.DeepEqual(err, want) {
+	if !errors.Is(err, want) {
 		t.Errorf("Error = %#v, want %#v", err, want)
 	}
 	data, err2 := ioutil.ReadAll(err.Response.Body)
diff --git a/test/fields/fields.go b/test/fields/fields.go
index bc018e81..07cf9451 100644
--- a/test/fields/fields.go
+++ b/test/fields/fields.go
@@ -25,7 +25,7 @@ import (
 	"reflect"
 	"strings"
 
-	"github.com/google/go-github/v34/github"
+	"github.com/google/go-github/v35/github"
 	"golang.org/x/oauth2"
 )
 
diff --git a/test/integration/activity_test.go b/test/integration/activity_test.go
index 6cc952ef..013ac5f0 100644
--- a/test/integration/activity_test.go
+++ b/test/integration/activity_test.go
@@ -11,7 +11,7 @@ import (
 	"context"
 	"testing"
 
-	"github.com/google/go-github/v34/github"
+	"github.com/google/go-github/v35/github"
 )
 
 const (
diff --git a/test/integration/authorizations_test.go b/test/integration/authorizations_test.go
index 198bff54..5b832f33 100644
--- a/test/integration/authorizations_test.go
+++ b/test/integration/authorizations_test.go
@@ -16,7 +16,7 @@ import (
 	"testing"
 	"time"
 
-	"github.com/google/go-github/v34/github"
+	"github.com/google/go-github/v35/github"
 )
 
 const msgEnvMissing = "Skipping test because the required environment variable (%v) is not present."
diff --git a/test/integration/github_test.go b/test/integration/github_test.go
index c2852bae..52db8ac6 100644
--- a/test/integration/github_test.go
+++ b/test/integration/github_test.go
@@ -14,7 +14,7 @@ import (
 	"net/http"
 	"os"
 
-	"github.com/google/go-github/v34/github"
+	"github.com/google/go-github/v35/github"
 	"golang.org/x/oauth2"
 )
 
diff --git a/test/integration/repos_test.go b/test/integration/repos_test.go
index f479ee49..6c8efe2f 100644
--- a/test/integration/repos_test.go
+++ b/test/integration/repos_test.go
@@ -15,7 +15,7 @@ import (
 	"reflect"
 	"testing"
 
-	"github.com/google/go-github/v34/github"
+	"github.com/google/go-github/v35/github"
 )
 
 func TestRepositories_CRUD(t *testing.T) {
diff --git a/test/integration/users_test.go b/test/integration/users_test.go
index 404a644b..43186106 100644
--- a/test/integration/users_test.go
+++ b/test/integration/users_test.go
@@ -13,7 +13,7 @@ import (
 	"math/rand"
 	"testing"
 
-	"github.com/google/go-github/v34/github"
+	"github.com/google/go-github/v35/github"
 )
 
 func TestUsers_Get(t *testing.T) {