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_util::{BodyExt, Empty, StreamBody};
44use hyper::body::{Body, Frame, SizeHint};
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(hyper::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 hyper::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}