A lexicon-driven AppView for ATProto.
1use std::time::Duration;
2
3/// Per-request timeout for outbound atproto network fetches (relay, PLC/DID
4/// resolution, PDS reads). Bounds a single HTTP attempt so a host that connects
5/// but never responds fails instead of stalling a backfill job forever.
6pub const REQUEST_TIMEOUT: Duration = Duration::from_secs(30);
7
8/// Overall deadline for resolving a single DID's PDS endpoint. Unlike
9/// [`REQUEST_TIMEOUT`], this bounds the *entire* resolution of one DID —
10/// including DNS, connection setup, and any rate-limit retry/backoff loop — so
11/// a single stuck DID can never hang the resolver stream. On expiry the DID is
12/// treated as a resolution failure and skipped, and the backfill moves on.
13pub const RESOLVE_DEADLINE: Duration = Duration::from_secs(30);
14
15/// Parse rate-limit sleep duration from response headers.
16/// Checks `RateLimit-Reset` (Unix timestamp, used by XRPC servers) first,
17/// then `retry-after` (seconds), defaulting to 5s.
18pub fn parse_retry_after(headers: &reqwest::header::HeaderMap) -> u64 {
19 if let Some(reset) = headers
20 .get("ratelimit-reset")
21 .and_then(|v| v.to_str().ok())
22 .and_then(|v| v.parse::<i64>().ok())
23 {
24 let now = std::time::SystemTime::now()
25 .duration_since(std::time::UNIX_EPOCH)
26 .unwrap_or_default()
27 .as_secs() as i64;
28 let wait = (reset - now).max(1) as u64;
29 return wait.min(120);
30 }
31
32 headers
33 .get("retry-after")
34 .and_then(|v| v.to_str().ok())
35 .and_then(|v| v.parse::<u64>().ok())
36 .unwrap_or(5)
37 .min(120)
38}