Skip to main content

armature_core/
body.rs

1//! Zero-Copy HTTP Body Handling
2//!
3//! This module provides efficient body types that use `bytes::Bytes` internally
4//! for zero-copy operations. By avoiding `Vec<u8>` conversions, request and
5//! response bodies can be passed through without copying.
6//!
7//! ## Performance
8//!
9//! - **Request body**: Hyper's body is collected to `Bytes` once, no further copies
10//! - **Response body**: `Bytes` is passed directly to Hyper, no conversion needed
11//! - **Cloning**: `Bytes::clone()` is O(1) - just increments reference count
12//!
13//! ## Usage
14//!
15//! ```rust,ignore
16//! use armature_core::body::{RequestBody, ResponseBody};
17//!
18//! // Request body - zero-copy from Hyper
19//! let body = RequestBody::from_hyper(hyper_body).await?;
20//! let json: MyType = body.json()?;
21//!
22//! // Response body - zero-copy to Hyper
23//! let response = ResponseBody::from_json(&data)?;
24//! let hyper_body = response.into_hyper(); // No copy!
25//! ```
26
27use bytes::Bytes;
28use http_body_util::Full;
29use serde::{Serialize, de::DeserializeOwned};
30use std::ops::Deref;
31
32// Re-export Bytes for convenience
33pub use bytes;
34
35/// A request body backed by `Bytes` for zero-copy handling.
36///
37/// This wraps `bytes::Bytes` to provide efficient body handling
38/// without copying data from Hyper's incoming body.
39///
40/// # Example
41///
42/// ```rust,ignore
43/// // From Hyper body (zero-copy after initial collect)
44/// let body = RequestBody::from_bytes(hyper_bytes);
45///
46/// // Access as slice (zero-copy)
47/// let slice: &[u8] = body.as_ref();
48///
49/// // Parse as JSON (zero-copy read)
50/// let data: MyType = body.json()?;
51/// ```
52#[derive(Clone, Default)]
53pub struct RequestBody {
54    inner: Bytes,
55}
56
57impl RequestBody {
58    /// Create an empty request body.
59    #[inline]
60    pub const fn empty() -> Self {
61        Self {
62            inner: Bytes::new(),
63        }
64    }
65
66    /// Create from `Bytes` (zero-copy).
67    #[inline]
68    pub fn from_bytes(bytes: Bytes) -> Self {
69        Self { inner: bytes }
70    }
71
72    /// Create from a byte slice (copies data).
73    ///
74    /// Use `from_bytes` when possible to avoid copying.
75    #[inline]
76    pub fn from_slice(slice: &[u8]) -> Self {
77        Self {
78            inner: Bytes::copy_from_slice(slice),
79        }
80    }
81
82    /// Create from a `Vec<u8>` (zero-copy conversion).
83    #[inline]
84    pub fn from_vec(vec: Vec<u8>) -> Self {
85        Self {
86            inner: Bytes::from(vec),
87        }
88    }
89
90    /// Create from a static byte array (zero-copy).
91    #[inline]
92    pub fn from_static(bytes: &'static [u8]) -> Self {
93        Self {
94            inner: Bytes::from_static(bytes),
95        }
96    }
97
98    /// Get the body length.
99    #[inline]
100    pub fn len(&self) -> usize {
101        self.inner.len()
102    }
103
104    /// Check if the body is empty.
105    #[inline]
106    pub fn is_empty(&self) -> bool {
107        self.inner.is_empty()
108    }
109
110    /// Get the underlying `Bytes`.
111    #[inline]
112    pub fn as_bytes(&self) -> &Bytes {
113        &self.inner
114    }
115
116    /// Convert to `Bytes` (zero-copy).
117    #[inline]
118    pub fn into_bytes(self) -> Bytes {
119        self.inner
120    }
121
122    /// Convert to `Vec<u8>` (may copy if shared).
123    ///
124    /// If this is the only reference to the data, this is O(1).
125    /// If the data is shared, this will copy.
126    #[inline]
127    pub fn to_vec(&self) -> Vec<u8> {
128        self.inner.to_vec()
129    }
130
131    /// Parse body as JSON.
132    ///
133    /// Uses SIMD-accelerated parsing when the `simd-json` feature is enabled.
134    #[inline]
135    pub fn json<T: DeserializeOwned>(&self) -> Result<T, crate::Error> {
136        crate::json::from_slice(&self.inner)
137            .map_err(|e| crate::Error::Deserialization(e.to_string()))
138    }
139
140    /// Parse body as URL-encoded form data.
141    #[inline]
142    pub fn form<T: DeserializeOwned>(&self) -> Result<T, crate::Error> {
143        crate::form::parse_form(&self.inner)
144    }
145
146    /// Get body as UTF-8 string (zero-copy if valid UTF-8).
147    #[inline]
148    pub fn as_str(&self) -> Result<&str, std::str::Utf8Error> {
149        std::str::from_utf8(&self.inner)
150    }
151
152    /// Get body as UTF-8 string, replacing invalid sequences.
153    #[inline]
154    pub fn to_string_lossy(&self) -> std::borrow::Cow<'_, str> {
155        String::from_utf8_lossy(&self.inner)
156    }
157}
158
159impl Deref for RequestBody {
160    type Target = [u8];
161
162    #[inline]
163    fn deref(&self) -> &Self::Target {
164        &self.inner
165    }
166}
167
168impl AsRef<[u8]> for RequestBody {
169    #[inline]
170    fn as_ref(&self) -> &[u8] {
171        &self.inner
172    }
173}
174
175impl From<Bytes> for RequestBody {
176    #[inline]
177    fn from(bytes: Bytes) -> Self {
178        Self::from_bytes(bytes)
179    }
180}
181
182impl From<Vec<u8>> for RequestBody {
183    #[inline]
184    fn from(vec: Vec<u8>) -> Self {
185        Self::from_vec(vec)
186    }
187}
188
189impl From<&'static [u8]> for RequestBody {
190    #[inline]
191    fn from(slice: &'static [u8]) -> Self {
192        Self::from_static(slice)
193    }
194}
195
196impl From<String> for RequestBody {
197    #[inline]
198    fn from(s: String) -> Self {
199        Self::from_vec(s.into_bytes())
200    }
201}
202
203impl From<&'static str> for RequestBody {
204    #[inline]
205    fn from(s: &'static str) -> Self {
206        Self::from_static(s.as_bytes())
207    }
208}
209
210impl std::fmt::Debug for RequestBody {
211    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
212        f.debug_struct("RequestBody")
213            .field("len", &self.inner.len())
214            .finish()
215    }
216}
217
218// ============================================================================
219// Response Body
220// ============================================================================
221
222/// A response body backed by `Bytes` for zero-copy handling.
223///
224/// This wraps `bytes::Bytes` to provide efficient body handling
225/// that can be passed directly to Hyper without copying.
226///
227/// # Example
228///
229/// ```rust,ignore
230/// // Create from JSON (serializes once)
231/// let body = ResponseBody::from_json(&data)?;
232///
233/// // Convert to Hyper body (zero-copy)
234/// let hyper_body: Full<Bytes> = body.into_hyper();
235/// ```
236#[derive(Clone, Default)]
237pub struct ResponseBody {
238    inner: Bytes,
239}
240
241impl ResponseBody {
242    /// Create an empty response body.
243    #[inline]
244    pub const fn empty() -> Self {
245        Self {
246            inner: Bytes::new(),
247        }
248    }
249
250    /// Create from `Bytes` (zero-copy).
251    #[inline]
252    pub fn from_bytes(bytes: Bytes) -> Self {
253        Self { inner: bytes }
254    }
255
256    /// Create from a byte slice (copies data).
257    #[inline]
258    pub fn from_slice(slice: &[u8]) -> Self {
259        Self {
260            inner: Bytes::copy_from_slice(slice),
261        }
262    }
263
264    /// Create from a `Vec<u8>` (zero-copy conversion).
265    #[inline]
266    pub fn from_vec(vec: Vec<u8>) -> Self {
267        Self {
268            inner: Bytes::from(vec),
269        }
270    }
271
272    /// Create from a static byte array (zero-copy).
273    #[inline]
274    pub fn from_static(bytes: &'static [u8]) -> Self {
275        Self {
276            inner: Bytes::from_static(bytes),
277        }
278    }
279
280    /// Create from JSON serialization.
281    ///
282    /// Uses SIMD-accelerated serialization when the `simd-json` feature is enabled.
283    #[inline]
284    pub fn from_json<T: Serialize>(value: &T) -> Result<Self, crate::Error> {
285        let vec =
286            crate::json::to_vec(value).map_err(|e| crate::Error::Serialization(e.to_string()))?;
287        Ok(Self::from_vec(vec))
288    }
289
290    /// Create from JSON with pre-allocated capacity.
291    ///
292    /// Use this when you have a reasonable estimate of the output size.
293    #[inline]
294    pub fn from_json_with_capacity<T: Serialize>(
295        value: &T,
296        capacity: usize,
297    ) -> Result<Self, crate::Error> {
298        let vec = crate::json::to_vec_with_capacity(value, capacity)
299            .map_err(|e| crate::Error::Serialization(e.to_string()))?;
300        Ok(Self::from_vec(vec))
301    }
302
303    /// Get the body length.
304    #[inline]
305    pub fn len(&self) -> usize {
306        self.inner.len()
307    }
308
309    /// Check if the body is empty.
310    #[inline]
311    pub fn is_empty(&self) -> bool {
312        self.inner.is_empty()
313    }
314
315    /// Get the underlying `Bytes`.
316    #[inline]
317    pub fn as_bytes(&self) -> &Bytes {
318        &self.inner
319    }
320
321    /// Convert to `Bytes` (zero-copy).
322    #[inline]
323    pub fn into_bytes(self) -> Bytes {
324        self.inner
325    }
326
327    /// Convert to Hyper's body type (zero-copy).
328    ///
329    /// This is the key optimization - no copying needed!
330    #[inline]
331    pub fn into_hyper(self) -> Full<Bytes> {
332        Full::new(self.inner)
333    }
334
335    /// Convert to `Vec<u8>` (may copy if shared).
336    #[inline]
337    pub fn to_vec(&self) -> Vec<u8> {
338        self.inner.to_vec()
339    }
340}
341
342impl Deref for ResponseBody {
343    type Target = [u8];
344
345    #[inline]
346    fn deref(&self) -> &Self::Target {
347        &self.inner
348    }
349}
350
351impl AsRef<[u8]> for ResponseBody {
352    #[inline]
353    fn as_ref(&self) -> &[u8] {
354        &self.inner
355    }
356}
357
358impl From<Bytes> for ResponseBody {
359    #[inline]
360    fn from(bytes: Bytes) -> Self {
361        Self::from_bytes(bytes)
362    }
363}
364
365impl From<Vec<u8>> for ResponseBody {
366    #[inline]
367    fn from(vec: Vec<u8>) -> Self {
368        Self::from_vec(vec)
369    }
370}
371
372impl From<&'static [u8]> for ResponseBody {
373    #[inline]
374    fn from(slice: &'static [u8]) -> Self {
375        Self::from_static(slice)
376    }
377}
378
379impl From<String> for ResponseBody {
380    #[inline]
381    fn from(s: String) -> Self {
382        Self::from_vec(s.into_bytes())
383    }
384}
385
386impl From<&'static str> for ResponseBody {
387    #[inline]
388    fn from(s: &'static str) -> Self {
389        Self::from_static(s.as_bytes())
390    }
391}
392
393impl std::fmt::Debug for ResponseBody {
394    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
395        f.debug_struct("ResponseBody")
396            .field("len", &self.inner.len())
397            .finish()
398    }
399}
400
401// ============================================================================
402// Conversion to/from legacy types
403// ============================================================================
404
405impl From<RequestBody> for Vec<u8> {
406    #[inline]
407    fn from(body: RequestBody) -> Self {
408        body.to_vec()
409    }
410}
411
412impl From<ResponseBody> for Vec<u8> {
413    #[inline]
414    fn from(body: ResponseBody) -> Self {
415        body.to_vec()
416    }
417}
418
419// ============================================================================
420// Tests
421// ============================================================================
422
423#[cfg(test)]
424mod tests {
425    use super::*;
426
427    #[test]
428    fn test_request_body_from_bytes() {
429        let bytes = Bytes::from_static(b"hello world");
430        let body = RequestBody::from_bytes(bytes);
431        assert_eq!(body.len(), 11);
432        assert_eq!(&*body, b"hello world");
433    }
434
435    #[test]
436    fn test_request_body_from_vec() {
437        let vec = vec![1, 2, 3, 4, 5];
438        let body = RequestBody::from_vec(vec);
439        assert_eq!(body.len(), 5);
440        assert_eq!(&*body, &[1, 2, 3, 4, 5]);
441    }
442
443    #[test]
444    fn test_request_body_json() {
445        let json = br#"{"name":"John","age":30}"#;
446        let body = RequestBody::from_slice(json);
447
448        #[derive(serde::Deserialize, PartialEq, Debug)]
449        struct Person {
450            name: String,
451            age: u32,
452        }
453
454        let person: Person = body.json().unwrap();
455        assert_eq!(person.name, "John");
456        assert_eq!(person.age, 30);
457    }
458
459    #[test]
460    fn test_request_body_clone_is_cheap() {
461        let body = RequestBody::from_static(b"large data here that would be expensive to copy");
462        let _clone1 = body.clone(); // Should be O(1) - just ref count increment
463        let _clone2 = body.clone();
464        assert_eq!(body.len(), 47);
465    }
466
467    #[test]
468    fn test_response_body_from_json() {
469        #[derive(serde::Serialize)]
470        struct Response {
471            status: &'static str,
472            code: u32,
473        }
474
475        let data = Response {
476            status: "ok",
477            code: 200,
478        };
479
480        let body = ResponseBody::from_json(&data).unwrap();
481        assert!(!body.is_empty());
482        assert!(String::from_utf8_lossy(&body).contains("ok"));
483    }
484
485    #[test]
486    fn test_response_body_into_hyper() {
487        let body = ResponseBody::from_static(b"response content");
488        let hyper_body = body.into_hyper();
489        // Full<Bytes> is the type Hyper expects - zero copy!
490        let _ = hyper_body;
491    }
492
493    #[test]
494    fn test_response_body_from_string() {
495        let body = ResponseBody::from("hello world".to_string());
496        assert_eq!(&*body, b"hello world");
497    }
498
499    #[test]
500    fn test_request_body_as_str() {
501        let body = RequestBody::from_static(b"valid utf-8");
502        assert_eq!(body.as_str().unwrap(), "valid utf-8");
503
504        let invalid = RequestBody::from_slice(&[0xff, 0xfe]);
505        assert!(invalid.as_str().is_err());
506    }
507
508    #[test]
509    fn test_empty_bodies() {
510        let req = RequestBody::empty();
511        assert!(req.is_empty());
512        assert_eq!(req.len(), 0);
513
514        let resp = ResponseBody::empty();
515        assert!(resp.is_empty());
516        assert_eq!(resp.len(), 0);
517    }
518}