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 or equal to 202 Accepted. API error responses are expected to have response body, and a JSON response body that maps to [ErrorResponse]. The
(r *http.Response)
| 1717 | // *[TwoFactorAuthError] for two-factor authentication errors, |
| 1718 | // and *[RedirectionError] for redirect status codes (only happens when ignoring redirections). |
| 1719 | func CheckResponse(r *http.Response) error { |
| 1720 | if r.StatusCode == http.StatusAccepted { |
| 1721 | return &AcceptedError{} |
| 1722 | } |
| 1723 | if c := r.StatusCode; 200 <= c && c <= 299 { |
| 1724 | return nil |
| 1725 | } |
| 1726 | |
| 1727 | errorResponse := &ErrorResponse{Response: r} |
| 1728 | data, err := io.ReadAll(io.LimitReader(r.Body, maxErrorBodySize)) |
| 1729 | if err == nil && data != nil { |
| 1730 | err = json.Unmarshal(data, errorResponse) |
| 1731 | if err != nil { |
| 1732 | // reset the response as if this never happened |
| 1733 | errorResponse = &ErrorResponse{Response: r} |
| 1734 | } |
| 1735 | } |
| 1736 | // Re-populate error response body because GitHub error responses are often |
| 1737 | // undocumented and inconsistent. |
| 1738 | // Issue #1136, #540. |
| 1739 | r.Body = io.NopCloser(bytes.NewBuffer(data)) |
| 1740 | switch { |
| 1741 | case r.StatusCode == http.StatusUnauthorized && strings.HasPrefix(r.Header.Get(headerOTP), "required"): |
| 1742 | return (*TwoFactorAuthError)(errorResponse) |
| 1743 | // Primary rate limit exceeded: GitHub returns 403 or 429 with X-RateLimit-Remaining: 0 |
| 1744 | // See: https://docs.github.com/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28 |
| 1745 | case (r.StatusCode == http.StatusForbidden || r.StatusCode == http.StatusTooManyRequests) && |
| 1746 | r.Header.Get(HeaderRateRemaining) == "0": |
| 1747 | return &RateLimitError{ |
| 1748 | Rate: parseRate(r), |
| 1749 | Response: errorResponse.Response, |
| 1750 | Message: errorResponse.Message, |
| 1751 | } |
| 1752 | // Secondary rate limit exceeded: GitHub returns 403 or 429 with specific documentation_url |
| 1753 | // See: https://docs.github.com/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28#about-secondary-rate-limits |
| 1754 | case (r.StatusCode == http.StatusForbidden || r.StatusCode == http.StatusTooManyRequests) && |
| 1755 | (strings.HasSuffix(errorResponse.DocumentationURL, "#abuse-rate-limits") || |
| 1756 | strings.HasSuffix(errorResponse.DocumentationURL, "secondary-rate-limits")): |
| 1757 | abuseRateLimitError := &AbuseRateLimitError{ |
| 1758 | Response: errorResponse.Response, |
| 1759 | Message: errorResponse.Message, |
| 1760 | } |
| 1761 | if retryAfter := parseSecondaryRate(r); retryAfter != nil { |
| 1762 | abuseRateLimitError.RetryAfter = retryAfter |
| 1763 | } |
| 1764 | return abuseRateLimitError |
| 1765 | // Check that the status code is a redirection and return a sentinel error that can be used to handle special cases |
| 1766 | // where 302 is considered a successful result. |
| 1767 | // This should never happen with the default `CheckRedirect`, because it would return a `url.Error` that should be handled upstream. |
| 1768 | case r.StatusCode == http.StatusMovedPermanently || |
| 1769 | r.StatusCode == http.StatusFound || |
| 1770 | r.StatusCode == http.StatusSeeOther || |
| 1771 | r.StatusCode == http.StatusTemporaryRedirect || |
| 1772 | r.StatusCode == http.StatusPermanentRedirect: |
| 1773 | |
| 1774 | locationStr := r.Header.Get("Location") |
| 1775 | var location *url.URL |
| 1776 | if locationStr != "" { |
searching dependent graphs…