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