Skip to main content

twitch_oauth2/
client.rs

1//! Provides different http clients
2
3// This module is heavily inspired (read: copied) by twitch_api2::client.
4
5use std::error::Error;
6use std::future::Future;
7
8/// The User-Agent `product` of this crate.
9pub static TWITCH_OAUTH2_USER_AGENT: &str =
10    concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
11
12/// A client that can do OAUTH2 requests
13pub trait Client: Sync + Send {
14    /// Error returned by the client
15    type Error: Error + Send + Sync + 'static;
16    /// Send a request
17    fn req(
18        &self,
19        request: http::Request<Vec<u8>>,
20    ) -> impl Future<Output = Result<http::Response<Vec<u8>>, <Self as Client>::Error>> + Send + use<Self>;
21}
22
23#[doc(hidden)]
24#[derive(Debug, thiserror::Error, Clone)]
25#[error("this client does not do anything, only used for documentation test that only checks code integrity")]
26pub struct DummyClient;
27
28#[cfg(feature = "reqwest")]
29impl Client for DummyClient {
30    type Error = DummyClient;
31
32    fn req(
33        &self,
34        _: http::Request<Vec<u8>>,
35    ) -> impl Future<Output = Result<http::Response<Vec<u8>>, Self::Error>> + Send + use<> {
36        std::future::ready(Err(self.clone()))
37    }
38}
39#[cfg(feature = "reqwest")]
40use reqwest::Client as ReqwestClient;
41
42#[cfg(feature = "reqwest")]
43impl Client for ReqwestClient {
44    type Error = reqwest::Error;
45
46    fn req(
47        &self,
48        request: http::Request<Vec<u8>>,
49    ) -> impl Future<Output = Result<http::Response<Vec<u8>>, Self::Error>> + Send + use<> {
50        use futures::future::Either;
51
52        // Reqwest plays really nice here and has a try_from on `http::Request` -> `reqwest::Request`
53        let req = match reqwest::Request::try_from(request) {
54            Ok(req) => req,
55            Err(e) => return Either::Right(async move { Err(e) }),
56        };
57        // We need to "call" the execute outside the async closure to not capture self.
58        let fut = self.execute(req);
59        // Await the request and translate to `http::Response`
60        let fut = async move {
61            let mut response = fut.await?;
62            let mut result = http::Response::builder().status(response.status());
63            let headers = result
64                .headers_mut()
65                // This should not fail, we just created the response.
66                .expect("expected to get headers mut when building response");
67            std::mem::swap(headers, response.headers_mut());
68            let result = result.version(response.version());
69            Ok(result
70                .body(response.bytes().await?.as_ref().to_vec())
71                .expect("mismatch reqwest -> http conversion should not fail"))
72        };
73        Either::Left(fut)
74    }
75}
76
77#[cfg(all(feature = "reqwest", test))]
78mod tests {
79    use super::*;
80
81    #[test]
82    /// Test that the returned future does not depend on the lifetime of `self`
83    fn reqwest_capture() {
84        fn inner() -> impl Future<Output = Result<http::Response<Vec<u8>>, reqwest::Error>> + Send {
85            let client = ReqwestClient::new();
86            client.req(http::Request::new(vec![]))
87        }
88        let _fut = inner();
89    }
90}