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}