1use std::pin::Pin;
12
13use async_trait::async_trait;
14use bytes::{Bytes, BytesMut};
15use futures_util::{Stream, StreamExt, stream};
16
17use crate::error::Error;
18
19pub use ::http::header::{self, HeaderMap, HeaderName, HeaderValue};
20pub use ::http::{Method, Request, Response, StatusCode, Version};
21
22#[cfg(feature = "reqwest")]
23mod reqwest;
24#[cfg(feature = "reqwest")]
25pub use self::reqwest::ReqwestClient;
26
27#[async_trait]
43pub trait HttpClient: Send + Sync {
44 async fn send(&self, request: Request<Bytes>) -> Result<Response<Body>, Error>;
46}
47
48pub struct Body {
54 stream: Pin<Box<dyn Stream<Item = Result<Bytes, Error>> + Send>>,
55 content_length: Option<u64>,
56}
57
58impl Body {
59 pub fn from_stream(
61 stream: impl Stream<Item = Result<Bytes, Error>> + Send + 'static,
62 content_length: Option<u64>,
63 ) -> Body {
64 Body {
65 stream: Box::pin(stream),
66 content_length,
67 }
68 }
69
70 pub fn empty() -> Body {
72 Body::from(Bytes::new())
73 }
74
75 pub fn content_length(&self) -> Option<u64> {
77 self.content_length
78 }
79
80 pub async fn chunk(&mut self) -> Result<Option<Bytes>, Error> {
82 self.stream.next().await.transpose()
83 }
84
85 pub async fn collect(
88 mut self,
89 limit: usize,
90 too_large: impl Fn() -> Error,
91 ) -> Result<Bytes, Error> {
92 if self
93 .content_length
94 .is_some_and(|length| length > limit as u64)
95 {
96 return Err(too_large());
97 }
98 let mut body = BytesMut::new();
99 while let Some(chunk) = self.chunk().await? {
100 if body.len() + chunk.len() > limit {
101 return Err(too_large());
102 }
103 body.extend_from_slice(&chunk);
104 }
105 Ok(body.freeze())
106 }
107
108 pub async fn prefix(mut self, limit: usize) -> Result<Bytes, Error> {
110 let mut body = BytesMut::new();
111 while body.len() < limit
112 && let Some(chunk) = self.chunk().await?
113 {
114 let room = limit - body.len();
115 body.extend_from_slice(&chunk[..chunk.len().min(room)]);
116 }
117 Ok(body.freeze())
118 }
119}
120
121impl From<Bytes> for Body {
122 fn from(bytes: Bytes) -> Body {
123 let content_length = Some(bytes.len() as u64);
124 Body::from_stream(stream::once(async move { Ok(bytes) }), content_length)
125 }
126}
127
128impl From<Vec<u8>> for Body {
129 fn from(bytes: Vec<u8>) -> Body {
130 Body::from(Bytes::from(bytes))
131 }
132}
133
134impl From<&'static str> for Body {
135 fn from(text: &'static str) -> Body {
136 Body::from(Bytes::from_static(text.as_bytes()))
137 }
138}
139
140impl std::fmt::Debug for Body {
141 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142 f.debug_struct("Body")
143 .field("content_length", &self.content_length)
144 .finish_non_exhaustive()
145 }
146}
147
148#[cfg(test)]
149#[allow(clippy::unwrap_used)]
150mod tests {
151 use super::*;
152
153 fn too_large() -> Error {
154 Error::api(0, "too large")
155 }
156
157 #[tokio::test]
158 async fn collects_a_body_up_to_the_limit() {
159 let body = Body::from(Bytes::from_static(b"hello"));
160 assert_eq!(body.content_length(), Some(5));
161 assert_eq!(body.collect(5, too_large).await.unwrap(), "hello");
162 }
163
164 #[tokio::test]
165 async fn refuses_a_body_declared_past_the_limit_before_reading_it() {
166 let body = Body::from_stream(
167 stream::once(async { panic!("the body should never be read") }),
168 Some(6),
169 );
170 assert_eq!(
171 body.collect(5, too_large).await.unwrap_err().to_string(),
172 "too large"
173 );
174 }
175
176 #[tokio::test]
177 async fn refuses_an_undeclared_body_on_the_first_byte_past_the_limit() {
178 let chunks = stream::iter([
179 Ok(Bytes::from_static(b"hel")),
180 Ok(Bytes::from_static(b"lo!")),
181 ]);
182 let body = Body::from_stream(chunks, None);
183 assert_eq!(
184 body.collect(5, too_large).await.unwrap_err().to_string(),
185 "too large"
186 );
187 }
188
189 #[tokio::test]
190 async fn a_prefix_stops_at_the_limit_without_reading_further() {
191 let chunks = stream::iter([
192 Ok(Bytes::from_static(b"hel")),
193 Ok(Bytes::from_static(b"lo!")),
194 Err(Error::api(0, "never reached")),
195 ]);
196 let body = Body::from_stream(chunks, None);
197 assert_eq!(body.prefix(5).await.unwrap(), "hello");
198 }
199
200 #[tokio::test]
201 async fn a_failing_stream_fails_the_read() {
202 let chunks = stream::iter([
203 Ok(Bytes::from_static(b"hel")),
204 Err(Error::api(0, "cut off")),
205 ]);
206 let body = Body::from_stream(chunks, None);
207 assert_eq!(
208 body.collect(100, too_large).await.unwrap_err().to_string(),
209 "cut off"
210 );
211 }
212}