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 removed: false,
164 }],
165 ));
166 }
167
168 let source_data_for_context =
170 resolve_parsed_source(config, message, &source, payload_fast_path);
171 set_nested_value_parts(&mut message.context, target_parts, source_data_for_context);
172
173 debug!("ParseJson: Successfully stored data to '{target_path_arc}'");
174
175 Ok((TaskOutcome::Success, Vec::new()))
176}
177
178fn resolve_parsed_source(
183 config: &ParseConfig,
184 message: &Message,
185 source: &str,
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, source);
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<'a>(
205 message: &mut Message,
206 config: &ParseConfig,
207 engine: &datalogic_rs::Engine,
208 arena_ctx: &mut ArenaContext<'a>,
209) -> Result<(TaskOutcome, Vec<Change>)> {
210 let p = ParamCtx::from_arena(engine, arena_ctx);
211 let result = parse_json_in(message, config, p)?;
212 let target = config.resolve_target_path(p)?;
217 let target_parts = &*target.1;
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 engine: &Engine,
229) -> Result<(TaskOutcome, Vec<Change>)> {
230 with_arena(|arena| {
231 let arena_ctx = ArenaContext::from_owned(&message.context, arena);
232 parse_xml_in(message, config, ParamCtx::from_arena(engine, &arena_ctx))
233 })
234}
235
236pub(crate) fn parse_xml_in(
237 message: &mut Message,
238 config: &ParseConfig,
239 p: ParamCtx<'_>,
240) -> Result<(TaskOutcome, Vec<Change>)> {
241 let source = config.resolve_source(p)?;
242 debug!("ParseXml: Extracting from '{source}'");
243 let source_data = config.extract_source(message, &source);
244
245 let xml_string = match &source_data {
246 OwnedDataValue::String(s) => s.clone(),
247 _ => {
248 return Err(DataflowError::Validation(format!(
249 "ParseXml: Source '{source}' is not a string"
250 )));
251 }
252 };
253
254 let parsed_json = xml_to_json(&xml_string)?;
255 let parsed_owned = OwnedDataValue::from(&parsed_json);
256
257 let target = config.resolve_target_path(p)?;
258 let (target_path_arc, target_parts) = (&target.0, &*target.1);
259 let old_value = get_nested_value_parts(&message.context, target_parts)
260 .cloned()
261 .unwrap_or(OwnedDataValue::Null);
262
263 set_nested_value_parts(&mut message.context, target_parts, parsed_owned.clone());
264
265 debug!("ParseXml: Successfully parsed and stored XML to '{target_path_arc}'");
266
267 Ok((
268 TaskOutcome::Success,
269 vec![Change {
270 path: Arc::clone(target_path_arc),
271 old_value,
272 new_value: parsed_owned,
273 removed: false,
274 }],
275 ))
276}
277
278fn xml_to_json(xml: &str) -> Result<Value> {
280 use quick_xml::de::from_str;
281
282 let parsed: Value = from_str(xml)
283 .map_err(|e| DataflowError::Validation(format!("Failed to parse XML: {}", e)))?;
284
285 Ok(parsed)
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291 use crate::engine::utils::set_nested_value;
292 use serde_json::json;
293 use std::sync::Arc;
294
295 fn test_engine() -> Engine {
298 crate::engine::compiler::datalogic_engine_builder().build()
299 }
300
301 fn dv(v: serde_json::Value) -> OwnedDataValue {
302 OwnedDataValue::from(&v)
303 }
304
305 #[test]
306 fn test_parse_config_from_json() {
307 let input = json!({"source": "payload", "target": "input_data"});
308 let config = ParseConfig::from_json(&input).unwrap();
309 assert_eq!(config.source.as_json(), &json!("payload"));
310 assert_eq!(config.target.as_json(), &json!("input_data"));
311 }
312
313 #[test]
314 fn test_parse_config_missing_source() {
315 assert!(ParseConfig::from_json(&json!({"target": "input_data"})).is_err());
316 }
317
318 #[test]
319 fn test_parse_config_missing_target() {
320 assert!(ParseConfig::from_json(&json!({"source": "payload"})).is_err());
321 }
322
323 #[test]
324 fn test_execute_parse_json_from_payload() {
325 let payload = json!({"name": "John", "age": 30});
326 let mut message = Message::from_value(&payload);
327
328 let config = ParseConfig {
329 source: Template::from(json!("payload")),
330 target: PathTemplate::from("input"),
331 };
332
333 let result = execute_parse_json(&mut message, &config, &test_engine());
334 assert!(result.is_ok());
335
336 let (outcome, changes) = result.unwrap();
337 assert_eq!(outcome, TaskOutcome::Success);
338 assert_eq!(changes.len(), 1);
339 assert_eq!(changes[0].path.as_ref(), "data.input");
340
341 assert_eq!(message.data()["input"]["name"], dv(json!("John")));
342 assert_eq!(message.data()["input"]["age"], dv(json!(30)));
343 }
344
345 #[test]
346 fn test_execute_parse_json_from_nested_payload() {
347 let payload = json!({"body": {"user": {"name": "Alice"}}});
348 let mut message = Message::from_value(&payload);
349
350 let config = ParseConfig {
351 source: Template::from(json!("payload.body.user")),
352 target: PathTemplate::from("user_data"),
353 };
354
355 let result = execute_parse_json(&mut message, &config, &test_engine());
356 assert!(result.is_ok());
357
358 let (outcome, _) = result.unwrap();
359 assert_eq!(outcome, TaskOutcome::Success);
360 assert_eq!(message.data()["user_data"]["name"], dv(json!("Alice")));
361 }
362
363 #[test]
364 fn test_execute_parse_json_from_data() {
365 let mut message = Message::new(Arc::new(dv(json!({}))));
366 set_nested_value(
367 &mut message.context,
368 "data",
369 dv(json!({"existing": {"value": 42}})),
370 );
371
372 let config = ParseConfig {
373 source: Template::from(json!("data.existing")),
374 target: PathTemplate::from("copied"),
375 };
376
377 let result = execute_parse_json(&mut message, &config, &test_engine());
378 assert!(result.is_ok());
379
380 assert_eq!(message.data()["copied"]["value"], dv(json!(42)));
381 }
382
383 #[test]
384 fn test_execute_parse_xml_simple() {
385 let xml_payload = json!("<root><name>John</name><age>30</age></root>");
386 let mut message = Message::from_value(&xml_payload);
387
388 let config = ParseConfig {
389 source: Template::from(json!("payload")),
390 target: PathTemplate::from("parsed"),
391 };
392
393 let result = execute_parse_xml(&mut message, &config, &test_engine());
394 assert!(result.is_ok());
395
396 let (outcome, _) = result.unwrap();
397 assert_eq!(outcome, TaskOutcome::Success);
398
399 let parsed = &message.data()["parsed"];
400 assert!(parsed.is_object());
401 }
402
403 #[test]
404 fn test_execute_parse_xml_not_string() {
405 let payload = json!({"not": "a string"});
406 let mut message = Message::from_value(&payload);
407
408 let config = ParseConfig {
409 source: Template::from(json!("payload")),
410 target: PathTemplate::from("parsed"),
411 };
412
413 assert!(execute_parse_xml(&mut message, &config, &test_engine()).is_err());
414 }
415
416 #[test]
417 fn test_xml_to_json_simple() {
418 let xml = "<root><name>Test</name></root>";
419 let result = xml_to_json(xml);
420 assert!(result.is_ok());
421 let json = result.unwrap();
422 assert!(json.is_object());
423 }
424
425 #[test]
426 fn test_xml_to_json_invalid() {
427 let xml = "<root><unclosed>";
428 assert!(xml_to_json(xml).is_err());
429 }
430
431 #[test]
432 fn test_xml_to_json_with_attributes() {
433 let xml = r#"<person id="123"><name>John</name></person>"#;
434 assert!(xml_to_json(xml).is_ok());
435 }
436
437 #[test]
438 fn test_xml_to_json_nested() {
439 let xml = r#"<root><user><name>Alice</name><email>alice@example.com</email></user></root>"#;
440 let result = xml_to_json(xml);
441 assert!(result.is_ok());
442 let json = result.unwrap();
443 assert!(json.is_object());
444 }
445
446 #[test]
447 fn test_execute_parse_json_from_string_payload() {
448 let payload = Value::String(r#"{"name":"John","age":30}"#.to_string());
449 let mut message = Message::from_value(&payload);
450
451 let config = ParseConfig {
452 source: Template::from(json!("payload")),
453 target: PathTemplate::from("input"),
454 };
455
456 let result = execute_parse_json(&mut message, &config, &test_engine());
457 assert!(result.is_ok());
458
459 let (outcome, _) = result.unwrap();
460 assert_eq!(outcome, TaskOutcome::Success);
461
462 assert_eq!(message.data()["input"]["name"], dv(json!("John")));
463 assert_eq!(message.data()["input"]["age"], dv(json!(30)));
464 }
465}