Skip to main content

camel_api/
body_converter.rs

1use crate::body::Body;
2use crate::error::CamelError;
3use bytes::Bytes;
4
5/// Target type for body conversion.
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum BodyType {
8    Text,
9    Json,
10    Bytes,
11    Empty,
12}
13
14/// Convert a `Body` to the target `BodyType`.
15///
16/// `Body::Stream` is always an error — materialize with `into_bytes()` first.
17/// Returns `CamelError::TypeConversionFailed` on any incompatible conversion.
18pub fn convert(body: Body, target: BodyType) -> Result<Body, CamelError> {
19    match (body, target) {
20        // noop: same variant
21        (b @ Body::Text(_), BodyType::Text) => Ok(b),
22        (b @ Body::Json(_), BodyType::Json) => Ok(b),
23        (b @ Body::Bytes(_), BodyType::Bytes) => Ok(b),
24        (Body::Empty, BodyType::Empty) => Ok(Body::Empty),
25
26        // Text conversions
27        (Body::Text(s), BodyType::Json) => {
28            let v = serde_json::from_str(&s).map_err(|e| {
29                CamelError::TypeConversionFailed(format!("cannot convert Body::Text to Json: {e}"))
30            })?;
31            Ok(Body::Json(v))
32        }
33        (Body::Text(s), BodyType::Bytes) => Ok(Body::Bytes(Bytes::from(s.into_bytes()))),
34        (Body::Text(_), BodyType::Empty) => Err(CamelError::TypeConversionFailed(
35            "cannot convert Body::Text to Empty".to_string(),
36        )),
37
38        // Json conversions
39        (Body::Json(v), BodyType::Text) => Ok(Body::Text(v.to_string())),
40        (Body::Json(v), BodyType::Bytes) => {
41            let b = serde_json::to_vec(&v).map_err(|e| {
42                CamelError::TypeConversionFailed(format!("cannot convert Body::Json to Bytes: {e}"))
43            })?;
44            Ok(Body::Bytes(Bytes::from(b)))
45        }
46        (Body::Json(_), BodyType::Empty) => Err(CamelError::TypeConversionFailed(
47            "cannot convert Body::Json to Empty".to_string(),
48        )),
49
50        // Bytes conversions
51        (Body::Bytes(b), BodyType::Text) => {
52            let s = String::from_utf8(b.to_vec()).map_err(|e| {
53                CamelError::TypeConversionFailed(format!(
54                    "cannot convert Body::Bytes to Text: invalid UTF-8 sequence: {e}"
55                ))
56            })?;
57            Ok(Body::Text(s))
58        }
59        (Body::Bytes(b), BodyType::Json) => {
60            let s = String::from_utf8(b.to_vec()).map_err(|e| {
61                CamelError::TypeConversionFailed(format!(
62                    "cannot convert Body::Bytes to Json (UTF-8 error): {e}"
63                ))
64            })?;
65            let v = serde_json::from_str(&s).map_err(|e| {
66                CamelError::TypeConversionFailed(format!("cannot convert Body::Bytes to Json: {e}"))
67            })?;
68            Ok(Body::Json(v))
69        }
70        (Body::Bytes(_), BodyType::Empty) => Err(CamelError::TypeConversionFailed(
71            "cannot convert Body::Bytes to Empty".to_string(),
72        )),
73
74        // Empty conversions
75        (Body::Empty, BodyType::Text) => Err(CamelError::TypeConversionFailed(
76            "cannot convert Empty body to Text".to_string(),
77        )),
78        (Body::Empty, BodyType::Json) => Err(CamelError::TypeConversionFailed(
79            "cannot convert Empty body to Json".to_string(),
80        )),
81        (Body::Empty, BodyType::Bytes) => Err(CamelError::TypeConversionFailed(
82            "cannot convert Empty body to Bytes".to_string(),
83        )),
84
85        // Stream: always fails
86        (Body::Stream(_), _) => Err(CamelError::TypeConversionFailed(
87            "cannot convert Body::Stream: materialize first with into_bytes()".to_string(),
88        )),
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95    use serde_json::json;
96
97    #[test]
98    fn text_to_json_valid() {
99        let body = Body::Text(r#"{"a":1}"#.to_string());
100        let result = convert(body, BodyType::Json).unwrap();
101        assert_eq!(result, Body::Json(json!({"a": 1})));
102    }
103
104    #[test]
105    fn text_to_json_invalid() {
106        let body = Body::Text("not json".to_string());
107        let result = convert(body, BodyType::Json);
108        assert!(matches!(result, Err(CamelError::TypeConversionFailed(_))));
109    }
110
111    #[test]
112    fn json_to_text() {
113        let body = Body::Json(json!({"a": 1}));
114        let result = convert(body, BodyType::Text).unwrap();
115        match result {
116            Body::Text(s) => assert!(s.contains("\"a\"")),
117            _ => panic!("expected Body::Text"),
118        }
119    }
120
121    #[test]
122    fn json_to_bytes() {
123        let body = Body::Json(json!({"x": 2}));
124        let result = convert(body, BodyType::Bytes).unwrap();
125        assert!(matches!(result, Body::Bytes(_)));
126    }
127
128    #[test]
129    fn bytes_to_text_valid() {
130        let body = Body::Bytes(Bytes::from_static(b"hello"));
131        let result = convert(body, BodyType::Text).unwrap();
132        assert_eq!(result, Body::Text("hello".to_string()));
133    }
134
135    #[test]
136    fn bytes_to_text_invalid_utf8() {
137        let body = Body::Bytes(Bytes::from_static(&[0xFF, 0xFE]));
138        let result = convert(body, BodyType::Text);
139        assert!(matches!(result, Err(CamelError::TypeConversionFailed(_))));
140    }
141
142    #[test]
143    fn text_to_bytes() {
144        let body = Body::Text("hi".to_string());
145        let result = convert(body, BodyType::Bytes).unwrap();
146        assert_eq!(result, Body::Bytes(Bytes::from_static(b"hi")));
147    }
148
149    #[test]
150    fn empty_to_text_fails() {
151        let result = convert(Body::Empty, BodyType::Text);
152        assert!(matches!(result, Err(CamelError::TypeConversionFailed(_))));
153    }
154
155    #[test]
156    fn empty_to_empty_noop() {
157        let result = convert(Body::Empty, BodyType::Empty).unwrap();
158        assert!(matches!(result, Body::Empty));
159    }
160
161    #[test]
162    fn noop_same_type_text() {
163        let body = Body::Text("x".to_string());
164        let result = convert(body, BodyType::Text).unwrap();
165        assert!(matches!(result, Body::Text(_)));
166    }
167
168    #[test]
169    fn noop_same_type_json() {
170        let body = Body::Json(json!(1));
171        let result = convert(body, BodyType::Json).unwrap();
172        assert!(matches!(result, Body::Json(_)));
173    }
174
175    #[test]
176    fn noop_same_type_bytes() {
177        let body = Body::Bytes(Bytes::from_static(b"x"));
178        let result = convert(body, BodyType::Bytes).unwrap();
179        assert!(matches!(result, Body::Bytes(_)));
180    }
181
182    #[test]
183    fn stream_to_any_fails() {
184        use crate::body::{StreamBody, StreamMetadata};
185        use futures::stream;
186        use std::sync::Arc;
187        use tokio::sync::Mutex;
188
189        let stream = stream::iter(vec![Ok(Bytes::from_static(b"data"))]);
190        let body = Body::Stream(StreamBody {
191            stream: Arc::new(Mutex::new(Some(Box::pin(stream)))),
192            metadata: StreamMetadata::default(),
193        });
194        let result = convert(body, BodyType::Text);
195        assert!(matches!(result, Err(CamelError::TypeConversionFailed(_))));
196    }
197
198    #[test]
199    fn bytes_to_json_valid() {
200        let body = Body::Bytes(Bytes::from_static(b"{\"k\":1}"));
201        let result = convert(body, BodyType::Json).unwrap();
202        assert!(matches!(result, Body::Json(_)));
203    }
204
205    #[test]
206    fn bytes_to_json_invalid_utf8() {
207        let body = Body::Bytes(Bytes::from_static(&[0xFF, 0xFE]));
208        let result = convert(body, BodyType::Json);
209        assert!(matches!(result, Err(CamelError::TypeConversionFailed(_))));
210    }
211
212    #[test]
213    fn to_empty_always_fails() {
214        assert!(matches!(
215            convert(Body::Text("x".into()), BodyType::Empty),
216            Err(CamelError::TypeConversionFailed(_))
217        ));
218        assert!(matches!(
219            convert(Body::Json(serde_json::json!(1)), BodyType::Empty),
220            Err(CamelError::TypeConversionFailed(_))
221        ));
222        assert!(matches!(
223            convert(Body::Bytes(Bytes::from_static(b"x")), BodyType::Empty),
224            Err(CamelError::TypeConversionFailed(_))
225        ));
226    }
227}