Skip to main content

camel_processor/data_format/
json.rs

1use camel_api::body::Body;
2use camel_api::data_format::DataFormat;
3use camel_api::error::CamelError;
4
5/// Maximum input size accepted by `JsonDataFormat::unmarshal` (DoS cap, R3-L2).
6/// `serde_json` retains its built-in 128-level recursion limit; this cap bounds
7/// the byte volume before parsing begins.
8const MAX_JSON_BYTES: usize = 16 * 1024 * 1024; // 16 MiB
9
10pub struct JsonDataFormat;
11
12impl DataFormat for JsonDataFormat {
13    fn name(&self) -> &str {
14        "json"
15    }
16
17    fn marshal(&self, body: Body) -> Result<Body, CamelError> {
18        match body {
19            Body::Json(v) => {
20                let s = serde_json::to_string(&v).map_err(|e| {
21                    CamelError::TypeConversionFailed(format!(
22                        "cannot marshal Body::Json to text: {e}"
23                    ))
24                })?;
25                Ok(Body::Text(s))
26            }
27            Body::Text(_) => Ok(body),
28            Body::Stream(_) => Err(CamelError::TypeConversionFailed(
29                "cannot marshal Body::Stream — add 'stream_cache' or 'convert_body_to' before this step".to_string(),
30            )),
31            // Empty body: no-op. A REST handler that produces no body (e.g. a
32            // 204 DELETE) must not 500 at marshal — leave the body empty so the
33            // reply finaliser ships an empty response (spec §8.1).
34            Body::Empty => Ok(Body::Empty),
35            Body::Xml(_) => Err(CamelError::TypeConversionFailed(
36                "JsonDataFormat::marshal only supports Body::Json, Body::Text, Body::Bytes, \
37                 and Body::Empty"
38                    .to_string(),
39            )),
40            Body::Bytes(b) => {
41                let v: serde_json::Value = serde_json::from_slice(&b).map_err(|e| {
42                    CamelError::TypeConversionFailed(format!(
43                        "cannot marshal Body::Bytes as JSON: {e}"
44                    ))
45                })?;
46                Ok(Body::Text(serde_json::to_string(&v).map_err(|e| {
47                    CamelError::TypeConversionFailed(format!(
48                        "cannot serialize JSON value to text: {e}"
49                    ))
50                })?))
51            }
52        }
53    }
54
55    fn unmarshal(&self, body: Body) -> Result<Body, CamelError> {
56        match body {
57            Body::Json(_) => Ok(body),
58            Body::Text(s) => {
59                if s.len() > MAX_JSON_BYTES {
60                    return Err(CamelError::TypeConversionFailed(format!(
61                        "JSON unmarshal input {} bytes exceeds max {MAX_JSON_BYTES}",
62                        s.len()
63                    )));
64                }
65                let v = serde_json::from_str(&s).map_err(|e| {
66                    CamelError::TypeConversionFailed(format!(
67                        "cannot unmarshal Body::Text as JSON: {e}"
68                    ))
69                })?;
70                Ok(Body::Json(v))
71            }
72            Body::Bytes(b) => {
73                if b.len() > MAX_JSON_BYTES {
74                    return Err(CamelError::TypeConversionFailed(format!(
75                        "JSON unmarshal input {} bytes exceeds max {MAX_JSON_BYTES}",
76                        b.len()
77                    )));
78                }
79                let v = serde_json::from_slice(&b).map_err(|e| {
80                    CamelError::TypeConversionFailed(format!(
81                        "cannot unmarshal Body::Bytes as JSON: {e}"
82                    ))
83                })?;
84                Ok(Body::Json(v))
85            }
86            Body::Stream(_) => Err(CamelError::TypeConversionFailed(
87                "cannot unmarshal Body::Stream directly — use UnmarshalService which auto-materializes"
88                    .to_string(),
89            )),
90            // Empty body: no-op. A body-less request (e.g. POST with no body)
91            // must skip unmarshal rather than 500 — spec §8.1: "if the body is
92            // empty, skip unmarshal".
93            Body::Empty => Ok(Body::Empty),
94            Body::Xml(_) => Err(CamelError::TypeConversionFailed(
95                "JsonDataFormat::unmarshal only supports Body::Json, Body::Text, Body::Bytes, \
96                 and Body::Empty"
97                    .to_string(),
98            )),
99        }
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106    use bytes::Bytes;
107    use serde_json::json;
108
109    #[test]
110    fn test_name() {
111        assert_eq!(JsonDataFormat.name(), "json");
112    }
113
114    #[test]
115    fn test_unmarshal_text_to_json() {
116        let body = Body::Text(r#"{"a":1}"#.to_string());
117        let result = JsonDataFormat.unmarshal(body).unwrap();
118        assert!(matches!(result, Body::Json(_)));
119        if let Body::Json(v) = result {
120            assert_eq!(v["a"], json!(1));
121        }
122    }
123
124    #[test]
125    fn test_unmarshal_bytes_to_json() {
126        let body = Body::Bytes(Bytes::from_static(b"{\"b\":2}"));
127        let result = JsonDataFormat.unmarshal(body).unwrap();
128        assert!(matches!(result, Body::Json(_)));
129    }
130
131    #[test]
132    fn test_unmarshal_invalid_json() {
133        let body = Body::Text("not json".to_string());
134        let result = JsonDataFormat.unmarshal(body);
135        assert!(matches!(result, Err(CamelError::TypeConversionFailed(_))));
136    }
137
138    #[test]
139    fn test_unmarshal_json_noop() {
140        let body = Body::Json(json!({"x": 1}));
141        let result = JsonDataFormat.unmarshal(body).unwrap();
142        assert!(matches!(result, Body::Json(_)));
143    }
144
145    #[test]
146    fn test_unmarshal_unsupported_variant() {
147        let body = Body::Xml("<root/>".to_string());
148        let result = JsonDataFormat.unmarshal(body);
149        assert!(matches!(result, Err(CamelError::TypeConversionFailed(_))));
150    }
151
152    #[test]
153    fn test_unmarshal_empty_is_noop() {
154        // spec §8.1: an empty request body skips unmarshal instead of erroring.
155        let result = JsonDataFormat.unmarshal(Body::Empty).unwrap();
156        assert!(matches!(result, Body::Empty));
157    }
158
159    #[test]
160    fn test_marshal_empty_is_noop() {
161        // spec §8.1: an empty response body marshals to empty (e.g. 204 DELETE).
162        let result = JsonDataFormat.marshal(Body::Empty).unwrap();
163        assert!(matches!(result, Body::Empty));
164    }
165
166    #[test]
167    fn test_unmarshal_stream_rejected() {
168        use camel_api::body::{StreamBody, StreamMetadata};
169        use futures::stream;
170        use std::sync::Arc;
171        use tokio::sync::Mutex;
172
173        let stream = stream::iter(vec![Ok(Bytes::from_static(b"data"))]);
174        let body = Body::Stream(StreamBody {
175            stream: Arc::new(Mutex::new(Some(Box::pin(stream)))),
176            metadata: StreamMetadata::default(),
177        });
178        let result = JsonDataFormat.unmarshal(body);
179        assert!(matches!(result, Err(CamelError::TypeConversionFailed(_))));
180    }
181
182    #[test]
183    fn test_marshal_json_to_text() {
184        let body = Body::Json(json!({"key": "value"}));
185        let result = JsonDataFormat.marshal(body).unwrap();
186        match result {
187            Body::Text(s) => assert!(s.contains("\"key\"")),
188            _ => panic!("expected Body::Text"),
189        }
190    }
191
192    #[test]
193    fn test_marshal_text_noop() {
194        let body = Body::Text("already text".to_string());
195        let result = JsonDataFormat.marshal(body).unwrap();
196        assert_eq!(result, Body::Text("already text".to_string()));
197    }
198
199    #[test]
200    fn test_marshal_unsupported_variant() {
201        let body = Body::Xml("<root/>".to_string());
202        let result = JsonDataFormat.marshal(body);
203        assert!(matches!(result, Err(CamelError::TypeConversionFailed(_))));
204    }
205
206    #[test]
207    fn test_marshal_bytes_to_text() {
208        let body = Body::Bytes(Bytes::from_static(b"{\"key\":\"val\"}"));
209        let result = JsonDataFormat.marshal(body).unwrap();
210        match result {
211            Body::Text(s) => assert!(s.contains("\"key\"")),
212            _ => panic!("expected Body::Text"),
213        }
214    }
215
216    #[test]
217    fn test_marshal_invalid_bytes_returns_error() {
218        let body = Body::Bytes(Bytes::from_static(b"not json"));
219        let result = JsonDataFormat.marshal(body);
220        assert!(matches!(result, Err(CamelError::TypeConversionFailed(_))));
221    }
222
223    #[test]
224    fn test_unmarshal_text_size_cap() {
225        // Build a valid JSON string exceeding the 16 MiB cap.
226        let big = format!(r#"{{"k":"{}"}}"#, "x".repeat(16 * 1024 * 1024 + 10));
227        let body = Body::Text(big);
228        let result = JsonDataFormat.unmarshal(body);
229        assert!(result.is_err(), "expected error for oversized JSON text");
230        let msg = format!("{}", result.unwrap_err());
231        assert!(
232            msg.contains("exceeds") && msg.contains("max"),
233            "error should mention size cap: {msg}"
234        );
235    }
236
237    #[test]
238    fn test_unmarshal_bytes_size_cap() {
239        let big = format!(r#"{{"k":"{}"}}"#, "x".repeat(16 * 1024 * 1024 + 10));
240        let body = Body::Bytes(bytes::Bytes::from(big));
241        let result = JsonDataFormat.unmarshal(body);
242        assert!(result.is_err());
243    }
244}