camel_processor/data_format/
xml.rs1use camel_api::body::Body;
2use camel_api::data_format::DataFormat;
3use camel_api::error::CamelError;
4use camel_api::xml_convert::{json_to_xml, xml_to_json_with_depth_limit};
5use serde::Deserialize;
6
7fn default_max_depth() -> usize {
8 camel_api::xml_convert::DEFAULT_MAX_XML_DEPTH
9}
10
11#[derive(Debug, Clone, Deserialize)]
14#[serde(deny_unknown_fields)]
15pub struct XmlConfig {
16 #[serde(default = "default_max_depth")]
19 pub max_depth: usize,
20}
21
22impl Default for XmlConfig {
23 fn default() -> Self {
24 Self {
25 max_depth: default_max_depth(),
26 }
27 }
28}
29
30#[derive(Debug, Clone, Default)]
31pub struct XmlDataFormat {
32 config: XmlConfig,
33}
34
35impl XmlDataFormat {
36 pub fn new(config: XmlConfig) -> Self {
37 Self { config }
38 }
39}
40
41impl DataFormat for XmlDataFormat {
42 fn name(&self) -> &str {
43 "xml"
44 }
45
46 fn marshal(&self, body: Body) -> Result<Body, CamelError> {
47 match body {
48 Body::Json(v) => {
49 let xml = json_to_xml(&v)?;
50 Ok(Body::Text(xml))
51 }
52 Body::Text(_) => Ok(body),
53 Body::Stream(_) => Err(CamelError::TypeConversionFailed(
54 "cannot marshal Body::Stream — add 'stream_cache' or 'convert_body_to' before this step".to_string(),
55 )),
56 Body::Empty | Body::Bytes(_) => Err(CamelError::TypeConversionFailed(
57 "XmlDataFormat::marshal only supports Body::Json and Body::Text".to_string(),
58 )),
59 Body::Xml(_) => Err(CamelError::TypeConversionFailed(
60 "XmlDataFormat::marshal does not accept Body::Xml — use unmarshal to convert XML to JSON"
61 .to_string(),
62 )),
63 _ => Err(CamelError::TypeConversionFailed(
64 "XmlDataFormat::marshal does not support this body type".to_string(),
65 )),
66 }
67 }
68
69 fn unmarshal(&self, body: Body) -> Result<Body, CamelError> {
70 match body {
71 Body::Json(_) => Ok(body),
72 Body::Text(s) => {
73 let v = xml_to_json_with_depth_limit(&s, self.config.max_depth)?;
74 Ok(Body::Json(v))
75 }
76 Body::Bytes(b) => {
77 let s = String::from_utf8(b.to_vec()).map_err(|e| {
78 CamelError::TypeConversionFailed(format!(
79 "cannot unmarshal Body::Bytes as XML: {e}"
80 ))
81 })?;
82 let v = xml_to_json_with_depth_limit(&s, self.config.max_depth)?;
83 Ok(Body::Json(v))
84 }
85 Body::Xml(s) => {
86 let v = xml_to_json_with_depth_limit(&s, self.config.max_depth)?;
87 Ok(Body::Json(v))
88 }
89 Body::Stream(_) => Err(CamelError::TypeConversionFailed(
90 "cannot unmarshal Body::Stream directly — use UnmarshalService which auto-materializes"
91 .to_string(),
92 )),
93 Body::Empty => Err(CamelError::TypeConversionFailed(
94 "XmlDataFormat::unmarshal only supports Body::Json, Body::Text, Body::Bytes, and Body::Xml"
95 .to_string(),
96 )),
97 _ => Err(CamelError::TypeConversionFailed(
98 "XmlDataFormat::unmarshal does not support this body type".to_string(),
99 )),
100 }
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use bytes::Bytes;
108 use serde_json::json;
109
110 #[test]
111 fn test_name() {
112 assert_eq!(XmlDataFormat::default().name(), "xml");
113 }
114
115 #[test]
116 fn test_unmarshal_text_to_json() {
117 let body = Body::Text("<root><child>value</child></root>".to_string());
118 let result = XmlDataFormat::default().unmarshal(body).unwrap();
119 match result {
120 Body::Json(v) => assert_eq!(v["root"]["child"], json!("value")),
121 _ => panic!("expected Body::Json"),
122 }
123 }
124
125 #[test]
126 fn test_unmarshal_bytes_to_json() {
127 let body = Body::Bytes(Bytes::from_static(b"<root/>"));
128 let result = XmlDataFormat::default().unmarshal(body).unwrap();
129 match result {
130 Body::Json(v) => assert_eq!(v["root"], serde_json::Value::Null),
131 _ => panic!("expected Body::Json"),
132 }
133 }
134
135 #[test]
136 fn test_unmarshal_xml_body_to_json() {
137 let body = Body::Xml("<root><a>1</a></root>".to_string());
138 let result = XmlDataFormat::default().unmarshal(body).unwrap();
139 match result {
140 Body::Json(v) => assert_eq!(v["root"]["a"], json!("1")),
141 _ => panic!("expected Body::Json"),
142 }
143 }
144
145 #[test]
146 fn test_unmarshal_invalid_xml() {
147 let body = Body::Text("not xml".to_string());
148 let result = XmlDataFormat::default().unmarshal(body);
149 assert!(matches!(result, Err(CamelError::TypeConversionFailed(_))));
150 }
151
152 #[test]
153 fn test_unmarshal_json_noop() {
154 let body = Body::Json(json!({"x": 1}));
155 let result = XmlDataFormat::default().unmarshal(body).unwrap();
156 assert!(matches!(result, Body::Json(_)));
157 }
158
159 #[test]
160 fn test_unmarshal_empty_returns_error() {
161 let body = Body::Empty;
162 let result = XmlDataFormat::default().unmarshal(body);
163 assert!(matches!(result, Err(CamelError::TypeConversionFailed(_))));
164 }
165
166 #[test]
167 fn test_marshal_json_to_text() {
168 let body = Body::Json(json!({"root": {"name": "Alice"}}));
169 let result = XmlDataFormat::default().marshal(body).unwrap();
170 match result {
171 Body::Text(s) => {
172 assert!(s.contains("<root>"));
173 assert!(s.contains("<name>Alice</name>"));
174 }
175 _ => panic!("expected Body::Text"),
176 }
177 }
178
179 #[test]
180 fn test_marshal_text_noop() {
181 let body = Body::Text("already text".to_string());
182 let result = XmlDataFormat::default().marshal(body).unwrap();
183 assert_eq!(result, Body::Text("already text".to_string()));
184 }
185
186 #[test]
187 fn test_marshal_empty_returns_error() {
188 let body = Body::Empty;
189 let result = XmlDataFormat::default().marshal(body);
190 assert!(matches!(result, Err(CamelError::TypeConversionFailed(_))));
191 }
192
193 #[test]
194 fn test_marshal_xml_body_rejected() {
195 let body = Body::Xml("<root/>".to_string());
196 let result = XmlDataFormat::default().marshal(body);
197 assert!(matches!(result, Err(CamelError::TypeConversionFailed(_))));
198 }
199
200 #[test]
201 fn test_unmarshal_with_raised_max_depth() {
202 let mut xml = String::new();
203 for _ in 0..150 {
204 xml.push_str("<a>");
205 }
206 xml.push('1');
207 for _ in 0..150 {
208 xml.push_str("</a>");
209 }
210 let body = Body::Text(xml);
212 let df = XmlDataFormat::new(XmlConfig { max_depth: 200 });
213 let result = df.unmarshal(body);
214 assert!(
215 result.is_ok(),
216 "should accept depth 150 under raised cap 200"
217 );
218 }
219}