A better Rust ATProto crate
at main 207 lines 6.9 kB view raw
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}