Skip to content

Commit d57b517

Browse files
committed
Print HTTP errors on stderr in api command
Most API errors are present in the response body itself, which will be sent to stdout normally, but if stdout is redirected somewhere (as it's common with scripts), failed HTTP requests will likely sabotage the rest of the script, but no useful info will be shown on stderr. This makes it so all REST and GraphQL errors are always shown on stderr. Additionally, this makes sure that the command exits with a nonzero status on any GraphQL errors.
1 parent 6254946 commit d57b517

File tree

2 files changed

+65
-5
lines changed

2 files changed

+65
-5
lines changed

pkg/cmd/api/api.go

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package api
22

33
import (
4+
"bytes"
5+
"encoding/json"
46
"fmt"
57
"io"
68
"io/ioutil"
@@ -109,24 +111,57 @@ func apiRun(opts *ApiOptions) error {
109111
if resp.StatusCode == 204 {
110112
return nil
111113
}
114+
var responseBody io.Reader = resp.Body
112115
defer resp.Body.Close()
113116

114117
isJSON, _ := regexp.MatchString(`[/+]json(;|$)`, resp.Header.Get("Content-Type"))
115118

119+
var serverError string
120+
if isJSON && (opts.RequestPath == "graphql" || resp.StatusCode >= 400) {
121+
bodyCopy := &bytes.Buffer{}
122+
b, err := ioutil.ReadAll(io.TeeReader(responseBody, bodyCopy))
123+
if err != nil {
124+
return err
125+
}
126+
var respData struct {
127+
Message string
128+
Errors []struct {
129+
Message string
130+
}
131+
}
132+
err = json.Unmarshal(b, &respData)
133+
if err != nil {
134+
return err
135+
}
136+
if respData.Message != "" {
137+
serverError = fmt.Sprintf("%s (HTTP %d)", respData.Message, resp.StatusCode)
138+
} else if len(respData.Errors) > 0 {
139+
msgs := make([]string, len(respData.Errors))
140+
for i, e := range respData.Errors {
141+
msgs[i] = e.Message
142+
}
143+
serverError = strings.Join(msgs, "\n")
144+
}
145+
responseBody = bodyCopy
146+
}
147+
116148
if isJSON && opts.IO.ColorEnabled() {
117-
err = jsoncolor.Write(opts.IO.Out, resp.Body, " ")
149+
err = jsoncolor.Write(opts.IO.Out, responseBody, " ")
118150
if err != nil {
119151
return err
120152
}
121153
} else {
122-
_, err = io.Copy(opts.IO.Out, resp.Body)
154+
_, err = io.Copy(opts.IO.Out, responseBody)
123155
if err != nil {
124156
return err
125157
}
126158
}
127159

128-
// TODO: detect GraphQL errors
129-
if resp.StatusCode > 299 {
160+
if serverError != "" {
161+
fmt.Fprintf(opts.IO.ErrOut, "gh: %s\n", serverError)
162+
return cmdutil.SilentError
163+
} else if resp.StatusCode > 299 {
164+
fmt.Fprintf(opts.IO.ErrOut, "gh: HTTP %d\n", resp.StatusCode)
130165
return cmdutil.SilentError
131166
}
132167

pkg/cmd/api/api_test.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,31 @@ func Test_apiRun(t *testing.T) {
158158
stdout: ``,
159159
stderr: ``,
160160
},
161+
{
162+
name: "REST error",
163+
httpResponse: &http.Response{
164+
StatusCode: 400,
165+
Body: ioutil.NopCloser(bytes.NewBufferString(`{"message": "THIS IS FINE"}`)),
166+
Header: http.Header{"Content-Type": []string{"application/json; charset=utf-8"}},
167+
},
168+
err: cmdutil.SilentError,
169+
stdout: `{"message": "THIS IS FINE"}`,
170+
stderr: "gh: THIS IS FINE (HTTP 400)\n",
171+
},
172+
{
173+
name: "GraphQL error",
174+
options: ApiOptions{
175+
RequestPath: "graphql",
176+
},
177+
httpResponse: &http.Response{
178+
StatusCode: 200,
179+
Body: ioutil.NopCloser(bytes.NewBufferString(`{"errors": [{"message":"AGAIN"}, {"message":"FINE"}]}`)),
180+
Header: http.Header{"Content-Type": []string{"application/json; charset=utf-8"}},
181+
},
182+
err: cmdutil.SilentError,
183+
stdout: `{"errors": [{"message":"AGAIN"}, {"message":"FINE"}]}`,
184+
stderr: "gh: AGAIN\nFINE\n",
185+
},
161186
{
162187
name: "failure",
163188
httpResponse: &http.Response{
@@ -166,7 +191,7 @@ func Test_apiRun(t *testing.T) {
166191
},
167192
err: cmdutil.SilentError,
168193
stdout: `gateway timeout`,
169-
stderr: ``,
194+
stderr: "gh: HTTP 502\n",
170195
},
171196
}
172197

0 commit comments

Comments
 (0)