ureq/
agent.rs

1use std::fmt;
2use std::ops::Deref;
3use std::sync::Arc;
4use std::time::Duration;
5use url::Url;
6
7use crate::middleware::Middleware;
8use crate::pool::ConnectionPool;
9use crate::proxy::Proxy;
10use crate::request::Request;
11use crate::resolve::{ArcResolver, StdResolver};
12use crate::stream::TlsConnector;
13
14#[cfg(feature = "cookies")]
15use {
16    crate::cookies::{CookieStoreGuard, CookieTin},
17    cookie_store::CookieStore,
18};
19
20/// Strategy for keeping `authorization` headers during redirects.
21///
22/// `Never` is the default strategy and never preserves `authorization` header in redirects.
23/// `SameHost` send the authorization header in redirects only if the host of the redirect is
24/// the same of the previous request, and both use the same scheme (or switch to a more secure one, i.e
25/// we can redirect from `http` to `https`, but not the reverse).
26#[derive(Debug, Clone, PartialEq, Eq)]
27#[non_exhaustive]
28pub enum RedirectAuthHeaders {
29    /// Never preserve the `authorization` header on redirect. This is the default.
30    Never,
31    /// Preserve the `authorization` header when the redirect is to the same host. Both hosts must use
32    /// the same scheme (or switch to a more secure one, i.e we can redirect from `http` to `https`,
33    /// but not the reverse).
34    SameHost,
35}
36
37/// Accumulates options towards building an [Agent].
38pub struct AgentBuilder {
39    config: AgentConfig,
40    try_proxy_from_env: bool,
41    max_idle_connections: usize,
42    max_idle_connections_per_host: usize,
43    /// Cookies saved between requests.
44    /// Invariant: All cookies must have a nonempty domain and path.
45    #[cfg(feature = "cookies")]
46    cookie_store: Option<CookieStore>,
47    resolver: ArcResolver,
48    middleware: Vec<Box<dyn Middleware>>,
49}
50
51#[derive(Clone)]
52pub(crate) struct TlsConfig(Arc<dyn TlsConnector>);
53
54impl fmt::Debug for TlsConfig {
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        f.debug_struct("TlsConfig").finish()
57    }
58}
59
60impl Deref for TlsConfig {
61    type Target = Arc<dyn TlsConnector>;
62
63    fn deref(&self) -> &Self::Target {
64        &self.0
65    }
66}
67
68/// Config as built by AgentBuilder and then static for the lifetime of the Agent.
69#[derive(Clone, Debug)]
70pub(crate) struct AgentConfig {
71    pub proxy: Option<Proxy>,
72    pub timeout_connect: Option<Duration>,
73    pub timeout_read: Option<Duration>,
74    pub timeout_write: Option<Duration>,
75    pub timeout: Option<Duration>,
76    pub https_only: bool,
77    pub no_delay: bool,
78    pub redirects: u32,
79    pub redirect_auth_headers: RedirectAuthHeaders,
80    pub user_agent: String,
81    pub tls_config: TlsConfig,
82}
83
84/// Agents keep state between requests.
85///
86/// By default, no state, such as cookies, is kept between requests.
87/// But by creating an agent as entry point for the request, we
88/// can keep a state.
89///
90/// ```
91/// # fn main() -> Result<(), ureq::Error> {
92/// # ureq::is_test(true);
93/// let mut agent = ureq::agent();
94///
95/// agent
96///     .post("http://example.com/post/login")
97///     .call()?;
98///
99/// let secret = agent
100///     .get("http://example.com/get/my-protected-page")
101///     .call()?
102///     .into_string()?;
103///
104///   println!("Secret is: {}", secret);
105/// # Ok(())
106/// # }
107/// ```
108///
109/// Agent uses an inner Arc, so cloning an Agent results in an instance
110/// that shares the same underlying connection pool and other state.
111#[derive(Debug, Clone)]
112pub struct Agent {
113    pub(crate) config: Arc<AgentConfig>,
114    /// Reused agent state for repeated requests from this agent.
115    pub(crate) state: Arc<AgentState>,
116}
117
118/// Container of the state
119///
120/// *Internal API*.
121pub(crate) struct AgentState {
122    /// Reused connections between requests.
123    pub(crate) pool: ConnectionPool,
124    /// Cookies saved between requests.
125    /// Invariant: All cookies must have a nonempty domain and path.
126    #[cfg(feature = "cookies")]
127    pub(crate) cookie_tin: CookieTin,
128    pub(crate) resolver: ArcResolver,
129    pub(crate) middleware: Vec<Box<dyn Middleware>>,
130}
131
132impl Agent {
133    /// Creates an Agent with default settings.
134    ///
135    /// Same as `AgentBuilder::new().build()`.
136    pub fn new() -> Self {
137        AgentBuilder::new().build()
138    }
139
140    /// Make a request with the HTTP verb as a parameter.
141    ///
142    /// This allows making requests with verbs that don't have a dedicated
143    /// method.
144    ///
145    /// If you've got an already-parsed [Url], try [request_url][Agent::request_url].
146    ///
147    /// ```
148    /// # fn main() -> Result<(), ureq::Error> {
149    /// # ureq::is_test(true);
150    /// use ureq::Response;
151    /// let agent = ureq::agent();
152    ///
153    /// let resp: Response = agent
154    ///     .request("OPTIONS", "http://example.com/")
155    ///     .call()?;
156    /// # Ok(())
157    /// # }
158    /// ```
159    pub fn request(&self, method: &str, path: &str) -> Request {
160        Request::new(self.clone(), method.into(), path.into())
161    }
162
163    /// Make a request using an already-parsed [Url].
164    ///
165    /// This is useful if you've got a parsed Url from some other source, or if
166    /// you want to parse the URL and then modify it before making the request.
167    /// If you'd just like to pass a String or a `&str`, try [request][Agent::request].
168    ///
169    /// ```
170    /// # fn main() -> Result<(), ureq::Error> {
171    /// # ureq::is_test(true);
172    /// use {url::Url, ureq::Response};
173    /// let agent = ureq::agent();
174    ///
175    /// let mut url: Url = "http://example.com/some-page".parse()?;
176    /// url.set_path("/get/robots.txt");
177    /// let resp: Response = agent
178    ///     .request_url("GET", &url)
179    ///     .call()?;
180    /// # Ok(())
181    /// # }
182    /// ```
183    pub fn request_url(&self, method: &str, url: &Url) -> Request {
184        Request::new(self.clone(), method.into(), url.to_string())
185    }
186
187    /// Make a GET request from this agent.
188    pub fn get(&self, path: &str) -> Request {
189        self.request("GET", path)
190    }
191
192    /// Make a HEAD request from this agent.
193    pub fn head(&self, path: &str) -> Request {
194        self.request("HEAD", path)
195    }
196
197    /// Make a PATCH request from this agent.
198    pub fn patch(&self, path: &str) -> Request {
199        self.request("PATCH", path)
200    }
201
202    /// Make a POST request from this agent.
203    pub fn post(&self, path: &str) -> Request {
204        self.request("POST", path)
205    }
206
207    /// Make a PUT request from this agent.
208    pub fn put(&self, path: &str) -> Request {
209        self.request("PUT", path)
210    }
211
212    /// Make a DELETE request from this agent.
213    pub fn delete(&self, path: &str) -> Request {
214        self.request("DELETE", path)
215    }
216
217    /// Read access to the cookie store.
218    ///
219    /// Used to persist the cookies to an external writer.
220    ///
221    /// ```no_run
222    /// use std::io::Write;
223    /// use std::fs::File;
224    ///
225    /// # fn main() -> Result<(), ureq::Error> {
226    /// # ureq::is_test(true);
227    /// let agent = ureq::agent();
228    ///
229    /// // Cookies set by www.google.com are stored in agent.
230    /// agent.get("https://www.google.com/").call()?;
231    ///
232    /// // Saves (persistent) cookies
233    /// let mut file = File::create("cookies.json")?;
234    /// agent.cookie_store().save_json(&mut file).unwrap();
235    /// # Ok(())
236    /// # }
237    /// ```
238    #[cfg(feature = "cookies")]
239    pub fn cookie_store(&self) -> CookieStoreGuard<'_> {
240        self.state.cookie_tin.read_lock()
241    }
242
243    pub(crate) fn weak_state(&self) -> std::sync::Weak<AgentState> {
244        Arc::downgrade(&self.state)
245    }
246}
247
248const DEFAULT_MAX_IDLE_CONNECTIONS: usize = 100;
249const DEFAULT_MAX_IDLE_CONNECTIONS_PER_HOST: usize = 1;
250
251impl AgentBuilder {
252    pub fn new() -> Self {
253        AgentBuilder {
254            config: AgentConfig {
255                proxy: None,
256                timeout_connect: Some(Duration::from_secs(30)),
257                timeout_read: None,
258                timeout_write: None,
259                timeout: None,
260                https_only: false,
261                no_delay: true,
262                redirects: 5,
263                redirect_auth_headers: RedirectAuthHeaders::Never,
264                user_agent: format!("ureq/{}", env!("CARGO_PKG_VERSION")),
265                tls_config: TlsConfig(crate::default_tls_config()),
266            },
267            #[cfg(feature = "proxy-from-env")]
268            try_proxy_from_env: true,
269            #[cfg(not(feature = "proxy-from-env"))]
270            try_proxy_from_env: false,
271            max_idle_connections: DEFAULT_MAX_IDLE_CONNECTIONS,
272            max_idle_connections_per_host: DEFAULT_MAX_IDLE_CONNECTIONS_PER_HOST,
273            resolver: StdResolver.into(),
274            #[cfg(feature = "cookies")]
275            cookie_store: None,
276            middleware: vec![],
277        }
278    }
279
280    /// Create a new agent.
281    // Note: This could take &self as the first argument, allowing one
282    // AgentBuilder to be used multiple times, except CookieStore does
283    // not implement clone, so we have to give ownership to the newly
284    // built Agent.
285    pub fn build(mut self) -> Agent {
286        if self.config.proxy.is_none() && self.try_proxy_from_env {
287            if let Some(proxy) = Proxy::try_from_system() {
288                self.config.proxy = Some(proxy);
289            }
290        }
291        Agent {
292            config: Arc::new(self.config),
293            state: Arc::new(AgentState {
294                pool: ConnectionPool::new_with_limits(
295                    self.max_idle_connections,
296                    self.max_idle_connections_per_host,
297                ),
298                #[cfg(feature = "cookies")]
299                cookie_tin: CookieTin::new(self.cookie_store.unwrap_or_else(CookieStore::default)),
300                resolver: self.resolver,
301                middleware: self.middleware,
302            }),
303        }
304    }
305
306    /// Set the proxy server to use for all connections from this Agent.
307    ///
308    /// Example:
309    /// ```
310    /// # fn main() -> Result<(), ureq::Error> {
311    /// # ureq::is_test(true);
312    /// let proxy = ureq::Proxy::new("user:password@cool.proxy:9090")?;
313    /// let agent = ureq::AgentBuilder::new()
314    ///     .proxy(proxy)
315    ///     .build();
316    /// # Ok(())
317    /// # }
318    /// ```
319    ///
320    /// Adding a proxy will disable `try_proxy_from_env`.
321    pub fn proxy(mut self, proxy: Proxy) -> Self {
322        self.config.proxy = Some(proxy);
323        self
324    }
325
326    /// Attempt to detect proxy settings from the environment, i.e. HTTP_PROXY
327    ///
328    /// The default is `false` without the `proxy-from-env` feature flag, i.e.
329    /// not detecting proxy from env, due to the potential security risk and to
330    /// maintain backward compatibility.
331    ///
332    /// If the `proxy` is set on the builder, this setting has no effect.
333    pub fn try_proxy_from_env(mut self, do_try: bool) -> Self {
334        self.try_proxy_from_env = do_try;
335        self
336    }
337
338    /// Enforce the client to only perform HTTPS requests.
339    /// This setting also makes the client refuse HTTPS to HTTP redirects.
340    /// Default is false
341    ///
342    /// Example:
343    /// ```
344    /// let agent = ureq::AgentBuilder::new()
345    ///     .https_only(true)
346    ///     .build();
347    /// ```
348    pub fn https_only(mut self, enforce: bool) -> Self {
349        self.config.https_only = enforce;
350        self
351    }
352
353    /// Sets the maximum number of connections allowed in the connection pool.
354    /// By default, this is set to 100. Setting this to zero would disable
355    /// connection pooling.
356    ///
357    /// ```
358    /// let agent = ureq::AgentBuilder::new()
359    ///     .max_idle_connections(200)
360    ///     .build();
361    /// ```
362    pub fn max_idle_connections(mut self, max: usize) -> Self {
363        self.max_idle_connections = max;
364        self
365    }
366
367    /// Sets the maximum number of connections per host to keep in the
368    /// connection pool. By default, this is set to 1. Setting this to zero
369    /// would disable connection pooling.
370    ///
371    /// ```
372    /// let agent = ureq::AgentBuilder::new()
373    ///     .max_idle_connections_per_host(200)
374    ///     .build();
375    /// ```
376    pub fn max_idle_connections_per_host(mut self, max: usize) -> Self {
377        self.max_idle_connections_per_host = max;
378        self
379    }
380
381    /// Configures a custom resolver to be used by this agent. By default,
382    /// address-resolution is done by std::net::ToSocketAddrs. This allows you
383    /// to override that resolution with your own alternative. Useful for
384    /// testing and special-cases like DNS-based load balancing.
385    ///
386    /// A `Fn(&str) -> io::Result<Vec<SocketAddr>>` is a valid resolver,
387    /// passing a closure is a simple way to override. Note that you might need
388    /// explicit type `&str` on the closure argument for type inference to
389    /// succeed.
390    /// ```
391    /// use std::net::ToSocketAddrs;
392    ///
393    /// let mut agent = ureq::AgentBuilder::new()
394    ///    .resolver(|addr: &str| match addr {
395    ///       "example.com" => Ok(vec![([127,0,0,1], 8096).into()]),
396    ///       addr => addr.to_socket_addrs().map(Iterator::collect),
397    ///    })
398    ///    .build();
399    /// ```
400    pub fn resolver(mut self, resolver: impl crate::Resolver + 'static) -> Self {
401        self.resolver = resolver.into();
402        self
403    }
404
405    /// Timeout for the socket connection to be successful.
406    /// If both this and `.timeout()` are both set, `.timeout_connect()`
407    /// takes precedence.
408    ///
409    /// The default is 30 seconds.
410    ///
411    /// ```
412    /// use std::time::Duration;
413    /// # fn main() -> Result<(), ureq::Error> {
414    /// # ureq::is_test(true);
415    /// let agent = ureq::builder()
416    ///     .timeout_connect(Duration::from_secs(1))
417    ///     .build();
418    /// let result = agent.get("http://httpbin.org/delay/20").call();
419    /// # Ok(())
420    /// # }
421    /// ```
422    pub fn timeout_connect(mut self, timeout: Duration) -> Self {
423        self.config.timeout_connect = Some(timeout);
424        self
425    }
426
427    /// Timeout for the individual reads of the socket.
428    /// If both this and `.timeout()` are both set, `.timeout()`
429    /// takes precedence.
430    ///
431    /// The default is no timeout. In other words, requests may block forever on reads by default.
432    ///
433    /// ```
434    /// use std::time::Duration;
435    /// # fn main() -> Result<(), ureq::Error> {
436    /// # ureq::is_test(true);
437    /// let agent = ureq::builder()
438    ///     .timeout_read(Duration::from_secs(1))
439    ///     .build();
440    /// let result = agent.get("http://httpbin.org/delay/20").call();
441    /// # Ok(())
442    /// # }
443    /// ```
444    pub fn timeout_read(mut self, timeout: Duration) -> Self {
445        self.config.timeout_read = Some(timeout);
446        self
447    }
448
449    /// Timeout for the individual writes to the socket.
450    /// If both this and `.timeout()` are both set, `.timeout()`
451    /// takes precedence.
452    ///
453    /// The default is no timeout. In other words, requests may block forever on writes by default.
454    ///
455    /// ```
456    /// use std::time::Duration;
457    /// # fn main() -> Result<(), ureq::Error> {
458    /// # ureq::is_test(true);
459    /// let agent = ureq::builder()
460    ///     .timeout_read(Duration::from_secs(1))
461    ///     .build();
462    /// let result = agent.get("http://httpbin.org/delay/20").call();
463    /// # Ok(())
464    /// # }
465    /// ```
466    pub fn timeout_write(mut self, timeout: Duration) -> Self {
467        self.config.timeout_write = Some(timeout);
468        self
469    }
470
471    /// Timeout for the overall request, including DNS resolution, connection
472    /// time, redirects, and reading the response body. Slow DNS resolution
473    /// may cause a request to exceed the timeout, because the DNS request
474    /// cannot be interrupted with the available APIs.
475    ///
476    /// This takes precedence over `.timeout_read()` and `.timeout_write()`, but
477    /// not `.timeout_connect()`.
478    ///
479    /// ```
480    /// # fn main() -> Result<(), ureq::Error> {
481    /// # ureq::is_test(true);
482    /// // wait max 1 second for whole request to complete.
483    /// let agent = ureq::builder()
484    ///     .timeout(std::time::Duration::from_secs(1))
485    ///     .build();
486    /// let result = agent.get("http://httpbin.org/delay/20").call();
487    /// # Ok(())
488    /// # }
489    /// ```
490    pub fn timeout(mut self, timeout: Duration) -> Self {
491        self.config.timeout = Some(timeout);
492        self
493    }
494
495    /// Whether no_delay will be set on the tcp socket.
496    /// Setting this to true disables Nagle's algorithm.
497    ///
498    /// Defaults to true.
499    ///
500    /// ```
501    /// # fn main() -> Result<(), ureq::Error> {
502    /// # ureq::is_test(true);
503    /// let agent = ureq::builder()
504    ///     .no_delay(false)
505    ///     .build();
506    /// let result = agent.get("http://httpbin.org/get").call();
507    /// # Ok(())
508    /// # }
509    /// ```
510    pub fn no_delay(mut self, no_delay: bool) -> Self {
511        self.config.no_delay = no_delay;
512        self
513    }
514
515    /// How many redirects to follow.
516    ///
517    /// Defaults to `5`. Set to `0` to avoid redirects and instead
518    /// get a response object with the 3xx status code.
519    ///
520    /// If the redirect count hits this limit (and it's > 0), TooManyRedirects is returned.
521    ///
522    /// WARNING: for 307 and 308 redirects, this value is ignored for methods that have a body.
523    /// You must handle 307 redirects yourself when sending a PUT, POST, PATCH, or DELETE request.
524    ///
525    /// ```no_run
526    /// # fn main() -> Result<(), ureq::Error> {
527    /// # ureq::is_test(true);
528    /// let result = ureq::builder()
529    ///     .redirects(1)
530    ///     .build()
531    ///     # ;
532    /// # let result = ureq::agent()
533    ///     .get("http://httpbin.org/status/301")
534    ///     .call()?;
535    /// assert_ne!(result.status(), 301);
536    ///
537    /// let result = ureq::post("http://httpbin.org/status/307")
538    ///     .send_bytes(b"some data")?;
539    /// assert_eq!(result.status(), 307);
540    /// # Ok(())
541    /// # }
542    /// ```
543    pub fn redirects(mut self, n: u32) -> Self {
544        self.config.redirects = n;
545        self
546    }
547
548    /// Set the strategy for propagation of authorization headers in redirects.
549    ///
550    /// Defaults to [`RedirectAuthHeaders::Never`].
551    ///
552    pub fn redirect_auth_headers(mut self, v: RedirectAuthHeaders) -> Self {
553        self.config.redirect_auth_headers = v;
554        self
555    }
556
557    /// The user-agent header to associate with all requests from this agent by default.
558    ///
559    /// Defaults to `ureq/[VERSION]`. You can override the user-agent on an individual request by
560    /// setting the `User-Agent` header when constructing the request.
561    ///
562    /// ```no_run
563    /// # #[cfg(feature = "json")]
564    /// # fn main() -> Result<(), ureq::Error> {
565    /// # ureq::is_test(true);
566    /// let agent = ureq::builder()
567    ///     .user_agent("ferris/1.0")
568    ///     .build();
569    ///
570    /// // Uses agent's header
571    /// let result: serde_json::Value =
572    ///     agent.get("http://httpbin.org/headers").call()?.into_json()?;
573    /// assert_eq!(&result["headers"]["User-Agent"], "ferris/1.0");
574    ///
575    /// // Overrides user-agent set on the agent
576    /// let result: serde_json::Value = agent.get("http://httpbin.org/headers")
577    ///     .set("User-Agent", "super-ferris/2.0")
578    ///     .call()?.into_json()?;
579    /// assert_eq!(&result["headers"]["User-Agent"], "super-ferris/2.0");
580    /// # Ok(())
581    /// # }
582    /// # #[cfg(not(feature = "json"))]
583    /// # fn main() {}
584    /// ```
585    pub fn user_agent(mut self, user_agent: &str) -> Self {
586        self.config.user_agent = user_agent.into();
587        self
588    }
589
590    /// Configure TLS options for rustls to use when making HTTPS connections from this Agent.
591    ///
592    /// This overrides any previous call to tls_config or tls_connector.
593    ///
594    /// ```
595    /// # fn main() -> Result<(), ureq::Error> {
596    /// # ureq::is_test(true);
597    /// use std::sync::Arc;
598    /// let mut root_store = rustls::RootCertStore {
599    ///   roots: webpki_roots::TLS_SERVER_ROOTS.iter().cloned().collect(),
600    /// };
601    ///
602    /// let tls_config = rustls::ClientConfig::builder()
603    ///     .with_root_certificates(root_store)
604    ///     .with_no_client_auth();
605    /// let agent = ureq::builder()
606    ///     .tls_config(Arc::new(tls_config))
607    ///     .build();
608    /// # Ok(())
609    /// # }
610    #[cfg(feature = "tls")]
611    pub fn tls_config(mut self, tls_config: Arc<rustls::ClientConfig>) -> Self {
612        self.config.tls_config = TlsConfig(Arc::new(tls_config));
613        self
614    }
615
616    /// Configure TLS options for a backend other than rustls. The parameter can be a
617    /// any type which implements the [`TlsConnector`] trait. If you enable the native-tls
618    /// feature, we provide `impl TlsConnector for native_tls::TlsConnector` so you can pass
619    /// [`Arc<native_tls::TlsConnector>`](https://docs.rs/native-tls/0.2.7/native_tls/struct.TlsConnector.html).
620    ///
621    /// This overrides any previous call to tls_config or tls_connector.
622    ///
623    /// ```
624    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
625    /// # ureq::is_test(true);
626    /// use std::sync::Arc;
627    /// # #[cfg(feature = "native-tls")]
628    /// let tls_connector = Arc::new(native_tls::TlsConnector::new()?);
629    /// # #[cfg(feature = "native-tls")]
630    /// let agent = ureq::builder()
631    ///     .tls_connector(tls_connector.clone())
632    ///     .build();
633    /// # Ok(())
634    /// # }
635    /// ```
636    pub fn tls_connector<T: TlsConnector + 'static>(mut self, tls_config: Arc<T>) -> Self {
637        self.config.tls_config = TlsConfig(tls_config);
638        self
639    }
640
641    /// Provide the cookie store to be used for all requests using this agent.
642    ///
643    /// This is useful in two cases. First when there is a need to persist cookies
644    /// to some backing store, and second when there's a need to prepare the agent
645    /// with some pre-existing cookies.
646    ///
647    /// Example
648    /// ```no_run
649    /// # fn main() -> Result<(), ureq::Error> {
650    /// # ureq::is_test(true);
651    /// use cookie_store::CookieStore;
652    /// use std::fs::File;
653    /// use std::io::BufReader;
654    /// let file = File::open("cookies.json")?;
655    /// let read = BufReader::new(file);
656    ///
657    /// // Read persisted cookies from cookies.json
658    /// let my_store = CookieStore::load_json(read).unwrap();
659    ///
660    /// // Cookies will be used for requests done through agent.
661    /// let agent = ureq::builder()
662    ///     .cookie_store(my_store)
663    ///     .build();
664    /// # Ok(())
665    /// # }
666    /// ```
667    #[cfg(feature = "cookies")]
668    pub fn cookie_store(mut self, cookie_store: CookieStore) -> Self {
669        self.cookie_store = Some(cookie_store);
670        self
671    }
672
673    /// Add middleware handler to this agent.
674    ///
675    /// All requests made by the agent will use this middleware. Middleware is invoked
676    /// in the order they are added to the builder.
677    pub fn middleware(mut self, m: impl Middleware) -> Self {
678        self.middleware.push(Box::new(m));
679        self
680    }
681}
682
683impl fmt::Debug for AgentBuilder {
684    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
685        f.debug_struct("AgentBuilder")
686            .field("config", &self.config)
687            .field("max_idle_connections", &self.max_idle_connections)
688            .field(
689                "max_idle_connections_per_host",
690                &self.max_idle_connections_per_host,
691            )
692            .field("resolver", &self.resolver)
693            // self.cookies missing because it's feature flagged.
694            // self.middleware missing because we don't want to force Debug on Middleware trait.
695            .finish_non_exhaustive()
696    }
697}
698
699impl fmt::Debug for AgentState {
700    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
701        f.debug_struct("AgentState")
702            .field("pool", &self.pool)
703            .field("resolver", &self.resolver)
704            // self.cookie_tin missing because it's feature flagged.
705            // self.middleware missing because we don't want to force Debug on Middleware trait.
706            .finish_non_exhaustive()
707    }
708}
709
710#[cfg(test)]
711mod tests {
712    use super::*;
713
714    ///////////////////// AGENT TESTS //////////////////////////////
715
716    #[test]
717    fn agent_implements_send_and_sync() {
718        let _agent: Box<dyn Send> = Box::new(AgentBuilder::new().build());
719        let _agent: Box<dyn Sync> = Box::new(AgentBuilder::new().build());
720    }
721
722    #[test]
723    fn agent_config_debug() {
724        let agent = AgentBuilder::new().build();
725        let debug_format = format!("{:?}", agent);
726        assert!(debug_format.contains("Agent"));
727        assert!(debug_format.contains("config:"));
728        assert!(debug_format.contains("proxy:"));
729        assert!(debug_format.contains("timeout_connect:"));
730        assert!(debug_format.contains("timeout_read:"));
731        assert!(debug_format.contains("timeout_write:"));
732        assert!(debug_format.contains("timeout:"));
733        assert!(debug_format.contains("https_only:"));
734        assert!(debug_format.contains("no_delay:"));
735        assert!(debug_format.contains("redirects:"));
736        assert!(debug_format.contains("redirect_auth_headers:"));
737        assert!(debug_format.contains("user_agent:"));
738        assert!(debug_format.contains("tls_config:"));
739        assert!(debug_format.contains("state:"));
740        assert!(debug_format.contains("pool:"));
741    }
742}