rskit_httpclient/
config.rs1use crate::auth::Auth;
4use crate::destination::DestinationPolicy;
5use rskit_resilience::Policy;
6use rskit_security::TlsConfig;
7use std::collections::HashMap;
8use std::time::Duration;
9
10const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
11const DEFAULT_CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
12const DEFAULT_MAX_RESPONSE_BODY_BYTES: usize = 10 * 1024 * 1024;
13
14#[derive(Clone)]
16#[non_exhaustive]
17pub struct HttpClientConfig {
18 pub base_url: Option<String>,
20
21 pub timeout: Duration,
23
24 pub connect_timeout: Duration,
26
27 pub user_agent: Option<String>,
29
30 pub default_headers: HashMap<String, String>,
32
33 pub auth: Option<Auth>,
35
36 pub follow_redirects: bool,
38
39 pub max_redirects: usize,
41
42 pub max_response_body_bytes: usize,
44
45 pub destination_policy: DestinationPolicy,
47
48 pub resilience_policy: Option<Policy>,
50
51 pub tls: Option<TlsConfig>,
53}
54
55impl std::fmt::Debug for HttpClientConfig {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 f.debug_struct("HttpClientConfig")
58 .field("base_url", &self.base_url)
59 .field("timeout", &self.timeout)
60 .field("connect_timeout", &self.connect_timeout)
61 .field("user_agent", &self.user_agent)
62 .field("default_headers", &self.default_headers)
63 .field("auth", &self.auth)
64 .field("follow_redirects", &self.follow_redirects)
65 .field("max_redirects", &self.max_redirects)
66 .field("max_response_body_bytes", &self.max_response_body_bytes)
67 .field("destination_policy", &self.destination_policy)
68 .field("has_resilience_policy", &self.resilience_policy.is_some())
69 .field("tls", &self.tls)
70 .finish()
71 }
72}
73
74impl HttpClientConfig {
75 #[must_use]
77 pub fn new() -> Self {
78 Self::default()
79 }
80
81 #[must_use]
83 pub fn with_base_url(mut self, url: impl Into<String>) -> Self {
84 self.base_url = Some(url.into());
85 self
86 }
87
88 #[must_use]
90 pub fn with_timeout(mut self, timeout: Duration) -> Self {
91 self.timeout = timeout;
92 self
93 }
94
95 #[must_use]
97 pub fn with_connect_timeout(mut self, timeout: Duration) -> Self {
98 self.connect_timeout = timeout;
99 self
100 }
101
102 #[must_use]
104 pub fn with_user_agent(mut self, ua: impl Into<String>) -> Self {
105 self.user_agent = Some(ua.into());
106 self
107 }
108
109 #[must_use]
111 pub fn with_header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
112 self.default_headers.insert(name.into(), value.into());
113 self
114 }
115
116 #[must_use]
118 pub fn with_headers(mut self, headers: HashMap<String, String>) -> Self {
119 self.default_headers = headers;
120 self
121 }
122
123 #[must_use]
125 pub fn with_auth(mut self, auth: Auth) -> Self {
126 self.auth = Some(auth);
127 self
128 }
129
130 #[must_use]
132 pub fn with_follow_redirects(mut self, follow: bool) -> Self {
133 self.follow_redirects = follow;
134 self
135 }
136
137 #[must_use]
139 pub fn with_max_redirects(mut self, max: usize) -> Self {
140 self.max_redirects = max;
141 self
142 }
143
144 #[must_use]
146 pub fn with_max_response_body_bytes(mut self, max: usize) -> Self {
147 self.max_response_body_bytes = max;
148 self
149 }
150
151 #[must_use]
153 pub fn with_destination_policy(mut self, policy: DestinationPolicy) -> Self {
154 self.destination_policy = policy;
155 self
156 }
157
158 #[must_use]
160 pub fn with_resilience_policy(mut self, policy: Policy) -> Self {
161 self.resilience_policy = Some(policy);
162 self
163 }
164
165 #[must_use]
167 pub fn with_tls(mut self, tls: TlsConfig) -> Self {
168 self.tls = Some(tls);
169 self
170 }
171}
172
173impl Default for HttpClientConfig {
174 fn default() -> Self {
175 Self {
176 base_url: None,
177 timeout: DEFAULT_TIMEOUT,
178 connect_timeout: DEFAULT_CONNECT_TIMEOUT,
179 user_agent: None,
180 default_headers: HashMap::new(),
181 auth: None,
182 follow_redirects: true,
183 max_redirects: 5,
184 max_response_body_bytes: DEFAULT_MAX_RESPONSE_BODY_BYTES,
185 destination_policy: DestinationPolicy::default(),
186 resilience_policy: None,
187 tls: None,
188 }
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[test]
197 fn debug_redacts_auth_secret_values() {
198 let config = HttpClientConfig::new().with_auth(Auth::bearer("secret-token"));
199
200 let formatted = format!("{config:?}");
201
202 assert!(formatted.contains("SecretString(***)"));
203 assert!(!formatted.contains("secret-token"));
204 }
205
206 #[test]
207 fn builder_methods_override_defaults() {
208 let headers = HashMap::from([("x-default".to_string(), "yes".to_string())]);
209 let policy = DestinationPolicy::new()
210 .with_allowed_schemes(["https"])
211 .with_block_metadata(false);
212 let config = HttpClientConfig::new()
213 .with_timeout(Duration::from_secs(5))
214 .with_connect_timeout(Duration::from_secs(2))
215 .with_headers(headers.clone())
216 .with_follow_redirects(false)
217 .with_max_redirects(0)
218 .with_destination_policy(policy.clone());
219
220 assert_eq!(config.timeout, Duration::from_secs(5));
221 assert_eq!(config.connect_timeout, Duration::from_secs(2));
222 assert_eq!(config.default_headers, headers);
223 assert!(!config.follow_redirects);
224 assert_eq!(config.max_redirects, 0);
225 assert_eq!(config.destination_policy, policy);
226 }
227}