1use std::error::Error;
6use std::future::Future;
7
8pub static TWITCH_OAUTH2_USER_AGENT: &str =
10 concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
11
12type BoxedFuture<'a, T> = std::pin::Pin<Box<dyn Future<Output = T> + Send + 'a>>;
14
15pub trait Client: Sync + Send {
17 type Error: Error + Send + Sync + 'static;
19 fn req(
21 &self,
22 request: http::Request<Vec<u8>>,
23 ) -> BoxedFuture<'_, Result<http::Response<Vec<u8>>, <Self as Client>::Error>>;
24}
25
26#[doc(hidden)]
27#[derive(Debug, thiserror::Error, Clone)]
28#[error("this client does not do anything, only used for documentation test that only checks code integrity")]
29pub struct DummyClient;
30
31#[cfg(feature = "reqwest")]
32impl Client for DummyClient {
33 type Error = DummyClient;
34
35 fn req(
36 &self,
37 _: http::Request<Vec<u8>>,
38 ) -> BoxedFuture<'_, Result<http::Response<Vec<u8>>, Self::Error>> {
39 Box::pin(async move { Err(self.clone()) })
40 }
41}
42#[cfg(feature = "reqwest")]
43use reqwest::Client as ReqwestClient;
44
45#[cfg(feature = "reqwest")]
46impl Client for ReqwestClient {
47 type Error = reqwest::Error;
48
49 fn req(
50 &self,
51 request: http::Request<Vec<u8>>,
52 ) -> BoxedFuture<'_, Result<http::Response<Vec<u8>>, Self::Error>> {
53 let req = match reqwest::Request::try_from(request) {
55 Ok(req) => req,
56 Err(e) => return Box::pin(async { Err(e) }),
57 };
58 let fut = self.execute(req);
60 Box::pin(async move {
61 let mut response = fut.await?;
63 let mut result = http::Response::builder().status(response.status());
64 let headers = result
65 .headers_mut()
66 .expect("expected to get headers mut when building response");
68 std::mem::swap(headers, response.headers_mut());
69 let result = result.version(response.version());
70 Ok(result
71 .body(response.bytes().await?.as_ref().to_vec())
72 .expect("mismatch reqwest -> http conversion should not fail"))
73 })
74 }
75}
76
77#[cfg(feature = "surf")]
78use surf::Client as SurfClient;
79
80#[cfg(feature = "surf")]
82#[derive(Debug, displaydoc::Display, thiserror::Error)]
83pub enum SurfError {
84 Surf(surf::Error),
86 InvalidHeaderValue(#[from] http::header::InvalidHeaderValue),
88 InvalidHeaderName(#[from] http::header::InvalidHeaderName),
90 UrlError(#[from] url::ParseError),
92}
93
94#[cfg(feature = "surf")]
96fn http1_to_surf(m: &http::Method) -> surf::http::Method {
97 match *m {
98 http::Method::GET => surf::http::Method::Get,
99 http::Method::CONNECT => http_types::Method::Connect,
100 http::Method::DELETE => http_types::Method::Delete,
101 http::Method::HEAD => http_types::Method::Head,
102 http::Method::OPTIONS => http_types::Method::Options,
103 http::Method::PATCH => http_types::Method::Patch,
104 http::Method::POST => http_types::Method::Post,
105 http::Method::PUT => http_types::Method::Put,
106 http::Method::TRACE => http_types::Method::Trace,
107 _ => unimplemented!(),
108 }
109}
110
111#[cfg(feature = "surf")]
112impl Client for SurfClient {
113 type Error = SurfError;
114
115 fn req(
116 &self,
117 request: http::Request<Vec<u8>>,
118 ) -> BoxedFuture<'_, Result<http::Response<Vec<u8>>, Self::Error>> {
119 let method = http1_to_surf(request.method());
122
123 let url = match url::Url::parse(&request.uri().to_string()) {
124 Ok(url) => url,
125 Err(err) => return Box::pin(async move { Err(err.into()) }),
126 };
127 let mut req = surf::Request::new(method, url);
129
130 for (name, value) in request.headers().iter() {
132 let value =
133 match surf::http::headers::HeaderValue::from_bytes(value.as_bytes().to_vec())
134 .map_err(SurfError::Surf)
135 {
136 Ok(val) => val,
137 Err(err) => return Box::pin(async { Err(err) }),
138 };
139 req.append_header(name.as_str(), value);
140 }
141
142 req.body_bytes(request.body());
144
145 let client = self.clone();
146 Box::pin(async move {
147 let mut response = client.send(req).await.map_err(SurfError::Surf)?;
149 let mut result = http::Response::builder().status(
150 http::StatusCode::from_u16(response.status().into())
151 .expect("http_types::StatusCode only contains valid status codes"),
152 );
153
154 let mut response_headers: http::header::HeaderMap = response
155 .iter()
156 .map(|(k, v)| {
157 Ok((
158 http::header::HeaderName::from_bytes(k.as_str().as_bytes())?,
159 http::HeaderValue::from_str(v.as_str())?,
160 ))
161 })
162 .collect::<Result<_, SurfError>>()?;
163
164 let _ = std::mem::replace(&mut result.headers_mut(), Some(&mut response_headers));
165 let result = if let Some(v) = response.version() {
166 result.version(match v {
167 surf::http::Version::Http0_9 => http::Version::HTTP_09,
168 surf::http::Version::Http1_0 => http::Version::HTTP_10,
169 surf::http::Version::Http1_1 => http::Version::HTTP_11,
170 surf::http::Version::Http2_0 => http::Version::HTTP_2,
171 surf::http::Version::Http3_0 => http::Version::HTTP_3,
172 _ => http::Version::HTTP_3,
174 })
175 } else {
176 result
177 };
178 Ok(result
179 .body(response.body_bytes().await.map_err(SurfError::Surf)?)
180 .expect("mismatch surf -> http conversion should not fail"))
181 })
182 }
183}