1use bytes::Bytes;
8use futures_core::Stream;
9use http_body::Frame;
10use pin_project_lite::pin_project;
11use std::pin::Pin;
12use std::task::{Context, Poll};
13
14use crate::OxiHttpError;
15
16#[derive(Debug, Default)]
23pub enum Body {
24 #[default]
26 Empty,
27 Full(FullBody),
29 Stream(StreamBody),
31}
32
33impl Body {
34 pub fn empty() -> Self {
36 Self::Empty
37 }
38
39 pub fn full(data: impl Into<Bytes>) -> Self {
41 Self::Full(FullBody {
42 data: Some(data.into()),
43 })
44 }
45
46 pub fn stream(inner: Pin<Box<dyn Stream<Item = Result<Bytes, OxiHttpError>> + Send>>) -> Self {
48 Self::Stream(StreamBody { inner })
49 }
50
51 pub fn content_length(&self) -> Option<u64> {
56 match self {
57 Self::Empty => Some(0),
58 Self::Full(full) => full.data.as_ref().map(|d| d.len() as u64),
59 Self::Stream(_) => None,
60 }
61 }
62
63 pub fn into_pinned(self) -> PinnedBody {
65 PinnedBody::from(self)
66 }
67}
68
69impl From<()> for Body {
70 fn from(_: ()) -> Self {
71 Self::Empty
72 }
73}
74
75impl From<Bytes> for Body {
76 fn from(b: Bytes) -> Self {
77 if b.is_empty() {
78 Self::Empty
79 } else {
80 Self::full(b)
81 }
82 }
83}
84
85impl From<Vec<u8>> for Body {
86 fn from(v: Vec<u8>) -> Self {
87 Self::from(Bytes::from(v))
88 }
89}
90
91impl From<String> for Body {
92 fn from(s: String) -> Self {
93 Self::from(Bytes::from(s))
94 }
95}
96
97impl From<&'static str> for Body {
98 fn from(s: &'static str) -> Self {
99 Self::from(Bytes::from_static(s.as_bytes()))
100 }
101}
102
103impl From<&'static [u8]> for Body {
104 fn from(s: &'static [u8]) -> Self {
105 Self::from(Bytes::from_static(s))
106 }
107}
108
109#[derive(Debug)]
111pub struct FullBody {
112 data: Option<Bytes>,
113}
114
115pub struct StreamBody {
117 inner: Pin<Box<dyn Stream<Item = Result<Bytes, OxiHttpError>> + Send>>,
118}
119
120impl std::fmt::Debug for StreamBody {
121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122 f.debug_struct("StreamBody").finish()
123 }
124}
125
126pin_project! {
131 #[project = PinnedBodyProj]
135 pub enum PinnedBody {
136 Empty,
138 Full { data: Option<Bytes> },
140 Stream { #[pin] inner: Pin<Box<dyn Stream<Item = Result<Bytes, OxiHttpError>> + Send>> },
142 }
143}
144
145impl From<Body> for PinnedBody {
146 fn from(body: Body) -> Self {
147 match body {
148 Body::Empty => PinnedBody::Empty,
149 Body::Full(f) => PinnedBody::Full { data: f.data },
150 Body::Stream(s) => PinnedBody::Stream { inner: s.inner },
151 }
152 }
153}
154
155impl http_body::Body for PinnedBody {
156 type Data = Bytes;
157 type Error = OxiHttpError;
158
159 fn poll_frame(
160 self: Pin<&mut Self>,
161 cx: &mut Context<'_>,
162 ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
163 match self.project() {
164 PinnedBodyProj::Empty => Poll::Ready(None),
165 PinnedBodyProj::Full { data } => {
166 let chunk = data.take();
167 match chunk {
168 Some(d) if !d.is_empty() => Poll::Ready(Some(Ok(Frame::data(d)))),
169 _ => Poll::Ready(None),
170 }
171 }
172 PinnedBodyProj::Stream { mut inner } => match inner.as_mut().poll_next(cx) {
173 Poll::Ready(Some(Ok(chunk))) => Poll::Ready(Some(Ok(Frame::data(chunk)))),
174 Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(e))),
175 Poll::Ready(None) => Poll::Ready(None),
176 Poll::Pending => Poll::Pending,
177 },
178 }
179 }
180
181 fn is_end_stream(&self) -> bool {
182 match self {
183 PinnedBody::Empty => true,
184 PinnedBody::Full { data } => data.is_none(),
185 PinnedBody::Stream { .. } => false,
186 }
187 }
188
189 fn size_hint(&self) -> http_body::SizeHint {
190 match self {
191 PinnedBody::Empty => http_body::SizeHint::with_exact(0),
192 PinnedBody::Full { data } => match data {
193 Some(d) => http_body::SizeHint::with_exact(d.len() as u64),
194 None => http_body::SizeHint::with_exact(0),
195 },
196 PinnedBody::Stream { .. } => http_body::SizeHint::default(),
197 }
198 }
199}