camel_processor/data_format/
json.rs1use 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; fn default_max_bytes() -> usize {
9 DEFAULT_MAX_JSON_BYTES
10}
11
12#[derive(Debug, Clone, Deserialize)]
15#[serde(deny_unknown_fields)]
16pub struct JsonConfig {
17 #[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 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 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 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 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 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}