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}