http.rs

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