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