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 smol::future::FutureExt;
10#[cfg(feature = "test-support")]
11use std::fmt;
12use std::{sync::Arc, time::Duration};
13pub use url::Url;
14
15pub trait HttpClient: Send + Sync {
16 fn send(&self, req: Request<AsyncBody>) -> BoxFuture<Result<Response<AsyncBody>, Error>>;
17
18 fn get<'a>(
19 &'a self,
20 uri: &str,
21 body: AsyncBody,
22 follow_redirects: bool,
23 ) -> BoxFuture<'a, Result<Response<AsyncBody>, Error>> {
24 let request = isahc::Request::builder()
25 .redirect_policy(if follow_redirects {
26 RedirectPolicy::Follow
27 } else {
28 RedirectPolicy::None
29 })
30 .method(Method::GET)
31 .uri(uri)
32 .body(body);
33 match request {
34 Ok(request) => self.send(request),
35 Err(error) => async move { Err(error.into()) }.boxed(),
36 }
37 }
38
39 fn post_json<'a>(
40 &'a self,
41 uri: &str,
42 body: AsyncBody,
43 ) -> BoxFuture<'a, Result<Response<AsyncBody>, Error>> {
44 let request = isahc::Request::builder()
45 .method(Method::POST)
46 .uri(uri)
47 .header("Content-Type", "application/json")
48 .body(body);
49 match request {
50 Ok(request) => self.send(request),
51 Err(error) => async move { Err(error.into()) }.boxed(),
52 }
53 }
54}
55
56pub fn client() -> Arc<dyn HttpClient> {
57 Arc::new(
58 isahc::HttpClient::builder()
59 .connect_timeout(Duration::from_secs(5))
60 .low_speed_timeout(100, Duration::from_secs(5))
61 .build()
62 .unwrap(),
63 )
64}
65
66impl HttpClient for isahc::HttpClient {
67 fn send(&self, req: Request<AsyncBody>) -> BoxFuture<Result<Response<AsyncBody>, Error>> {
68 Box::pin(async move { self.send_async(req).await })
69 }
70}
71
72#[cfg(feature = "test-support")]
73pub struct FakeHttpClient {
74 handler: Box<
75 dyn 'static
76 + Send
77 + Sync
78 + Fn(Request<AsyncBody>) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>>,
79 >,
80}
81
82#[cfg(feature = "test-support")]
83impl FakeHttpClient {
84 pub fn create<Fut, F>(handler: F) -> Arc<dyn HttpClient>
85 where
86 Fut: 'static + Send + futures::Future<Output = Result<Response<AsyncBody>, Error>>,
87 F: 'static + Send + Sync + Fn(Request<AsyncBody>) -> Fut,
88 {
89 Arc::new(Self {
90 handler: Box::new(move |req| Box::pin(handler(req))),
91 })
92 }
93
94 pub fn with_404_response() -> Arc<dyn HttpClient> {
95 Self::create(|_| async move {
96 Ok(Response::builder()
97 .status(404)
98 .body(Default::default())
99 .unwrap())
100 })
101 }
102
103 pub fn with_200_response() -> Arc<dyn HttpClient> {
104 Self::create(|_| async move {
105 Ok(Response::builder()
106 .status(200)
107 .body(Default::default())
108 .unwrap())
109 })
110 }
111}
112
113#[cfg(feature = "test-support")]
114impl fmt::Debug for FakeHttpClient {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 f.debug_struct("FakeHttpClient").finish()
117 }
118}
119
120#[cfg(feature = "test-support")]
121impl HttpClient for FakeHttpClient {
122 fn send(&self, req: Request<AsyncBody>) -> BoxFuture<Result<Response<AsyncBody>, Error>> {
123 let future = (self.handler)(req);
124 Box::pin(async move { future.await.map(Into::into) })
125 }
126}