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 }
84 }
85
86 fn unmarshal(&self, body: Body) -> Result<Body, CamelError> {
87 match body {
88 Body::Json(_) => Ok(body),
89 Body::Text(s) => {
90 if s.len() > self.config.max_bytes {
91 return Err(CamelError::TypeConversionFailed(format!(
92 "JSON unmarshal input {} bytes exceeds max {}",
93 s.len(),
94 self.config.max_bytes
95 )));
96 }
97 let v = serde_json::from_str(&s).map_err(|e| {
98 CamelError::TypeConversionFailed(format!(
99 "cannot unmarshal Body::Text as JSON: {e}"
100 ))
101 })?;
102 Ok(Body::Json(v))
103 }
104 Body::Bytes(b) => {
105 if b.len() > self.config.max_bytes {
106 return Err(CamelError::TypeConversionFailed(format!(
107 "JSON unmarshal input {} bytes exceeds max {}",
108 b.len(),
109 self.config.max_bytes
110 )));
111 }
112 let v = serde_json::from_slice(&b).map_err(|e| {
113 CamelError::TypeConversionFailed(format!(
114 "cannot unmarshal Body::Bytes as JSON: {e}"
115 ))
116 })?;
117 Ok(Body::Json(v))
118 }
119 Body::Stream(_) => Err(CamelError::TypeConversionFailed(
120 "cannot unmarshal Body::Stream directly — use UnmarshalService which auto-materializes"
121 .to_string(),
122 )),
123 Body::Empty => Ok(Body::Empty),
127 Body::Xml(_) => Err(CamelError::TypeConversionFailed(
128 "JsonDataFormat::unmarshal only supports Body::Json, Body::Text, Body::Bytes, \
129 and Body::Empty"
130 .to_string(),
131 )),
132 }
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139 use bytes::Bytes;
140 use serde_json::json;
141
142 #[test]
143 fn test_name() {
144 assert_eq!(JsonDataFormat::default().name(), "json");
145 }
146
147 #[test]
148 fn test_unmarshal_text_to_json() {
149 let body = Body::Text(r#"{"a":1}"#.to_string());
150 let result = JsonDataFormat::default().unmarshal(body).unwrap();
151 assert!(matches!(result, Body::Json(_)));
152 if let Body::Json(v) = result {
153 assert_eq!(v["a"], json!(1));
154 }
155 }
156
157 #[test]
158 fn test_unmarshal_bytes_to_json() {
159 let body = Body::Bytes(Bytes::from_static(b"{\"b\":2}"));
160 let result = JsonDataFormat::default().unmarshal(body).unwrap();
161 assert!(matches!(result, Body::Json(_)));
162 }
163
164 #[test]
165 fn test_unmarshal_invalid_json() {
166 let body = Body::Text("not json".to_string());
167 let result = JsonDataFormat::default().unmarshal(body);
168 assert!(matches!(result, Err(CamelError::TypeConversionFailed(_))));
169 }
170
171 #[test]
172 fn test_unmarshal_json_noop() {
173 let body = Body::Json(json!({"x": 1}));
174 let result = JsonDataFormat::default().unmarshal(body).unwrap();
175 assert!(matches!(result, Body::Json(_)));
176 }
177
178 #[test]
179 fn test_unmarshal_unsupported_variant() {
180 let body = Body::Xml("<root/>".to_string());
181 let result = JsonDataFormat::default().unmarshal(body);
182 assert!(matches!(result, Err(CamelError::TypeConversionFailed(_))));
183 }
184
185 #[test]
186 fn test_unmarshal_empty_is_noop() {
187 let result = JsonDataFormat::default().unmarshal(Body::Empty).unwrap();
189 assert!(matches!(result, Body::Empty));
190 }
191
192 #[test]
193 fn test_marshal_empty_is_noop() {
194 let result = JsonDataFormat::default().marshal(Body::Empty).unwrap();
196 assert!(matches!(result, Body::Empty));
197 }
198
199 #[test]
200 fn test_unmarshal_stream_rejected() {
201 use camel_api::body::{StreamBody, StreamMetadata};
202 use futures::stream;
203 use std::sync::Arc;
204 use tokio::sync::Mutex;
205
206 let stream = stream::iter(vec![Ok(Bytes::from_static(b"data"))]);
207 let body = Body::Stream(StreamBody {
208 stream: Arc::new(Mutex::new(Some(Box::pin(stream)))),
209 metadata: StreamMetadata::default(),
210 });
211 let result = JsonDataFormat::default().unmarshal(body);
212 assert!(matches!(result, Err(CamelError::TypeConversionFailed(_))));
213 }
214
215 #[test]
216 fn test_marshal_json_to_text() {
217 let body = Body::Json(json!({"key": "value"}));
218 let result = JsonDataFormat::default().marshal(body).unwrap();
219 match result {
220 Body::Text(s) => assert!(s.contains("\"key\"")),
221 _ => panic!("expected Body::Text"),
222 }
223 }
224
225 #[test]
226 fn test_marshal_text_noop() {
227 let body = Body::Text("already text".to_string());
228 let result = JsonDataFormat::default().marshal(body).unwrap();
229 assert_eq!(result, Body::Text("already text".to_string()));
230 }
231
232 #[test]
233 fn test_marshal_unsupported_variant() {
234 let body = Body::Xml("<root/>".to_string());
235 let result = JsonDataFormat::default().marshal(body);
236 assert!(matches!(result, Err(CamelError::TypeConversionFailed(_))));
237 }
238
239 #[test]
240 fn test_marshal_bytes_to_text() {
241 let body = Body::Bytes(Bytes::from_static(b"{\"key\":\"val\"}"));
242 let result = JsonDataFormat::default().marshal(body).unwrap();
243 match result {
244 Body::Text(s) => assert!(s.contains("\"key\"")),
245 _ => panic!("expected Body::Text"),
246 }
247 }
248
249 #[test]
250 fn test_marshal_invalid_bytes_returns_error() {
251 let body = Body::Bytes(Bytes::from_static(b"not json"));
252 let result = JsonDataFormat::default().marshal(body);
253 assert!(matches!(result, Err(CamelError::TypeConversionFailed(_))));
254 }
255
256 #[test]
257 fn test_unmarshal_text_size_cap() {
258 let big = format!(r#"{{"k":"{}"}}"#, "x".repeat(16 * 1024 * 1024 + 10));
260 let body = Body::Text(big);
261 let result = JsonDataFormat::default().unmarshal(body);
262 assert!(result.is_err(), "expected error for oversized JSON text");
263 let msg = format!("{}", result.unwrap_err());
264 assert!(
265 msg.contains("exceeds") && msg.contains("max"),
266 "error should mention size cap: {msg}"
267 );
268 }
269
270 #[test]
271 fn test_unmarshal_bytes_size_cap() {
272 let big = format!(r#"{{"k":"{}"}}"#, "x".repeat(16 * 1024 * 1024 + 10));
273 let body = Body::Bytes(bytes::Bytes::from(big));
274 let result = JsonDataFormat::default().unmarshal(body);
275 assert!(result.is_err());
276 }
277
278 #[test]
279 fn test_unmarshal_text_with_raised_max_bytes() {
280 let big = format!(r#"{{"k":"{}"}}"#, "x".repeat(17 * 1024 * 1024));
281 let body = Body::Text(big);
282 let df = JsonDataFormat::new(JsonConfig {
283 max_bytes: 20 * 1024 * 1024,
284 });
285 let result = df.unmarshal(body);
286 assert!(result.is_ok(), "should accept under raised cap");
287 }
288
289 #[test]
290 fn test_unmarshal_default_rejects_oversized() {
291 let big = format!(r#"{{"k":"{}"}}"#, "x".repeat(17 * 1024 * 1024));
292 let body = Body::Text(big);
293 let df = JsonDataFormat::default();
294 let result = df.unmarshal(body);
295 assert!(result.is_err(), "should reject over default 16 MiB");
296 }
297}