Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Merge branch 'worktree-agent-a8752162fea25f63f' into spend-guardrails | 1 | //! Rate limits: Workers Rate Limiting bindings, asked once per request with |
| 2 | //! a key (a client's address, a repository's id, a hash of a token). | |
| 3 | //! | |
| 4 | //! Every binding, its namespace id and its limit is listed in `RATE_LIMITS` | |
| 5 | //! (packages/contracts/src/rate-limits.ts), which apps/web's tests check | |
| 6 | //! each wrangler.jsonc against; docs/RATE-LIMITS.md says why each is what | |
| 7 | //! it is. | |
| 8 | //! | |
| 9 | //! A limit fails open: a binding that is not there (self-hosted) or that | |
| 10 | //! fails lets the request through. A limit guards against floods; it is | |
| 11 | //! never a reason for g1t to stop answering. | |
| 12 | ||
| 13 | use worker::Env; | |
| 14 | ||
| 15 | /// Every limit's window, in seconds, and how long a client past one is | |
| 16 | /// told to wait (`Retry-After`). Workers Rate Limiting counts over 10 or 60. | |
| 17 | pub const PERIOD_SECONDS: u32 = 60; | |
| 18 | ||
| 19 | /// What asking a limit said. Only `Limited` refuses the request. | |
| 20 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] | |
| 21 | pub enum Verdict { | |
| 22 | Allowed, | |
| 23 | Limited, | |
| 24 | /// No binding, or it failed: let through. | |
| 25 | Unavailable, | |
| 26 | } | |
| 27 | ||
| 28 | impl Verdict { | |
| 29 | pub fn limited(self) -> bool { | |
| 30 | self == Verdict::Limited | |
| 31 | } | |
| 32 | } | |
| 33 | ||
| 34 | /// The verdict from what the binding answered: `None` when there is no | |
| 35 | /// binding, `Some(Err)` when asking it failed, else whether it let the | |
| 36 | /// request through. | |
| 37 | pub fn verdict<E>(answered: Option<std::result::Result<bool, E>>) -> Verdict { | |
| 38 | match answered { | |
| 39 | Some(Ok(true)) => Verdict::Allowed, | |
| 40 | Some(Ok(false)) => Verdict::Limited, | |
| 41 | Some(Err(_)) | None => Verdict::Unavailable, | |
| 42 | } | |
| 43 | } | |
| 44 | ||
| 45 | /// Counts one request against the binding named `binding` under `key`. | |
| 46 | /// Never fails; a failure to ask is logged and lets the request through. | |
| 47 | pub async fn check(env: &Env, binding: &str, key: String) -> Verdict { | |
| 48 | let Ok(limiter) = env.rate_limiter(binding) else { | |
| 49 | return Verdict::Unavailable; | |
| 50 | }; | |
| 51 | let answered = limiter.limit(key).await.map(|outcome| outcome.success); | |
| 52 | if let Err(problem) = &answered { | |
| 53 | worker::console_error!("rate limit {binding} could not be asked: {problem}"); | |
| 54 | } | |
| 55 | verdict(Some(answered)) | |
| 56 | } | |
| 57 | ||
| 58 | /// A client's address for a key: `ip:` and `CF-Connecting-IP`, or | |
| 59 | /// `ip:unknown` when there is none (local development). | |
| 60 | pub fn address_key(address: Option<&str>) -> String { | |
| 61 | format!("ip:{}", address.map(str::trim).filter(|a| !a.is_empty()).unwrap_or("unknown")) | |
| 62 | } | |
| 63 | ||
| 64 | #[cfg(test)] | |
| 65 | mod tests { | |
| 66 | use super::*; | |
| 67 | ||
| 68 | #[test] | |
| 69 | fn only_a_limit_the_binding_says_is_reached_refuses() { | |
| 70 | assert_eq!(verdict::<()>(Some(Ok(true))), Verdict::Allowed); | |
| 71 | assert_eq!(verdict::<()>(Some(Ok(false))), Verdict::Limited); | |
| 72 | assert!(verdict::<()>(Some(Ok(false))).limited()); | |
| 73 | } | |
| 74 | ||
| 75 | #[test] | |
| 76 | fn no_binding_or_a_failing_one_lets_requests_through() { | |
| 77 | assert_eq!(verdict::<()>(None), Verdict::Unavailable); | |
| 78 | assert_eq!(verdict(Some(Err("binding threw"))), Verdict::Unavailable); | |
| 79 | assert!(!verdict::<()>(None).limited()); | |
| 80 | assert!(!verdict(Some(Err("binding threw"))).limited()); | |
| 81 | } | |
| 82 | ||
| 83 | #[test] | |
| 84 | fn addresses_are_keyed_as_given_or_unknown() { | |
| 85 | assert_eq!(address_key(Some(" 203.0.113.9 ")), "ip:203.0.113.9"); | |
| 86 | assert_eq!(address_key(Some("")), "ip:unknown"); | |
| 87 | assert_eq!(address_key(None), "ip:unknown"); | |
| 88 | } | |
| 89 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.