azure_functions/http/
body.rs

1use crate::rpc::{typed_data::Data, TypedData};
2use serde::{de::Error, Deserialize};
3use serde_json::{from_str, Result, Value};
4use std::borrow::Cow;
5use std::fmt;
6use std::str::from_utf8;
7
8/// Represents the body of a HTTP request or response.
9#[derive(Clone, Debug)]
10pub enum Body<'a> {
11    /// Represents an empty body.
12    Empty,
13    /// Represents a string body with a default content type of `text/plain`.
14    String(Cow<'a, str>),
15    /// Represents a JSON body with a default content type of `application/json`.
16    Json(Cow<'a, str>),
17    /// Represents a body from a slice of bytes with a default content type of `application/octet-stream`.
18    Bytes(Cow<'a, [u8]>),
19}
20
21impl Body<'_> {
22    /// Gets the default content type for a body.
23    ///
24    /// Returns `application/json` for `Body::Json`.
25    ///
26    /// Returns `application/octet-stream` for `Body::Bytes`.
27    ///
28    /// Returns `text/plain` for all other `Body` values.
29    ///
30    /// # Examples
31    ///
32    /// ```rust
33    /// use azure_functions::http::Body;
34    ///
35    /// let body: Body = [1, 2, 3][..].into();
36    ///
37    /// assert_eq!(body.default_content_type(), "application/octet-stream");
38    /// ```
39    pub fn default_content_type(&self) -> &str {
40        match self {
41            Body::Empty | Body::String(_) => "text/plain",
42            Body::Json(_) => "application/json",
43            Body::Bytes(_) => "application/octet-stream",
44        }
45    }
46
47    /// Gets the body as a string.
48    ///
49    /// Returns None if there is no valid string representation of the message.
50    ///
51    /// # Examples
52    ///
53    /// ```rust
54    /// use azure_functions::http::Body;
55    /// use std::borrow::Cow;
56    ///
57    /// let body = Body::String(Cow::Borrowed("test"));
58    /// assert_eq!(body.as_str().unwrap(), "test");
59    /// ```
60    pub fn as_str(&self) -> Option<&str> {
61        match self {
62            Body::Empty => Some(""),
63            Body::String(s) => Some(s),
64            Body::Json(s) => Some(s),
65            Body::Bytes(b) => from_utf8(b).map(|s| s).ok(),
66        }
67    }
68
69    /// Gets the body as a slice of bytes.
70    ///
71    /// # Examples
72    ///
73    /// ```rust
74    /// use azure_functions::http::Body;
75    /// use std::borrow::Cow;
76    ///
77    /// let body = Body::String(Cow::Borrowed("test"));
78    /// assert_eq!(body.as_bytes(), "test".as_bytes());
79    /// ```
80    pub fn as_bytes(&self) -> &[u8] {
81        match self {
82            Body::Empty => &[],
83            Body::String(s) => s.as_bytes(),
84            Body::Json(s) => s.as_bytes(),
85            Body::Bytes(b) => b,
86        }
87    }
88
89    /// Deserializes the body as JSON to the requested type.
90    ///
91    /// # Examples
92    ///
93    /// ```rust
94    /// use azure_functions::http::Body;
95    /// use std::borrow::Cow;
96    /// use serde::Deserialize;
97    ///
98    /// #[derive(Deserialize)]
99    /// struct Data {
100    ///     message: String
101    /// }
102    ///
103    /// let body = Body::String(Cow::Borrowed(r#"{ "message": "hello" }"#));
104    /// let data = body.as_json::<Data>().unwrap();
105    /// assert_eq!(data.message, "hello");
106    /// ```
107    pub fn as_json<'b, T>(&'b self) -> Result<T>
108    where
109        T: Deserialize<'b>,
110    {
111        match self {
112            Body::Empty => from_str(""),
113            Body::String(s) => from_str(s.as_ref()),
114            Body::Json(s) => from_str(s.as_ref()),
115            Body::Bytes(b) => from_str(from_utf8(b).map_err(|e| {
116                ::serde_json::Error::custom(format!("body is not valid UTF-8: {}", e))
117            })?),
118        }
119    }
120}
121
122impl fmt::Display for Body<'_> {
123    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
124        write!(f, "{}", self.as_str().unwrap_or(""))
125    }
126}
127
128#[doc(hidden)]
129impl<'a> From<&'a TypedData> for Body<'a> {
130    fn from(data: &'a TypedData) -> Self {
131        match &data.data {
132            Some(Data::String(s)) => Body::String(Cow::from(s)),
133            Some(Data::Json(s)) => Body::Json(Cow::from(s)),
134            Some(Data::Bytes(b)) => Body::Bytes(Cow::from(b)),
135            Some(Data::Stream(s)) => Body::Bytes(Cow::from(s)),
136            _ => Body::Empty,
137        }
138    }
139}
140
141impl<'a> From<&'a str> for Body<'a> {
142    fn from(data: &'a str) -> Self {
143        Body::String(Cow::Borrowed(data))
144    }
145}
146
147impl From<String> for Body<'_> {
148    fn from(data: String) -> Self {
149        Body::String(Cow::Owned(data))
150    }
151}
152
153impl From<&Value> for Body<'_> {
154    fn from(data: &Value) -> Self {
155        Body::Json(Cow::Owned(data.to_string()))
156    }
157}
158
159impl From<Value> for Body<'_> {
160    fn from(data: Value) -> Self {
161        Body::Json(Cow::Owned(data.to_string()))
162    }
163}
164
165impl<'a> From<&'a [u8]> for Body<'a> {
166    fn from(data: &'a [u8]) -> Self {
167        Body::Bytes(Cow::Borrowed(data))
168    }
169}
170
171impl From<Vec<u8>> for Body<'_> {
172    fn from(data: Vec<u8>) -> Self {
173        Body::Bytes(Cow::Owned(data))
174    }
175}
176
177#[doc(hidden)]
178impl Into<TypedData> for Body<'_> {
179    fn into(self) -> TypedData {
180        TypedData {
181            data: match self {
182                Body::Empty => None,
183                Body::String(s) => Some(Data::String(s.into_owned())),
184                Body::Json(s) => Some(Data::Json(s.into_owned())),
185                Body::Bytes(b) => Some(Data::Bytes(b.into_owned())),
186            },
187        }
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194    use matches::matches;
195    use serde::{Deserialize, Serialize};
196    use serde_json::to_value;
197    use std::fmt::Write;
198
199    #[test]
200    fn it_has_a_default_content_type() {
201        let body = Body::Empty;
202        assert_eq!(body.default_content_type(), "text/plain");
203
204        let body = Body::String(Cow::Borrowed("test"));
205        assert_eq!(body.default_content_type(), "text/plain");
206
207        let body = Body::Json(Cow::Borrowed("1"));
208        assert_eq!(body.default_content_type(), "application/json");
209
210        let body = Body::Bytes(Cow::Borrowed(&[]));
211        assert_eq!(body.default_content_type(), "application/octet-stream");
212    }
213
214    #[test]
215    fn it_has_a_string_body() {
216        const BODY: &'static str = "test body";
217
218        let body: Body = BODY.into();
219        assert_eq!(body.as_str().unwrap(), BODY);
220
221        let data: TypedData = body.into();
222        assert_eq!(data.data, Some(Data::String(BODY.to_string())));
223    }
224
225    #[test]
226    fn it_has_a_json_body() {
227        #[derive(Serialize, Deserialize)]
228        struct SerializedData {
229            message: String,
230        };
231
232        const MESSAGE: &'static str = "test";
233
234        let data = SerializedData {
235            message: MESSAGE.to_string(),
236        };
237
238        let body: Body = ::serde_json::to_value(data).unwrap().into();
239        assert_eq!(body.as_json::<SerializedData>().unwrap().message, MESSAGE);
240
241        let data: TypedData = body.into();
242        assert_eq!(
243            data.data,
244            Some(Data::Json(r#"{"message":"test"}"#.to_string()))
245        );
246    }
247
248    #[test]
249    fn it_has_a_bytes_body() {
250        const BODY: &'static [u8] = &[1, 2, 3];
251
252        let body: Body = BODY.into();
253        assert_eq!(body.as_bytes(), BODY);
254
255        let data: TypedData = body.into();
256        assert_eq!(data.data, Some(Data::Bytes(BODY.to_vec())));
257    }
258
259    #[test]
260    fn it_displays_as_a_string() {
261        const BODY: &'static str = "test";
262
263        let body: Body = BODY.into();
264
265        let mut s = String::new();
266        write!(s, "{}", body).unwrap();
267
268        assert_eq!(s, BODY);
269    }
270
271    #[test]
272    fn it_converts_from_typed_data() {
273        let data = TypedData {
274            data: Some(Data::String("test".to_string())),
275        };
276
277        let body: Body = (&data).into();
278        assert!(matches!(body, Body::String(_)));
279        assert_eq!(body.as_str().unwrap(), "test");
280
281        let data = TypedData {
282            data: Some(Data::Json("test".to_string())),
283        };
284        let body: Body = (&data).into();
285        assert!(matches!(body, Body::Json(_)));
286        assert_eq!(body.as_str().unwrap(), "test");
287
288        let data = TypedData {
289            data: Some(Data::Bytes([0, 1, 2].to_vec())),
290        };
291        let body: Body = (&data).into();
292        assert!(matches!(body, Body::Bytes(_)));
293        assert_eq!(body.as_bytes(), [0, 1, 2]);
294
295        let data = TypedData {
296            data: Some(Data::Stream([0, 1, 2].to_vec())),
297        };
298        let body: Body = (&data).into();
299        assert!(matches!(body, Body::Bytes(_)));
300        assert_eq!(body.as_bytes(), [0, 1, 2]);
301    }
302
303    #[test]
304    fn it_converts_from_str() {
305        let body: Body = "test".into();
306        assert!(matches!(body, Body::String(Cow::Borrowed(_))));
307        assert_eq!(body.as_str().unwrap(), "test");
308    }
309
310    #[test]
311    fn it_converts_from_string() {
312        let body: Body = "test".to_string().into();
313        assert!(matches!(body, Body::String(Cow::Owned(_))));
314        assert_eq!(body.as_str().unwrap(), "test");
315    }
316
317    #[test]
318    fn it_converts_from_json() {
319        let body: Body = to_value("hello world").unwrap().into();
320        assert!(matches!(body, Body::Json(Cow::Owned(_))));
321        assert_eq!(body.as_str().unwrap(), r#""hello world""#);
322    }
323
324    #[test]
325    fn it_converts_from_u8_slice() {
326        let body: Body = [0, 1, 2][..].into();
327        assert!(matches!(body, Body::Bytes(Cow::Borrowed(_))));
328        assert_eq!(body.as_bytes(), [0, 1, 2]);
329    }
330
331    #[test]
332    fn it_converts_from_u8_vec() {
333        let body: Body = vec![0, 1, 2].into();
334        assert!(matches!(body, Body::Bytes(Cow::Owned(_))));
335        assert_eq!(body.as_bytes(), [0, 1, 2]);
336    }
337
338    #[test]
339    fn it_converts_to_typed_data() {
340        let body = Body::Empty;
341        let data: TypedData = body.into();
342        assert!(data.data.is_none());
343
344        let body: Body = "test".into();
345        let data: TypedData = body.into();
346        assert_eq!(data.data, Some(Data::String("test".to_string())));
347
348        let body: Body = to_value("test").unwrap().into();
349        let data: TypedData = body.into();
350        assert_eq!(data.data, Some(Data::Json(r#""test""#.to_string())));
351
352        let body: Body = vec![1, 2, 3].into();
353        let data: TypedData = body.into();
354        assert_eq!(data.data, Some(Data::Bytes([1, 2, 3].to_vec())));
355    }
356}