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    BearerCredential, 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    credential: BearerCredential,
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        credential: BearerCredential,
36        user_agent: UserAgent,
37        timeouts: RequestTimeouts,
38    ) -> Self {
39        Self {
40            endpoint,
41            credential,
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        if !self.credential.scope.matches_endpoint(&self.endpoint) {
58            return Err(BuildError::CredentialEndpointMismatch);
59        }
60        let client = configured_client(&self.user_agent, self.timeouts, https_only)?;
61        Ok(AsyncClient::new(
62            client,
63            self.endpoint,
64            self.credential,
65            !https_only,
66        ))
67    }
68
69    #[cfg(test)]
70    pub(super) fn build_for_loopback(self) -> Result<AsyncClient, BuildError> {
71        self.build_inner(false)
72    }
73}
74
75impl RawAsyncClientBuilder {
76    /// Creates a complete asynchronous raw executor configuration.
77    #[must_use]
78    pub const fn new(
79        endpoint: HttpsEndpoint,
80        user_agent: UserAgent,
81        timeouts: RequestTimeouts,
82    ) -> Self {
83        Self {
84            endpoint,
85            user_agent,
86            timeouts,
87        }
88    }
89
90    /// Builds an HTTPS-only executor with no implicit authorization.
91    pub fn build(self) -> Result<RawAsyncClient, BuildError> {
92        self.build_inner(true)
93    }
94
95    fn build_inner(self, https_only: bool) -> Result<RawAsyncClient, BuildError> {
96        let tls_config = platform_client_config()?;
97        let client = RawHyperClient::new(
98            self.endpoint.clone(),
99            &self.user_agent,
100            self.timeouts,
101            tls_config,
102            https_only,
103        )?;
104        Ok(RawAsyncClient::new(client, self.endpoint))
105    }
106
107    #[cfg(test)]
108    pub(super) fn build_for_loopback(self) -> Result<RawAsyncClient, BuildError> {
109        self.build_inner(false)
110    }
111}
112
113impl fmt::Debug for AsyncClientBuilder {
114    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
115        formatter
116            .debug_struct("AsyncClientBuilder")
117            .field("endpoint", &"[redacted]")
118            .field("credential", &"[redacted]")
119            .field("user_agent", &self.user_agent)
120            .field("timeouts", &self.timeouts)
121            .finish()
122    }
123}
124
125impl fmt::Debug for RawAsyncClientBuilder {
126    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
127        formatter
128            .debug_struct("RawAsyncClientBuilder")
129            .field("endpoint", &"[redacted]")
130            .field("user_agent", &self.user_agent)
131            .field("timeouts", &self.timeouts)
132            .finish()
133    }
134}
135
136fn configured_client(
137    user_agent: &UserAgent,
138    timeouts: RequestTimeouts,
139    https_only: bool,
140) -> Result<Client, BuildError> {
141    Client::builder()
142        .tls_backend_rustls()
143        .https_only(https_only)
144        .http1_only()
145        .no_hickory_dns()
146        .min_tls_version(Version::TLS_1_2)
147        .redirect(Policy::none())
148        .retry(reqwest::retry::never())
149        .referer(false)
150        .no_proxy()
151        .no_gzip()
152        .no_brotli()
153        .no_zstd()
154        .no_deflate()
155        .timeout(timeouts.total())
156        .connect_timeout(timeouts.connect())
157        .connection_verbose(false)
158        .user_agent(user_agent.value.clone())
159        .build()
160        .map_err(|_| BuildError::ClientBuildFailed)
161}