Skip to main content

reqwest_rate_limit/reqwest_wrapper/
client.rs

1use crate::{NoopResponseMiddleware, RequestBuilder, ResponseMiddleware};
2use std::sync::Arc;
3
4/// Wrapper client that exposes ergonomic request builders with rate limiting.
5#[derive(Debug, Clone)]
6pub struct Client<MW = NoopResponseMiddleware> {
7    inner: reqwest::Client,
8    response_middleware: MW,
9    rate_limiter: Option<Arc<governor::DefaultDirectRateLimiter>>,
10}
11
12impl Client {
13    /// Start building a wrapper client with default middleware.
14    pub fn builder() -> ClientBuilder<NoopResponseMiddleware> {
15        ClientBuilder::new()
16    }
17}
18
19impl<MW> Client<MW>
20where
21    MW: ResponseMiddleware + Clone,
22{
23    /// Begin a GET request.
24    pub fn get<U: reqwest::IntoUrl>(&self, url: U) -> RequestBuilder<MW> {
25        let inner = self.inner.get(url);
26        RequestBuilder::from_parts(self.clone(), inner)
27    }
28
29    /// Begin a POST request.
30    pub fn post<U: reqwest::IntoUrl>(&self, url: U) -> RequestBuilder<MW> {
31        let inner = self.inner.post(url);
32        RequestBuilder::from_parts(self.clone(), inner)
33    }
34
35    /// Begin a PUT request.
36    pub fn put<U: reqwest::IntoUrl>(&self, url: U) -> RequestBuilder<MW> {
37        let inner = self.inner.put(url);
38        RequestBuilder::from_parts(self.clone(), inner)
39    }
40
41    /// Begin a PATCH request.
42    pub fn patch<U: reqwest::IntoUrl>(&self, url: U) -> RequestBuilder<MW> {
43        let inner = self.inner.patch(url);
44        RequestBuilder::from_parts(self.clone(), inner)
45    }
46
47    /// Begin a DELETE request.
48    pub fn delete<U: reqwest::IntoUrl>(&self, url: U) -> RequestBuilder<MW> {
49        let inner = self.inner.delete(url);
50        RequestBuilder::from_parts(self.clone(), inner)
51    }
52
53    /// Begin a HEAD request.
54    pub fn head<U: reqwest::IntoUrl>(&self, url: U) -> RequestBuilder<MW> {
55        let inner = self.inner.head(url);
56        RequestBuilder::from_parts(self.clone(), inner)
57    }
58
59    /// Begin a request with an explicit HTTP method.
60    pub fn request<U: reqwest::IntoUrl>(
61        &self,
62        method: reqwest::Method,
63        url: U,
64    ) -> RequestBuilder<MW> {
65        let inner = self.inner.request(method, url);
66        RequestBuilder::from_parts(self.clone(), inner)
67    }
68
69    pub(crate) fn middleware(&self) -> &MW {
70        &self.response_middleware
71    }
72
73    pub(crate) fn rate_limiter(&self) -> Option<Arc<governor::DefaultDirectRateLimiter>> {
74        self.rate_limiter.clone()
75    }
76}
77
78/// Builder for the wrapper client.
79pub struct ClientBuilder<MW> {
80    inner: reqwest::ClientBuilder,
81    response_middleware: MW,
82    rate_limiter: Option<Arc<governor::DefaultDirectRateLimiter>>,
83}
84
85impl Default for ClientBuilder<NoopResponseMiddleware> {
86    fn default() -> Self {
87        Self::new()
88    }
89}
90
91impl ClientBuilder<NoopResponseMiddleware> {
92    /// Create a new builder with default middleware and no rate limiter.
93    pub fn new() -> Self {
94        Self {
95            inner: reqwest::Client::builder(),
96            response_middleware: NoopResponseMiddleware,
97            rate_limiter: None,
98        }
99    }
100}
101
102impl<MW> ClientBuilder<MW>
103where
104    MW: ResponseMiddleware + Clone,
105{
106    /// Wrap an existing `reqwest::ClientBuilder`.
107    ///
108    /// # Examples
109    ///
110    /// ```no_run
111    /// let base = reqwest::Client::builder().https_only(true);
112    /// let client = reqwest_rate_limit::ClientBuilder::from_reqwest_builder(
113    ///     base,
114    ///     reqwest_rate_limit::NoopResponseMiddleware,
115    /// )
116    /// .build()
117    /// .unwrap();
118    /// ```
119    pub fn from_reqwest_builder(inner: reqwest::ClientBuilder, response_middleware: MW) -> Self {
120        Self {
121            inner,
122            response_middleware,
123            rate_limiter: None,
124        }
125    }
126
127    /// Configure the underlying `reqwest::ClientBuilder`.
128    ///
129    /// # Examples
130    ///
131    /// ```no_run
132    /// let client = reqwest_rate_limit::Client::builder()
133    ///     .configure(|b| b.timeout(std::time::Duration::from_secs(5)))
134    ///     .build()
135    ///     .unwrap();
136    /// ```
137    pub fn configure<F>(self, f: F) -> Self
138    where
139        F: FnOnce(reqwest::ClientBuilder) -> reqwest::ClientBuilder,
140    {
141        Self {
142            inner: f(self.inner),
143            response_middleware: self.response_middleware,
144            rate_limiter: self.rate_limiter,
145        }
146    }
147
148    /// Replace the response middleware for this client.
149    pub fn response_middleware<NewMW>(self, response_middleware: NewMW) -> ClientBuilder<NewMW>
150    where
151        NewMW: ResponseMiddleware + Clone,
152    {
153        ClientBuilder {
154            inner: self.inner,
155            response_middleware,
156            rate_limiter: self.rate_limiter,
157        }
158    }
159
160    /// Attach a shared rate limiter used by default for requests.
161    ///
162    /// # Examples
163    ///
164    /// ```no_run
165    /// use governor::Quota;
166    /// use std::num::NonZeroU32;
167    /// use std::sync::Arc;
168    ///
169    /// let limiter = Arc::new(governor::RateLimiter::direct(Quota::per_minute(
170    ///     NonZeroU32::new(60).unwrap(),
171    /// )));
172    ///
173    /// let client = reqwest_rate_limit::Client::builder()
174    ///     .rate_limiter(limiter)
175    ///     .build()
176    ///     .unwrap();
177    /// ```
178    pub fn rate_limiter(
179        self,
180        rate_limiter: Arc<governor::DefaultDirectRateLimiter>,
181    ) -> ClientBuilder<MW> {
182        ClientBuilder {
183            inner: self.inner,
184            response_middleware: self.response_middleware,
185            rate_limiter: Some(rate_limiter),
186        }
187    }
188
189    /// Set the User-Agent header for all requests.
190    pub fn user_agent<V>(self, value: V) -> Self
191    where
192        V: TryInto<reqwest::header::HeaderValue>,
193        V::Error: Into<http::Error>,
194    {
195        Self {
196            inner: self.inner.user_agent(value),
197            response_middleware: self.response_middleware,
198            rate_limiter: self.rate_limiter,
199        }
200    }
201
202    /// Set default headers for all requests.
203    pub fn default_headers(self, headers: reqwest::header::HeaderMap) -> Self {
204        Self {
205            inner: self.inner.default_headers(headers),
206            response_middleware: self.response_middleware,
207            rate_limiter: self.rate_limiter,
208        }
209    }
210
211    /// Set a default request timeout.
212    pub fn timeout(self, timeout: std::time::Duration) -> Self {
213        Self {
214            inner: self.inner.timeout(timeout),
215            response_middleware: self.response_middleware,
216            rate_limiter: self.rate_limiter,
217        }
218    }
219
220    /// Build the wrapper client.
221    pub fn build(self) -> Result<Client<MW>, reqwest::Error> {
222        let inner = self.inner.build()?;
223        Ok(Client {
224            inner,
225            response_middleware: self.response_middleware,
226            rate_limiter: self.rate_limiter,
227        })
228    }
229
230    /// Attach an existing `reqwest::Client`.
231    pub fn attach_client(self, client: reqwest::Client) -> Client<MW> {
232        Client {
233            inner: client,
234            response_middleware: self.response_middleware,
235            rate_limiter: self.rate_limiter,
236        }
237    }
238}