use std::error::Error;
use std::future::Future;
pub use hyper::body::Bytes;
pub use hyper::Body;
#[cfg(feature = "ureq")]
mod ureq_impl;
#[cfg(feature = "ureq")]
pub use ureq_impl::UreqError;
#[cfg(feature = "surf")]
mod surf_impl;
#[cfg(feature = "surf")]
pub use surf_impl::SurfError;
#[cfg(feature = "reqwest")]
mod reqwest_impl;
#[cfg(feature = "reqwest")]
pub use reqwest_impl::ReqwestClientDefaultError;
pub static TWITCH_API2_USER_AGENT: &str =
concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
pub type BoxedFuture<'a, T> = std::pin::Pin<Box<dyn Future<Output = T> + Send + 'a>>;
pub type Request = http::Request<Bytes>;
pub type Response = http::Response<Body>;
pub trait ResponseExt {
type Error;
fn into_response_vec<'a>(self)
-> BoxedFuture<'a, Result<http::Response<Vec<u8>>, Self::Error>>;
fn into_response_bytes<'a>(
self,
) -> BoxedFuture<'a, Result<http::Response<hyper::body::Bytes>, Self::Error>>;
}
impl<Buffer> ResponseExt for http::Response<Buffer>
where Buffer: Into<hyper::body::Body>
{
type Error = hyper::Error;
fn into_response_vec<'a>(
self,
) -> BoxedFuture<'a, Result<http::Response<Vec<u8>>, Self::Error>> {
let (parts, body) = self.into_parts();
let body: Body = body.into();
Box::pin(async move {
let body = hyper::body::to_bytes(body).await?.to_vec();
Ok(http::Response::from_parts(parts, body))
})
}
fn into_response_bytes<'a>(
self,
) -> BoxedFuture<'a, Result<http::Response<hyper::body::Bytes>, Self::Error>> {
let (parts, body) = self.into_parts();
let body: Body = body.into();
Box::pin(async move {
let body = hyper::body::to_bytes(body).await?;
Ok(http::Response::from_parts(parts, body))
})
}
}
pub trait RequestExt {
type Error;
fn into_request_vec<'a>(self) -> BoxedFuture<'a, Result<http::Request<Vec<u8>>, Self::Error>>;
}
impl<Buffer> RequestExt for http::Request<Buffer>
where Buffer: Into<hyper::body::Body>
{
type Error = hyper::Error;
fn into_request_vec<'a>(self) -> BoxedFuture<'a, Result<http::Request<Vec<u8>>, Self::Error>> {
let (parts, body) = self.into_parts();
let body = body.into();
Box::pin(async move {
let body = hyper::body::to_bytes(body).await?.to_vec();
Ok(http::Request::from_parts(parts, body))
})
}
}
pub trait Client<'a>: Send + Sync + 'a {
type Error: Error + Send + Sync + 'static;
fn req(
&'a self,
request: Request,
) -> BoxedFuture<'a, Result<Response, <Self as Client>::Error>>;
}
pub trait ClientDefault<'a>: Clone + Sized {
type Error: std::error::Error + Send + Sync + 'static;
fn default_client() -> Self {
Self::default_client_with_name(None)
.expect("a new twitch_api2 client without an extra product should never fail")
}
fn default_client_with_name(product: Option<http::HeaderValue>) -> Result<Self, Self::Error>;
}
#[derive(Debug, Default, thiserror::Error, Clone)]
#[error("this client does not do anything, only used for documentation test that only checks")]
pub struct DummyHttpClient;
impl<'a> Client<'a> for DummyHttpClient {
type Error = DummyHttpClient;
fn req(&'a self, _: Request) -> BoxedFuture<'a, Result<Response, Self::Error>> {
Box::pin(async { Err(DummyHttpClient) })
}
}
impl<'a> Client<'a> for twitch_oauth2::client::DummyClient {
type Error = twitch_oauth2::client::DummyClient;
fn req(&'a self, _: Request) -> BoxedFuture<'a, Result<Response, Self::Error>> {
Box::pin(async { Err(twitch_oauth2::client::DummyClient) })
}
}
impl<'a, C> Client<'a> for std::sync::Arc<C>
where C: Client<'a>
{
type Error = <C as Client<'a>>::Error;
fn req(&'a self, req: Request) -> BoxedFuture<'a, Result<Response, Self::Error>> {
self.as_ref().req(req)
}
}
#[cfg(feature = "surf")]
impl ClientDefault<'static> for DummyHttpClient
where Self: Default
{
type Error = DummyHttpClient;
fn default_client_with_name(_: Option<http::HeaderValue>) -> Result<Self, Self::Error> {
Ok(Self)
}
}
#[derive(Debug, thiserror::Error)]
pub enum CompatError<E> {
#[error("could not get the body of the response")]
BodyError(#[source] hyper::Error),
#[error(transparent)]
Other(#[from] E),
}
#[cfg(feature = "helix")]
impl<'a, C: Client<'a> + Sync> twitch_oauth2::client::Client<'a> for crate::HelixClient<'a, C> {
type Error = CompatError<<C as Client<'a>>::Error>;
fn req(
&'a self,
request: http::Request<Vec<u8>>,
) -> BoxedFuture<
'a,
Result<http::Response<Vec<u8>>, <Self as twitch_oauth2::client::Client>::Error>,
> {
let client = self.get_client();
{
let request = request.map(Bytes::from);
let resp = client.req(request);
Box::pin(async {
let resp = resp.await?;
let (parts, mut body) = resp.into_parts();
Ok(http::Response::from_parts(
parts,
hyper::body::to_bytes(&mut body)
.await
.map_err(CompatError::BodyError)?
.to_vec(),
))
})
}
}
}
#[cfg(feature = "tmi")]
impl<'a, C: Client<'a> + Sync> twitch_oauth2::client::Client<'a> for crate::TmiClient<'a, C> {
type Error = CompatError<<C as Client<'a>>::Error>;
fn req(
&'a self,
request: http::Request<Vec<u8>>,
) -> BoxedFuture<
'a,
Result<http::Response<Vec<u8>>, <Self as twitch_oauth2::client::Client>::Error>,
> {
let client = self.get_client();
{
let request = request.map(|b| Bytes::from(b));
let resp = client.req(request);
Box::pin(async {
let resp = resp.await?;
let (parts, mut body) = resp.into_parts();
Ok(http::Response::from_parts(
parts,
hyper::body::to_bytes(&mut body)
.await
.map_err(CompatError::BodyError)?
.to_vec(),
))
})
}
}
}
#[cfg(any(feature = "tmi", feature = "helix"))]
impl<'a, C: Client<'a> + Sync> twitch_oauth2::client::Client<'a> for crate::TwitchClient<'a, C> {
type Error = CompatError<<C as Client<'a>>::Error>;
fn req(
&'a self,
request: http::Request<Vec<u8>>,
) -> BoxedFuture<
'a,
Result<http::Response<Vec<u8>>, <Self as twitch_oauth2::client::Client>::Error>,
> {
let client = self.get_client();
{
let request = request.map(|b| Bytes::from(b));
let resp = client.req(request);
Box::pin(async {
let resp = resp.await?;
let (parts, mut body) = resp.into_parts();
Ok(http::Response::from_parts(
parts,
hyper::body::to_bytes(&mut body)
.await
.map_err(CompatError::BodyError)?
.to_vec(),
))
})
}
}
}
pub fn user_agent(
product: Option<http::HeaderValue>,
) -> Result<http::HeaderValue, http::header::InvalidHeaderValue> {
use std::convert::TryInto;
if let Some(product) = product {
let mut user_agent = product.as_bytes().to_owned();
user_agent.push(b' ');
user_agent.extend(TWITCH_API2_USER_AGENT.as_bytes());
user_agent.as_slice().try_into()
} else {
http::HeaderValue::from_str(TWITCH_API2_USER_AGENT)
}
}