Skip to main content

camel_api/
from_body.rs

1//! `FromBody` trait and built-in implementations.
2//!
3//! Provides typed deserialization from a [`Body`] variant.
4//! For custom types that implement [`serde::de::DeserializeOwned`],
5//! use the [`impl_from_body_via_serde!`] macro.
6
7use bytes::Bytes;
8use serde_json::Value;
9
10use crate::body::Body;
11use crate::error::CamelError;
12
13/// Convert a [`Body`] into a typed value.
14///
15/// Built-in implementations are provided for `String`, `Vec<u8>`, `Bytes`,
16/// and `serde_json::Value`.
17///
18/// For custom types, implement this trait manually or use
19/// [`impl_from_body_via_serde!`].
20pub trait FromBody: Sized {
21    fn from_body(body: &Body) -> Result<Self, CamelError>;
22}
23
24// ---------------------------------------------------------------------------
25// String
26// ---------------------------------------------------------------------------
27
28impl FromBody for String {
29    fn from_body(body: &Body) -> Result<Self, CamelError> {
30        match body {
31            Body::Text(s) => Ok(s.clone()),
32            // Unwrap bare string literals to avoid surprising "abc" -> "\"abc\""
33            Body::Json(Value::String(s)) => Ok(s.clone()),
34            // Other JSON variants: serialize to string
35            Body::Json(v) => Ok(v.to_string()),
36            Body::Bytes(b) => String::from_utf8(b.to_vec()).map_err(|e| {
37                CamelError::TypeConversionFailed(format!(
38                    "cannot convert Body::Bytes to String: invalid UTF-8: {e}"
39                ))
40            }),
41            Body::Xml(s) => Ok(s.clone()),
42            Body::Empty | Body::Stream(_) => Err(CamelError::TypeConversionFailed(
43                "cannot convert empty or stream body to String".into(),
44            )),
45        }
46    }
47}
48
49// ---------------------------------------------------------------------------
50// Vec<u8>
51// ---------------------------------------------------------------------------
52
53impl FromBody for Vec<u8> {
54    fn from_body(body: &Body) -> Result<Self, CamelError> {
55        match body {
56            Body::Text(s) => Ok(s.clone().into_bytes()),
57            Body::Json(v) => serde_json::to_vec(v).map_err(|e| {
58                CamelError::TypeConversionFailed(format!(
59                    "cannot serialize Body::Json to Vec<u8>: {e}"
60                ))
61            }),
62            Body::Bytes(b) => Ok(b.to_vec()),
63            Body::Xml(s) => Ok(s.clone().into_bytes()),
64            Body::Empty | Body::Stream(_) => Err(CamelError::TypeConversionFailed(
65                "cannot convert empty or stream body to Vec<u8>".into(),
66            )),
67        }
68    }
69}
70
71// ---------------------------------------------------------------------------
72// Bytes
73// ---------------------------------------------------------------------------
74
75impl FromBody for Bytes {
76    fn from_body(body: &Body) -> Result<Self, CamelError> {
77        match body {
78            Body::Text(s) => Ok(Bytes::from(s.clone().into_bytes())),
79            Body::Json(v) => {
80                let b = serde_json::to_vec(v).map_err(|e| {
81                    CamelError::TypeConversionFailed(format!(
82                        "cannot serialize Body::Json to Bytes: {e}"
83                    ))
84                })?;
85                Ok(Bytes::from(b))
86            }
87            Body::Bytes(b) => Ok(b.clone()),
88            Body::Xml(s) => Ok(Bytes::from(s.clone().into_bytes())),
89            Body::Empty | Body::Stream(_) => Err(CamelError::TypeConversionFailed(
90                "cannot convert empty or stream body to Bytes".into(),
91            )),
92        }
93    }
94}
95
96// ---------------------------------------------------------------------------
97// serde_json::Value
98// ---------------------------------------------------------------------------
99
100impl FromBody for Value {
101    fn from_body(body: &Body) -> Result<Self, CamelError> {
102        match body {
103            Body::Json(v) => Ok(v.clone()),
104            Body::Text(s) => serde_json::from_str(s).map_err(|e| {
105                CamelError::TypeConversionFailed(format!("cannot parse Body::Text as JSON: {e}"))
106            }),
107            Body::Bytes(b) => serde_json::from_slice(b).map_err(|e| {
108                CamelError::TypeConversionFailed(format!("cannot parse Body::Bytes as JSON: {e}"))
109            }),
110            Body::Xml(_) | Body::Empty | Body::Stream(_) => Err(CamelError::TypeConversionFailed(
111                "cannot convert Xml, Empty or Stream body to serde_json::Value".into(),
112            )),
113        }
114    }
115}
116
117// ---------------------------------------------------------------------------
118// Macro for custom serde types
119// ---------------------------------------------------------------------------
120
121/// Implements [`FromBody`] for a type that implements [`serde::de::DeserializeOwned`].
122///
123/// # Example
124/// ```rust
125/// use camel_api::{impl_from_body_via_serde, FromBody};
126/// use serde::Deserialize;
127///
128/// #[derive(Deserialize)]
129/// struct Order { id: u64 }
130///
131/// impl_from_body_via_serde!(Order);
132/// ```
133#[macro_export]
134macro_rules! impl_from_body_via_serde {
135    ($t:ty) => {
136        impl $crate::FromBody for $t {
137            fn from_body(body: &$crate::body::Body) -> Result<Self, $crate::error::CamelError> {
138                match body {
139                    $crate::body::Body::Json(v) => serde_json::from_value(v.clone()).map_err(|e| {
140                        $crate::error::CamelError::TypeConversionFailed(e.to_string())
141                    }),
142                    $crate::body::Body::Text(s) => serde_json::from_str(s).map_err(|e| {
143                        $crate::error::CamelError::TypeConversionFailed(e.to_string())
144                    }),
145                    $crate::body::Body::Bytes(b) => serde_json::from_slice(b).map_err(|e| {
146                        $crate::error::CamelError::TypeConversionFailed(e.to_string())
147                    }),
148                    _ => Err($crate::error::CamelError::TypeConversionFailed(format!(
149                        "impl_from_body_via_serde: unsupported body variant for {}",
150                        std::any::type_name::<$t>()
151                    ))),
152                }
153            }
154        }
155    };
156}
157
158// ---------------------------------------------------------------------------
159// Unit tests
160// ---------------------------------------------------------------------------
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165    use bytes::Bytes;
166    use serde::Deserialize;
167    use serde_json::json;
168
169    // --- String ---
170
171    #[test]
172    fn string_from_text() {
173        let body = Body::Text("hello".into());
174        assert_eq!(String::from_body(&body).unwrap(), "hello");
175    }
176
177    #[test]
178    fn string_from_json_string_literal() {
179        // Body::Json(Value::String) must NOT produce extra quotes
180        let body = Body::Json(json!("hello"));
181        assert_eq!(String::from_body(&body).unwrap(), "hello");
182    }
183
184    #[test]
185    fn string_from_json_number() {
186        let body = Body::Json(json!(42));
187        assert_eq!(String::from_body(&body).unwrap(), "42");
188    }
189
190    #[test]
191    fn string_from_json_object() {
192        let body = Body::Json(json!({"a": 1}));
193        let s = String::from_body(&body).unwrap();
194        assert!(s.contains("\"a\""));
195    }
196
197    #[test]
198    fn string_from_bytes_valid_utf8() {
199        let body = Body::Bytes(Bytes::from_static(b"world"));
200        assert_eq!(String::from_body(&body).unwrap(), "world");
201    }
202
203    #[test]
204    fn string_from_bytes_invalid_utf8() {
205        let body = Body::Bytes(Bytes::from_static(&[0xFF, 0xFE]));
206        assert!(matches!(
207            String::from_body(&body),
208            Err(CamelError::TypeConversionFailed(_))
209        ));
210    }
211
212    #[test]
213    fn string_from_xml() {
214        let body = Body::Xml("<root/>".into());
215        assert_eq!(String::from_body(&body).unwrap(), "<root/>");
216    }
217
218    #[test]
219    fn string_from_empty_fails() {
220        assert!(matches!(
221            String::from_body(&Body::Empty),
222            Err(CamelError::TypeConversionFailed(_))
223        ));
224    }
225
226    // --- Vec<u8> ---
227
228    #[test]
229    fn vec_u8_from_text() {
230        let body = Body::Text("hi".into());
231        assert_eq!(Vec::<u8>::from_body(&body).unwrap(), b"hi");
232    }
233
234    #[test]
235    fn vec_u8_from_json() {
236        let body = Body::Json(json!({"k": 1}));
237        let v = Vec::<u8>::from_body(&body).unwrap();
238        let s = String::from_utf8(v).unwrap();
239        assert!(s.contains("\"k\""));
240    }
241
242    #[test]
243    fn vec_u8_from_bytes() {
244        let body = Body::Bytes(Bytes::from_static(b"data"));
245        assert_eq!(Vec::<u8>::from_body(&body).unwrap(), b"data");
246    }
247
248    #[test]
249    fn vec_u8_from_xml() {
250        let body = Body::Xml("<r/>".into());
251        assert_eq!(Vec::<u8>::from_body(&body).unwrap(), b"<r/>");
252    }
253
254    #[test]
255    fn vec_u8_from_empty_fails() {
256        assert!(matches!(
257            Vec::<u8>::from_body(&Body::Empty),
258            Err(CamelError::TypeConversionFailed(_))
259        ));
260    }
261
262    // --- Bytes ---
263
264    #[test]
265    fn bytes_from_text() {
266        let body = Body::Text("hi".into());
267        assert_eq!(Bytes::from_body(&body).unwrap(), Bytes::from_static(b"hi"));
268    }
269
270    #[test]
271    fn bytes_from_json() {
272        let body = Body::Json(json!(1));
273        let b = Bytes::from_body(&body).unwrap();
274        assert_eq!(&b[..], b"1");
275    }
276
277    #[test]
278    fn bytes_from_bytes() {
279        let body = Body::Bytes(Bytes::from_static(b"raw"));
280        assert_eq!(Bytes::from_body(&body).unwrap(), Bytes::from_static(b"raw"));
281    }
282
283    #[test]
284    fn bytes_from_xml() {
285        let body = Body::Xml("<x/>".into());
286        assert_eq!(
287            Bytes::from_body(&body).unwrap(),
288            Bytes::from_static(b"<x/>")
289        );
290    }
291
292    #[test]
293    fn bytes_from_empty_fails() {
294        assert!(matches!(
295            Bytes::from_body(&Body::Empty),
296            Err(CamelError::TypeConversionFailed(_))
297        ));
298    }
299
300    // --- serde_json::Value ---
301
302    #[test]
303    fn value_from_json() {
304        let body = Body::Json(json!({"x": 2}));
305        assert_eq!(Value::from_body(&body).unwrap(), json!({"x": 2}));
306    }
307
308    #[test]
309    fn value_from_text_valid_json() {
310        let body = Body::Text(r#"{"a":1}"#.into());
311        let v = Value::from_body(&body).unwrap();
312        assert_eq!(v["a"], 1);
313    }
314
315    #[test]
316    fn value_from_text_invalid_json() {
317        let body = Body::Text("not json".into());
318        assert!(matches!(
319            Value::from_body(&body),
320            Err(CamelError::TypeConversionFailed(_))
321        ));
322    }
323
324    #[test]
325    fn value_from_bytes_valid_json() {
326        let body = Body::Bytes(Bytes::from_static(b"{\"z\":3}"));
327        let v = Value::from_body(&body).unwrap();
328        assert_eq!(v["z"], 3);
329    }
330
331    #[test]
332    fn value_from_xml_fails() {
333        let body = Body::Xml("<root/>".into());
334        assert!(matches!(
335            Value::from_body(&body),
336            Err(CamelError::TypeConversionFailed(_))
337        ));
338    }
339
340    #[test]
341    fn value_from_empty_fails() {
342        assert!(matches!(
343            Value::from_body(&Body::Empty),
344            Err(CamelError::TypeConversionFailed(_))
345        ));
346    }
347
348    // --- impl_from_body_via_serde! ---
349
350    #[derive(Debug, PartialEq, Deserialize)]
351    struct Order {
352        id: u64,
353        amount: f64,
354    }
355
356    crate::impl_from_body_via_serde!(Order);
357
358    #[test]
359    fn serde_macro_from_json() {
360        let body = Body::Json(json!({"id": 1, "amount": 9.99}));
361        let order = Order::from_body(&body).unwrap();
362        assert_eq!(
363            order,
364            Order {
365                id: 1,
366                amount: 9.99
367            }
368        );
369    }
370
371    #[test]
372    fn serde_macro_from_text() {
373        let body = Body::Text(r#"{"id": 2, "amount": 5.0}"#.into());
374        let order = Order::from_body(&body).unwrap();
375        assert_eq!(order, Order { id: 2, amount: 5.0 });
376    }
377
378    #[test]
379    fn serde_macro_from_bytes() {
380        let body = Body::Bytes(Bytes::from_static(br#"{"id": 3, "amount": 1.0}"#));
381        let order = Order::from_body(&body).unwrap();
382        assert_eq!(order, Order { id: 3, amount: 1.0 });
383    }
384
385    #[test]
386    fn serde_macro_from_xml_fails() {
387        let body = Body::Xml("<root/>".into());
388        assert!(matches!(
389            Order::from_body(&body),
390            Err(CamelError::TypeConversionFailed(_))
391        ));
392    }
393
394    #[test]
395    fn serde_macro_invalid_json_fails() {
396        let body = Body::Json(json!({"wrong_field": "x"}));
397        assert!(matches!(
398            Order::from_body(&body),
399            Err(CamelError::TypeConversionFailed(_))
400        ));
401    }
402}