config_disassembler/xml/builders/
build_disassembled_file.rs1use crate::xml::builders::build_xml_string;
4use crate::xml::parsers::parse_unique_id_element;
5use crate::xml::transformers::transform_format;
6use crate::xml::types::BuildDisassembledFileOptions;
7use serde_json::{Map, Value};
8use std::path::Path;
9use tokio::fs;
10use tokio::io::AsyncWriteExt;
11
12pub async fn build_disassembled_file(
13 options: BuildDisassembledFileOptions<'_>,
14) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
15 let BuildDisassembledFileOptions {
16 content,
17 disassembled_path,
18 output_file_name,
19 subdirectory,
20 wrap_key,
21 is_grouped_array,
22 root_element_name,
23 root_attributes,
24 xml_declaration,
25 format,
26 unique_id_elements,
27 precomputed_unique_id,
28 } = options;
29
30 let target_directory = if let Some(subdir) = subdirectory {
31 Path::new(disassembled_path).join(subdir)
32 } else {
33 Path::new(disassembled_path).to_path_buf()
34 };
35
36 let file_name = if let Some(name) = output_file_name {
37 name.to_string()
38 } else if let Some(wk) = wrap_key {
39 if !is_grouped_array && content.is_object() {
40 let id = precomputed_unique_id
46 .map(str::to_string)
47 .unwrap_or_else(|| parse_unique_id_element(&content, unique_id_elements));
48 format!("{}.{}-meta.{}", id, wk, format)
49 } else {
50 "output".to_string()
51 }
52 } else {
53 "output".to_string()
54 };
55
56 let output_path = target_directory.join(&file_name);
57
58 fs::create_dir_all(&target_directory).await?;
59
60 let root_attrs_obj = root_attributes.as_object().cloned().unwrap_or_default();
61 let mut inner = root_attrs_obj.clone();
62
63 if let Some(wk) = wrap_key {
64 inner.insert(wk.to_string(), content.clone());
65 } else if let Some(obj) = content.as_object() {
66 for (k, v) in obj {
67 inner.insert(k.clone(), v.clone());
68 }
69 }
70
71 let mut wrapped_inner = Map::new();
72 wrapped_inner.insert(root_element_name.to_string(), Value::Object(inner));
73
74 if let Some(decl) = xml_declaration.filter(|d| d.is_object()) {
75 let mut root = Map::new();
76 root.insert("?xml".to_string(), decl);
77 for (k, v) in wrapped_inner {
78 root.insert(k, v);
79 }
80 wrapped_inner = root;
81 }
82
83 let wrapped_xml = Value::Object(wrapped_inner);
84
85 let output_string = if let Some(s) = transform_format(format, &wrapped_xml).await {
86 s
87 } else {
88 build_xml_string(&wrapped_xml)
89 };
90
91 let mut file = fs::File::create(&output_path).await?;
101 file.write_all(output_string.as_bytes()).await?;
102 file.flush().await?;
103 file.shutdown().await?;
104 log::debug!("Created disassembled file: {}", output_path.display());
105
106 Ok(())
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use serde_json::json;
113
114 fn opts_base(disassembled_path: &str) -> BuildDisassembledFileOptions<'_> {
115 BuildDisassembledFileOptions {
116 content: json!({ "a": "b" }),
117 disassembled_path,
118 output_file_name: Some("out.xml"),
119 subdirectory: None,
120 wrap_key: None,
121 is_grouped_array: false,
122 root_element_name: "Root",
123 root_attributes: Value::Object(Map::new()),
124 xml_declaration: None,
125 format: "xml",
126 unique_id_elements: None,
127 precomputed_unique_id: None,
128 }
129 }
130
131 #[tokio::test]
132 async fn build_disassembled_file_file_name_output_when_wrap_key_no_output_name_grouped_array() {
133 let temp = tempfile::tempdir().unwrap();
135 let path = temp.path().to_str().unwrap();
136 let mut opts = opts_base(path);
137 opts.output_file_name = None;
138 opts.wrap_key = Some("wrap");
139 opts.is_grouped_array = true;
140 opts.content = json!([{ "x": "1" }]);
141 build_disassembled_file(opts).await.unwrap();
142 assert!(temp.path().join("output").exists());
143 }
144
145 #[tokio::test]
146 async fn build_disassembled_file_file_name_output_when_wrap_key_content_not_object() {
147 let temp = tempfile::tempdir().unwrap();
149 let path = temp.path().to_str().unwrap();
150 let mut opts = opts_base(path);
151 opts.output_file_name = None;
152 opts.wrap_key = Some("wrap");
153 opts.is_grouped_array = false;
154 opts.content = json!([{ "id": "a" }]);
155 build_disassembled_file(opts).await.unwrap();
156 assert!(temp.path().join("output").exists());
157 }
158
159 #[tokio::test]
160 async fn build_disassembled_file_file_name_output_when_no_wrap_key_no_output_name() {
161 let temp = tempfile::tempdir().unwrap();
163 let path = temp.path().to_str().unwrap();
164 let mut opts = opts_base(path);
165 opts.output_file_name = None;
166 opts.wrap_key = None;
167 build_disassembled_file(opts).await.unwrap();
168 assert!(temp.path().join("output").exists());
169 }
170
171 #[tokio::test]
172 async fn build_disassembled_file_content_not_object_no_spread() {
173 let temp = tempfile::tempdir().unwrap();
175 let path = temp.path().to_str().unwrap();
176 let mut opts = opts_base(path);
177 opts.output_file_name = Some("single.xml");
178 opts.wrap_key = None;
179 opts.content = json!(42);
180 opts.root_attributes = json!({ "marker": "kept" });
181 build_disassembled_file(opts).await.unwrap();
182 let out = fs::read_to_string(temp.path().join("single.xml"))
183 .await
184 .unwrap();
185 assert!(out.contains("<Root"), "expected Root element, got: {out}");
186 assert!(out.contains("marker"), "expected root metadata, got: {out}");
187 assert!(
188 out.contains("kept"),
189 "expected root metadata value, got: {out}"
190 );
191 assert!(!out.contains("42"));
192 }
193}