1use crate::error::BodyError;
4
5pub const DEFAULT_BODY_LIMIT: usize = 2 * 1024 * 1024;
7
8fn is_length_limit(err: &axum::Error) -> bool {
9 let mut source: Option<&(dyn std::error::Error + 'static)> = Some(err);
10 while let Some(current) = source {
11 if current.is::<http_body_util::LengthLimitError>() {
12 return true;
13 }
14 source = current.source();
15 }
16 false
17}
18
19#[derive(Debug, Default)]
21pub struct Body(axum::body::Body);
22
23impl Body {
24 pub fn empty() -> Self {
26 Self::default()
27 }
28
29 pub async fn into_bytes(self) -> Result<Vec<u8>, BodyError> {
37 self.into_bytes_limited(DEFAULT_BODY_LIMIT).await
38 }
39
40 pub async fn into_bytes_limited(self, max_bytes: usize) -> Result<Vec<u8>, BodyError> {
46 if self.exact_len().is_some_and(|len| len > max_bytes as u64) {
47 return Err(BodyError::too_large(max_bytes));
48 }
49 match axum::body::to_bytes(self.0, max_bytes).await {
50 Ok(bytes) => Ok(bytes.to_vec()),
51 Err(err) if is_length_limit(&err) => Err(BodyError::too_large(max_bytes)),
52 Err(err) => Err(BodyError::failed(err.to_string())),
53 }
54 }
55
56 pub fn exact_len(&self) -> Option<u64> {
61 axum::body::HttpBody::size_hint(&self.0).exact()
62 }
63
64 pub(crate) fn from_inner(inner: axum::body::Body) -> Self {
65 Self(inner)
66 }
67
68 pub(crate) fn into_inner(self) -> axum::body::Body {
69 self.0
70 }
71}
72
73impl From<Vec<u8>> for Body {
74 fn from(bytes: Vec<u8>) -> Self {
75 Self(axum::body::Body::from(bytes))
76 }
77}
78
79impl From<String> for Body {
80 fn from(text: String) -> Self {
81 Self(axum::body::Body::from(text))
82 }
83}
84
85impl From<&'static str> for Body {
86 fn from(text: &'static str) -> Self {
87 Self(axum::body::Body::from(text))
88 }
89}
90
91#[cfg(test)]
92#[allow(clippy::unwrap_used)]
93mod limit_tests {
94 use super::*;
95 use crate::error::ApiError;
96 use http::StatusCode;
97
98 fn streamed(chunks: usize, size: usize) -> Body {
99 let stream = futures_util::stream::iter(
100 (0..chunks).map(move |_| Ok::<_, std::io::Error>(vec![b'x'; size])),
101 );
102 Body::from_inner(axum::body::Body::from_stream(stream))
103 }
104
105 #[tokio::test]
106 async fn buffered_body_over_limit_is_too_large() {
107 let err = Body::from(vec![0u8; 11])
108 .into_bytes_limited(10)
109 .await
110 .unwrap_err();
111 assert!(err.is_too_large());
112 assert_eq!(ApiError::from(err).status(), StatusCode::PAYLOAD_TOO_LARGE);
113 }
114
115 #[tokio::test]
116 async fn streamed_body_over_limit_is_too_large() {
117 let err = streamed(4, 4).into_bytes_limited(10).await.unwrap_err();
118 assert!(err.is_too_large());
119 }
120
121 #[tokio::test]
122 async fn default_limit_applies_and_bodies_within_it_pass() {
123 assert_eq!(
124 streamed(2, 5).into_bytes_limited(10).await.unwrap().len(),
125 10
126 );
127 let err = streamed(DEFAULT_BODY_LIMIT / 1024 + 1, 1024)
128 .into_bytes()
129 .await
130 .unwrap_err();
131 assert!(err.is_too_large());
132 }
133}