use std::error::Error;
use std::future::Future;
pub use hyper::body::Bytes;
#[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;
#[cfg(feature = "tower")]
mod tower_impl;
#[cfg(feature = "tower")]
pub use tower_impl::{TowerError, TowerService};
pub static TWITCH_API_USER_AGENT: &str = concat!(
env!("CARGO_PKG_NAME"),
"/",
env!("CARGO_PKG_VERSION"),
" (+https://github.com/twitch-rs)"
);
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<Bytes>;
pub trait ResponseExt {
fn into_response_vec(self) -> http::Response<Vec<u8>>;
}
impl ResponseExt for http::Response<Bytes> {
fn into_response_vec(self) -> http::Response<Vec<u8>> {
let (parts, body) = self.into_parts();
http::Response::from_parts(parts, body.to_vec())
}
}
pub trait Client: Send + Sync {
type Error: Error + Send + Sync + 'static;
fn req(&self, request: Request) -> BoxedFuture<'_, 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_api 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 Client for DummyHttpClient {
type Error = Self;
fn req(&self, _: Request) -> BoxedFuture<'_, Result<Response, Self::Error>> {
Box::pin(async { Err(Self) })
}
}
impl Client for twitch_oauth2::client::DummyClient {
type Error = Self;
fn req(&self, _: Request) -> BoxedFuture<'_, Result<Response, Self::Error>> {
Box::pin(async { Err(Self) })
}
}
impl<C> Client for std::sync::Arc<C>
where C: Client
{
type Error = <C as Client>::Error;
fn req(&self, req: Request) -> BoxedFuture<'_, Result<Response, Self::Error>> {
self.as_ref().req(req)
}
}
impl<C: ?Sized> Client for Box<C>
where C: Client
{
type Error = <C as Client>::Error;
fn req(&self, req: Request) -> BoxedFuture<'_, Result<Response, Self::Error>> {
self.as_ref().req(req)
}
}
impl ClientDefault<'static> for DummyHttpClient
where Self: Default
{
type Error = Self;
fn default_client_with_name(_: Option<http::HeaderValue>) -> Result<Self, Self::Error> {
Ok(Self)
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
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<'c, C: Client + Sync + 'c> twitch_oauth2::client::Client for crate::HelixClient<'c, C> {
type Error = CompatError<<C as Client>::Error>;
fn req(
&self,
request: http::Request<Vec<u8>>,
) -> BoxedFuture<
'_,
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, body) = resp.into_parts();
Ok(http::Response::from_parts(parts, body.to_vec()))
})
}
}
}
#[cfg(all(feature = "client", feature = "helix"))]
impl<C: Client + Sync> twitch_oauth2::client::Client for crate::TwitchClient<'_, C> {
type Error = CompatError<<C as Client>::Error>;
fn req(
&self,
request: http::Request<Vec<u8>>,
) -> BoxedFuture<
'_,
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, body) = resp.into_parts();
Ok(http::Response::from_parts(parts, body.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_API_USER_AGENT.as_bytes());
user_agent.as_slice().try_into()
} else {
http::HeaderValue::from_str(TWITCH_API_USER_AGENT)
}
}