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