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>,
21
22 pub timeout: Duration,
24
25 pub connect_timeout: Duration,
27
28 pub user_agent: Option<String>,
30
31 pub default_headers: HashMap<String, String>,
33
34 pub auth: Option<Auth>,
36
37 pub follow_redirects: bool,
39
40 pub max_redirects: usize,
42
43 pub max_response_body_bytes: usize,
45
46 pub destination_policy: DestinationPolicy,
48
49 pub resilience_policy: Option<Policy>,
51
52 pub tls: Option<TlsConfig>,
54}
55
56impl std::fmt::Debug for HttpClientConfig {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 f.debug_struct("HttpClientConfig")
59 .field("base_url", &self.base_url)
60 .field("timeout", &self.timeout)
61 .field("connect_timeout", &self.connect_timeout)
62 .field("user_agent", &self.user_agent)
63 .field("default_headers", &self.default_headers)
64 .field("auth", &self.auth)
65 .field("follow_redirects", &self.follow_redirects)
66 .field("max_redirects", &self.max_redirects)
67 .field("max_response_body_bytes", &self.max_response_body_bytes)
68 .field("destination_policy", &self.destination_policy)
69 .field("has_resilience_policy", &self.resilience_policy.is_some())
70 .field("tls", &self.tls)
71 .finish()
72 }
73}
74
75impl HttpClientConfig {
76 #[must_use]
78 pub fn new() -> Self {
79 Self::default()
80 }
81
82 #[must_use]
84 pub fn with_base_url(mut self, url: impl Into<String>) -> Self {
85 self.base_url = Some(url.into());
86 self
87 }
88
89 #[must_use]
91 pub fn with_timeout(mut self, timeout: Duration) -> Self {
92 self.timeout = timeout;
93 self
94 }
95
96 #[must_use]
98 pub fn with_connect_timeout(mut self, timeout: Duration) -> Self {
99 self.connect_timeout = timeout;
100 self
101 }
102
103 #[must_use]
105 pub fn with_user_agent(mut self, ua: impl Into<String>) -> Self {
106 self.user_agent = Some(ua.into());
107 self
108 }
109
110 #[must_use]
112 pub fn with_header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
113 self.default_headers.insert(name.into(), value.into());
114 self
115 }
116
117 #[must_use]
119 pub fn with_headers(mut self, headers: HashMap<String, String>) -> Self {
120 self.default_headers = headers;
121 self
122 }
123
124 #[must_use]
126 pub fn with_auth(mut self, auth: Auth) -> Self {
127 self.auth = Some(auth);
128 self
129 }
130
131 #[must_use]
133 pub fn with_follow_redirects(mut self, follow: bool) -> Self {
134 self.follow_redirects = follow;
135 self
136 }
137
138 #[must_use]
140 pub fn with_max_redirects(mut self, max: usize) -> Self {
141 self.max_redirects = max;
142 self
143 }
144
145 #[must_use]
147 pub fn with_max_response_body_bytes(mut self, max: usize) -> Self {
148 self.max_response_body_bytes = max;
149 self
150 }
151
152 #[must_use]
154 pub fn with_destination_policy(mut self, policy: DestinationPolicy) -> Self {
155 self.destination_policy = policy;
156 self
157 }
158
159 #[must_use]
161 pub fn with_resilience_policy(mut self, policy: Policy) -> Self {
162 self.resilience_policy = Some(policy);
163 self
164 }
165
166 #[must_use]
168 pub fn with_tls(mut self, tls: TlsConfig) -> Self {
169 self.tls = Some(tls);
170 self
171 }
172}
173
174impl Default for HttpClientConfig {
175 fn default() -> Self {
176 Self {
177 base_url: None,
178 timeout: DEFAULT_TIMEOUT,
179 connect_timeout: DEFAULT_CONNECT_TIMEOUT,
180 user_agent: None,
181 default_headers: HashMap::new(),
182 auth: None,
183 follow_redirects: true,
184 max_redirects: 5,
185 max_response_body_bytes: DEFAULT_MAX_RESPONSE_BODY_BYTES,
186 destination_policy: DestinationPolicy::default(),
187 resilience_policy: None,
188 tls: None,
189 }
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn debug_redacts_auth_secret_values() {
199 let config = HttpClientConfig::new().with_auth(Auth::bearer("secret-token"));
200
201 let formatted = format!("{config:?}");
202
203 assert!(formatted.contains("SecretString(***)"));
204 assert!(!formatted.contains("secret-token"));
205 }
206
207 #[test]
208 fn builder_methods_override_defaults() {
209 let headers = HashMap::from([("x-default".to_string(), "yes".to_string())]);
210 let policy = DestinationPolicy::new()
211 .with_allowed_schemes(["https"])
212 .with_block_metadata(false);
213 let config = HttpClientConfig::new()
214 .with_timeout(Duration::from_secs(5))
215 .with_connect_timeout(Duration::from_secs(2))
216 .with_headers(headers.clone())
217 .with_follow_redirects(false)
218 .with_max_redirects(0)
219 .with_destination_policy(policy.clone());
220
221 assert_eq!(config.timeout, Duration::from_secs(5));
222 assert_eq!(config.connect_timeout, Duration::from_secs(2));
223 assert_eq!(config.default_headers, headers);
224 assert!(!config.follow_redirects);
225 assert_eq!(config.max_redirects, 0);
226 assert_eq!(config.destination_policy, policy);
227 }
228}