@@ -191,6 +191,9 @@ type HTTPError struct {
191191
192192func (err HTTPError ) Error () string {
193193 if err .Message != "" {
194+ if msgs := strings .SplitN (err .Message , "\n " , 2 ); len (msgs ) > 1 {
195+ return fmt .Sprintf ("HTTP %d: %s (%s)\n %s" , err .StatusCode , msgs [0 ], err .RequestURL , msgs [1 ])
196+ }
194197 return fmt .Sprintf ("HTTP %d: %s (%s)" , err .StatusCode , err .Message , err .RequestURL )
195198 }
196199 return fmt .Sprintf ("HTTP %d (%s)" , err .StatusCode , err .RequestURL )
@@ -222,7 +225,7 @@ func (c Client) HasMinimumScopes(hostname string) error {
222225 }()
223226
224227 if res .StatusCode != 200 {
225- return handleHTTPError (res )
228+ return HandleHTTPError (res )
226229 }
227230
228231 hasScopes := strings .Split (res .Header .Get ("X-Oauth-Scopes" ), "," )
@@ -298,7 +301,7 @@ func (c Client) REST(hostname string, method string, p string, body io.Reader, d
298301
299302 success := resp .StatusCode >= 200 && resp .StatusCode < 300
300303 if ! success {
301- return handleHTTPError (resp )
304+ return HandleHTTPError (resp )
302305 }
303306
304307 if resp .StatusCode == http .StatusNoContent {
@@ -322,7 +325,7 @@ func handleResponse(resp *http.Response, data interface{}) error {
322325 success := resp .StatusCode >= 200 && resp .StatusCode < 300
323326
324327 if ! success {
325- return handleHTTPError (resp )
328+ return HandleHTTPError (resp )
326329 }
327330
328331 body , err := ioutil .ReadAll (resp .Body )
@@ -342,13 +345,18 @@ func handleResponse(resp *http.Response, data interface{}) error {
342345 return nil
343346}
344347
345- func handleHTTPError (resp * http.Response ) error {
348+ func HandleHTTPError (resp * http.Response ) error {
346349 httpError := HTTPError {
347350 StatusCode : resp .StatusCode ,
348351 RequestURL : resp .Request .URL ,
349352 OAuthScopes : resp .Header .Get ("X-Oauth-Scopes" ),
350353 }
351354
355+ if ! jsonTypeRE .MatchString (resp .Header .Get ("Content-Type" )) {
356+ httpError .Message = resp .Status
357+ return httpError
358+ }
359+
352360 body , err := ioutil .ReadAll (resp .Body )
353361 if err != nil {
354362 httpError .Message = err .Error ()
@@ -357,14 +365,57 @@ func handleHTTPError(resp *http.Response) error {
357365
358366 var parsedBody struct {
359367 Message string `json:"message"`
368+ Errors []json.RawMessage
360369 }
361- if err := json .Unmarshal (body , & parsedBody ); err = = nil {
362- httpError . Message = parsedBody . Message
370+ if err := json .Unmarshal (body , & parsedBody ); err ! = nil {
371+ return httpError
363372 }
364373
374+ type errorObject struct {
375+ Message string
376+ Resource string
377+ Field string
378+ Code string
379+ }
380+
381+ messages := []string {parsedBody .Message }
382+ for _ , raw := range parsedBody .Errors {
383+ switch raw [0 ] {
384+ case '"' :
385+ var errString string
386+ _ = json .Unmarshal (raw , & errString )
387+ messages = append (messages , errString )
388+ case '{' :
389+ var errInfo errorObject
390+ _ = json .Unmarshal (raw , & errInfo )
391+ msg := errInfo .Message
392+ if errInfo .Code != "custom" {
393+ msg = fmt .Sprintf ("%s.%s %s" , errInfo .Resource , errInfo .Field , errorCodeToMessage (errInfo .Code ))
394+ }
395+ if msg != "" {
396+ messages = append (messages , msg )
397+ }
398+ }
399+ }
400+ httpError .Message = strings .Join (messages , "\n " )
401+
365402 return httpError
366403}
367404
405+ func errorCodeToMessage (code string ) string {
406+ // https://docs.github.com/en/rest/overview/resources-in-the-rest-api#client-errors
407+ switch code {
408+ case "missing" , "missing_field" :
409+ return "is missing"
410+ case "invalid" , "unprocessable" :
411+ return "is invalid"
412+ case "already_exists" :
413+ return "already exists"
414+ default :
415+ return code
416+ }
417+ }
418+
368419var jsonTypeRE = regexp .MustCompile (`[/+]json($|;)` )
369420
370421func inspectableMIMEType (t string ) bool {
0 commit comments