Skip to main content

proton_sdk/
config.rs

1//! Client configuration and defaults.
2
3use std::time::Duration;
4
5/// Default base URL for the Proton Drive API.
6pub const DEFAULT_BASE_URL: &str = "https://drive-api.proton.me/";
7
8/// Default redirect URI sent with token refresh requests.
9pub const DEFAULT_REFRESH_REDIRECT_URI: &str = "https://proton.me";
10
11/// Default per-request timeout, matching `ProtonApiDefaults.DefaultTimeoutSeconds`.
12/// Applies to the JSON API calls (link details, listings, revision creation) —
13/// small requests where 30s is generous and a stall should fail fast.
14pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
15
16/// Default timeout for a single block-storage transfer (`get_storage_blob` /
17/// `post_storage_blob`). A block is up to 4 MiB and rides a different host from
18/// the API; on a slow uplink that transfer legitimately takes far longer than a
19/// JSON call, so it gets its own, much larger budget rather than tripping the
20/// 30s API timeout spuriously.
21pub const DEFAULT_STORAGE_TIMEOUT: Duration = Duration::from_secs(300);
22
23/// Default number of automatic retries on a retryable failure (rate-limit,
24/// transient transport error). The original attempt is not counted, so the
25/// total request budget is `1 + max_retries`.
26pub const DEFAULT_MAX_RETRIES: u32 = 3;
27
28/// Default base delay for exponential backoff between retries.
29pub const DEFAULT_RETRY_BASE_DELAY: Duration = Duration::from_millis(500);
30
31/// Default upper bound for a single backoff sleep. A server-supplied
32/// `Retry-After` is honoured even if it exceeds this.
33pub const DEFAULT_RETRY_MAX_DELAY: Duration = Duration::from_secs(30);
34
35/// Controls automatic retries on retryable responses (HTTP 408/429/502/503/504)
36/// and transient transport errors (timeout, connect).
37///
38/// Mirrors the Polly-style retry pipeline in the C# SDK: a server-supplied
39/// `Retry-After` header wins; otherwise the delay is exponential backoff
40/// (`base_delay * 2^attempt`, capped at `max_delay`) with full jitter.
41#[derive(Debug, Clone)]
42pub struct RetryPolicy {
43    /// Maximum number of retries after the initial attempt. `0` disables retry.
44    pub max_retries: u32,
45    /// Base delay for exponential backoff.
46    pub base_delay: Duration,
47    /// Cap on a single computed backoff sleep.
48    pub max_delay: Duration,
49}
50
51impl Default for RetryPolicy {
52    fn default() -> Self {
53        Self {
54            max_retries: DEFAULT_MAX_RETRIES,
55            base_delay: DEFAULT_RETRY_BASE_DELAY,
56            max_delay: DEFAULT_RETRY_MAX_DELAY,
57        }
58    }
59}
60
61impl RetryPolicy {
62    /// A policy that performs no automatic retries.
63    pub fn disabled() -> Self {
64        Self {
65            max_retries: 0,
66            ..Self::default()
67        }
68    }
69}
70
71/// Content type used for the `Accept` header on API requests.
72pub const API_CONTENT_TYPE: &str = "application/vnd.protonmail.api+json";
73
74/// Configuration for a Proton client session.
75///
76/// `app_version` is mandatory and must honour the operational requirements in
77/// the SDK README (`external-drive-{name}@{semver}-{channel}`), sent as the
78/// `x-pm-appversion` header.
79#[derive(Debug, Clone)]
80pub struct ProtonClientConfiguration {
81    pub base_url: String,
82    pub app_version: String,
83    pub user_agent: String,
84    pub refresh_redirect_uri: String,
85    pub request_timeout: Duration,
86    /// Timeout for a single block-storage transfer. Separate from
87    /// `request_timeout` so a large block on a slow link does not fail against
88    /// the (short) API timeout. See [`DEFAULT_STORAGE_TIMEOUT`].
89    pub storage_timeout: Duration,
90    pub retry_policy: RetryPolicy,
91}
92
93impl ProtonClientConfiguration {
94    /// Create a configuration with the given app version and otherwise defaults.
95    pub fn new(app_version: impl Into<String>) -> Self {
96        Self {
97            base_url: DEFAULT_BASE_URL.to_owned(),
98            app_version: app_version.into(),
99            user_agent: String::new(),
100            refresh_redirect_uri: DEFAULT_REFRESH_REDIRECT_URI.to_owned(),
101            request_timeout: DEFAULT_TIMEOUT,
102            storage_timeout: DEFAULT_STORAGE_TIMEOUT,
103            retry_policy: RetryPolicy::default(),
104        }
105    }
106
107    pub fn with_base_url(mut self, base_url: impl Into<String>) -> Self {
108        self.base_url = base_url.into();
109        self
110    }
111
112    pub fn with_user_agent(mut self, user_agent: impl Into<String>) -> Self {
113        self.user_agent = user_agent.into();
114        self
115    }
116
117    pub fn with_request_timeout(mut self, timeout: Duration) -> Self {
118        self.request_timeout = timeout;
119        self
120    }
121
122    pub fn with_storage_timeout(mut self, timeout: Duration) -> Self {
123        self.storage_timeout = timeout;
124        self
125    }
126
127    pub fn with_retry_policy(mut self, retry_policy: RetryPolicy) -> Self {
128        self.retry_policy = retry_policy;
129        self
130    }
131}