1pub use anyhow::{anyhow, Result};
2use futures::future::BoxFuture;
3use isahc::config::{Configurable, RedirectPolicy};
4pub use isahc::{
5 http::{Method, 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 follow_redirects: bool,
44 ) -> BoxFuture<'a, Result<Response<AsyncBody>, Error>> {
45 let request = isahc::Request::builder()
46 .redirect_policy(if follow_redirects {
47 RedirectPolicy::Follow
48 } else {
49 RedirectPolicy::None
50 })
51 .method(Method::POST)
52 .uri(uri)
53 .header("Content-Type", "application/json")
54 .body(body);
55 match request {
56 Ok(request) => self.send(request),
57 Err(error) => async move { Err(error.into()) }.boxed(),
58 }
59 }
60}
61
62pub fn client() -> Arc<dyn HttpClient> {
63 Arc::new(
64 isahc::HttpClient::builder()
65 .connect_timeout(Duration::from_secs(5))
66 .low_speed_timeout(100, Duration::from_secs(5))
67 .build()
68 .unwrap(),
69 )
70}
71
72impl HttpClient for isahc::HttpClient {
73 fn send(&self, req: Request<AsyncBody>) -> BoxFuture<Result<Response<AsyncBody>, Error>> {
74 Box::pin(async move { self.send_async(req).await })
75 }
76}
77
78#[cfg(feature = "test-support")]
79pub struct FakeHttpClient {
80 handler: Box<
81 dyn 'static
82 + Send
83 + Sync
84 + Fn(Request<AsyncBody>) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>>,
85 >,
86}
87
88#[cfg(feature = "test-support")]
89impl FakeHttpClient {
90 pub fn create<Fut, F>(handler: F) -> Arc<dyn HttpClient>
91 where
92 Fut: 'static + Send + futures::Future<Output = Result<Response<AsyncBody>, Error>>,
93 F: 'static + Send + Sync + Fn(Request<AsyncBody>) -> Fut,
94 {
95 Arc::new(Self {
96 handler: Box::new(move |req| Box::pin(handler(req))),
97 })
98 }
99
100 pub fn with_404_response() -> Arc<dyn HttpClient> {
101 Self::create(|_| async move {
102 Ok(Response::builder()
103 .status(404)
104 .body(Default::default())
105 .unwrap())
106 })
107 }
108}
109
110#[cfg(feature = "test-support")]
111impl fmt::Debug for FakeHttpClient {
112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113 f.debug_struct("FakeHttpClient").finish()
114 }
115}
116
117#[cfg(feature = "test-support")]
118impl HttpClient for FakeHttpClient {
119 fn send(&self, req: Request<AsyncBody>) -> BoxFuture<Result<Response<AsyncBody>, Error>> {
120 let future = (self.handler)(req);
121 Box::pin(async move { future.await.map(Into::into) })
122 }
123}