In CreateFork, return the repo info when 202 Accepted occurs.#878
In CreateFork, return the repo info when 202 Accepted occurs.#878gmlewis merged 1 commit intogoogle:masterfrom dsymonds:master
Conversation
|
I'm not clear what's happening, or who this is blocked on. #862 hasn't had any activity in a month. |
|
cc @gmlewis |
|
Sorry I missed this. Ideally, we would like to do something similar to this for all endpoints that potentially return 202, but my opinion is to go ahead and move forward with this, since there is an immediate need. |
|
Awaiting second LGTM before merging. |
dmitshur
left a comment
There was a problem hiding this comment.
Ideally, we would like to do something similar to this for all endpoints that potentially return 202, but my opinion is to go ahead and move forward with this, since there is an immediate need.
Then, if it becomes clear that more endpoints need processing like this, we can address that then.
Agreed, that's a good way to proceed.
It would be nice to add a test case, so this behavior doesn't regress (especially if we refactor this code in the future to make it more general). @dsymonds Can you add a test case?
I also left one minor style suggestion, see inline comment.
| json.NewDecoder(resp.Body).Decode(fork) | ||
| return fork, resp, err | ||
| } | ||
| if err != nil { |
There was a problem hiding this comment.
Consider merging this with the if statement added above, so it's a little easier to see that this if statement deals with the same err value from line 81:
if _, ok := err.(*AcceptedError); ok {
...
} else if err != nil {
...
}There was a problem hiding this comment.
I think it's fine keeping them separate. They are very different code paths; the AcceptedError case is only nominally an error.
|
Good call on adding a test. It turned up a bug in Client.Do that would close the response body on error, preventing the caller from being able to process it at all. |
dmitshur
left a comment
There was a problem hiding this comment.
Thanks for the test case, it looks great.
github/github.go
Outdated
| // function, read its contents now so the caller can use it, | ||
| // and wrap it such that it doesn't matter if they close it. | ||
| body, _ := ioutil.ReadAll(response.Body) | ||
| response.Body = ioutil.NopCloser(bytes.NewReader(body)) |
There was a problem hiding this comment.
Doing this makes sense, but I don't think it's the right place.
We don't want it to run in all cases when err != nil. If you look at the code of CheckResponse, it does the following things:
- if status code is accepted, it immediately returns
&AcceptedError{} - if status code is in 2xx range, it returns
nil - it reads the response body into a byte slice (
data, err := ioutil.ReadAll(r.Body)) - it returns some specific error type or a general
&ErrorResponse{Response: r}
We don't want this code to run after ioutil.ReadAll(r.Body) already happened in CheckResponse.
I think a good fix is to move this code into CheckResponse's first case:
func CheckResponse(r *http.Response) error {
if r.StatusCode == http.StatusAccepted {
// Since Do closes the original response body upon returning,
// read its contents now so the caller can use it,
// and wrap it such that it doesn't matter if they close it.
body, _ := ioutil.ReadAll(r.Body)
r.Body = ioutil.NopCloser(bytes.NewReader(body))
return &AcceptedError{}
}
...
}There was a problem hiding this comment.
This gives me a followup idea. Instead of using r.Body as a way of transmitting the body from here into CreateFork, we can put it inside AcceptedError:
type AcceptedError struct {
body []byte
}
func CheckResponse(r *http.Response) error {
if r.StatusCode == http.StatusAccepted {
// Since Do closes the original response body upon returning,
// read its contents now so that CreateFork can use it.
body, _ := ioutil.ReadAll(r.Body)
return &AcceptedError{body: body}
}
...
}Then CreateFork code becomes more clear:
fork := new(Repository)
resp, err := s.client.Do(ctx, req, fork)
+ if ae, ok := err.(*AcceptedError); ok {
+ json.NewDecoder(bytes.NewReader(ae.body)).Decode(fork)
+ return fork, resp, err
+ }
if err != nil {I think this has the benefits of not changing externally visible behavior of the library (edit: actually, it still changes CheckResponse slightly), and it's easier to read and be sure is correct. If resp.Body is modified as currently done, then all other endpoints which don't specifically handle AcceptedError will be returning a resp with a body. Users may start to rely on that, and we don't neccessary want to promise it'll always work that way.
In the future, we can consider either exporting AcceptedError.body so users of this package can use it, or implement a different solution if we find a better one.
What do you think about this idea?
There was a problem hiding this comment.
Doing that in CheckResponse merely hides the issue from the non-trivial control flow in Do (which is where the body closing is happening). Right now, CheckResponse doesn't touch the body in the HTTP 202 case, just like in any of the other 2xx cases. Your suggestion would mean that Do needs to know that CheckResponse does that transformation and therefore closing the body is okay.
Now Do currently handles all the non-error decoding, so perhaps it should handle this case too. It has the destination object there already. Then the body closing is moot. Let me take that approach; I think it may be the best option.
There was a problem hiding this comment.
In regard to your follow-up idea: I think that's unnecessary. The AcceptedError flows are generally all places where the response body is some subset of the regular response (that's why it is a 2xx code rather than a different code), so forcing the callers to do the parsing seems like unnecessary work. I think my latest revision (having Do do the decoding when there's an AcceptedError) is the simplest option, and it still preserves existing code behaviour unless the caller is doing something truly odd.
|
One more note. When making changes to this PR, you don't have to squash and force push. Feel free to send distinct followup commits into this PR. We will squash them into a single commit when merging this PR. GitHub PRs don't have a way of seeing history, so keeping changes as separate commits makes them easier to review. |
Per the docs (https://developer.github.com/v3/repos/forks/#create-a-fork), there is still a useful body, and it is helpful for automatic fork creation to get that response.
|
PTAL. I think this is much more straightforward now. |
dmitshur
left a comment
There was a problem hiding this comment.
Yeah, I’m in agreement, this is better. This way of treating AcceptedError more like nil error in Do and reusing the applicable decoding code there makes sense. There might be minor refactoring opportunities, but that can be done later.
LGTM.
Thanks @dsymonds.
@gmlewis Does it still look okay to you, given the code has changed?
…#878) Per the docs (https://developer.github.com/v3/repos/forks/#create-a-fork), there is still a useful body, and it is helpful for automatic fork creation to get that response.
Per the docs
(https://developer.github.com/v3/repos/forks/#create-a-fork), there is
still a useful body, and it is helpful for automatic fork creation to
get that response.
I'm not sure if this approach is okay. An alternate is to do the decoding inside Do, so this works for the ~dozen other places that might get an AcceptedError, but I didn't want to go check the docs for every single case to see whether the response body is meant to have useful data.