1use std::error::Error;
67use std::future::Future;
68
69pub use hyper::body::Bytes;
70pub use hyper::Body;
71
72#[cfg(feature = "ureq")]
73mod ureq_impl;
74#[cfg(feature = "ureq")]
75pub use ureq_impl::UreqError;
76
77#[cfg(feature = "surf")]
78mod surf_impl;
79#[cfg(feature = "surf")]
80pub use surf_impl::SurfError;
81
82#[cfg(feature = "reqwest")]
83mod reqwest_impl;
84#[cfg(feature = "reqwest")]
85pub use reqwest_impl::ReqwestClientDefaultError;
86
87pub static TWITCH_API2_USER_AGENT: &str =
89 concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
90
91pub type BoxedFuture<'a, T> = std::pin::Pin<Box<dyn Future<Output = T> + Send + 'a>>;
93
94pub type Request = http::Request<Bytes>;
96pub type Response = http::Response<Body>;
98
99pub trait ResponseExt {
101 type Error;
103 fn into_response_vec<'a>(self)
105 -> BoxedFuture<'a, Result<http::Response<Vec<u8>>, Self::Error>>;
106 fn into_response_bytes<'a>(
108 self,
109 ) -> BoxedFuture<'a, Result<http::Response<hyper::body::Bytes>, Self::Error>>;
110}
111
112impl<Buffer> ResponseExt for http::Response<Buffer>
113where Buffer: Into<hyper::body::Body>
114{
115 type Error = hyper::Error;
116
117 fn into_response_vec<'a>(
118 self,
119 ) -> BoxedFuture<'a, Result<http::Response<Vec<u8>>, Self::Error>> {
120 let (parts, body) = self.into_parts();
121 let body: Body = body.into();
122 Box::pin(async move {
123 let body = hyper::body::to_bytes(body).await?.to_vec();
124 Ok(http::Response::from_parts(parts, body))
125 })
126 }
127
128 fn into_response_bytes<'a>(
129 self,
130 ) -> BoxedFuture<'a, Result<http::Response<hyper::body::Bytes>, Self::Error>> {
131 let (parts, body) = self.into_parts();
132 let body: Body = body.into();
133 Box::pin(async move {
134 let body = hyper::body::to_bytes(body).await?;
135 Ok(http::Response::from_parts(parts, body))
136 })
137 }
138}
139
140pub trait RequestExt {
142 type Error;
144 fn into_request_vec<'a>(self) -> BoxedFuture<'a, Result<http::Request<Vec<u8>>, Self::Error>>;
146}
147
148impl<Buffer> RequestExt for http::Request<Buffer>
149where Buffer: Into<hyper::body::Body>
150{
151 type Error = hyper::Error;
152
153 fn into_request_vec<'a>(self) -> BoxedFuture<'a, Result<http::Request<Vec<u8>>, Self::Error>> {
155 let (parts, body) = self.into_parts();
156 let body = body.into();
157 Box::pin(async move {
158 let body = hyper::body::to_bytes(body).await?.to_vec();
159 Ok(http::Request::from_parts(parts, body))
160 })
161 }
162}
163
164pub trait Client<'a>: Send + Sync + 'a {
166 type Error: Error + Send + Sync + 'static;
168 fn req(
170 &'a self,
171 request: Request,
172 ) -> BoxedFuture<'a, Result<Response, <Self as Client>::Error>>;
173}
174
175pub trait ClientDefault<'a>: Clone + Sized {
177 type Error: std::error::Error + Send + Sync + 'static;
179 fn default_client() -> Self {
181 Self::default_client_with_name(None)
182 .expect("a new twitch_api2 client without an extra product should never fail")
183 }
184
185 fn default_client_with_name(product: Option<http::HeaderValue>) -> Result<Self, Self::Error>;
197}
198
199#[derive(Debug, Default, thiserror::Error, Clone)]
214#[error("this client does not do anything, only used for documentation test that only checks")]
216pub struct DummyHttpClient;
217
218impl<'a> Client<'a> for DummyHttpClient {
219 type Error = DummyHttpClient;
220
221 fn req(&'a self, _: Request) -> BoxedFuture<'a, Result<Response, Self::Error>> {
222 Box::pin(async { Err(DummyHttpClient) })
223 }
224}
225
226impl<'a> Client<'a> for twitch_oauth2::client::DummyClient {
227 type Error = twitch_oauth2::client::DummyClient;
228
229 fn req(&'a self, _: Request) -> BoxedFuture<'a, Result<Response, Self::Error>> {
230 Box::pin(async { Err(twitch_oauth2::client::DummyClient) })
231 }
232}
233
234impl<'a, C> Client<'a> for std::sync::Arc<C>
235where C: Client<'a>
236{
237 type Error = <C as Client<'a>>::Error;
238
239 fn req(&'a self, req: Request) -> BoxedFuture<'a, Result<Response, Self::Error>> {
240 self.as_ref().req(req)
241 }
242}
243
244#[cfg(feature = "surf")]
245impl ClientDefault<'static> for DummyHttpClient
246where Self: Default
247{
248 type Error = DummyHttpClient;
249
250 fn default_client_with_name(_: Option<http::HeaderValue>) -> Result<Self, Self::Error> {
251 Ok(Self)
252 }
253}
254
255#[derive(Debug, thiserror::Error)]
257pub enum CompatError<E> {
258 #[error("could not get the body of the response")]
260 BodyError(#[source] hyper::Error),
261 #[error(transparent)]
263 Other(#[from] E),
264}
265
266#[cfg(feature = "helix")]
267impl<'a, C: Client<'a> + Sync> twitch_oauth2::client::Client<'a> for crate::HelixClient<'a, C> {
268 type Error = CompatError<<C as Client<'a>>::Error>;
269
270 fn req(
271 &'a self,
272 request: http::Request<Vec<u8>>,
273 ) -> BoxedFuture<
274 'a,
275 Result<http::Response<Vec<u8>>, <Self as twitch_oauth2::client::Client>::Error>,
276 > {
277 let client = self.get_client();
278 {
279 let request = request.map(Bytes::from);
280 let resp = client.req(request);
281 Box::pin(async {
282 let resp = resp.await?;
283 let (parts, mut body) = resp.into_parts();
284 Ok(http::Response::from_parts(
285 parts,
286 hyper::body::to_bytes(&mut body)
287 .await
288 .map_err(CompatError::BodyError)?
289 .to_vec(),
290 ))
291 })
292 }
293 }
294}
295
296#[cfg(feature = "tmi")]
297impl<'a, C: Client<'a> + Sync> twitch_oauth2::client::Client<'a> for crate::TmiClient<'a, C> {
298 type Error = CompatError<<C as Client<'a>>::Error>;
299
300 fn req(
301 &'a self,
302 request: http::Request<Vec<u8>>,
303 ) -> BoxedFuture<
304 'a,
305 Result<http::Response<Vec<u8>>, <Self as twitch_oauth2::client::Client>::Error>,
306 > {
307 let client = self.get_client();
308 {
309 let request = request.map(|b| Bytes::from(b));
310 let resp = client.req(request);
311 Box::pin(async {
312 let resp = resp.await?;
313 let (parts, mut body) = resp.into_parts();
314 Ok(http::Response::from_parts(
315 parts,
316 hyper::body::to_bytes(&mut body)
317 .await
318 .map_err(CompatError::BodyError)?
319 .to_vec(),
320 ))
321 })
322 }
323 }
324}
325
326#[cfg(any(feature = "tmi", feature = "helix"))]
327impl<'a, C: Client<'a> + Sync> twitch_oauth2::client::Client<'a> for crate::TwitchClient<'a, C> {
328 type Error = CompatError<<C as Client<'a>>::Error>;
329
330 fn req(
331 &'a self,
332 request: http::Request<Vec<u8>>,
333 ) -> BoxedFuture<
334 'a,
335 Result<http::Response<Vec<u8>>, <Self as twitch_oauth2::client::Client>::Error>,
336 > {
337 let client = self.get_client();
338 {
339 let request = request.map(|b| Bytes::from(b));
340 let resp = client.req(request);
341 Box::pin(async {
342 let resp = resp.await?;
343 let (parts, mut body) = resp.into_parts();
344 Ok(http::Response::from_parts(
345 parts,
346 hyper::body::to_bytes(&mut body)
347 .await
348 .map_err(CompatError::BodyError)?
349 .to_vec(),
350 ))
351 })
352 }
353 }
354}
355
356pub fn user_agent(
358 product: Option<http::HeaderValue>,
359) -> Result<http::HeaderValue, http::header::InvalidHeaderValue> {
360 use std::convert::TryInto;
361
362 if let Some(product) = product {
363 let mut user_agent = product.as_bytes().to_owned();
364 user_agent.push(b' ');
365 user_agent.extend(TWITCH_API2_USER_AGENT.as_bytes());
366 user_agent.as_slice().try_into()
367 } else {
368 http::HeaderValue::from_str(TWITCH_API2_USER_AGENT)
369 }
370}