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::{
8    BearerToken, BuildError, HttpsEndpoint, RawHyperClient, RequestTimeouts, UserAgent,
9    platform_client_config,
10};
11
12use super::{AsyncClient, RawAsyncClient};
13
14/// Builder requiring endpoint, bearer token, user agent, and all timeout
15/// dimensions before an asynchronous client can be constructed.
16pub struct AsyncClientBuilder {
17    endpoint: HttpsEndpoint,
18    token: BearerToken,
19    user_agent: UserAgent,
20    timeouts: RequestTimeouts,
21}
22
23/// Builder for a raw async client with no credentials or provider policy.
24pub struct RawAsyncClientBuilder {
25    endpoint: HttpsEndpoint,
26    user_agent: UserAgent,
27    timeouts: RequestTimeouts,
28}
29
30impl AsyncClientBuilder {
31    /// Creates a complete asynchronous-client configuration.
32    #[must_use]
33    pub const fn new(
34        endpoint: HttpsEndpoint,
35        token: BearerToken,
36        user_agent: UserAgent,
37        timeouts: RequestTimeouts,
38    ) -> Self {
39        Self {
40            endpoint,
41            token,
42            user_agent,
43            timeouts,
44        }
45    }
46
47    /// Builds a hardened HTTPS-only client.
48    ///
49    /// Sending requests requires an active Tokio executor because reqwest uses
50    /// Tokio internally. The core [`cloud_sdk::transport::AsyncTransport`]
51    /// contract remains executor-neutral.
52    pub fn build(self) -> Result<AsyncClient, BuildError> {
53        self.build_inner(true)
54    }
55
56    fn build_inner(self, https_only: bool) -> Result<AsyncClient, BuildError> {
57        let client = configured_client(&self.user_agent, self.timeouts, https_only)?;
58        Ok(AsyncClient::new(client, self.endpoint, self.token))
59    }
60
61    #[cfg(test)]
62    pub(super) fn build_for_loopback(self) -> Result<AsyncClient, BuildError> {
63        self.build_inner(false)
64    }
65}
66
67impl RawAsyncClientBuilder {
68    /// Creates a complete asynchronous raw executor configuration.
69    #[must_use]
70    pub const fn new(
71        endpoint: HttpsEndpoint,
72        user_agent: UserAgent,
73        timeouts: RequestTimeouts,
74    ) -> Self {
75        Self {
76            endpoint,
77            user_agent,
78            timeouts,
79        }
80    }
81
82    /// Builds an HTTPS-only executor with no implicit authorization.
83    pub fn build(self) -> Result<RawAsyncClient, BuildError> {
84        self.build_inner(true)
85    }
86
87    fn build_inner(self, https_only: bool) -> Result<RawAsyncClient, BuildError> {
88        let tls_config = platform_client_config()?;
89        let client = RawHyperClient::new(
90            self.endpoint.clone(),
91            &self.user_agent,
92            self.timeouts,
93            tls_config,
94            https_only,
95        )?;
96        Ok(RawAsyncClient::new(client, self.endpoint))
97    }
98
99    #[cfg(test)]
100    pub(super) fn build_for_loopback(self) -> Result<RawAsyncClient, BuildError> {
101        self.build_inner(false)
102    }
103}
104
105impl fmt::Debug for AsyncClientBuilder {
106    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
107        formatter
108            .debug_struct("AsyncClientBuilder")
109            .field("endpoint", &"[redacted]")
110            .field("token", &"[redacted]")
111            .field("user_agent", &self.user_agent)
112            .field("timeouts", &self.timeouts)
113            .finish()
114    }
115}
116
117impl fmt::Debug for RawAsyncClientBuilder {
118    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
119        formatter
120            .debug_struct("RawAsyncClientBuilder")
121            .field("endpoint", &"[redacted]")
122            .field("user_agent", &self.user_agent)
123            .field("timeouts", &self.timeouts)
124            .finish()
125    }
126}
127
128fn configured_client(
129    user_agent: &UserAgent,
130    timeouts: RequestTimeouts,
131    https_only: bool,
132) -> Result<Client, BuildError> {
133    Client::builder()
134        .tls_backend_rustls()
135        .https_only(https_only)
136        .http1_only()
137        .no_hickory_dns()
138        .min_tls_version(Version::TLS_1_2)
139        .redirect(Policy::none())
140        .retry(reqwest::retry::never())
141        .referer(false)
142        .no_proxy()
143        .no_gzip()
144        .no_brotli()
145        .no_zstd()
146        .no_deflate()
147        .timeout(timeouts.total())
148        .connect_timeout(timeouts.connect())
149        .connection_verbose(false)
150        .user_agent(user_agent.value.clone())
151        .build()
152        .map_err(|_| BuildError::ClientBuildFailed)
153}