1//! Minimal HTTP client abstraction shared across crates.
2
3use std::fmt::Display;
4use std::future::Future;
5use std::sync::Arc;
6
7/// HTTP client trait for sending raw HTTP requests.
8#[cfg_attr(not(target_arch = "wasm32"), trait_variant::make(Send))]
9pub trait HttpClient {
10 /// Error type returned by the HTTP client
11 type Error: std::error::Error + Display + Send + Sync + 'static;
12
13 /// Send an HTTP request and return the response.
14 fn send_http(
15 &self,
16 request: http::Request<Vec<u8>>,
17 ) -> impl Future<Output = core::result::Result<http::Response<Vec<u8>>, Self::Error>>;
18}
19
20#[cfg(feature = "streaming")]
21use crate::stream::{ByteStream, StreamError};
22
23/// Extension trait for HTTP client with streaming support
24#[cfg(feature = "streaming")]
25#[cfg_attr(not(target_arch = "wasm32"), trait_variant::make(Send))]
26pub trait HttpClientExt: HttpClient {
27 /// Send HTTP request and return streaming response
28 fn send_http_streaming(
29 &self,
30 request: http::Request<Vec<u8>>,
31 ) -> impl Future<Output = Result<http::Response<ByteStream>, Self::Error>>;
32
33 /// Send HTTP request with streaming body and receive streaming response
34 #[cfg(not(target_arch = "wasm32"))]
35 fn send_http_bidirectional<S>(
36 &self,
37 parts: http::request::Parts,
38 body: S,
39 ) -> impl Future<Output = Result<http::Response<ByteStream>, Self::Error>>
40 where
41 S: n0_future::Stream<Item = Result<bytes::Bytes, StreamError>> + Send + 'static;
42
43 /// Send HTTP request with streaming body and receive streaming response (WASM)
44 #[cfg(target_arch = "wasm32")]
45 fn send_http_bidirectional<S>(
46 &self,
47 parts: http::request::Parts,
48 body: S,
49 ) -> impl Future<Output = Result<http::Response<ByteStream>, Self::Error>>
50 where
51 S: n0_future::Stream<Item = Result<bytes::Bytes, StreamError>> + 'static;
52}
53
54#[cfg(feature = "reqwest-client")]
55impl HttpClient for reqwest::Client {
56 type Error = reqwest::Error;
57
58 async fn send_http(
59 &self,
60 request: http::Request<Vec<u8>>,
61 ) -> core::result::Result<http::Response<Vec<u8>>, Self::Error> {
62 // Convert http::Request to reqwest::Request
63 let (parts, body) = request.into_parts();
64
65 let mut req = self.request(parts.method, parts.uri.to_string()).body(body);
66
67 // Copy headers
68 for (name, value) in parts.headers.iter() {
69 req = req.header(name.as_str(), value.as_bytes());
70 }
71
72 // Send request
73 let resp = req.send().await?;
74
75 // Convert reqwest::Response to http::Response
76 let mut builder = http::Response::builder().status(resp.status());
77
78 // Copy headers
79 for (name, value) in resp.headers().iter() {
80 builder = builder.header(name.as_str(), value.as_bytes());
81 }
82
83 // Read body
84 let body = resp.bytes().await?.to_vec();
85
86 Ok(builder.body(body).expect("Failed to build response"))
87 }
88}
89
90#[cfg(not(target_arch = "wasm32"))]
91impl<T: HttpClient + Sync> HttpClient for Arc<T> {
92 type Error = T::Error;
93
94 fn send_http(
95 &self,
96 request: http::Request<Vec<u8>>,
97 ) -> impl Future<Output = core::result::Result<http::Response<Vec<u8>>, Self::Error>> + Send
98 {
99 self.as_ref().send_http(request)
100 }
101}
102
103#[cfg(target_arch = "wasm32")]
104impl<T: HttpClient> HttpClient for Arc<T> {
105 type Error = T::Error;
106
107 fn send_http(
108 &self,
109 request: http::Request<Vec<u8>>,
110 ) -> impl Future<Output = core::result::Result<http::Response<Vec<u8>>, Self::Error>> {
111 self.as_ref().send_http(request)
112 }
113}
114
115#[cfg(all(feature = "streaming", feature = "reqwest-client"))]
116impl HttpClientExt for reqwest::Client {
117 async fn send_http_streaming(
118 &self,
119 request: http::Request<Vec<u8>>,
120 ) -> Result<http::Response<ByteStream>, Self::Error> {
121 // Convert http::Request to reqwest::Request
122 let (parts, body) = request.into_parts();
123
124 let mut req = self.request(parts.method, parts.uri.to_string()).body(body);
125
126 // Copy headers
127 for (name, value) in parts.headers.iter() {
128 req = req.header(name.as_str(), value.as_bytes());
129 }
130
131 // Send request and get streaming response
132 let resp = req.send().await?;
133
134 // Convert reqwest::Response to http::Response with ByteStream
135 let mut builder = http::Response::builder().status(resp.status());
136
137 // Copy headers
138 for (name, value) in resp.headers().iter() {
139 builder = builder.header(name.as_str(), value.as_bytes());
140 }
141
142 // Convert bytes_stream to ByteStream
143 use futures::StreamExt;
144 let stream = resp
145 .bytes_stream()
146 .map(|result| result.map_err(|e| StreamError::transport(e)));
147 let byte_stream = ByteStream::new(stream);
148
149 Ok(builder.body(byte_stream).expect("Failed to build response"))
150 }
151
152 #[cfg(not(target_arch = "wasm32"))]
153 async fn send_http_bidirectional<S>(
154 &self,
155 parts: http::request::Parts,
156 body: S,
157 ) -> Result<http::Response<ByteStream>, Self::Error>
158 where
159 S: n0_future::Stream<Item = Result<bytes::Bytes, StreamError>> + Send + 'static,
160 {
161 // Convert stream to reqwest::Body
162 use futures::StreamExt;
163 let reqwest_body = reqwest::Body::wrap_stream(body);
164
165 let mut req = self
166 .request(parts.method, parts.uri.to_string())
167 .body(reqwest_body);
168
169 // Copy headers
170 for (name, value) in parts.headers.iter() {
171 req = req.header(name.as_str(), value.as_bytes());
172 }
173
174 // Send and convert response
175 let resp = req.send().await?;
176
177 let mut builder = http::Response::builder().status(resp.status());
178
179 for (name, value) in resp.headers().iter() {
180 builder = builder.header(name.as_str(), value.as_bytes());
181 }
182
183 let stream = resp
184 .bytes_stream()
185 .map(|result| result.map_err(|e| StreamError::transport(e)));
186 let byte_stream = ByteStream::new(stream);
187
188 Ok(builder.body(byte_stream).expect("Failed to build response"))
189 }
190
191 #[cfg(target_arch = "wasm32")]
192 async fn send_http_bidirectional<S>(
193 &self,
194 _parts: http::request::Parts,
195 _body: S,
196 ) -> Result<http::Response<ByteStream>, Self::Error>
197 where
198 S: n0_future::Stream<Item = Result<bytes::Bytes, StreamError>> + 'static,
199 {
200 // WASM reqwest doesn't support streaming request bodies
201 // This would require ReadableStream/WritableStream integration
202 unimplemented!("Bidirectional streaming not yet supported on WASM")
203 }
204}