1mod client;
9mod error;
10mod request;
11mod response;
12
13#[cfg(feature = "reqwest-client")]
14mod reqwest_impl;
15
16#[cfg(feature = "reqwest-middleware-client")]
17mod reqwest_middleware_impl;
18
19pub use client::{Http, HttpClient};
20pub use error::{BoxError, HttpError};
21pub use request::{HttpRequest, HttpRequestBuilder};
22pub use response::{BodyStream, HttpResponse};
23
24pub use http::{HeaderMap, HeaderName, HeaderValue, Method, StatusCode, Uri, header};
25
26#[cfg(test)]
27mod tests {
28 use super::*;
29 use async_trait::async_trait;
30 use bytes::Bytes;
31 use futures_util::stream;
32 use std::sync::Arc;
33 use std::sync::atomic::{AtomicUsize, Ordering};
34
35 struct StubClient {
36 calls: AtomicUsize,
37 status: StatusCode,
38 body: Bytes,
39 expected_body: Option<Bytes>,
40 }
41
42 #[async_trait]
43 impl HttpClient for StubClient {
44 async fn execute(&self, request: HttpRequest) -> Result<HttpResponse, HttpError> {
45 self.calls.fetch_add(1, Ordering::SeqCst);
46 if let Some(expected) = &self.expected_body {
47 assert_eq!(request.body.as_deref(), Some(expected.as_ref()));
48 }
49 let body = self.body.clone();
50 let stream = stream::once(async move { Ok::<_, HttpError>(body) });
51 Ok(HttpResponse::new(
52 self.status,
53 request.headers.clone(),
54 request.url.clone(),
55 Box::pin(stream),
56 ))
57 }
58 }
59
60 #[tokio::test]
61 async fn builder_sends_json_body_and_decodes_response() {
62 #[derive(serde::Serialize)]
63 struct Req {
64 name: &'static str,
65 }
66 #[derive(serde::Deserialize, Debug, PartialEq)]
67 struct Resp {
68 ok: bool,
69 }
70
71 let stub = StubClient {
72 calls: AtomicUsize::new(0),
73 status: StatusCode::OK,
74 body: Bytes::from_static(br#"{"ok":true}"#),
75 expected_body: Some(Bytes::from_static(br#"{"name":"agentkit"}"#)),
76 };
77 let http = Http::from_arc(Arc::new(stub));
78
79 let resp = http
80 .post("https://example.test/echo")
81 .bearer_auth("tok")
82 .json(&Req { name: "agentkit" })
83 .send()
84 .await
85 .expect("send");
86
87 assert_eq!(resp.status(), StatusCode::OK);
88 let auth = resp.headers().get(http::header::AUTHORIZATION).unwrap();
89 assert_eq!(auth, "Bearer tok");
90 let ct = resp.headers().get(http::header::CONTENT_TYPE).unwrap();
91 assert_eq!(ct, "application/json");
92
93 let decoded: Resp = resp.json().await.expect("json");
94 assert_eq!(decoded, Resp { ok: true });
95 }
96
97 #[tokio::test]
98 async fn error_for_status_flags_4xx() {
99 let stub = StubClient {
100 calls: AtomicUsize::new(0),
101 status: StatusCode::BAD_REQUEST,
102 body: Bytes::from_static(b"nope"),
103 expected_body: None,
104 };
105 let http = Http::from_arc(Arc::new(stub));
106
107 let resp = http.get("https://example.test").send().await.unwrap();
108 assert!(resp.error_for_status().is_err());
109 }
110}