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}