use std::fmt;
#[cfg(not(target_family = "wasm"))]
use std::sync::{Mutex, PoisonError};
use std::task::{Context, Poll};
#[cfg(not(target_family = "wasm"))]
use tower::util::BoxCloneSyncService;
use tower::{Layer, Service, ServiceExt};
use crate::maybe::BoxFuture;
use crate::{Body, BoxError, Error, MaybeSend, MaybeSync, Method, RequestBuilder, Response};
#[cfg(not(target_family = "wasm"))]
pub type HttpService = BoxCloneSyncService<http::Request<Body>, http::Response<Body>, BoxError>;
#[cfg(target_family = "wasm")]
pub type HttpService =
crate::LocalBoxCloneService<http::Request<Body>, http::Response<Body>, BoxError>;
#[derive(Clone)]
pub struct Client {
service: HttpService,
}
impl Client {
#[cfg(feature = "reqwest")]
pub fn new() -> Self {
Self::from_reqwest(reqwest::Client::new())
}
#[cfg(feature = "reqwest")]
pub fn from_reqwest(client: reqwest::Client) -> Self {
let client = Self::from_service(crate::ReqwestService::new(client));
#[cfg(feature = "retry")]
let client = client.layer(crate::retry::RetryLayer::default());
client
}
pub fn from_service<S, B>(service: S) -> Self
where
S: Service<http::Request<Body>, Response = http::Response<B>> + Clone + MaybeSend + 'static,
S::Error: Into<BoxError>,
S::Future: MaybeSend + 'static,
B: http_body::Body + MaybeSend + MaybeSync + 'static,
B::Data: Into<bytes::Bytes>,
B::Error: Into<BoxError>,
{
let service = service
.map_response(|response: http::Response<B>| response.map(Body::wrap))
.map_err(Into::into);
#[cfg(not(target_family = "wasm"))]
let service = BoxCloneSyncService::new(SyncService(Mutex::new(service)));
#[cfg(target_family = "wasm")]
let service = crate::LocalBoxCloneService::new(service);
Self { service }
}
pub fn layer<L, B>(self, layer: L) -> Self
where
L: Layer<HttpService>,
L::Service: Service<http::Request<Body>, Response = http::Response<B>>
+ Clone
+ MaybeSend
+ 'static,
<L::Service as Service<http::Request<Body>>>::Error: Into<BoxError>,
<L::Service as Service<http::Request<Body>>>::Future: MaybeSend + 'static,
B: http_body::Body + MaybeSend + MaybeSync + 'static,
B::Data: Into<bytes::Bytes>,
B::Error: Into<BoxError>,
{
Self::from_service(layer.layer(self.service))
}
pub fn service(&self) -> &HttpService {
&self.service
}
pub fn request(&self, method: Method, url: &str) -> RequestBuilder {
RequestBuilder::new(self.clone(), method, url)
}
pub async fn execute(&self, request: http::Request<Body>) -> Result<Response, Error> {
let response = self
.service
.clone()
.oneshot(request)
.await
.map_err(Error::request)?;
Ok(Response::from(response))
}
}
impl Default for Client {
fn default() -> Self {
#[cfg(feature = "reqwest")]
{
Self::new()
}
#[cfg(not(feature = "reqwest"))]
{
Self::from_service(NoService)
}
}
}
impl fmt::Debug for Client {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Client").finish_non_exhaustive()
}
}
#[cfg(not(target_family = "wasm"))]
struct SyncService<S>(Mutex<S>);
#[cfg(not(target_family = "wasm"))]
impl<S: Clone> Clone for SyncService<S> {
fn clone(&self) -> Self {
let service = self
.0
.lock()
.unwrap_or_else(PoisonError::into_inner)
.clone();
Self(Mutex::new(service))
}
}
#[cfg(not(target_family = "wasm"))]
impl<S: Service<R>, R> Service<R> for SyncService<S> {
type Response = S::Response;
type Error = S::Error;
type Future = S::Future;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.0
.get_mut()
.unwrap_or_else(PoisonError::into_inner)
.poll_ready(cx)
}
fn call(&mut self, request: R) -> Self::Future {
self.0
.get_mut()
.unwrap_or_else(PoisonError::into_inner)
.call(request)
}
}
#[cfg_attr(feature = "reqwest", allow(dead_code))]
#[derive(Clone, Copy, Debug)]
struct NoService;
impl Service<http::Request<Body>> for NoService {
type Response = http::Response<Body>;
type Error = BoxError;
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, _: http::Request<Body>) -> Self::Future {
Box::pin(async {
Err("no HTTP client: enable the `reqwest` feature of timvw-rust-runtime or set one with Client::from_service".into())
})
}
}