Skip to content

deal with 202 Accepted status code#488

Closed
caarlos0 wants to merge 11 commits intogoogle:masterfrom
caarlos0:202
Closed

deal with 202 Accepted status code#488
caarlos0 wants to merge 11 commits intogoogle:masterfrom
caarlos0:202

Conversation

@caarlos0
Copy link
Copy Markdown
Contributor

@caarlos0 caarlos0 commented Dec 8, 2016

When github returns a 202, it means that they don't have the data you asked, and it is considered a rater heavy operation to get it, so they schedule a background job to fetch and cache it, and subsequent calls to it might work.

go-github was failing to deal with that, because in some places it was trying to parse the body to a list of some object, and github actually returns a {}.

With this change we would be able to do the same as we do with rate limiting issues, e.g.:

stats, _, err := client.Repositories.ListContributorsStats(org, repo)
if err != nil {
	if _, ok := err.(*github.AcceptedError); ok {
		// handle accepted error, likely by trying again later
	}
	// handle other errors
}

fixes #486

Copy link
Copy Markdown
Member

@dmitshur dmitshur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change affects CheckResponse, which is an exported method. Its current documentation says:

// CheckResponse checks the API response for errors, and returns them if
// present.  A response is considered an error if it has a status code outside
// the 200 range.  API error responses are expected to have either no response
// body, or a JSON response body that maps to ErrorResponse.  Any other
// response body will be silently ignored.
//
// The error type will be *RateLimitError for rate limit exceeded errors,
// and *TwoFactorAuthError for two-factor authentication errors.
func CheckResponse(r *http.Response) error {

That is obviously no longer true, and needs to be updated to match reality. Specifically, this sentence "A response is considered an error if it has a status code outside the 200 range.", and the second paragraph should document that AcceptedError can be returned.

Also, you overlooked the first 3 lines of CheckResponse, which will cause this change to not work.

 if c := r.StatusCode; 200 <= c && c <= 299 {
 	return nil
 }

That'll need to be updated too.

github/github.go Outdated
}
return abuseRateLimitError
case r.StatusCode == http.StatusAccepted:
return &AcceptedError{}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm tempted to suggest we turn this into a value rather than a pointer. It's an empty struct. Pointer seems unneeded and unhelpful.

return AcceptedError{}

github/github.go Outdated
type AcceptedError struct{}

