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