twilight_http/client/
builder.rs

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