1use crate::error::{Error, Result};
5use bytes::{Bytes, BytesMut};
6use futures_util::stream::{Stream, StreamExt};
7use std::pin::Pin;
8
9pub enum Body {
16 Bytes(Bytes),
18 Stream(Pin<Box<dyn Stream<Item = Result<Bytes>> + Send + 'static>>),
20}
21
22impl Body {
23 pub fn empty() -> Self {
25 Body::Bytes(Bytes::new())
26 }
27
28 pub fn from_stream<S, E>(stream: S) -> Self
31 where
32 S: Stream<Item = std::result::Result<Bytes, E>> + Send + 'static,
33 E: std::fmt::Display,
34 {
35 let mapped =
36 stream.map(|chunk| chunk.map_err(|e| Error::internal(format!("body stream: {e}"))));
37 Body::Stream(Box::pin(mapped))
38 }
39
40 pub fn as_bytes(&self) -> Option<&Bytes> {
43 match self {
44 Body::Bytes(b) => Some(b),
45 Body::Stream(_) => None,
46 }
47 }
48
49 pub fn as_slice(&self) -> Option<&[u8]> {
51 self.as_bytes().map(|b| b.as_ref())
52 }
53
54 pub fn is_empty(&self) -> bool {
57 matches!(self, Body::Bytes(b) if b.is_empty())
58 }
59
60 pub async fn into_bytes(self) -> Result<Bytes> {
63 match self {
64 Body::Bytes(b) => Ok(b),
65 Body::Stream(mut s) => {
66 let mut buf = BytesMut::new();
67 while let Some(chunk) = s.next().await {
68 buf.extend_from_slice(&chunk?);
69 }
70 Ok(buf.freeze())
71 }
72 }
73 }
74}
75
76impl Default for Body {
77 fn default() -> Self {
78 Body::empty()
79 }
80}
81
82impl From<Bytes> for Body {
83 fn from(b: Bytes) -> Self {
84 Body::Bytes(b)
85 }
86}
87impl From<Vec<u8>> for Body {
88 fn from(b: Vec<u8>) -> Self {
89 Body::Bytes(Bytes::from(b))
90 }
91}
92impl From<String> for Body {
93 fn from(s: String) -> Self {
94 Body::Bytes(Bytes::from(s))
95 }
96}
97impl From<&'static str> for Body {
98 fn from(s: &'static str) -> Self {
99 Body::Bytes(Bytes::from_static(s.as_bytes()))
100 }
101}
102
103impl PartialEq<Bytes> for Body {
106 fn eq(&self, other: &Bytes) -> bool {
107 matches!(self, Body::Bytes(b) if b == other)
108 }
109}
110
111impl std::fmt::Debug for Body {
112 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113 match self {
114 Body::Bytes(b) => f.debug_tuple("Body::Bytes").field(b).finish(),
115 Body::Stream(_) => f.write_str("Body::Stream(..)"),
116 }
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[tokio::test]
125 async fn buffered_round_trips() {
126 let body = Body::from(Bytes::from("hello"));
127 assert_eq!(body.as_slice(), Some(&b"hello"[..]));
128 assert!(!body.is_empty());
129 assert_eq!(body.into_bytes().await.unwrap(), Bytes::from("hello"));
130 }
131
132 #[tokio::test]
133 async fn empty_is_empty() {
134 assert!(Body::empty().is_empty());
135 assert_eq!(Body::empty().into_bytes().await.unwrap(), Bytes::new());
136 }
137
138 #[tokio::test]
139 async fn stream_collects_and_has_no_bytes_view() {
140 let chunks = futures_util::stream::iter(vec![
141 Ok::<_, std::io::Error>(Bytes::from("ab")),
142 Ok(Bytes::from("cd")),
143 ]);
144 let body = Body::from_stream(chunks);
145 assert!(body.as_bytes().is_none()); assert!(!body.is_empty());
147 assert_eq!(body.into_bytes().await.unwrap(), Bytes::from("abcd"));
148 }
149
150 #[test]
151 #[allow(clippy::cmp_owned)] fn partial_eq_bytes() {
153 assert!(Body::from("x".to_string()) == Bytes::from("x"));
154 let s = Body::from_stream(futures_util::stream::iter(vec![Ok::<_, std::io::Error>(
155 Bytes::new(),
156 )]));
157 assert!(!(s == Bytes::new())); }
159}