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;
4use serde::Deserialize;
5
6const DEFAULT_MAX_JSON_BYTES: usize = 16 * 1024 * 1024; // 16 MiB
7
8fn default_max_bytes() -> usize {
9    DEFAULT_MAX_JSON_BYTES
10}
11
12/// Configuration for [`JsonDataFormat`]. All fields have hardened defaults;
13/// setting a non-default value is the per-item explicit choice per ADR-0033.
14#[derive(Debug, Clone, Deserialize)]
15#[serde(deny_unknown_fields)]
16pub struct JsonConfig {
17    /// Maximum input size accepted by `unmarshal` (DoS cap).
18    /// Default 16 MiB. The `max_bytes` cap is inert during `marshal`
19    /// (marshal serializes; it never parses untrusted input).
20    #[serde(default = "default_max_bytes")]
21    pub max_bytes: usize,
22}
23
24impl Default for JsonConfig {
25    fn default() -> Self {
26        Self {
27            max_bytes: DEFAULT_MAX_JSON_BYTES,
28        }
29    }
30}
31
32#[derive(Debug, Clone, Default)]
33pub struct JsonDataFormat {
34    config: JsonConfig,
35}
36
37impl JsonDataFormat {
38    pub fn new(config: JsonConfig) -> Self {
39        Self { config }
40    }
41}
42
43impl DataFormat for JsonDataFormat {
44    fn name(&self) -> &str {
45        "json"
46    }
47
48    fn marshal(&self, body: Body) -> Result<Body, CamelError> {
49        match body {
50            Body::Json(v) => {
51                let s = serde_json::to_string(&v).map_err(|e| {
52                    CamelError::TypeConversionFailed(format!(
53                        "cannot marshal Body::Json to text: {e}"
54                    ))
55                })?;
56                Ok(Body::Text(s))
57            }
58            Body::Text(_) => Ok(body),
59            Body::Stream(_) => Err(CamelError::TypeConversionFailed(
60                "cannot marshal Body::Stream — add 'stream_cache' or 'convert_body_to' before this step".to_string(),
61            )),
62            // Empty body: no-op. A REST handler that produces no body (e.g. a
63            // 204 DELETE) must not 500 at marshal — leave the body empty so the
64            // reply finaliser ships an empty response (spec §8.1).
65            Body::Empty => Ok(Body::Empty),
66            Body::Xml(_) => Err(CamelError::TypeConversionFailed(
67                "JsonDataFormat::marshal only supports Body::Json, Body::Text, Body::Bytes, \
68                 and Body::Empty"
69                    .to_string(),
70            )),
71            Body::Bytes(b) => {
72                let v: serde_json::Value = serde_json::from_slice(&b).map_err(|e| {
73                    CamelError::TypeConversionFailed(format!(
74                        "cannot marshal Body::Bytes as JSON: {e}"
75                    ))
76                })?;
77                Ok(Body::Text(serde_json::to_string(&v).map_err(|e| {
78                    CamelError::TypeConversionFailed(format!(
79                        "cannot serialize JSON value to text: {e}"
80                    ))
81                })?))
82            }
83            _ => Err(CamelError::TypeConversionFailed(
84                "JsonDataFormat::marshal does not support this body type".to_string(),
85            )),
86        }
87    }
88
89    fn unmarshal(&self, body: Body) -> Result<Body, CamelError> {
90        match body {
91            Body::Json(_) => Ok(body),
92            Body::Text(s) => {
93                if s.len() > self.config.max_bytes {
94                    return Err(CamelError::TypeConversionFailed(format!(
95                        "JSON unmarshal input {} bytes exceeds max {}",
96                        s.len(),
97                        self.config.max_bytes
98                    )));
99                }
100                let v = serde_json::from_str(&s).map_err(|e| {
101                    CamelError::TypeConversionFailed(format!(
102                        "cannot unmarshal Body::Text as JSON: {e}"
103                    ))
104                })?;
105                Ok(Body::Json(v))
106            }
107            Body::Bytes(b) => {
108                if b.len() > self.config.max_bytes {
109                    return Err(CamelError::TypeConversionFailed(format!(
110                        "JSON unmarshal input {} bytes exceeds max {}",
111                        b.len(),
112                        self.config.max_bytes
113                    )));
114                }
115                let v = serde_json::from_slice(&b).map_err(|e| {
116                    CamelError::TypeConversionFailed(format!(
117                        "cannot unmarshal Body::Bytes as JSON: {e}"
118                    ))
119                })?;
120                Ok(Body::Json(v))
121            }
122            Body::Stream(_) => Err(CamelError::TypeConversionFailed(
123                "cannot unmarshal Body::Stream directly — use UnmarshalService which auto-materializes"
124                    .to_string(),
125            )),
126            // Empty body: no-op. A body-less request (e.g. POST with no body)
127            // must skip unmarshal rather than 500 — spec §8.1: "if the body is
128            // empty, skip unmarshal".
129            Body::Empty => Ok(Body::Empty),
130            Body::Xml(_) => Err(CamelError::TypeConversionFailed(
131                "JsonDataFormat::unmarshal only supports Body::Json, Body::Text, Body::Bytes, \
132                 and Body::Empty"
133                    .to_string(),
134            )),
135            _ => Err(CamelError::TypeConversionFailed(
136                "JsonDataFormat::unmarshal does not support this body type".to_string(),
137            )),
138        }
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145    use bytes::Bytes;
146    use serde_json::json;
147
148    #[test]
149    fn test_name() {
150        assert_eq!(JsonDataFormat::default().name(), "json");
151    }
152
153    #[test]
154    fn test_unmarshal_text_to_json() {
155        let body = Body::Text(r#"{"a":1}"#.to_string());
156        let result = JsonDataFormat::default().unmarshal(body).unwrap();
157        assert!(matches!(result, Body::Json(_)));
158        if let Body::Json(v) = result {
159            assert_eq!(v["a"], json!(1));
160        }
161    }
162
163    #[test]
164    fn test_unmarshal_bytes_to_json() {
165        let body = Body::Bytes(Bytes::from_static(b"{\"b\":2}"));
166        let result = JsonDataFormat::default().unmarshal(body).unwrap();
167        assert!(matches!(result, Body::Json(_)));
168    }
169
170    #[test]
171    fn test_unmarshal_invalid_json() {
172        let body = Body::Text("not json".to_string());
173        let result = JsonDataFormat::default().unmarshal(body);
174        assert!(matches!(result, Err(CamelError::TypeConversionFailed(_))));
175    }
176
177    #[test]
178    fn test_unmarshal_json_noop() {
179        let body = Body::Json(json!({"x": 1}));
180        let result = JsonDataFormat::default().unmarshal(body).unwrap();
181        assert!(matches!(result, Body::Json(_)));
182    }
183
184    #[test]
185    fn test_unmarshal_unsupported_variant() {
186        let body = Body::Xml("<root/>".to_string());
187        let result = JsonDataFormat::default().unmarshal(body);
188        assert!(matches!(result, Err(CamelError::TypeConversionFailed(_))));
189    }
190
191    #[test]
192    fn test_unmarshal_empty_is_noop() {
193        // spec §8.1: an empty request body skips unmarshal instead of erroring.
194        let result = JsonDataFormat::default().unmarshal(Body::Empty).unwrap();
195        assert!(matches!(result, Body::Empty));
196    }
197
198    #[test]
199    fn test_marshal_empty_is_noop() {
200        // spec §8.1: an empty response body marshals to empty (e.g. 204 DELETE).
201        let result = JsonDataFormat::default().marshal(Body::Empty).unwrap();
202        assert!(matches!(result, Body::Empty));
203    }
204
205    #[test]
206    fn test_unmarshal_stream_rejected() {
207        use camel_api::body::{StreamBody, StreamMetadata};
208        use futures::stream;
209        use std::sync::Arc;
210        use tokio::sync::Mutex;
211
212        let stream = stream::iter(vec![Ok(Bytes::from_static(b"data"))]);
213        let body = Body::Stream(StreamBody {
214            stream: Arc::new(Mutex::new(Some(Box::pin(stream)))),
215            metadata: StreamMetadata::default(),
216        });
217        let result = JsonDataFormat::default().unmarshal(body);
218        assert!(matches!(result, Err(CamelError::TypeConversionFailed(_))));
219    }
220
221    #[test]
222    fn test_marshal_json_to_text() {
223        let body = Body::Json(json!({"key": "value"}));
224        let result = JsonDataFormat::default().marshal(body).unwrap();
225        match result {
226            Body::Text(s) => assert!(s.contains("\"key\"")),
227            _ => panic!("expected Body::Text"),
228        }
229    }
230
231    #[test]
232    fn test_marshal_text_noop() {
233        let body = Body::Text("already text".to_string());
234        let result = JsonDataFormat::default().marshal(body).unwrap();
235        assert_eq!(result, Body::Text("already text".to_string()));
236    }
237
238    #[test]
239    fn test_marshal_unsupported_variant() {
240        let body = Body::Xml("<root/>".to_string());
241        let result = JsonDataFormat::default().marshal(body);
242        assert!(matches!(result, Err(CamelError::TypeConversionFailed(_))));
243    }
244
245    #[test]
246    fn test_marshal_bytes_to_text() {
247        let body = Body::Bytes(Bytes::from_static(b"{\"key\":\"val\"}"));
248        let result = JsonDataFormat::default().marshal(body).unwrap();
249        match result {
250            Body::Text(s) => assert!(s.contains("\"key\"")),
251            _ => panic!("expected Body::Text"),
252        }
253    }
254
255    #[test]
256    fn test_marshal_invalid_bytes_returns_error() {
257        let body = Body::Bytes(Bytes::from_static(b"not json"));
258        let result = JsonDataFormat::default().marshal(body);
259        assert!(matches!(result, Err(CamelError::TypeConversionFailed(_))));
260    }
261
262    #[test]
263    fn test_unmarshal_text_size_cap() {
264        // Build a valid JSON string exceeding the 16 MiB cap.
265        let big = format!(r#"{{"k":"{}"}}"#, "x".repeat(16 * 1024 * 1024 + 10));
266        let body = Body::Text(big);
267        let result = JsonDataFormat::default().unmarshal(body);
268        assert!(result.is_err(), "expected error for oversized JSON text");
269        let msg = format!("{}", result.unwrap_err());
270        assert!(
271            msg.contains("exceeds") && msg.contains("max"),
272            "error should mention size cap: {msg}"
273        );
274    }
275
276    #[test]
277    fn test_unmarshal_bytes_size_cap() {
278        let big = format!(r#"{{"k":"{}"}}"#, "x".repeat(16 * 1024 * 1024 + 10));
279        let body = Body::Bytes(bytes::Bytes::from(big));
280        let result = JsonDataFormat::default().unmarshal(body);
281        assert!(result.is_err());
282    }
283
284    #[test]
285    fn test_unmarshal_text_with_raised_max_bytes() {
286        let big = format!(r#"{{"k":"{}"}}"#, "x".repeat(17 * 1024 * 1024));
287        let body = Body::Text(big);
288        let df = JsonDataFormat::new(JsonConfig {
289            max_bytes: 20 * 1024 * 1024,
290        });
291        let result = df.unmarshal(body);
292        assert!(result.is_ok(), "should accept under raised cap");
293    }
294
295    #[test]
296    fn test_unmarshal_default_rejects_oversized() {
297        let big = format!(r#"{{"k":"{}"}}"#, "x".repeat(17 * 1024 * 1024));
298        let body = Body::Text(big);
299        let df = JsonDataFormat::default();
300        let result = df.unmarshal(body);
301        assert!(result.is_err(), "should reject over default 16 MiB");
302    }
303}