this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Extract additional info from HTTP response into the returned error (#418)

This provides the caller additional info, in particular about rate
limits. (Unless they're using `util.RobustHTTPClient()`)

authored by

Jaz and committed by
GitHub
236c6150 cbc83565

+65 -2
+65 -2
xrpc/xrpc.go
··· 9 9 "io" 10 10 "net/http" 11 11 "net/url" 12 + "strconv" 12 13 "strings" 14 + "time" 13 15 14 16 "github.com/bluesky-social/indigo/util" 15 17 "github.com/carlmjohnson/versioninfo" 16 18 ) 17 19 18 20 type Client struct { 21 + // Client is an HTTP client to use. If not set, defaults to http.RobustHTTPClient(). 22 + // Note that http.RobustHTTPClient() swallows retryable errors (including hitting a rate limit), 23 + // not allowing your code to handle them differently. 19 24 Client *http.Client 20 25 Auth *AuthInfo 21 26 AdminToken *string ··· 49 54 return fmt.Sprintf("%s: %s", xe.ErrStr, xe.Message) 50 55 } 51 56 57 + type Error struct { 58 + StatusCode int 59 + Wrapped error 60 + Ratelimit *RatelimitInfo 61 + } 62 + 63 + func (e *Error) Error() string { 64 + // Preserving "XRPC ERROR %d" prefix for compatibility - previously matching this string was the only way 65 + // to obtain the status code. 66 + if e.Wrapped == nil { 67 + return fmt.Sprintf("XRPC ERROR %d", e.StatusCode) 68 + } 69 + if e.StatusCode == http.StatusTooManyRequests && e.Ratelimit != nil { 70 + return fmt.Sprintf("XRPC ERROR %d: %s (throttled until %s)", e.StatusCode, e.Wrapped, e.Ratelimit.Reset.Local()) 71 + } 72 + return fmt.Sprintf("XRPC ERROR %d: %s", e.StatusCode, e.Wrapped) 73 + } 74 + 75 + func (e *Error) Unwrap() error { 76 + if e.Wrapped == nil { 77 + return nil 78 + } 79 + return e.Wrapped 80 + } 81 + 82 + func (e *Error) IsThrottled() bool { 83 + return e.StatusCode == http.StatusTooManyRequests 84 + } 85 + 86 + func errorFromHTTPResponse(resp *http.Response, err error) error { 87 + r := &Error{ 88 + StatusCode: resp.StatusCode, 89 + Wrapped: err, 90 + } 91 + if resp.Header.Get("ratelimit-limit") != "" { 92 + r.Ratelimit = &RatelimitInfo{ 93 + Policy: resp.Header.Get("ratelimit-policy"), 94 + } 95 + if n, err := strconv.ParseInt(resp.Header.Get("ratelimit-reset"), 10, 64); err == nil { 96 + r.Ratelimit.Reset = time.Unix(n, 0) 97 + } 98 + if n, err := strconv.ParseInt(resp.Header.Get("ratelimit-limit"), 10, 64); err == nil { 99 + r.Ratelimit.Limit = int(n) 100 + } 101 + if n, err := strconv.ParseInt(resp.Header.Get("ratelimit-remaining"), 10, 64); err == nil { 102 + r.Ratelimit.Remaining = int(n) 103 + } 104 + } 105 + return r 106 + } 107 + 108 + type RatelimitInfo struct { 109 + Limit int 110 + Remaining int 111 + Policy string 112 + Reset time.Time 113 + } 114 + 52 115 const ( 53 116 Query = XRPCRequestType(iota) 54 117 Procedure ··· 137 200 if resp.StatusCode != 200 { 138 201 var xe XRPCError 139 202 if err := json.NewDecoder(resp.Body).Decode(&xe); err != nil { 140 - return fmt.Errorf("failed to decode xrpc error message (status: %d): %w", resp.StatusCode, err) 203 + return errorFromHTTPResponse(resp, fmt.Errorf("failed to decode xrpc error message: %w", err)) 141 204 } 142 - return fmt.Errorf("XRPC ERROR %d: %w", resp.StatusCode, &xe) 205 + return errorFromHTTPResponse(resp, &xe) 143 206 } 144 207 145 208 if out != nil {