switchy_http/
lib.rs

1#![cfg_attr(feature = "fail-on-warnings", deny(warnings))]
2#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
3#![allow(clippy::multiple_crate_versions)]
4
5use std::{collections::BTreeMap, marker::PhantomData};
6
7use async_trait::async_trait;
8use bytes::Bytes;
9use strum::{AsRefStr, EnumString};
10use switchy_http_models::{Method, StatusCode};
11use thiserror::Error;
12
13pub use switchy_http_models as models;
14
15#[cfg(feature = "reqwest")]
16pub mod reqwest;
17
18#[cfg(feature = "simulator")]
19pub mod simulator;
20
21#[derive(Debug, Error)]
22pub enum Error {
23    #[error("Decode")]
24    Decode,
25
26    #[cfg(feature = "json")]
27    #[error(transparent)]
28    Deserialize(#[from] serde_json::Error),
29
30    #[cfg(feature = "reqwest")]
31    #[error(transparent)]
32    Reqwest(#[from] ::reqwest::Error),
33}
34
35#[derive(Debug, Clone, Copy, EnumString, AsRefStr)]
36#[strum(serialize_all = "kebab-case")]
37pub enum Header {
38    Authorization,
39    UserAgent,
40    Range,
41    ContentLength,
42}
43
44#[async_trait]
45pub trait GenericRequestBuilder<R>: Send + Sync {
46    fn header(&mut self, name: &str, value: &str);
47    #[allow(unused)]
48    fn body(&mut self, body: Bytes);
49    #[cfg(feature = "json")]
50    fn form(&mut self, form: &serde_json::Value);
51    async fn send(&mut self) -> Result<R, Error>;
52}
53
54pub trait GenericClientBuilder<RB, C: GenericClient<RB>>: Send + Sync {
55    /// # Errors
56    ///
57    /// * If the `Client` fails to build
58    fn build(self) -> Result<C, Error>;
59}
60
61pub trait GenericClient<RB>: Send + Sync {
62    fn get(&self, url: &str) -> RB {
63        self.request(Method::Get, url)
64    }
65
66    fn post(&self, url: &str) -> RB {
67        self.request(Method::Post, url)
68    }
69
70    fn put(&self, url: &str) -> RB {
71        self.request(Method::Put, url)
72    }
73
74    fn patch(&self, url: &str) -> RB {
75        self.request(Method::Patch, url)
76    }
77
78    fn delete(&self, url: &str) -> RB {
79        self.request(Method::Delete, url)
80    }
81
82    fn head(&self, url: &str) -> RB {
83        self.request(Method::Head, url)
84    }
85
86    fn options(&self, url: &str) -> RB {
87        self.request(Method::Options, url)
88    }
89
90    fn request(&self, method: Method, url: &str) -> RB;
91}
92
93#[async_trait]
94pub trait GenericResponse: Send + Sync {
95    fn status(&self) -> StatusCode;
96    fn headers(&mut self) -> &BTreeMap<String, String>;
97    async fn text(&mut self) -> Result<String, Error>;
98    async fn bytes(&mut self) -> Result<Bytes, Error>;
99    #[cfg(feature = "stream")]
100    fn bytes_stream(
101        &mut self,
102    ) -> std::pin::Pin<Box<dyn futures_core::Stream<Item = Result<Bytes, Error>> + Send>>;
103}
104
105pub struct RequestBuilderWrapper<R, B: GenericRequestBuilder<R>>(
106    pub(crate) B,
107    pub(crate) PhantomData<R>,
108);
109pub struct ClientWrapper<RB, T: GenericClient<RB>>(pub(crate) T, pub(crate) PhantomData<RB>);
110pub struct ClientBuilderWrapper<RB, C: GenericClient<RB>, T: GenericClientBuilder<RB, C>>(
111    pub(crate) T,
112    PhantomData<RB>,
113    PhantomData<C>,
114);
115pub struct ResponseWrapper<T: GenericResponse>(pub(crate) T);
116
117#[allow(unused)]
118macro_rules! impl_http {
119    ($module:ident, $local_module:ident $(,)?) => {
120        paste::paste! {
121            pub use [< impl_ $module >]::*;
122        }
123
124        mod $local_module {
125            use crate::*;
126
127            paste::paste! {
128                pub type [< $module:camel Response >] = ResponseWrapper<$module::Response>;
129                type ModuleResponse = [< $module:camel Response >];
130
131                pub type [< $module:camel RequestBuilder >] = RequestBuilderWrapper<ModuleResponse, $module::RequestBuilder>;
132                type ModuleRequestBuilder = [< $module:camel RequestBuilder >];
133
134                pub type [< $module:camel Client >] = ClientWrapper<ModuleRequestBuilder, $module::Client>;
135                type ModuleClient = [< $module:camel Client >];
136
137                pub type [< $module:camel ClientBuilder >] = ClientBuilderWrapper<ModuleRequestBuilder, ModuleClient, $module::ClientBuilder>;
138                type ModuleClientBuilder = [< $module:camel ClientBuilder >];
139            }
140
141            impl ModuleRequestBuilder {
142                #[must_use]
143                pub fn header(mut self, name: &str, value: &str) -> Self {
144                    self.0.header(name, value);
145                    self
146                }
147
148                /// # Errors
149                ///
150                /// * If there was an error while sending request, redirect loop was
151                ///   detected or redirect limit was exhausted.
152                pub async fn send(mut self) -> Result<ModuleResponse, Error> {
153                    self.0.send().await
154                }
155            }
156
157            #[async_trait]
158            impl GenericRequestBuilder<ModuleResponse> for ModuleRequestBuilder {
159                fn header(&mut self, name: &str, value: &str) {
160                    self.0.header(name, value);
161                }
162
163                fn body(&mut self, body: Bytes) {
164                    self.0.body(body);
165                }
166
167                #[cfg(feature = "json")]
168                fn form(&mut self, form: &serde_json::Value) {
169                    self.0.form(form);
170                }
171
172                async fn send(&mut self) -> Result<ModuleResponse, Error> {
173                    self.0.send().await
174                }
175            }
176
177            #[cfg(feature = "json")]
178            impl ModuleRequestBuilder {
179                /// # Panics
180                ///
181                /// * If the `serde_json` serialization to bytes fails
182                #[must_use]
183                pub fn json<T: serde::Serialize + ?Sized>(mut self, body: &T) -> Self {
184                    let mut bytes: Vec<u8> = Vec::new();
185                    serde_json::to_writer(&mut bytes, body).unwrap();
186                    <Self as GenericRequestBuilder<ModuleResponse>>::body(&mut self, bytes.into());
187                    self
188                }
189
190                /// # Panics
191                ///
192                /// * If the `serde_json` serialization to bytes fails
193                #[must_use]
194                pub fn form<T: serde::Serialize + ?Sized>(mut self, form: &T) -> Self {
195                    let value = serde_json::to_value(form).unwrap();
196                    <Self as GenericRequestBuilder<ModuleResponse>>::form(&mut self, &value);
197                    self
198                }
199            }
200
201            #[async_trait]
202            impl GenericResponse for ModuleResponse {
203                #[must_use]
204                fn status(&self) -> StatusCode {
205                    self.0.status()
206                }
207
208                #[must_use]
209                fn headers(&mut self) -> &BTreeMap<String, String> {
210                    self.0.headers()
211                }
212
213                #[must_use]
214                async fn text(&mut self) -> Result<String, Error> {
215                    self.0.text().await
216                }
217
218                #[must_use]
219                async fn bytes(&mut self) -> Result<Bytes, Error> {
220                    self.0.bytes().await
221                }
222
223                #[must_use]
224                #[cfg(feature = "stream")]
225                fn bytes_stream(
226                    &mut self,
227                ) -> std::pin::Pin<Box<dyn futures_core::Stream<Item = Result<Bytes, Error>> + Send>>
228                {
229                    self.0.bytes_stream()
230                }
231            }
232
233            impl ModuleResponse {
234                #[must_use]
235                pub fn status(&self) -> StatusCode {
236                    <Self as GenericResponse>::status(self)
237                }
238
239                #[must_use]
240                pub fn headers(&mut self) -> &BTreeMap<String, String> {
241                    <Self as GenericResponse>::headers(self)
242                }
243
244                /// # Errors
245                ///
246                /// * If the text response fails
247                pub async fn text(mut self) -> Result<String, Error> {
248                    <Self as GenericResponse>::text(&mut self).await
249                }
250
251                /// # Errors
252                ///
253                /// * If the bytes response fails
254                pub async fn bytes(mut self) -> Result<Bytes, Error> {
255                    <Self as GenericResponse>::bytes(&mut self).await
256                }
257            }
258
259            impl GenericClientBuilder<ModuleRequestBuilder, ModuleClient> for ModuleClientBuilder {
260                fn build(self) -> Result<ModuleClient, Error> {
261                    self.0.build()
262                }
263            }
264
265            impl ModuleClientBuilder {
266                /// # Errors
267                ///
268                /// * If the `Client` fails to build
269                pub fn build(self) -> Result<ModuleClient, Error> {
270                    <Self as GenericClientBuilder<ModuleRequestBuilder, ModuleClient>>::build(self)
271                }
272            }
273
274            impl ModuleResponse {
275                /// # Errors
276                ///
277                /// * If the `bytes_stream` response fails
278                #[cfg(feature = "stream")]
279                pub fn bytes_stream(
280                    mut self,
281                ) -> impl futures_core::Stream<Item = Result<Bytes, Error>> {
282                    <Self as GenericResponse>::bytes_stream(&mut self)
283                }
284            }
285
286            impl ModuleResponse {
287                /// # Errors
288                ///
289                /// * If the json response fails
290                #[cfg(feature = "json")]
291                pub async fn json<T: serde::de::DeserializeOwned>(mut self) -> Result<T, Error> {
292                    let bytes = <Self as GenericResponse>::bytes(&mut self).await?;
293                    Ok(serde_json::from_slice(&bytes)?)
294                }
295            }
296
297            impl Default for ModuleClient {
298                fn default() -> Self {
299                    Self::new()
300                }
301            }
302
303            impl ModuleClient {
304                /// # Panics
305                ///
306                /// * If the empty `ClientBuilder` somehow fails to build
307                #[must_use]
308                pub fn new() -> Self {
309                    Self::builder().0.build().unwrap()
310                }
311
312                #[must_use]
313                pub const fn builder() -> ModuleClientBuilder {
314                    ModuleClientBuilder::new()
315                }
316
317                #[must_use]
318                pub fn get(&self, url: &str) -> ModuleRequestBuilder {
319                    <Self as GenericClient<ModuleRequestBuilder>>::get(self, url)
320                }
321
322                #[must_use]
323                pub fn post(&self, url: &str) -> ModuleRequestBuilder {
324                    <Self as GenericClient<ModuleRequestBuilder>>::post(self, url)
325                }
326
327                #[must_use]
328                pub fn put(&self, url: &str) -> ModuleRequestBuilder {
329                    <Self as GenericClient<ModuleRequestBuilder>>::put(self, url)
330                }
331
332                #[must_use]
333                pub fn patch(&self, url: &str) -> ModuleRequestBuilder {
334                    <Self as GenericClient<ModuleRequestBuilder>>::patch(self, url)
335                }
336
337                #[must_use]
338                pub fn delete(&self, url: &str) -> ModuleRequestBuilder {
339                    <Self as GenericClient<ModuleRequestBuilder>>::delete(self, url)
340                }
341
342                #[must_use]
343                pub fn head(&self, url: &str) -> ModuleRequestBuilder {
344                    <Self as GenericClient<ModuleRequestBuilder>>::head(self, url)
345                }
346
347                #[must_use]
348                pub fn options(&self, url: &str) -> ModuleRequestBuilder {
349                    <Self as GenericClient<ModuleRequestBuilder>>::options(self, url)
350                }
351
352                #[must_use]
353                pub fn request(&self, method: Method, url: &str) -> ModuleRequestBuilder {
354                    <Self as GenericClient<ModuleRequestBuilder>>::request(self, method, url)
355                }
356            }
357
358            impl Default for ModuleClientBuilder {
359                fn default() -> Self {
360                    Self::new()
361                }
362            }
363
364            impl GenericClient<ModuleRequestBuilder> for ModuleClient {
365                fn request(&self, method: Method, url: &str) -> ModuleRequestBuilder {
366                    self.0.request(method, url)
367                }
368            }
369        }
370    };
371}
372
373#[cfg(feature = "simulator")]
374impl_http!(simulator, impl_simulator);
375
376#[cfg(feature = "reqwest")]
377impl_http!(reqwest, impl_reqwest);
378
379#[allow(unused)]
380macro_rules! impl_gen_types {
381    ($module:ident $(,)?) => {
382        paste::paste! {
383            pub type RequestBuilder = [< $module:camel RequestBuilder >];
384            pub type Client = [< $module:camel Client >];
385            pub type Response = [< $module:camel Response >];
386        }
387    };
388}
389
390#[cfg(feature = "simulator")]
391impl_gen_types!(simulator);
392
393#[cfg(all(not(feature = "simulator"), feature = "reqwest"))]
394impl_gen_types!(reqwest);