Skip to main content

twilight_http/client/
builder.rs

1#[cfg(not(target_os = "wasi"))]
2use super::Token;
3use crate::Client;
4#[cfg(not(target_os = "wasi"))]
5use crate::client::connector;
6use http::header::HeaderMap;
7#[cfg(not(target_os = "wasi"))]
8use hyper_util::rt::TokioExecutor;
9use std::{
10    sync::{Arc, atomic::AtomicBool},
11    time::Duration,
12};
13#[cfg(not(target_os = "wasi"))]
14use twilight_http_ratelimiting::RateLimiter;
15use twilight_model::channel::message::AllowedMentions;
16
17/// A builder for [`Client`].
18#[derive(Debug)]
19#[must_use = "has no effect if not built into a Client"]
20#[cfg(not(target_os = "wasi"))]
21pub struct ClientBuilder {
22    pub(crate) default_allowed_mentions: Option<AllowedMentions>,
23    pub(crate) proxy: Option<Box<str>>,
24    ratelimiter: Option<RateLimiter>,
25    remember_invalid_token: bool,
26    pub(crate) default_headers: Option<HeaderMap>,
27    pub(crate) timeout: Duration,
28    pub(super) token: Option<Token>,
29    pub(crate) use_http: bool,
30}
31
32/// A builder for [`Client`].
33#[derive(Debug)]
34#[must_use = "has no effect if not built into a Client"]
35#[cfg(target_os = "wasi")]
36pub struct ClientBuilder {
37    pub(crate) default_allowed_mentions: Option<AllowedMentions>,
38}
39
40impl ClientBuilder {
41    /// Create a new builder to create a [`Client`].
42    pub fn new() -> Self {
43        Self::default()
44    }
45
46    /// Build the [`Client`].
47    #[cfg(not(target_os = "wasi"))]
48    pub fn build(self) -> Client {
49        let connector = connector::create();
50
51        let http =
52            hyper_util::client::legacy::Client::builder(TokioExecutor::new()).build(connector);
53
54        let token_invalidated = if self.remember_invalid_token {
55            Some(Arc::new(AtomicBool::new(false)))
56        } else {
57            None
58        };
59
60        Client {
61            http,
62            default_headers: self.default_headers,
63            proxy: self.proxy,
64            ratelimiter: self.ratelimiter,
65            timeout: self.timeout,
66            token_invalidated,
67            token: self.token,
68            default_allowed_mentions: self.default_allowed_mentions,
69            use_http: self.use_http,
70        }
71    }
72
73    /// Build the [`Client`].
74    #[cfg(target_os = "wasi")]
75    pub fn build(self) -> Client {
76        Client {
77            default_allowed_mentions: self.default_allowed_mentions,
78        }
79    }
80
81    /// Set the default allowed mentions setting to use on all messages sent through the HTTP
82    /// client.
83    pub fn default_allowed_mentions(mut self, allowed_mentions: AllowedMentions) -> Self {
84        self.default_allowed_mentions.replace(allowed_mentions);
85
86        self
87    }
88
89    /// Set the proxy to use for all HTTP(S) requests.
90    ///
91    /// **Note** that this isn't currently a traditional proxy, but is for
92    /// working with something like [twilight's HTTP proxy server].
93    ///
94    /// # Examples
95    ///
96    /// Set the proxy to `twilight_http_proxy.internal`:
97    ///
98    /// ```no_run
99    /// use twilight_http::Client;
100    ///
101    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
102    /// let client = Client::builder()
103    ///     .proxy("twilight_http_proxy.internal".to_owned(), true)
104    ///     .build();
105    /// # Ok(()) }
106    /// ```
107    ///
108    /// [twilight's HTTP proxy server]: https://github.com/twilight-rs/http-proxy
109    #[cfg(not(target_os = "wasi"))]
110    pub fn proxy(mut self, proxy_url: String, use_http: bool) -> Self {
111        self.proxy.replace(proxy_url.into_boxed_str());
112        self.use_http = use_http;
113
114        self
115    }
116
117    /// Set a ratelimiter to use.
118    ///
119    /// If the argument is `None` then the client's ratelimiter will be skipped
120    /// before making a request.
121    ///
122    /// If not called, then a default [`RateLimiter`] will be created by
123    /// [`ClientBuilder::build`].
124    #[allow(clippy::missing_const_for_fn)]
125    #[cfg(not(target_os = "wasi"))]
126    pub fn ratelimiter(mut self, ratelimiter: Option<RateLimiter>) -> Self {
127        self.ratelimiter = ratelimiter;
128
129        self
130    }
131
132    /// Set the timeout for HTTP requests.
133    ///
134    /// The default is 10 seconds.
135    #[cfg(not(target_os = "wasi"))]
136    pub const fn timeout(mut self, duration: Duration) -> Self {
137        self.timeout = duration;
138
139        self
140    }
141
142    /// Set a group headers which are sent in every request.
143    #[cfg(not(target_os = "wasi"))]
144    pub fn default_headers(mut self, headers: HeaderMap) -> Self {
145        self.default_headers.replace(headers);
146
147        self
148    }
149
150    /// Whether to remember whether the client has encountered an Unauthorized
151    /// response status.
152    ///
153    /// If the client remembers encountering an Unauthorized response, then it
154    /// will not process future requests.
155    ///
156    /// Defaults to true.
157    #[cfg(not(target_os = "wasi"))]
158    pub const fn remember_invalid_token(mut self, remember: bool) -> Self {
159        self.remember_invalid_token = remember;
160
161        self
162    }
163
164    /// Set the token to use for HTTP requests.
165    #[cfg(not(target_os = "wasi"))]
166    pub fn token(mut self, mut token: String) -> Self {
167        let is_bot = token.starts_with("Bot ");
168        let is_bearer = token.starts_with("Bearer ");
169
170        // Make sure it is either a bot or bearer token, and assume it's a bot
171        // token if no prefix is given
172        if !is_bot && !is_bearer {
173            token.insert_str(0, "Bot ");
174        }
175
176        self.token.replace(Token::new(token.into_boxed_str()));
177
178        self
179    }
180}
181
182impl Default for ClientBuilder {
183    #[cfg(not(target_os = "wasi"))]
184    fn default() -> Self {
185        Self {
186            default_allowed_mentions: None,
187            default_headers: None,
188            proxy: None,
189            ratelimiter: (!cfg!(test)).then(RateLimiter::default),
190            remember_invalid_token: true,
191            timeout: Duration::from_secs(10),
192            token: None,
193            use_http: false,
194        }
195    }
196
197    #[cfg(target_os = "wasi")]
198    fn default() -> Self {
199        Self {
200            default_allowed_mentions: None,
201        }
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use super::ClientBuilder;
208    use static_assertions::assert_impl_all;
209    use std::fmt::Debug;
210
211    assert_impl_all!(ClientBuilder: Debug, Default, Send, Sync);
212
213    #[test]
214    fn client_debug() {
215        assert!(
216            format!("{:?}", ClientBuilder::new().token("Bot foo".to_owned()))
217                .contains("token: Some(<redacted>)")
218        );
219        assert!(format!("{:?}", ClientBuilder::new()).contains("token: None"));
220    }
221}