1use crate::classifier::{DefaultResponseClassifier, ResponseClassifier};
4use std::fmt;
5use std::sync::Arc;
6use std::time::Duration;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum ProxySelectionStrategy {
11 FastestResponse,
13 MostReliable,
15 Random,
17 RoundRobin,
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum RetryStrategy {
24 DefaultSelection,
27 NewProxyOnRetry,
29}
30
31#[derive(Clone)]
35pub struct HostConfig {
36 pub(crate) host: String,
37 pub(crate) primary: bool,
38 pub(crate) health_check_interval: Duration,
40 pub(crate) health_check_timeout: Duration,
42 pub(crate) min_available_proxies: usize,
44 pub(crate) health_check_url: String,
46 pub(crate) retry_count: usize,
48 pub(crate) retry_strategy: RetryStrategy,
50 pub(crate) selection_strategy: ProxySelectionStrategy,
52 pub(crate) min_request_interval_ms: u64,
54 pub(crate) response_classifier: Arc<dyn ResponseClassifier>,
56 pub(crate) danger_accept_invalid_certs: bool,
58}
59
60impl fmt::Debug for HostConfig {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 f.debug_struct("HostConfig")
63 .field("host", &self.host)
64 .field("primary", &self.primary)
65 .field("health_check_interval", &self.health_check_interval)
66 .field("health_check_timeout", &self.health_check_timeout)
67 .field("min_available_proxies", &self.min_available_proxies)
68 .field("health_check_url", &self.health_check_url)
69 .field("retry_count", &self.retry_count)
70 .field("retry_strategy", &self.retry_strategy)
71 .field("selection_strategy", &self.selection_strategy)
72 .field("min_request_interval_ms", &self.min_request_interval_ms)
73 .field("response_classifier", &"<dyn ResponseClassifier>")
74 .field(
75 "danger_accept_invalid_certs",
76 &self.danger_accept_invalid_certs,
77 )
78 .finish()
79 }
80}
81
82impl HostConfig {
83 pub fn builder(host: impl Into<String>) -> HostConfigBuilder {
85 HostConfigBuilder::new(host)
86 }
87
88 pub fn host(&self) -> &str {
90 &self.host
91 }
92
93 pub fn primary(&self) -> bool {
95 self.primary
96 }
97
98 pub fn health_check_interval(&self) -> Duration {
100 self.health_check_interval
101 }
102
103 pub fn health_check_timeout(&self) -> Duration {
105 self.health_check_timeout
106 }
107
108 pub fn min_available_proxies(&self) -> usize {
110 self.min_available_proxies
111 }
112
113 pub fn health_check_url(&self) -> &str {
115 &self.health_check_url
116 }
117
118 pub fn retry_count(&self) -> usize {
120 self.retry_count
121 }
122
123 pub fn retry_strategy(&self) -> RetryStrategy {
125 self.retry_strategy
126 }
127
128 pub fn selection_strategy(&self) -> ProxySelectionStrategy {
130 self.selection_strategy
131 }
132
133 pub fn min_request_interval_ms(&self) -> u64 {
135 self.min_request_interval_ms
136 }
137
138 pub fn response_classifier(&self) -> &Arc<dyn ResponseClassifier> {
140 &self.response_classifier
141 }
142
143 pub fn danger_accept_invalid_certs(&self) -> bool {
145 self.danger_accept_invalid_certs
146 }
147}
148
149pub struct HostConfigBuilder {
151 host: String,
152 primary: bool,
153 health_check_interval: Option<Duration>,
154 health_check_timeout: Option<Duration>,
155 min_available_proxies: Option<usize>,
156 health_check_url: Option<String>,
157 retry_count: Option<usize>,
158 retry_strategy: Option<RetryStrategy>,
159 selection_strategy: Option<ProxySelectionStrategy>,
160 min_request_interval_ms: Option<u64>,
161 response_classifier: Option<Arc<dyn ResponseClassifier>>,
162 danger_accept_invalid_certs: bool,
163}
164
165impl HostConfigBuilder {
166 pub fn new(host: impl Into<String>) -> Self {
168 Self {
169 host: normalize_host(host.into()),
170 primary: false,
171 health_check_interval: None,
172 health_check_timeout: None,
173 min_available_proxies: None,
174 health_check_url: None,
175 retry_count: None,
176 retry_strategy: None,
177 selection_strategy: None,
178 min_request_interval_ms: None,
179 response_classifier: None,
180 danger_accept_invalid_certs: false,
181 }
182 }
183
184 pub fn health_check_interval(mut self, interval: Duration) -> Self {
186 self.health_check_interval = Some(interval);
187 self
188 }
189
190 pub fn primary(mut self, primary: bool) -> Self {
192 self.primary = primary;
193 self
194 }
195
196 pub fn health_check_timeout(mut self, timeout: Duration) -> Self {
198 self.health_check_timeout = Some(timeout);
199 self
200 }
201
202 pub fn min_available_proxies(mut self, count: usize) -> Self {
204 self.min_available_proxies = Some(count);
205 self
206 }
207
208 pub fn health_check_url(mut self, url: impl Into<String>) -> Self {
210 self.health_check_url = Some(url.into());
211 self
212 }
213
214 pub fn retry_count(mut self, count: usize) -> Self {
216 self.retry_count = Some(count);
217 self
218 }
219
220 pub fn retry_strategy(mut self, strategy: RetryStrategy) -> Self {
222 self.retry_strategy = Some(strategy);
223 self
224 }
225
226 pub fn selection_strategy(mut self, strategy: ProxySelectionStrategy) -> Self {
228 self.selection_strategy = Some(strategy);
229 self
230 }
231
232 pub fn min_request_interval_ms(mut self, interval_ms: u64) -> Self {
234 self.min_request_interval_ms = Some(interval_ms);
235 self
236 }
237
238 pub fn response_classifier(mut self, classifier: impl ResponseClassifier) -> Self {
240 self.response_classifier = Some(Arc::new(classifier));
241 self
242 }
243
244 pub fn danger_accept_invalid_certs(mut self, accept: bool) -> Self {
246 self.danger_accept_invalid_certs = accept;
247 self
248 }
249
250 pub fn build(self) -> HostConfig {
252 let health_check_url = self
253 .health_check_url
254 .unwrap_or_else(|| "https://www.google.com".to_string());
255 let health_check_url = if health_check_url.trim().is_empty() {
256 "https://www.google.com".to_string()
257 } else {
258 health_check_url
259 };
260
261 HostConfig {
262 host: if self.host.is_empty() {
263 "default".to_string()
264 } else {
265 self.host
266 },
267 primary: self.primary,
268 health_check_interval: self
269 .health_check_interval
270 .unwrap_or(Duration::from_secs(300)),
271 health_check_timeout: self.health_check_timeout.unwrap_or(Duration::from_secs(10)),
272 min_available_proxies: self.min_available_proxies.unwrap_or(3),
273 health_check_url,
274 retry_count: self.retry_count.unwrap_or(3),
275 retry_strategy: self
276 .retry_strategy
277 .unwrap_or(RetryStrategy::DefaultSelection),
278 selection_strategy: self
279 .selection_strategy
280 .unwrap_or(ProxySelectionStrategy::FastestResponse),
281 min_request_interval_ms: self.min_request_interval_ms.unwrap_or(500).max(1),
282 response_classifier: self
283 .response_classifier
284 .unwrap_or_else(|| Arc::new(DefaultResponseClassifier)),
285 danger_accept_invalid_certs: self.danger_accept_invalid_certs,
286 }
287 }
288}
289
290#[derive(Clone, Debug)]
292pub struct ProxyPoolConfig {
293 pub(crate) sources: Vec<String>,
295 pub(crate) hosts: Vec<HostConfig>,
297}
298
299impl ProxyPoolConfig {
300 pub fn builder() -> ProxyPoolConfigBuilder {
302 ProxyPoolConfigBuilder::new()
303 }
304
305 pub fn sources(&self) -> &[String] {
307 &self.sources
308 }
309
310 pub fn hosts(&self) -> &[HostConfig] {
312 &self.hosts
313 }
314}
315
316pub struct ProxyPoolConfigBuilder {
318 sources: Vec<String>,
319 hosts: Vec<HostConfig>,
320}
321
322impl ProxyPoolConfigBuilder {
323 pub fn new() -> Self {
325 Self {
326 sources: Vec::new(),
327 hosts: Vec::new(),
328 }
329 }
330
331 pub fn sources(mut self, sources: Vec<impl Into<String>>) -> Self {
333 self.sources = sources.into_iter().map(Into::into).collect();
334 self
335 }
336
337 pub fn hosts(mut self, hosts: Vec<HostConfig>) -> Self {
341 self.hosts = hosts;
342 self
343 }
344
345 pub fn add_host(mut self, host: HostConfig) -> Self {
349 self.hosts.push(host);
350 self
351 }
352
353 pub fn build(self) -> ProxyPoolConfig {
355 ProxyPoolConfig {
356 sources: self.sources,
357 hosts: self.hosts,
358 }
359 }
360}
361
362impl Default for ProxyPoolConfigBuilder {
363 fn default() -> Self {
364 Self::new()
365 }
366}
367
368fn normalize_host(host: String) -> String {
369 host.trim().to_ascii_lowercase()
370}
371
372#[cfg(test)]
373mod tests {
374 use super::{HostConfig, ProxyPoolConfig};
375
376 #[test]
377 fn host_config_normalizes_host() {
378 let host = HostConfig::builder(" API.EXAMPLE.COM ").build();
379 assert_eq!(host.host(), "api.example.com");
380 }
381
382 #[test]
383 fn pool_config_keeps_hosts() {
384 let api = HostConfig::builder("api.example.com").build();
385 let web = HostConfig::builder("web.example.com").build();
386 let config = ProxyPoolConfig::builder().hosts(vec![api, web]).build();
387 assert_eq!(config.hosts().len(), 2);
388 }
389}