tako/
body.rs

1//! HTTP request and response body handling utilities for efficient data processing.
2//!
3//! This module provides `TakoBody`, a flexible wrapper around HTTP body implementations
4//! that supports various data sources including static content, streams, and dynamic
5//! generation. It integrates with Hyper's body system while providing convenience methods
6//! for common use cases like creating empty bodies, streaming data, and converting from
7//! different input types with efficient memory management.
8//!
9//! # Examples
10//!
11//! ```rust
12//! use tako::body::TakoBody;
13//! use bytes::Bytes;
14//! use futures_util::stream;
15//!
16//! // Create empty body
17//! let empty = TakoBody::empty();
18//!
19//! // Create from string
20//! let text_body = TakoBody::from("Hello, World!");
21//!
22//! // Create from bytes
23//! let bytes_body = TakoBody::from(Bytes::from("Binary data"));
24//!
25//! // Create from stream
26//! let stream_data = stream::iter(vec![
27//!     Ok(Bytes::from("chunk1")),
28//!     Ok(Bytes::from("chunk2")),
29//! ]);
30//! let stream_body = TakoBody::from_stream(stream_data);
31//! ```
32
33use std::{
34  fmt::Debug,
35  pin::Pin,
36  task::{Context, Poll},
37};
38
39use bytes::Bytes;
40
41use anyhow::Result;
42use futures_util::{Stream, TryStream, TryStreamExt};
43use http_body::{Body, Frame, SizeHint};
44use http_body_util::{BodyExt, Empty, StreamBody};
45
46use crate::types::{BoxBody, BoxError};
47
48/// HTTP body wrapper with streaming and conversion support.
49///
50/// `TakoBody` provides a unified interface for handling HTTP request and response bodies
51/// with support for various data sources. It wraps Hyper's body system with additional
52/// convenience methods and efficient conversion capabilities. The implementation supports
53/// both static content and streaming data while maintaining performance through zero-copy
54/// operations where possible.
55///
56/// # Examples
57///
58/// ```rust
59/// use tako::body::TakoBody;
60/// use http_body_util::Full;
61/// use bytes::Bytes;
62///
63/// // Static content
64/// let static_body = TakoBody::from("Static response");
65///
66/// // Dynamic content
67/// let dynamic = format!("User count: {}", 42);
68/// let dynamic_body = TakoBody::from(dynamic);
69///
70/// // Binary data
71/// let binary_data = vec![0u8, 1, 2, 3, 4];
72/// let binary_body = TakoBody::from(binary_data);
73///
74/// // Empty response
75/// let empty_body = TakoBody::empty();
76/// ```
77pub struct TakoBody(BoxBody);
78
79impl TakoBody {
80  /// Creates a new body from any type implementing the `Body` trait.
81  pub fn new<B>(body: B) -> Self
82  where
83    B: Body<Data = Bytes> + Send + 'static,
84    B::Error: Into<BoxError>,
85  {
86    Self(body.map_err(|e| e.into()).boxed_unsync())
87  }
88
89  /// Creates a body from a stream of byte results.
90  pub fn from_stream<S, E>(stream: S) -> Self
91  where
92    S: Stream<Item = Result<Bytes, E>> + Send + 'static,
93    E: Into<BoxError> + Debug + 'static,
94  {
95    let stream = stream.map_err(Into::into).map_ok(http_body::Frame::data);
96    let body = StreamBody::new(stream).boxed_unsync();
97    Self(body)
98  }
99
100  /// Creates a body from a stream of HTTP frames.
101  pub fn from_try_stream<S, E>(stream: S) -> Self
102  where
103    S: TryStream<Ok = Frame<Bytes>, Error = E> + Send + 'static,
104    E: Into<BoxError> + 'static,
105  {
106    let body = StreamBody::new(stream.map_err(Into::into)).boxed_unsync();
107    Self(body)
108  }
109
110  /// Creates an empty body with no content.
111  pub fn empty() -> Self {
112    Self::new(Empty::new())
113  }
114}
115
116/// Provides a default empty body implementation.
117impl Default for TakoBody {
118  fn default() -> Self {
119    Self::empty()
120  }
121}
122
123impl From<()> for TakoBody {
124  fn from(_: ()) -> Self {
125    Self::empty()
126  }
127}
128
129impl From<&str> for TakoBody {
130  fn from(buf: &str) -> Self {
131    let owned = buf.to_owned();
132    Self::new(http_body_util::Full::from(owned))
133  }
134}
135
136/// Macro for implementing `From` conversions for various types.
137macro_rules! body_from_impl {
138  ($ty:ty) => {
139    impl From<$ty> for TakoBody {
140      fn from(buf: $ty) -> Self {
141        Self::new(http_body_util::Full::from(buf))
142      }
143    }
144  };
145}
146
147body_from_impl!(String);
148body_from_impl!(Vec<u8>);
149body_from_impl!(Bytes);
150
151/// Implements the HTTP `Body` trait for streaming and polling operations.
152///
153/// This implementation enables `TakoBody` to be used as an HTTP body in Hyper
154/// and other HTTP libraries. It delegates all operations to the inner boxed
155/// body while providing the required type information and polling behavior.
156///
157/// # Examples
158///
159/// ```rust,no_run
160/// use tako::body::TakoBody;
161/// use http_body::Body;
162/// use std::pin::Pin;
163/// use std::task::{Context, Poll};
164///
165/// async fn consume_body(mut body: TakoBody) {
166///     // Body can be polled for frames
167///     let size_hint = body.size_hint();
168///     let is_empty = body.is_end_stream();
169/// }
170/// ```
171impl Body for TakoBody {
172  type Data = Bytes;
173  type Error = BoxError;
174
175  /// Polls for the next frame of body data.
176  #[inline]
177  fn poll_frame(
178    mut self: Pin<&mut Self>,
179    cx: &mut Context<'_>,
180  ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
181    Pin::new(&mut self.0).poll_frame(cx)
182  }
183
184  /// Provides size hints for the body content.
185  #[inline]
186  fn size_hint(&self) -> SizeHint {
187    self.0.size_hint()
188  }
189
190  /// Indicates whether the body has reached the end of the stream.
191  #[inline]
192  fn is_end_stream(&self) -> bool {
193    self.0.is_end_stream()
194  }
195}