cfgd_core/http.rs
1//! Canonical `ureq::Agent` construction + named HTTP timeout constants.
2//!
3//! Before this module, `ureq::AgentBuilder::new().timeout(...).build()` was
4//! inlined at 7 production sites (server_client, daemon, oci x3, upgrade x2)
5//! with three of the timeouts already drifted. Worse, `cfgd/src/ai/client.rs`
6//! used bare `ureq::post(...)` with no timeout at all — the Anthropic call
7//! could hang the CLI indefinitely. Using these constants + `http_agent`
8//! replaces every inline `Duration::from_secs(...)` literal and keeps future
9//! timeout changes in one place.
10
11use std::time::Duration;
12
13/// Device gateway API calls (checkin, drift, enrollment) — small JSON payloads.
14/// Kept short because the gateway is nearby and responses are small.
15pub const HTTP_API_TIMEOUT: Duration = Duration::from_secs(30);
16
17/// OCI registry blob / manifest operations (push, pull, multi-platform index).
18/// Module layers and image blobs can be hundreds of MiB; 300s accommodates
19/// cold caches and slow registry peers.
20pub const HTTP_OCI_TIMEOUT: Duration = Duration::from_secs(300);
21
22/// GitHub Releases API queries + binary archive downloads for self-upgrade.
23/// 300s covers slow mirrors; the streaming download applies per-chunk.
24pub const HTTP_UPGRADE_TIMEOUT: Duration = Duration::from_secs(300);
25
26/// Anthropic API requests from `cfgd generate` and `cfgd ai`.
27/// Claude latency for agentic tool use is normally a few seconds; 120s is a
28/// ceiling for pathological slow networks. Must have *some* timeout — before
29/// this constant the request had none at all.
30pub const HTTP_AI_TIMEOUT: Duration = Duration::from_secs(120);
31
32/// Outbound webhook notifications (drift alerts, check-in callbacks).
33/// Short — the caller doesn't wait on user-visible output; a slow peer
34/// should not starve the daemon's notification loop.
35pub const HTTP_WEBHOOK_TIMEOUT: Duration = Duration::from_secs(10);
36
37/// Build a `ureq::Agent` with `timeout` applied. Exists so every call site
38/// that wants a timeout can use one line and we can change `AgentBuilder`
39/// configuration (user-agent defaults, connection pooling, TLS options)
40/// in exactly one place if it becomes necessary.
41pub fn http_agent(timeout: Duration) -> ureq::Agent {
42 ureq::AgentBuilder::new().timeout(timeout).build()
43}
44
45#[cfg(test)]
46mod tests {
47 use super::*;
48
49 #[test]
50 fn named_timeouts_are_distinct_and_sane() {
51 assert_eq!(HTTP_API_TIMEOUT, Duration::from_secs(30));
52 assert_eq!(HTTP_OCI_TIMEOUT, Duration::from_secs(300));
53 assert_eq!(HTTP_UPGRADE_TIMEOUT, Duration::from_secs(300));
54 assert_eq!(HTTP_AI_TIMEOUT, Duration::from_secs(120));
55 assert_eq!(HTTP_WEBHOOK_TIMEOUT, Duration::from_secs(10));
56 // AI timeout must be positive — the original bug was "no timeout"
57 assert!(HTTP_AI_TIMEOUT > Duration::ZERO);
58 }
59
60 #[test]
61 fn http_agent_builds_without_panic() {
62 let _ = http_agent(HTTP_API_TIMEOUT);
63 }
64}