http.rs

  1pub use anyhow::{anyhow, Result};
  2use futures::future::BoxFuture;
  3use isahc::config::{Configurable, RedirectPolicy};
  4pub use isahc::{
  5    http::{Method, StatusCode, Uri},
  6    Error,
  7};
  8pub use isahc::{AsyncBody, Request, Response};
  9use parking_lot::Mutex;
 10use smol::future::FutureExt;
 11#[cfg(feature = "test-support")]
 12use std::fmt;
 13use std::{sync::Arc, time::Duration};
 14pub use url::Url;
 15
 16pub struct ZedHttpClient {
 17    pub zed_host: Mutex<String>,
 18    client: Box<dyn HttpClient>,
 19}
 20
 21impl ZedHttpClient {
 22    pub fn zed_url(&self, path: &str) -> String {
 23        format!("{}{}", self.zed_host.lock(), path)
 24    }
 25}
 26
 27impl HttpClient for Arc<ZedHttpClient> {
 28    fn send(&self, req: Request<AsyncBody>) -> BoxFuture<Result<Response<AsyncBody>, Error>> {
 29        self.client.send(req)
 30    }
 31}
 32
 33impl HttpClient for ZedHttpClient {
 34    fn send(&self, req: Request<AsyncBody>) -> BoxFuture<Result<Response<AsyncBody>, Error>> {
 35        self.client.send(req)
 36    }
 37}
 38
 39pub fn zed_client(zed_host: &str) -> Arc<ZedHttpClient> {
 40    Arc::new(ZedHttpClient {
 41        zed_host: Mutex::new(zed_host.to_string()),
 42        client: Box::new(
 43            isahc::HttpClient::builder()
 44                .connect_timeout(Duration::from_secs(5))
 45                .low_speed_timeout(100, Duration::from_secs(5))
 46                .build()
 47                .unwrap(),
 48        ),
 49    })
 50}
 51
 52pub trait HttpClient: Send + Sync {
 53    fn send(&self, req: Request<AsyncBody>) -> BoxFuture<Result<Response<AsyncBody>, Error>>;
 54
 55    fn get<'a>(
 56        &'a self,
 57        uri: &str,
 58        body: AsyncBody,
 59        follow_redirects: bool,
 60    ) -> BoxFuture<'a, Result<Response<AsyncBody>, Error>> {
 61        let request = isahc::Request::builder()
 62            .redirect_policy(if follow_redirects {
 63                RedirectPolicy::Follow
 64            } else {
 65                RedirectPolicy::None
 66            })
 67            .method(Method::GET)
 68            .uri(uri)
 69            .body(body);
 70        match request {
 71            Ok(request) => self.send(request),
 72            Err(error) => async move { Err(error.into()) }.boxed(),
 73        }
 74    }
 75
 76    fn post_json<'a>(
 77        &'a self,
 78        uri: &str,
 79        body: AsyncBody,
 80    ) -> BoxFuture<'a, Result<Response<AsyncBody>, Error>> {
 81        let request = isahc::Request::builder()
 82            .method(Method::POST)
 83            .uri(uri)
 84            .header("Content-Type", "application/json")
 85            .body(body);
 86        match request {
 87            Ok(request) => self.send(request),
 88            Err(error) => async move { Err(error.into()) }.boxed(),
 89        }
 90    }
 91}
 92
 93pub fn client() -> Arc<dyn HttpClient> {
 94    Arc::new(
 95        isahc::HttpClient::builder()
 96            .connect_timeout(Duration::from_secs(5))
 97            .low_speed_timeout(100, Duration::from_secs(5))
 98            .build()
 99            .unwrap(),
100    )
101}
102
103impl HttpClient for isahc::HttpClient {
104    fn send(&self, req: Request<AsyncBody>) -> BoxFuture<Result<Response<AsyncBody>, Error>> {
105        Box::pin(async move { self.send_async(req).await })
106    }
107}
108
109#[cfg(feature = "test-support")]
110pub struct FakeHttpClient {
111    handler: Box<
112        dyn 'static
113            + Send
114            + Sync
115            + Fn(Request<AsyncBody>) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>>,
116    >,
117}
118
119#[cfg(feature = "test-support")]
120impl FakeHttpClient {
121    pub fn create<Fut, F>(handler: F) -> Arc<ZedHttpClient>
122    where
123        Fut: 'static + Send + futures::Future<Output = Result<Response<AsyncBody>, Error>>,
124        F: 'static + Send + Sync + Fn(Request<AsyncBody>) -> Fut,
125    {
126        Arc::new(ZedHttpClient {
127            zed_host: Mutex::new("http://test.example".into()),
128            client: Box::new(Self {
129                handler: Box::new(move |req| Box::pin(handler(req))),
130            }),
131        })
132    }
133
134    pub fn with_404_response() -> Arc<ZedHttpClient> {
135        Self::create(|_| async move {
136            Ok(Response::builder()
137                .status(404)
138                .body(Default::default())
139                .unwrap())
140        })
141    }
142
143    pub fn with_200_response() -> Arc<ZedHttpClient> {
144        Self::create(|_| async move {
145            Ok(Response::builder()
146                .status(200)
147                .body(Default::default())
148                .unwrap())
149        })
150    }
151}
152
153#[cfg(feature = "test-support")]
154impl fmt::Debug for FakeHttpClient {
155    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156        f.debug_struct("FakeHttpClient").finish()
157    }
158}
159
160#[cfg(feature = "test-support")]
161impl HttpClient for FakeHttpClient {
162    fn send(&self, req: Request<AsyncBody>) -> BoxFuture<Result<Response<AsyncBody>, Error>> {
163        let future = (self.handler)(req);
164        Box::pin(async move { future.await.map(Into::into) })
165    }
166}