1use crate::engine::error::{DataflowError, Result};
9use crate::engine::executor::{ArenaContext, with_arena};
10use crate::engine::functions::path_template::{DataRoot, ParamCtx, PathTemplate, ResolvedPath};
11use crate::engine::functions::template::Template;
12use crate::engine::message::{Change, Message};
13use crate::engine::task_outcome::TaskOutcome;
14use crate::engine::utils::{get_nested_value, get_nested_value_parts, set_nested_value_parts};
15use datalogic_rs::Engine;
16use datavalue::OwnedDataValue;
17use log::debug;
18use serde::Deserialize;
19use serde_json::Value;
20use std::borrow::Cow;
21use std::sync::Arc;
22
23#[derive(Debug, Clone, Deserialize)]
25pub struct PublishConfig {
26 pub source: Template,
29
30 pub target: PathTemplate<DataRoot>,
34
35 #[serde(default)]
37 pub pretty: bool,
38
39 #[serde(default = "default_root_element")]
42 pub root_element: Template,
43}
44
45impl Default for PublishConfig {
48 fn default() -> Self {
49 Self {
50 source: Template::from(Value::String(String::new())),
51 target: PathTemplate::default(),
52 pretty: false,
53 root_element: default_root_element(),
54 }
55 }
56}
57
58fn default_root_element() -> Template {
59 Template::from(Value::from(DEFAULT_ROOT_ELEMENT))
60}
61
62pub const DEFAULT_ROOT_ELEMENT: &str = "root";
64
65impl PublishConfig {
66 pub fn from_json(input: &Value) -> Result<Self> {
67 let source = input.get("source").cloned().ok_or_else(|| {
68 DataflowError::Validation("Missing 'source' in publish config".to_string())
69 })?;
70
71 let target = input.get("target").cloned().ok_or_else(|| {
72 DataflowError::Validation("Missing 'target' in publish config".to_string())
73 })?;
74
75 let pretty = input
76 .get("pretty")
77 .and_then(Value::as_bool)
78 .unwrap_or(false);
79
80 let root_element = input
81 .get("root_element")
82 .cloned()
83 .map(Template::from)
84 .unwrap_or_else(default_root_element);
85
86 Ok(Self {
87 source: Template::from(source),
88 target: PathTemplate::from(target),
89 pretty,
90 root_element,
91 })
92 }
93
94 pub(crate) fn resolve_target_path(&self, p: ParamCtx<'_>) -> Result<Cow<'_, ResolvedPath>> {
100 self.target.resolve_in_arena(p)
101 }
102
103 fn resolve_source<'m>(&self, message: &'m Message, source: &str) -> Option<&'m OwnedDataValue> {
108 if let Some(value) = message.data().get(source) {
111 return Some(value);
112 }
113
114 if let Some(value) = get_nested_value(message.data(), source) {
116 return Some(value);
117 }
118
119 if let Some(path) = source.strip_prefix("data.") {
121 return get_nested_value(message.data(), path);
122 }
123
124 None
125 }
126}
127
128fn finish_publish(
134 message: &mut Message,
135 target: &ResolvedPath,
136 serialized: String,
137) -> (TaskOutcome, Vec<Change>) {
138 let (target_path_arc, target_parts) = (&target.0, &*target.1);
139 let old_value = get_nested_value_parts(&message.context, target_parts)
140 .cloned()
141 .unwrap_or(OwnedDataValue::Null);
142 let new_value = OwnedDataValue::String(serialized);
143
144 set_nested_value_parts(&mut message.context, target_parts, new_value.clone());
145
146 (
147 TaskOutcome::Success,
148 vec![Change {
149 path: Arc::clone(target_path_arc),
150 old_value,
151 new_value,
152 }],
153 )
154}
155
156pub fn execute_publish_json(
159 message: &mut Message,
160 config: &PublishConfig,
161 engine: &Engine,
162) -> Result<(TaskOutcome, Vec<Change>)> {
163 with_arena(|arena| {
164 let arena_ctx = ArenaContext::from_owned(&message.context, arena);
165 publish_json_in(message, config, ParamCtx::from_arena(engine, &arena_ctx))
166 })
167}
168
169pub(crate) fn publish_json_in(
170 message: &mut Message,
171 config: &PublishConfig,
172 p: ParamCtx<'_>,
173) -> Result<(TaskOutcome, Vec<Change>)> {
174 let source = config.source.resolve_str_in_arena(p)?;
175 let target = config.resolve_target_path(p)?;
176 debug!("PublishJson: Serializing 'data.{source}' to '{}'", target.0);
177
178 let source_data = match config.resolve_source(message, &source) {
181 Some(v) if !matches!(v, OwnedDataValue::Null) => v,
182 _ => {
183 return Err(DataflowError::Validation(format!(
184 "PublishJson: Source 'data.{source}' not found or is null"
185 )));
186 }
187 };
188
189 let json_string = if config.pretty {
195 let bridge = Value::from(source_data);
196 serde_json::to_string_pretty(&bridge)
197 .map_err(|e| DataflowError::Validation(format!("Failed to serialize to JSON: {}", e)))?
198 } else {
199 source_data.to_json_string()
200 };
201
202 Ok(finish_publish(message, &target, json_string))
203}
204
205pub fn execute_publish_xml(
209 message: &mut Message,
210 config: &PublishConfig,
211 engine: &Engine,
212) -> Result<(TaskOutcome, Vec<Change>)> {
213 with_arena(|arena| {
214 let arena_ctx = ArenaContext::from_owned(&message.context, arena);
215 publish_xml_in(message, config, ParamCtx::from_arena(engine, &arena_ctx))
216 })
217}
218
219pub(crate) fn publish_xml_in(
220 message: &mut Message,
221 config: &PublishConfig,
222 p: ParamCtx<'_>,
223) -> Result<(TaskOutcome, Vec<Change>)> {
224 let source = config.source.resolve_str_in_arena(p)?;
225 let target = config.resolve_target_path(p)?;
226 debug!("PublishXml: Serializing 'data.{source}' to '{}'", target.0);
227
228 let source_data = match config.resolve_source(message, &source) {
231 Some(v) if !matches!(v, OwnedDataValue::Null) => v,
232 _ => {
233 return Err(DataflowError::Validation(format!(
234 "PublishXml: Source 'data.{source}' not found or is null"
235 )));
236 }
237 };
238
239 let bridge = Value::from(source_data);
240 let root_element = config.root_element.resolve_str_in_arena(p)?;
241 let xml_string = json_to_xml(&bridge, &root_element)?;
242
243 Ok(finish_publish(message, &target, xml_string))
244}
245
246fn json_to_xml(value: &Value, root_element: &str) -> Result<String> {
250 let mut buffer = String::new();
251
252 match value {
253 Value::Object(_) => {
254 buffer.push_str(&format!("<{}>", root_element));
255 let content = serialize_value_to_xml_content(value)?;
256 buffer.push_str(&content);
257 buffer.push_str(&format!("</{}>", root_element));
258 }
259 Value::Array(arr) => {
260 buffer.push_str(&format!("<{}>", root_element));
261 for item in arr {
262 buffer.push_str("<item>");
263 let content = serialize_value_to_xml_content(item)?;
264 buffer.push_str(&content);
265 buffer.push_str("</item>");
266 }
267 buffer.push_str(&format!("</{}>", root_element));
268 }
269 _ => {
270 buffer.push_str(&format!("<{}>", root_element));
271 buffer.push_str(&value_to_xml_string(value));
272 buffer.push_str(&format!("</{}>", root_element));
273 }
274 }
275
276 Ok(buffer)
277}
278
279fn serialize_value_to_xml_content(value: &Value) -> Result<String> {
280 let mut result = String::new();
281
282 match value {
283 Value::Object(map) => {
284 for (key, val) in map {
285 let safe_key = sanitize_xml_name(key);
286 result.push_str(&format!("<{}>", safe_key));
287 match val {
288 Value::Object(_) | Value::Array(_) => {
289 result.push_str(&serialize_value_to_xml_content(val)?);
290 }
291 _ => {
292 result.push_str(&value_to_xml_string(val));
293 }
294 }
295 result.push_str(&format!("</{}>", safe_key));
296 }
297 }
298 Value::Array(arr) => {
299 for item in arr {
300 result.push_str("<item>");
301 match item {
302 Value::Object(_) | Value::Array(_) => {
303 result.push_str(&serialize_value_to_xml_content(item)?);
304 }
305 _ => {
306 result.push_str(&value_to_xml_string(item));
307 }
308 }
309 result.push_str("</item>");
310 }
311 }
312 _ => {
313 result.push_str(&value_to_xml_string(value));
314 }
315 }
316
317 Ok(result)
318}
319
320fn value_to_xml_string(value: &Value) -> String {
321 match value {
322 Value::Null => String::new(),
323 Value::Bool(b) => b.to_string(),
324 Value::Number(n) => n.to_string(),
325 Value::String(s) => escape_xml(s),
326 _ => String::new(),
327 }
328}
329
330fn escape_xml(s: &str) -> String {
331 s.replace('&', "&")
332 .replace('<', "<")
333 .replace('>', ">")
334 .replace('"', """)
335 .replace('\'', "'")
336}
337
338fn sanitize_xml_name(name: &str) -> String {
339 let mut result = String::new();
340
341 for (i, c) in name.chars().enumerate() {
342 if i == 0 {
343 if c.is_ascii_alphabetic() || c == '_' {
344 result.push(c);
345 } else {
346 result.push('_');
347 if c.is_ascii_alphanumeric() {
348 result.push(c);
349 }
350 }
351 } else if c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.' {
352 result.push(c);
353 } else {
354 result.push('_');
355 }
356 }
357
358 if result.is_empty() {
359 result = "_element".to_string();
360 }
361
362 result
363}
364
365#[cfg(test)]
366mod tests {
367 use super::*;
368 use serde_json::json;
369 use std::sync::Arc;
370
371 fn test_engine() -> Engine {
374 crate::engine::compiler::datalogic_engine_builder().build()
375 }
376
377 fn dv(v: serde_json::Value) -> OwnedDataValue {
378 OwnedDataValue::from(&v)
379 }
380
381 fn message_with_data(initial: serde_json::Value) -> Message {
382 Message::builder().data(dv(initial)).build()
383 }
384
385 #[test]
386 fn test_publish_config_from_json() {
387 let input = json!({"source": "output", "target": "json_string"});
388 let config = PublishConfig::from_json(&input).unwrap();
389 assert_eq!(config.source.as_json(), &json!("output"));
390 assert_eq!(config.target.as_json(), &json!("json_string"));
391 assert!(!config.pretty);
392 assert_eq!(config.root_element.as_json(), &json!("root"));
393 }
394
395 #[test]
396 fn test_publish_config_with_options() {
397 let input = json!({
398 "source": "data",
399 "target": "xml_output",
400 "pretty": true,
401 "root_element": "document"
402 });
403
404 let config = PublishConfig::from_json(&input).unwrap();
405 assert_eq!(config.source.as_json(), &json!("data"));
406 assert_eq!(config.target.as_json(), &json!("xml_output"));
407 assert!(config.pretty);
408 assert_eq!(config.root_element.as_json(), &json!("document"));
409 }
410
411 #[test]
412 fn test_publish_config_missing_source() {
413 assert!(PublishConfig::from_json(&json!({"target": "output"})).is_err());
414 }
415
416 #[test]
417 fn test_publish_config_missing_target() {
418 assert!(PublishConfig::from_json(&json!({"source": "input"})).is_err());
419 }
420
421 #[test]
422 fn test_execute_publish_json() {
423 let mut message = message_with_data(json!({"user": {"name": "John", "age": 30}}));
424
425 let config = PublishConfig {
426 source: Template::from(json!("user")),
427 target: PathTemplate::from("user_json"),
428 pretty: false,
429 root_element: Template::from(json!("root")),
430 };
431
432 let result = execute_publish_json(&mut message, &config, &test_engine());
433 assert!(result.is_ok());
434
435 let (outcome, changes) = result.unwrap();
436 assert_eq!(outcome, TaskOutcome::Success);
437 assert_eq!(changes.len(), 1);
438
439 let json_string = message.data()["user_json"].as_str().unwrap();
440 assert!(json_string.contains("John"));
441 assert!(json_string.contains("30"));
442 }
443
444 #[test]
445 fn test_execute_publish_json_pretty() {
446 let mut message = message_with_data(json!({"user": {"name": "Alice"}}));
447
448 let config = PublishConfig {
449 source: Template::from(json!("user")),
450 target: PathTemplate::from("output"),
451 pretty: true,
452 root_element: Template::from(json!("root")),
453 };
454
455 let result = execute_publish_json(&mut message, &config, &test_engine());
456 assert!(result.is_ok());
457
458 let json_string = message.data()["output"].as_str().unwrap();
459 assert!(json_string.contains('\n'));
460 }
461
462 #[test]
463 fn test_execute_publish_json_not_found() {
464 let mut message = Message::new(Arc::new(dv(json!({}))));
465
466 let config = PublishConfig {
467 source: Template::from(json!("nonexistent")),
468 target: PathTemplate::from("output"),
469 pretty: false,
470 root_element: Template::from(json!("root")),
471 };
472
473 assert!(execute_publish_json(&mut message, &config, &test_engine()).is_err());
474 }
475
476 #[test]
477 fn test_execute_publish_xml() {
478 let mut message = message_with_data(json!({"user": {"name": "John", "age": 30}}));
479
480 let config = PublishConfig {
481 source: Template::from(json!("user")),
482 target: PathTemplate::from("user_xml"),
483 pretty: false,
484 root_element: Template::from(json!("user")),
485 };
486
487 let result = execute_publish_xml(&mut message, &config, &test_engine());
488 assert!(result.is_ok());
489
490 let (outcome, _) = result.unwrap();
491 assert_eq!(outcome, TaskOutcome::Success);
492
493 let xml_string = message.data()["user_xml"].as_str().unwrap();
494 assert!(xml_string.contains("<user>"));
495 assert!(xml_string.contains("</user>"));
496 assert!(xml_string.contains("<name>John</name>"));
497 }
498
499 #[test]
500 fn test_execute_publish_xml_not_found() {
501 let mut message = Message::new(Arc::new(dv(json!({}))));
502
503 let config = PublishConfig {
504 source: Template::from(json!("nonexistent")),
505 target: PathTemplate::from("output"),
506 pretty: false,
507 root_element: Template::from(json!("root")),
508 };
509
510 assert!(execute_publish_xml(&mut message, &config, &test_engine()).is_err());
511 }
512
513 #[test]
514 fn test_json_to_xml_simple() {
515 let value = json!({"name": "Test", "value": 42});
516 let xml = json_to_xml(&value, "root").unwrap();
517 assert!(xml.contains("<root>"));
518 assert!(xml.contains("</root>"));
519 assert!(xml.contains("<name>Test</name>"));
520 assert!(xml.contains("<value>42</value>"));
521 }
522
523 #[test]
524 fn test_json_to_xml_nested() {
525 let value = json!({"user": {"name": "Alice", "email": "alice@example.com"}});
526 let xml = json_to_xml(&value, "data").unwrap();
527 assert!(xml.contains("<data>"));
528 assert!(xml.contains("<user>"));
529 assert!(xml.contains("<name>Alice</name>"));
530 }
531
532 #[test]
533 fn test_json_to_xml_array() {
534 let value = json!([1, 2, 3]);
535 let xml = json_to_xml(&value, "numbers").unwrap();
536 assert!(xml.contains("<numbers>"));
537 assert!(xml.contains("<item>1</item>"));
538 assert!(xml.contains("<item>2</item>"));
539 assert!(xml.contains("<item>3</item>"));
540 }
541
542 #[test]
543 fn test_json_to_xml_special_chars() {
544 let value = json!({"text": "<script>alert('xss')</script>"});
545 let xml = json_to_xml(&value, "root").unwrap();
546 assert!(xml.contains("<script>"));
547 assert!(!xml.contains("<script>"));
548 }
549
550 #[test]
551 fn test_escape_xml() {
552 assert_eq!(escape_xml("hello"), "hello");
553 assert_eq!(escape_xml("<tag>"), "<tag>");
554 assert_eq!(escape_xml("a & b"), "a & b");
555 assert_eq!(escape_xml("\"quoted\""), ""quoted"");
556 }
557
558 #[test]
559 fn test_sanitize_xml_name() {
560 assert_eq!(sanitize_xml_name("valid"), "valid");
561 assert_eq!(sanitize_xml_name("_valid"), "_valid");
562 assert_eq!(sanitize_xml_name("123invalid"), "_123invalid");
563 assert_eq!(sanitize_xml_name("has spaces"), "has_spaces");
564 assert_eq!(sanitize_xml_name("has-dash"), "has-dash");
565 assert_eq!(sanitize_xml_name(""), "_element");
566 }
567
568 #[test]
569 fn test_execute_publish_json_nested_source() {
570 let mut message = message_with_data(json!({
571 "response": {"body": {"message": "success"}}
572 }));
573
574 let config = PublishConfig {
575 source: Template::from(json!("response.body")),
576 target: PathTemplate::from("output"),
577 pretty: false,
578 root_element: Template::from(json!("root")),
579 };
580
581 let result = execute_publish_json(&mut message, &config, &test_engine());
582 assert!(result.is_ok());
583
584 let json_string = message.data()["output"].as_str().unwrap();
585 assert!(json_string.contains("success"));
586 }
587}