func (r *AcceptedError) Error() string {
return "Job scheduled on GitHub side, try again later"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error strings should not be capitalized. See https://github.com/golang/go/wiki/CodeReviewComments#error-strings.

github/github.go Outdated
// the information needed and cache it.
type AcceptedError struct{}

func (r *AcceptedError) Error() string {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Receiver is unused and can be removed. See rationale here.

// 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.
type AcceptedError struct{}
Copy link
Copy Markdown
Member

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 it's obvious or worth adding another sentence to the current documentation, something like:

Technically, 202 Accepted is not a real error, it's just used to indicate that results are not ready yet, but should be available soon. The request should be repeated after some time.

The part that's missing to me is that the documentation should indicate that the this is a "retryable" error. Trying again after some time is completely normal and expected to succeed (but maybe not on the first try).

@caarlos0
Copy link
Copy Markdown
Contributor Author

caarlos0 commented Dec 9, 2016

@shurcooL thanks for the review! I think I fixed everything, I still not sure about the messages docs wording though.

Thanks!

Copy link
Copy Markdown
Member

@dmitshur dmitshur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is starting to look good.

See an inline comment. Additionally, I think this PR would benefit significantly if we can document the endpoints where AcceptedError can be returned. As far as I can tell, it'd be the 5 endpoints listed at https://developer.github.com/v3/repos/statistics/. Do you know/think any others can return 202?

An alternative is to document AcceptedError in doc.go package documentation. That'd be easier, but not as helpful IMO.

// response body will be silently ignored.
//
// The error type will be *RateLimitError for rate limit exceeded errors,
// and *TwoFactorAuthError for two-factor authentication errors.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's mention AcceptedError here.

// The error type will be *RateLimitError for rate limit exceeded errors,
// AcceptedError for 202 Accepted status codes,
// and *TwoFactorAuthError for two-factor authentication errors.

This makes me realize that there's actually a good reason to revert back to using a pointer to *AcceptedError rather than value: for consistency of type assertion code to other error types. Currently, it'd be:

switch err.(type) {
case *github.RateLimitError:
	// handle rate limit exceeded error
case *github.TwoFactorAuthError:
	// handle two-factor authentication error
case github.AcceptedError: // inconsistent
	// handle accepted error
}

If we revert to using a pointer to *AcceptedError, it becomes consistent.

switch err.(type) {
case *github.RateLimitError:
	// handle rate limit exceeded error
case *github.TwoFactorAuthError:
	// handle two-factor authentication error
case *github.AcceptedError: // consistent
	// handle accepted error
}

I think that's a good argument to use pointer here too. Do you agree?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I agree... looks more consistent indeed

@dmitshur
Copy link
Copy Markdown
Member

dmitshur commented Dec 9, 2016

However, at this point I'd like to ping @willnorris or @gmlewis to take a look at this API change to CheckResponse and see if they agree this is an improvement. It's a slightly breaking API change (well, it changes the behavior more than API), so I wanna get their thoughts.

Also, it's kinda going against the HTTP spec which says that 2xx range status codes are "success" cases. But I think in Go it's expected/most idiomatic to return a non-nil error whenever the result you asked for cannot be returned to you.

My main argument for this change in #486 (comment) was:

Returning a non-nil error when the result that was asked for is not returned seems very reasonable to me. Otherwise every user of this library and that function will need to check both err and resp.StatusCode, which isn't as friendly.

@caarlos0
Copy link
Copy Markdown
Contributor Author

caarlos0 commented Dec 9, 2016

This is starting to look good.

See an inline comment. Additionally, I think this PR would benefit significantly if we can document the endpoints where AcceptedError can be returned. As far as I can tell, it'd be the 5 endpoints listed at https://developer.github.com/v3/repos/statistics/. Do you know/think any others can return 202?

An alternative is to document AcceptedError in doc.go package documentation. That'd be easier, but not as helpful IMO.

I did some search and found:

Seems like the enterprise endpoints listed here aren't implemented on go-github.

BTW I found this by searching for 202 accepted site:developer.github.com on Google.

@dmitshur
Copy link
Copy Markdown
Member

dmitshur commented Dec 9, 2016

I did some search and found

Thanks for doing that.

Thinking more about it, I think we should document the 202 Accepted status being translated into AcceptedError at the package level rather than individual method. It seems that this can apply to many endpoints, and it's a general behavior, not specific to a few endpoints. That way, as new endpoints suddenly decide to use 202 status, our documentation won't get outdated.

How about adding a new section to the package doc.go (also to README.md, the two should be in sync now) then. It could mention when *github.AcceptedError is returned, and a snippet how to detect it (similar to *github.RateLimitError).

Feel free to delay working on this until we hear from another maintainer of this library that this is a good change to go with.

// repository, this method will return an *AcceptedError and a status code of
// 202. This is because this is the status that github returns to signify that
// it is now computing the requested statistics. A follow up request, after a
// delay of a second or so, should result in a successful request.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I didn't see we already had that written up. In that case, it makes a lot of sense to update it as you've done, and copy it over to the other methods (as you've also already done).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I haven't seen that before either =)

@caarlos0
Copy link
Copy Markdown
Contributor Author

caarlos0 commented Dec 9, 2016

@shurcooL agreed, done.

@googlebot
Copy link
Copy Markdown

We found a Contributor License Agreement for you (the sender of this pull request) and all commit authors, but as best as we can tell these commits were authored by someone else. If that's the case, please add them to this pull request and have them confirm that they're okay with these commits being contributed to Google. If we're mistaken and you did author these commits, just reply here to confirm.

Plus a few other minor changes for improved consistency.
README.md Outdated
**Build Status:** [![Build Status](https://travis-ci.org/google/go-github.svg?branch=master)](https://travis-ci.org/google/go-github)
**Documentation:** [![GoDoc](https://godoc.org/github.com/google/go-github/github?status.svg)](https://godoc.org/github.com/google/go-github/github)
**Mailing List:** [go-github@googlegroups.com](https://groups.google.com/group/go-github)
**Build Status:** [![Build Status](https://travis-ci.org/google/go-github.svg?branch=master)](https://travis-ci.org/google/go-github)
Copy link
Copy Markdown
Member

@dmitshur dmitshur Dec 10, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should undo these changes. I'm guessing your editor did them for you.

The two spaces at the end of those lines are significant, they mark a line break after each line.

Compare what the README looks like now:

image

And after this change, it's less readable:

image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch, fixed

@dmitshur
Copy link
Copy Markdown
Member

@caarlos0, I've applied a few typo fixes in 50886af.

@caarlos0
Copy link
Copy Markdown
Contributor Author

@caarlos0, I've applied a few typo fixes in 50886af.

thanks for that =)

I also fixed the header of the readme.

dmitshur
dmitshur previously approved these changes Dec 11, 2016
@dmitshur dmitshur dismissed their stale review December 11, 2016 00:09

What's been approved are the changes to my previous "request changes" review, not the entire PR.

@dmitshur
Copy link
Copy Markdown
Member

dmitshur commented Dec 11, 2016

To make the latest status of this PR more visible, I'm waiting to hear from @gmlewis or another maintainer on their thoughts. All my feedback so far has already been addressed.

That said, I'm more confident this is a good direction after realizing that the following is already the current behavior for ListCommitActivity and similar, as documented:

// If this is the first time these statistics are requested for the given
// repository, this method will return a non-nil error and a status code of
// 202.

So this PR is about turning "non-nil error" into "*AcceptedError", not something more drastic as I originally thought.

@gmlewis
Copy link
Copy Markdown
Collaborator

gmlewis commented Dec 12, 2016

Yes, I like this and think it will be very helpful for developers.
LGTM.
Thank you, @caarlos0 and @shurcooL!
I'm going to tweak a couple minor nits like s/github/GitHub/g and then merge it.

@gmlewis gmlewis closed this in 231da32 Dec 12, 2016
@gmlewis
Copy link
Copy Markdown
Collaborator

gmlewis commented Dec 12, 2016

Included in 231da32.

@caarlos0
Copy link
Copy Markdown
Contributor Author

Thanks @gmlewis 👍

@caarlos0 caarlos0 deleted the 202 branch December 12, 2016 20:14
bubg-dev pushed a commit to bubg-dev/go-github that referenced this pull request Jun 16, 2017
Fixes google#486.
Closes google#488.

Change-Id: I31ff003552dbffaca78c740b89d54150b81c525c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make empty responses due to 202 Accepted status code more visible.

4 participants