Skip to main content

crux_http/
body.rs

1use mime::Mime;
2use serde::Serialize;
3
4/// An in-memory HTTP request body with an optional MIME type.
5///
6/// Cheaply cloneable; all body data is in a `Vec<u8>` so there is no async
7/// reading involved.
8#[derive(Clone, Default)]
9pub struct Body {
10    bytes: Vec<u8>,
11    mime: Option<Mime>,
12}
13
14impl Body {
15    /// Consume the body and return its bytes.
16    #[must_use]
17    pub fn into_bytes(self) -> Vec<u8> {
18        self.bytes
19    }
20
21    /// The MIME type of the body, if one has been set.
22    #[must_use]
23    #[allow(clippy::missing_const_for_fn)]
24    pub fn mime(&self) -> Option<&Mime> {
25        self.mime.as_ref()
26    }
27
28    /// The number of bytes in the body.
29    #[must_use]
30    #[allow(clippy::missing_const_for_fn)]
31    pub fn len(&self) -> usize {
32        self.bytes.len()
33    }
34
35    /// Returns `true` if the body is empty.
36    #[must_use]
37    #[allow(clippy::missing_const_for_fn)]
38    pub fn is_empty(&self) -> bool {
39        self.bytes.is_empty()
40    }
41
42    /// Create a body from a string with `text/plain; charset=utf-8` content type.
43    #[must_use]
44    #[allow(clippy::missing_const_for_fn)]
45    pub fn from_string(s: String) -> Self {
46        Self {
47            bytes: s.into_bytes(),
48            mime: Some(mime::TEXT_PLAIN_UTF_8),
49        }
50    }
51
52    /// Serialize `value` to JSON bytes with `application/json` content type.
53    ///
54    /// # Errors
55    /// Returns a `serde_json::Error` if serialization fails.
56    pub fn from_json(value: &impl Serialize) -> Result<Self, serde_json::Error> {
57        let bytes = serde_json::to_vec(value)?;
58        Ok(Self {
59            bytes,
60            mime: Some(mime::APPLICATION_JSON),
61        })
62    }
63
64    /// Serialize `value` as `application/x-www-form-urlencoded`.
65    ///
66    /// # Errors
67    /// Returns a `serde_qs::Error` if serialization fails.
68    pub fn from_form(value: &impl Serialize) -> Result<Self, serde_qs::Error> {
69        let bytes = serde_qs::to_string(value)?.into_bytes();
70        Ok(Self {
71            bytes,
72            mime: Some(mime::APPLICATION_WWW_FORM_URLENCODED),
73        })
74    }
75}
76
77impl From<String> for Body {
78    fn from(s: String) -> Self {
79        Self::from_string(s)
80    }
81}
82
83impl From<&str> for Body {
84    fn from(s: &str) -> Self {
85        Self::from_string(s.to_owned())
86    }
87}
88
89impl From<Vec<u8>> for Body {
90    fn from(bytes: Vec<u8>) -> Self {
91        Self {
92            bytes,
93            mime: Some(mime::APPLICATION_OCTET_STREAM),
94        }
95    }
96}
97
98impl<'a> From<&'a [u8]> for Body {
99    fn from(bytes: &'a [u8]) -> Self {
100        Self {
101            bytes: bytes.to_vec(),
102            mime: Some(mime::APPLICATION_OCTET_STREAM),
103        }
104    }
105}
106
107impl From<serde_json::Value> for Body {
108    fn from(value: serde_json::Value) -> Self {
109        // serde_json::Value always serialises without error.
110        let bytes = serde_json::to_vec(&value).unwrap_or_default();
111        Self {
112            bytes,
113            mime: Some(mime::APPLICATION_JSON),
114        }
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn default_body_is_empty() {
124        let body = Body::default();
125        assert!(body.is_empty());
126        assert_eq!(body.len(), 0);
127        assert!(body.mime().is_none());
128    }
129
130    #[test]
131    fn from_string_sets_text_plain_utf8() {
132        let body = Body::from("hello".to_string());
133        assert_eq!(body.into_bytes(), b"hello");
134        // mime created from into() on String
135        let body2 = Body::from_string("world".to_owned());
136        assert_eq!(body2.mime().unwrap(), &mime::TEXT_PLAIN_UTF_8);
137    }
138
139    #[test]
140    fn from_str_ref_sets_text_plain_utf8() {
141        let body = Body::from("hello");
142        assert_eq!(body.mime().unwrap(), &mime::TEXT_PLAIN_UTF_8);
143    }
144
145    #[test]
146    fn from_bytes_sets_octet_stream() {
147        let body = Body::from(vec![1u8, 2, 3]);
148        assert_eq!(body.mime().unwrap(), &mime::APPLICATION_OCTET_STREAM);
149        assert_eq!(body.into_bytes(), vec![1, 2, 3]);
150    }
151
152    #[test]
153    fn from_byte_slice_sets_octet_stream() {
154        let body = Body::from(&[4u8, 5, 6][..]);
155        assert_eq!(body.mime().unwrap(), &mime::APPLICATION_OCTET_STREAM);
156    }
157
158    #[test]
159    fn from_json_serialises_and_sets_application_json() {
160        #[derive(serde::Serialize)]
161        struct Foo {
162            x: u32,
163        }
164        let body = Body::from_json(&Foo { x: 42 }).unwrap();
165        assert_eq!(body.mime().unwrap(), &mime::APPLICATION_JSON);
166        let parsed: serde_json::Value = serde_json::from_slice(&body.into_bytes()).unwrap();
167        assert_eq!(parsed["x"], 42);
168    }
169
170    #[test]
171    fn from_json_value_sets_application_json() {
172        let val = serde_json::json!({"key": "value"});
173        let body = Body::from(val);
174        assert_eq!(body.mime().unwrap(), &mime::APPLICATION_JSON);
175    }
176
177    #[test]
178    fn from_form_serialises_and_sets_form_urlencoded() {
179        #[derive(serde::Serialize)]
180        struct Form {
181            name: String,
182            age: u32,
183        }
184        let body = Body::from_form(&Form {
185            name: "Alice".into(),
186            age: 30,
187        })
188        .unwrap();
189        assert_eq!(body.mime().unwrap(), &mime::APPLICATION_WWW_FORM_URLENCODED);
190        let s = String::from_utf8(body.into_bytes()).unwrap();
191        assert!(s.contains("name=Alice"));
192        assert!(s.contains("age=30"));
193    }
194}