dataflow_rs/engine/functions/
parse.rs1use crate::engine::error::{DataflowError, Result};
14use crate::engine::executor::ArenaContext;
15use crate::engine::message::{Change, Message};
16use crate::engine::task_outcome::TaskOutcome;
17use crate::engine::utils::{get_nested_value, get_nested_value_parts, set_nested_value_parts};
18use datavalue::OwnedDataValue;
19use log::debug;
20use serde::Deserialize;
21use serde_json::Value;
22use std::sync::Arc;
23
24#[derive(Debug, Clone, Default, Deserialize)]
26pub struct ParseConfig {
27 pub source: String,
29
30 pub target: String,
32
33 #[doc(hidden)]
38 #[serde(skip)]
39 pub target_path_arc: Arc<str>,
40
41 #[doc(hidden)]
45 #[serde(skip)]
46 pub target_path_parts: Arc<[Arc<str>]>,
47}
48
49impl ParseConfig {
50 pub fn from_json(input: &Value) -> Result<Self> {
51 let source = input
52 .get("source")
53 .and_then(Value::as_str)
54 .ok_or_else(|| {
55 DataflowError::Validation("Missing 'source' in parse config".to_string())
56 })?
57 .to_string();
58
59 let target = input
60 .get("target")
61 .and_then(Value::as_str)
62 .ok_or_else(|| {
63 DataflowError::Validation("Missing 'target' in parse config".to_string())
64 })?
65 .to_string();
66
67 let mut config = ParseConfig {
68 source,
69 target,
70 ..Default::default()
71 };
72 config.precompute_target_path();
73 Ok(config)
74 }
75
76 pub(crate) fn precompute_target_path(&mut self) {
79 let path = format!("data.{}", self.target);
80 let parts: Vec<Arc<str>> = path.split('.').map(Arc::from).collect();
81 self.target_path_parts = parts.into();
82 self.target_path_arc = Arc::from(path);
83 }
84
85 fn resolve_target_path(&self) -> (Arc<str>, Arc<[Arc<str>]>) {
89 if self.target_path_parts.is_empty() {
90 let path = format!("data.{}", self.target);
91 let parts: Vec<Arc<str>> = path.split('.').map(Arc::from).collect();
92 (Arc::from(path), parts.into())
93 } else {
94 (
95 Arc::clone(&self.target_path_arc),
96 Arc::clone(&self.target_path_parts),
97 )
98 }
99 }
100
101 fn extract_source(&self, message: &Message) -> OwnedDataValue {
103 if self.source == "payload" {
104 (*message.payload).clone()
105 } else if let Some(path) = self.source.strip_prefix("payload.") {
106 get_nested_value(&message.payload, path)
107 .cloned()
108 .unwrap_or(OwnedDataValue::Null)
109 } else if let Some(path) = self.source.strip_prefix("data.") {
110 get_nested_value(message.data(), path)
111 .cloned()
112 .unwrap_or(OwnedDataValue::Null)
113 } else {
114 get_nested_value(&message.context, &self.source)
115 .cloned()
116 .unwrap_or(OwnedDataValue::Null)
117 }
118 }
119}
120
121pub fn execute_parse_json(
125 message: &mut Message,
126 config: &ParseConfig,
127) -> Result<(TaskOutcome, Vec<Change>)> {
128 debug!(
129 "ParseJson: Extracting from '{}' to 'data.{}'",
130 config.source, config.target
131 );
132
133 let (target_path_arc, target_parts) = config.resolve_target_path();
134
135 let payload_fast_path =
140 config.source == "payload" && !matches!(*message.payload, OwnedDataValue::String(_));
141
142 if message.capture_changes {
143 let old_value = get_nested_value_parts(&message.context, &target_parts)
144 .cloned()
145 .unwrap_or(OwnedDataValue::Null);
146
147 let source_data = if payload_fast_path {
151 (*message.payload).clone()
152 } else {
153 let raw = config.extract_source(message);
154 match &raw {
155 OwnedDataValue::String(s) => {
156 OwnedDataValue::from_json(s).unwrap_or_else(|_| raw.clone())
157 }
158 _ => raw,
159 }
160 };
161
162 let new_value = source_data.clone();
166
167 set_nested_value_parts(&mut message.context, &target_parts, source_data);
168 debug!(
169 "ParseJson: Successfully stored data to 'data.{}'",
170 config.target
171 );
172 return Ok((
173 TaskOutcome::Success,
174 vec![Change {
175 path: target_path_arc,
176 old_value,
177 new_value,
178 }],
179 ));
180 }
181
182 let source_data_for_context: OwnedDataValue = if payload_fast_path {
184 (*message.payload).clone()
185 } else {
186 let raw = config.extract_source(message);
187 match &raw {
188 OwnedDataValue::String(s) => {
189 OwnedDataValue::from_json(s).unwrap_or_else(|_| raw.clone())
190 }
191 _ => raw,
192 }
193 };
194 set_nested_value_parts(&mut message.context, &target_parts, source_data_for_context);
195
196 debug!(
197 "ParseJson: Successfully stored data to 'data.{}'",
198 config.target
199 );
200
201 Ok((TaskOutcome::Success, Vec::new()))
202}
203
204pub(crate) fn execute_parse_json_in_arena(
208 message: &mut Message,
209 config: &ParseConfig,
210 arena_ctx: &mut ArenaContext<'_>,
211) -> Result<(TaskOutcome, Vec<Change>)> {
212 let result = execute_parse_json(message, config)?;
213 let (_, target_parts) = config.resolve_target_path();
218 arena_ctx.refresh_for_path_parts(&message.context, &target_parts);
219 Ok(result)
220}
221
222pub fn execute_parse_xml(
226 message: &mut Message,
227 config: &ParseConfig,
228) -> Result<(TaskOutcome, Vec<Change>)> {
229 debug!(
230 "ParseXml: Extracting from '{}' to 'data.{}'",
231 config.source, config.target
232 );
233
234 let source_data = config.extract_source(message);
235
236 let xml_string = match &source_data {
237 OwnedDataValue::String(s) => s.clone(),
238 _ => {
239 return Err(DataflowError::Validation(format!(
240 "ParseXml: Source '{}' is not a string",
241 config.source
242 )));
243 }
244 };
245
246 let parsed_json = xml_to_json(&xml_string)?;
247 let parsed_owned = OwnedDataValue::from(&parsed_json);
248
249 let (target_path_arc, target_parts) = config.resolve_target_path();
250 let old_value = get_nested_value_parts(&message.context, &target_parts)
251 .cloned()
252 .unwrap_or(OwnedDataValue::Null);
253
254 set_nested_value_parts(&mut message.context, &target_parts, parsed_owned.clone());
255
256 debug!(
257 "ParseXml: Successfully parsed and stored XML to 'data.{}'",
258 config.target
259 );
260
261 Ok((
262 TaskOutcome::Success,
263 vec![Change {
264 path: target_path_arc,
265 old_value,
266 new_value: parsed_owned,
267 }],
268 ))
269}
270
271fn xml_to_json(xml: &str) -> Result<Value> {
273 use quick_xml::de::from_str;
274
275 let parsed: Value = from_str(xml)
276 .map_err(|e| DataflowError::Validation(format!("Failed to parse XML: {}", e)))?;
277
278 Ok(parsed)
279}
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284 use crate::engine::utils::set_nested_value;
285 use serde_json::json;
286
287 fn dv(v: serde_json::Value) -> OwnedDataValue {
288 OwnedDataValue::from(&v)
289 }
290
291 #[test]
292 fn test_parse_config_from_json() {
293 let input = json!({"source": "payload", "target": "input_data"});
294 let config = ParseConfig::from_json(&input).unwrap();
295 assert_eq!(config.source, "payload");
296 assert_eq!(config.target, "input_data");
297 }
298
299 #[test]
300 fn test_parse_config_missing_source() {
301 assert!(ParseConfig::from_json(&json!({"target": "input_data"})).is_err());
302 }
303
304 #[test]
305 fn test_parse_config_missing_target() {
306 assert!(ParseConfig::from_json(&json!({"source": "payload"})).is_err());
307 }
308
309 #[test]
310 fn test_execute_parse_json_from_payload() {
311 let payload = json!({"name": "John", "age": 30});
312 let mut message = Message::from_value(&payload);
313
314 let config = ParseConfig {
315 source: "payload".to_string(),
316 target: "input".to_string(),
317 ..Default::default()
318 };
319
320 let result = execute_parse_json(&mut message, &config);
321 assert!(result.is_ok());
322
323 let (outcome, changes) = result.unwrap();
324 assert_eq!(outcome, TaskOutcome::Success);
325 assert_eq!(changes.len(), 1);
326 assert_eq!(changes[0].path.as_ref(), "data.input");
327
328 assert_eq!(message.data()["input"]["name"], dv(json!("John")));
329 assert_eq!(message.data()["input"]["age"], dv(json!(30)));
330 }
331
332 #[test]
333 fn test_execute_parse_json_from_nested_payload() {
334 let payload = json!({"body": {"user": {"name": "Alice"}}});
335 let mut message = Message::from_value(&payload);
336
337 let config = ParseConfig {
338 source: "payload.body.user".to_string(),
339 target: "user_data".to_string(),
340 ..Default::default()
341 };
342
343 let result = execute_parse_json(&mut message, &config);
344 assert!(result.is_ok());
345
346 let (outcome, _) = result.unwrap();
347 assert_eq!(outcome, TaskOutcome::Success);
348 assert_eq!(message.data()["user_data"]["name"], dv(json!("Alice")));
349 }
350
351 #[test]
352 fn test_execute_parse_json_from_data() {
353 let mut message = Message::new(Arc::new(dv(json!({}))));
354 set_nested_value(
355 &mut message.context,
356 "data",
357 dv(json!({"existing": {"value": 42}})),
358 );
359
360 let config = ParseConfig {
361 source: "data.existing".to_string(),
362 target: "copied".to_string(),
363 ..Default::default()
364 };
365
366 let result = execute_parse_json(&mut message, &config);
367 assert!(result.is_ok());
368
369 assert_eq!(message.data()["copied"]["value"], dv(json!(42)));
370 }
371
372 #[test]
373 fn test_execute_parse_xml_simple() {
374 let xml_payload = json!("<root><name>John</name><age>30</age></root>");
375 let mut message = Message::from_value(&xml_payload);
376
377 let config = ParseConfig {
378 source: "payload".to_string(),
379 target: "parsed".to_string(),
380 ..Default::default()
381 };
382
383 let result = execute_parse_xml(&mut message, &config);
384 assert!(result.is_ok());
385
386 let (outcome, _) = result.unwrap();
387 assert_eq!(outcome, TaskOutcome::Success);
388
389 let parsed = &message.data()["parsed"];
390 assert!(parsed.is_object());
391 }
392
393 #[test]
394 fn test_execute_parse_xml_not_string() {
395 let payload = json!({"not": "a string"});
396 let mut message = Message::from_value(&payload);
397
398 let config = ParseConfig {
399 source: "payload".to_string(),
400 target: "parsed".to_string(),
401 ..Default::default()
402 };
403
404 assert!(execute_parse_xml(&mut message, &config).is_err());
405 }
406
407 #[test]
408 fn test_xml_to_json_simple() {
409 let xml = "<root><name>Test</name></root>";
410 let result = xml_to_json(xml);
411 assert!(result.is_ok());
412 let json = result.unwrap();
413 assert!(json.is_object());
414 }
415
416 #[test]
417 fn test_xml_to_json_invalid() {
418 let xml = "<root><unclosed>";
419 assert!(xml_to_json(xml).is_err());
420 }
421
422 #[test]
423 fn test_xml_to_json_with_attributes() {
424 let xml = r#"<person id="123"><name>John</name></person>"#;
425 assert!(xml_to_json(xml).is_ok());
426 }
427
428 #[test]
429 fn test_xml_to_json_nested() {
430 let xml = r#"<root><user><name>Alice</name><email>alice@example.com</email></user></root>"#;
431 let result = xml_to_json(xml);
432 assert!(result.is_ok());
433 let json = result.unwrap();
434 assert!(json.is_object());
435 }
436
437 #[test]
438 fn test_execute_parse_json_from_string_payload() {
439 let payload = Value::String(r#"{"name":"John","age":30}"#.to_string());
440 let mut message = Message::from_value(&payload);
441
442 let config = ParseConfig {
443 source: "payload".to_string(),
444 target: "input".to_string(),
445 ..Default::default()
446 };
447
448 let result = execute_parse_json(&mut message, &config);
449 assert!(result.is_ok());
450
451 let (outcome, _) = result.unwrap();
452 assert_eq!(outcome, TaskOutcome::Success);
453
454 assert_eq!(message.data()["input"]["name"], dv(json!("John")));
455 assert_eq!(message.data()["input"]["age"], dv(json!(30)));
456 }
457}