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")]
25#[cfg_attr(docsrs, doc(cfg(feature = "reqwest")))]
26pub use self::reqwest::ReqwestClient;
27
28#[async_trait]
44pub trait HttpClient: Send + Sync {
45 async fn send(&self, request: Request<Bytes>) -> Result<Response<Body>, Error>;
47}
48
49pub struct Body {
55 stream: Pin<Box<dyn Stream<Item = Result<Bytes, Error>> + Send>>,
56 content_length: Option<u64>,
57}
58
59impl Body {
60 pub fn from_stream(
62 stream: impl Stream<Item = Result<Bytes, Error>> + Send + 'static,
63 content_length: Option<u64>,
64 ) -> Body {
65 Body {
66 stream: Box::pin(stream),
67 content_length,
68 }
69 }
70
71 pub fn empty() -> Body {
73 Body::from(Bytes::new())
74 }
75
76 pub fn content_length(&self) -> Option<u64> {
78 self.content_length
79 }
80
81 pub async fn chunk(&mut self) -> Result<Option<Bytes>, Error> {
83 self.stream.next().await.transpose()
84 }
85
86 pub async fn collect(
89 mut self,
90 limit: usize,
91 too_large: impl Fn() -> Error,
92 ) -> Result<Bytes, Error> {
93 if self
94 .content_length
95 .is_some_and(|length| length > limit as u64)
96 {
97 return Err(too_large());
98 }
99 let mut body = BytesMut::new();
100 while let Some(chunk) = self.chunk().await? {
101 if body.len() + chunk.len() > limit {
102 return Err(too_large());
103 }
104 body.extend_from_slice(&chunk);
105 }
106 Ok(body.freeze())
107 }
108}
109
110impl From<Bytes> for Body {
111 fn from(bytes: Bytes) -> Body {
112 let content_length = Some(bytes.len() as u64);
113 Body::from_stream(stream::once(async move { Ok(bytes) }), content_length)
114 }
115}
116
117impl From<Vec<u8>> for Body {
118 fn from(bytes: Vec<u8>) -> Body {
119 Body::from(Bytes::from(bytes))
120 }
121}
122
123impl From<&'static str> for Body {
124 fn from(text: &'static str) -> Body {
125 Body::from(Bytes::from_static(text.as_bytes()))
126 }
127}
128
129impl std::fmt::Debug for Body {
130 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131 f.debug_struct("Body")
132 .field("content_length", &self.content_length)
133 .finish_non_exhaustive()
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 fn too_large() -> Error {
142 Error::api(0, "too large")
143 }
144
145 #[tokio::test]
146 async fn collects_a_body_up_to_the_limit() {
147 let body = Body::from(Bytes::from_static(b"hello"));
148 assert_eq!(body.content_length(), Some(5));
149 assert_eq!(body.collect(5, too_large).await.unwrap(), "hello");
150 }
151
152 #[tokio::test]
153 async fn refuses_a_body_declared_past_the_limit_before_reading_it() {
154 let body = Body::from_stream(
155 stream::once(async { panic!("the body should never be read") }),
156 Some(6),
157 );
158 assert_eq!(
159 body.collect(5, too_large).await.unwrap_err().to_string(),
160 "too large"
161 );
162 }
163
164 #[tokio::test]
165 async fn refuses_an_undeclared_body_on_the_first_byte_past_the_limit() {
166 let chunks = stream::iter([
167 Ok(Bytes::from_static(b"hel")),
168 Ok(Bytes::from_static(b"lo!")),
169 ]);
170 let body = Body::from_stream(chunks, None);
171 assert_eq!(
172 body.collect(5, too_large).await.unwrap_err().to_string(),
173 "too large"
174 );
175 }
176
177 #[tokio::test]
178 async fn a_failing_stream_fails_the_read() {
179 let chunks = stream::iter([
180 Ok(Bytes::from_static(b"hel")),
181 Err(Error::api(0, "cut off")),
182 ]);
183 let body = Body::from_stream(chunks, None);
184 assert_eq!(
185 body.collect(100, too_large).await.unwrap_err().to_string(),
186 "cut off"
187 );
188 }
189}