package ratelimit import ( "net/http" "time" ) // Map maps categories to rate limit deadlines. // // A rate limit is in effect for a given category if either the category's // deadline or the deadline for the special CategoryAll has not yet expired. // // Use IsRateLimited to check whether a category is rate-limited. type Map map[Category]Deadline // IsRateLimited returns true if the category is currently rate limited. func (m Map) IsRateLimited(c Category) bool { return m.isRateLimited(c, time.Now()) } func (m Map) isRateLimited(c Category, now time.Time) bool { return m.Deadline(c).After(Deadline(now)) } // Deadline returns the deadline when the rate limit for the given category or // the special CategoryAll expire, whichever is furthest into the future. func (m Map) Deadline(c Category) Deadline { categoryDeadline := m[c] allDeadline := m[CategoryAll] if categoryDeadline.After(allDeadline) { return categoryDeadline } return allDeadline } // Merge merges the other map into m. // // If a category appears in both maps, the deadline that is furthest into the // future is preserved. func (m Map) Merge(other Map) { for c, d := range other { if d.After(m[c]) { m[c] = d } } } // FromResponse returns a rate limit map from an HTTP response. func FromResponse(r *http.Response) Map { return fromResponse(r, time.Now()) } func fromResponse(r *http.Response, now time.Time) Map { s := r.Header.Get("X-Sentry-Rate-Limits") if s != "" { return parseXSentryRateLimits(s, now) } if r.StatusCode == http.StatusTooManyRequests { s := r.Header.Get("Retry-After") deadline, _ := parseRetryAfter(s, now) return Map{CategoryAll: deadline} } return Map{} }