viewpoint_core/api/options/
mod.rs

1//! API context options for configuring HTTP clients.
2
3use std::collections::HashMap;
4use std::time::Duration;
5
6/// Options for creating an API request context.
7///
8/// # Example
9///
10/// ```no_run
11/// use viewpoint_core::api::APIContextOptions;
12/// use std::time::Duration;
13///
14/// let options = APIContextOptions::new()
15///     .base_url("https://api.example.com")
16///     .timeout(Duration::from_secs(30))
17///     .extra_http_headers([
18///         ("Authorization".to_string(), "Bearer token".to_string()),
19///     ]);
20/// ```
21#[derive(Debug, Clone, Default)]
22pub struct APIContextOptions {
23    /// Base URL for all requests. Relative URLs will be resolved against this.
24    pub(crate) base_url: Option<String>,
25    /// Extra HTTP headers to include in all requests.
26    pub(crate) extra_http_headers: HashMap<String, String>,
27    /// HTTP credentials for Basic/Digest authentication.
28    pub(crate) http_credentials: Option<HttpCredentials>,
29    /// Whether to ignore HTTPS certificate errors.
30    pub(crate) ignore_https_errors: bool,
31    /// Proxy configuration.
32    pub(crate) proxy: Option<ProxyConfig>,
33    /// Default timeout for requests.
34    pub(crate) timeout: Option<Duration>,
35    /// User agent string.
36    pub(crate) user_agent: Option<String>,
37}
38
39/// HTTP authentication credentials.
40#[derive(Debug, Clone)]
41pub struct HttpCredentials {
42    /// Username for authentication.
43    pub username: String,
44    /// Password for authentication.
45    pub password: String,
46    /// Optional origin to send credentials only to specific domain.
47    pub origin: Option<String>,
48    /// Whether to send credentials preemptively.
49    pub send: Option<CredentialSend>,
50}
51
52impl HttpCredentials {
53    /// Create new HTTP credentials.
54    pub fn new(username: impl Into<String>, password: impl Into<String>) -> Self {
55        Self {
56            username: username.into(),
57            password: password.into(),
58            origin: None,
59            send: None,
60        }
61    }
62
63    /// Set the origin to send credentials only to specific domain.
64    #[must_use]
65    pub fn origin(mut self, origin: impl Into<String>) -> Self {
66        self.origin = Some(origin.into());
67        self
68    }
69
70    /// Set when to send credentials.
71    #[must_use]
72    pub fn send(mut self, send: CredentialSend) -> Self {
73        self.send = Some(send);
74        self
75    }
76}
77
78/// When to send credentials.
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub enum CredentialSend {
81    /// Send credentials only when the server requests them (401 response).
82    Unauthorized,
83    /// Always send credentials with every request (preemptive).
84    Always,
85}
86
87/// Proxy configuration.
88#[derive(Debug, Clone)]
89pub struct ProxyConfig {
90    /// Proxy server URL.
91    pub server: String,
92    /// Optional username for proxy authentication.
93    pub username: Option<String>,
94    /// Optional password for proxy authentication.
95    pub password: Option<String>,
96    /// Bypass proxy for these domains (comma-separated).
97    pub bypass: Option<String>,
98}
99
100impl ProxyConfig {
101    /// Create a new proxy configuration.
102    pub fn new(server: impl Into<String>) -> Self {
103        Self {
104            server: server.into(),
105            username: None,
106            password: None,
107            bypass: None,
108        }
109    }
110
111    /// Set proxy authentication credentials.
112    #[must_use]
113    pub fn credentials(mut self, username: impl Into<String>, password: impl Into<String>) -> Self {
114        self.username = Some(username.into());
115        self.password = Some(password.into());
116        self
117    }
118
119    /// Set domains to bypass the proxy.
120    #[must_use]
121    pub fn bypass(mut self, bypass: impl Into<String>) -> Self {
122        self.bypass = Some(bypass.into());
123        self
124    }
125}
126
127impl APIContextOptions {
128    /// Create a new options builder with default settings.
129    pub fn new() -> Self {
130        Self::default()
131    }
132
133    /// Set the base URL for all requests.
134    ///
135    /// Relative URLs passed to request methods will be resolved against this base URL.
136    ///
137    /// # Example
138    ///
139    /// ```no_run
140    /// use viewpoint_core::api::APIContextOptions;
141    ///
142    /// let options = APIContextOptions::new()
143    ///     .base_url("https://api.example.com/v1");
144    /// // Now api.get("/users") will request https://api.example.com/v1/users
145    /// ```
146    #[must_use]
147    pub fn base_url(mut self, url: impl Into<String>) -> Self {
148        self.base_url = Some(url.into());
149        self
150    }
151
152    /// Set extra HTTP headers to include in all requests.
153    ///
154    /// # Example
155    ///
156    /// ```no_run
157    /// use viewpoint_core::api::APIContextOptions;
158    ///
159    /// let options = APIContextOptions::new()
160    ///     .extra_http_headers([
161    ///         ("Authorization".to_string(), "Bearer token".to_string()),
162    ///         ("X-API-Key".to_string(), "secret".to_string()),
163    ///     ]);
164    /// ```
165    #[must_use]
166    pub fn extra_http_headers(
167        mut self,
168        headers: impl IntoIterator<Item = (String, String)>,
169    ) -> Self {
170        self.extra_http_headers = headers.into_iter().collect();
171        self
172    }
173
174    /// Add a single extra HTTP header.
175    #[must_use]
176    pub fn header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
177        self.extra_http_headers.insert(name.into(), value.into());
178        self
179    }
180
181    /// Set HTTP credentials for Basic authentication.
182    ///
183    /// # Example
184    ///
185    /// ```no_run
186    /// use viewpoint_core::api::{APIContextOptions, HttpCredentials};
187    ///
188    /// let options = APIContextOptions::new()
189    ///     .http_credentials(HttpCredentials::new("user", "pass"));
190    /// ```
191    #[must_use]
192    pub fn http_credentials(mut self, credentials: HttpCredentials) -> Self {
193        self.http_credentials = Some(credentials);
194        self
195    }
196
197    /// Set whether to ignore HTTPS certificate errors.
198    ///
199    /// **Warning**: This should only be used for testing. Never use this in production
200    /// as it makes the connection vulnerable to man-in-the-middle attacks.
201    #[must_use]
202    pub fn ignore_https_errors(mut self, ignore: bool) -> Self {
203        self.ignore_https_errors = ignore;
204        self
205    }
206
207    /// Set proxy configuration.
208    ///
209    /// # Example
210    ///
211    /// ```no_run
212    /// use viewpoint_core::api::{APIContextOptions, ProxyConfig};
213    ///
214    /// let options = APIContextOptions::new()
215    ///     .proxy(ProxyConfig::new("http://proxy.example.com:8080")
216    ///         .credentials("user", "pass"));
217    /// ```
218    #[must_use]
219    pub fn proxy(mut self, proxy: ProxyConfig) -> Self {
220        self.proxy = Some(proxy);
221        self
222    }
223
224    /// Set the default timeout for all requests.
225    ///
226    /// This can be overridden on a per-request basis.
227    #[must_use]
228    pub fn timeout(mut self, timeout: Duration) -> Self {
229        self.timeout = Some(timeout);
230        self
231    }
232
233    /// Set the user agent string.
234    #[must_use]
235    pub fn user_agent(mut self, user_agent: impl Into<String>) -> Self {
236        self.user_agent = Some(user_agent.into());
237        self
238    }
239}
240
241#[cfg(test)]
242mod tests;