Skip to main content

cloud_sdk_reqwest/asynchronous/
config.rs

1use core::fmt;
2
3use reqwest::Client;
4use reqwest::redirect::Policy;
5use reqwest::tls::Version;
6
7use crate::shared::{BearerToken, BuildError, HttpsEndpoint, RequestTimeouts, UserAgent};
8
9use super::AsyncClient;
10
11/// Builder requiring endpoint, bearer token, user agent, and all timeout
12/// dimensions before an asynchronous client can be constructed.
13pub struct AsyncClientBuilder {
14    endpoint: HttpsEndpoint,
15    token: BearerToken,
16    user_agent: UserAgent,
17    timeouts: RequestTimeouts,
18}
19
20impl AsyncClientBuilder {
21    /// Creates a complete asynchronous-client configuration.
22    #[must_use]
23    pub const fn new(
24        endpoint: HttpsEndpoint,
25        token: BearerToken,
26        user_agent: UserAgent,
27        timeouts: RequestTimeouts,
28    ) -> Self {
29        Self {
30            endpoint,
31            token,
32            user_agent,
33            timeouts,
34        }
35    }
36
37    /// Builds a hardened HTTPS-only client.
38    ///
39    /// Sending requests requires an active Tokio executor because reqwest uses
40    /// Tokio internally. The core [`cloud_sdk::transport::AsyncTransport`]
41    /// contract remains executor-neutral.
42    pub fn build(self) -> Result<AsyncClient, BuildError> {
43        self.build_inner(true)
44    }
45
46    fn build_inner(self, https_only: bool) -> Result<AsyncClient, BuildError> {
47        let client = configured_client(&self.user_agent, self.timeouts, https_only)?;
48        Ok(AsyncClient::new(client, self.endpoint, self.token))
49    }
50
51    #[cfg(test)]
52    pub(super) fn build_for_loopback(self) -> Result<AsyncClient, BuildError> {
53        self.build_inner(false)
54    }
55}
56
57impl fmt::Debug for AsyncClientBuilder {
58    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
59        formatter
60            .debug_struct("AsyncClientBuilder")
61            .field("endpoint", &"[redacted]")
62            .field("token", &"[redacted]")
63            .field("user_agent", &self.user_agent)
64            .field("timeouts", &self.timeouts)
65            .finish()
66    }
67}
68
69fn configured_client(
70    user_agent: &UserAgent,
71    timeouts: RequestTimeouts,
72    https_only: bool,
73) -> Result<Client, BuildError> {
74    Client::builder()
75        .tls_backend_rustls()
76        .https_only(https_only)
77        .http1_only()
78        .no_hickory_dns()
79        .min_tls_version(Version::TLS_1_2)
80        .redirect(Policy::none())
81        .retry(reqwest::retry::never())
82        .referer(false)
83        .no_proxy()
84        .no_gzip()
85        .no_brotli()
86        .no_zstd()
87        .no_deflate()
88        .timeout(timeouts.total())
89        .connect_timeout(timeouts.connect())
90        .connection_verbose(false)
91        .user_agent(user_agent.value.clone())
92        .build()
93        .map_err(|_| BuildError::ClientBuildFailed)
94}