rez_next_package/
serialization.rs

1//! Package serialization and deserialization
2
3use crate::{Package, PythonAstParser};
4use rez_next_common::RezCoreError;
5use rez_next_version::Version;
6use serde_json;
7use serde_yaml;
8use std::collections::HashMap;
9use std::fs;
10use std::path::Path;
11
12// PackageRequirement and PackageVariant imports removed as they're not used in this module
13// PyO3 imports removed as they're not used in this module
14
15/// Package serialization format
16#[derive(Debug, Clone)]
17pub enum PackageFormat {
18    /// YAML format (package.yaml)
19    Yaml,
20    /// JSON format (package.json)
21    Json,
22    /// Python format (package.py)
23    Python,
24}
25
26impl PackageFormat {
27    /// Detect format from file extension
28    pub fn from_extension(path: &Path) -> Option<Self> {
29        match path.extension()?.to_str()? {
30            "yaml" | "yml" => Some(Self::Yaml),
31            "json" => Some(Self::Json),
32            "py" => Some(Self::Python),
33            _ => None,
34        }
35    }
36
37    /// Get the default file name for this format
38    pub fn default_filename(&self) -> &'static str {
39        match self {
40            Self::Yaml => "package.yaml",
41            Self::Json => "package.json",
42            Self::Python => "package.py",
43        }
44    }
45}
46
47/// Package serializer/deserializer
48pub struct PackageSerializer;
49
50impl PackageSerializer {
51    /// Load a package from a file
52    pub fn load_from_file(path: &Path) -> Result<Package, RezCoreError> {
53        let format = PackageFormat::from_extension(path).ok_or_else(|| {
54            RezCoreError::PackageParse(format!("Unsupported file format: {}", path.display()))
55        })?;
56
57        let content = fs::read_to_string(path).map_err(|e| {
58            RezCoreError::PackageParse(format!("Failed to read file {}: {}", path.display(), e))
59        })?;
60
61        Self::load_from_string(&content, format)
62    }
63
64    /// Load a package from a string
65    pub fn load_from_string(content: &str, format: PackageFormat) -> Result<Package, RezCoreError> {
66        match format {
67            PackageFormat::Yaml => Self::load_from_yaml(content),
68            PackageFormat::Json => Self::load_from_json(content),
69            PackageFormat::Python => Self::load_from_python(content),
70        }
71    }
72
73    /// Load a package from YAML content
74    pub fn load_from_yaml(content: &str) -> Result<Package, RezCoreError> {
75        let data: HashMap<String, serde_yaml::Value> = serde_yaml::from_str(content)
76            .map_err(|e| RezCoreError::PackageParse(format!("Failed to parse YAML: {}", e)))?;
77
78        Self::load_from_yaml_data(data)
79    }
80
81    /// Load a package from JSON content
82    pub fn load_from_json(content: &str) -> Result<Package, RezCoreError> {
83        let data: HashMap<String, serde_json::Value> = serde_json::from_str(content)
84            .map_err(|e| RezCoreError::PackageParse(format!("Failed to parse JSON: {}", e)))?;
85
86        Self::load_from_data(data)
87    }
88
89    /// Load a package from Python content using advanced AST parsing
90    pub fn load_from_python(content: &str) -> Result<Package, RezCoreError> {
91        // Use the advanced Python AST parser for complete Python support
92        PythonAstParser::parse_package_py(content)
93    }
94
95    /// Load a package from generic data
96    fn load_from_data<T>(data: HashMap<String, T>) -> Result<Package, RezCoreError>
97    where
98        T: Into<serde_json::Value>,
99    {
100        // Convert to JSON values for easier processing
101        let json_data: HashMap<String, serde_json::Value> =
102            data.into_iter().map(|(k, v)| (k, v.into())).collect();
103
104        // Extract required name field
105        let name = json_data
106            .get("name")
107            .and_then(|v| v.as_str())
108            .ok_or_else(|| {
109                RezCoreError::PackageParse("Missing or invalid 'name' field".to_string())
110            })?
111            .to_string();
112
113        let mut package = Package::new(name);
114
115        // Extract optional fields
116        if let Some(version_value) = json_data.get("version") {
117            if let Some(version_str) = version_value.as_str() {
118                let version = Version::parse(version_str)
119                    .map_err(|e| RezCoreError::PackageParse(format!("Invalid version: {}", e)))?;
120                package.version = Some(version);
121            }
122        }
123
124        if let Some(description_value) = json_data.get("description") {
125            if let Some(description) = description_value.as_str() {
126                package.description = Some(description.to_string());
127            }
128        }
129
130        if let Some(authors_value) = json_data.get("authors") {
131            if let Some(authors_array) = authors_value.as_array() {
132                package.authors = authors_array
133                    .iter()
134                    .filter_map(|v| v.as_str())
135                    .map(|s| s.to_string())
136                    .collect();
137            }
138        }
139
140        if let Some(requires_value) = json_data.get("requires") {
141            if let Some(requires_array) = requires_value.as_array() {
142                package.requires = requires_array
143                    .iter()
144                    .filter_map(|v| v.as_str())
145                    .map(|s| s.to_string())
146                    .collect();
147            }
148        }
149
150        if let Some(build_requires_value) = json_data.get("build_requires") {
151            if let Some(build_requires_array) = build_requires_value.as_array() {
152                package.build_requires = build_requires_array
153                    .iter()
154                    .filter_map(|v| v.as_str())
155                    .map(|s| s.to_string())
156                    .collect();
157            }
158        }
159
160        if let Some(variants_value) = json_data.get("variants") {
161            if let Some(variants_array) = variants_value.as_array() {
162                package.variants = variants_array
163                    .iter()
164                    .filter_map(|v| v.as_array())
165                    .map(|variant_array| {
166                        variant_array
167                            .iter()
168                            .filter_map(|v| v.as_str())
169                            .map(|s| s.to_string())
170                            .collect()
171                    })
172                    .collect();
173            }
174        }
175
176        if let Some(tools_value) = json_data.get("tools") {
177            if let Some(tools_array) = tools_value.as_array() {
178                package.tools = tools_array
179                    .iter()
180                    .filter_map(|v| v.as_str())
181                    .map(|s| s.to_string())
182                    .collect();
183            }
184        }
185
186        // Validate the package
187        package.validate()?;
188
189        Ok(package)
190    }
191
192    /// Load a package from YAML data
193    fn load_from_yaml_data(
194        data: HashMap<String, serde_yaml::Value>,
195    ) -> Result<Package, RezCoreError> {
196        // Convert YAML values to JSON values for easier processing
197        let json_data: HashMap<String, serde_json::Value> = data
198            .into_iter()
199            .map(|(k, v)| (k, yaml_to_json_value(v)))
200            .collect();
201
202        // Extract required name field
203        let name = json_data
204            .get("name")
205            .and_then(|v| v.as_str())
206            .ok_or_else(|| {
207                RezCoreError::PackageParse("Missing or invalid 'name' field".to_string())
208            })?
209            .to_string();
210
211        let mut package = Package::new(name);
212
213        // Extract optional fields (same logic as load_from_data)
214        if let Some(version_value) = json_data.get("version") {
215            if let Some(version_str) = version_value.as_str() {
216                let version = Version::parse(version_str)
217                    .map_err(|e| RezCoreError::PackageParse(format!("Invalid version: {}", e)))?;
218                package.version = Some(version);
219            }
220        }
221
222        if let Some(description_value) = json_data.get("description") {
223            if let Some(description) = description_value.as_str() {
224                package.description = Some(description.to_string());
225            }
226        }
227
228        if let Some(authors_value) = json_data.get("authors") {
229            if let Some(authors_array) = authors_value.as_array() {
230                package.authors = authors_array
231                    .iter()
232                    .filter_map(|v| v.as_str())
233                    .map(|s| s.to_string())
234                    .collect();
235            }
236        }
237
238        if let Some(requires_value) = json_data.get("requires") {
239            if let Some(requires_array) = requires_value.as_array() {
240                package.requires = requires_array
241                    .iter()
242                    .filter_map(|v| v.as_str())
243                    .map(|s| s.to_string())
244                    .collect();
245            }
246        }
247
248        if let Some(build_requires_value) = json_data.get("build_requires") {
249            if let Some(build_requires_array) = build_requires_value.as_array() {
250                package.build_requires = build_requires_array
251                    .iter()
252                    .filter_map(|v| v.as_str())
253                    .map(|s| s.to_string())
254                    .collect();
255            }
256        }
257
258        if let Some(variants_value) = json_data.get("variants") {
259            if let Some(variants_array) = variants_value.as_array() {
260                package.variants = variants_array
261                    .iter()
262                    .filter_map(|v| v.as_array())
263                    .map(|variant_array| {
264                        variant_array
265                            .iter()
266                            .filter_map(|v| v.as_str())
267                            .map(|s| s.to_string())
268                            .collect()
269                    })
270                    .collect();
271            }
272        }
273
274        if let Some(tools_value) = json_data.get("tools") {
275            if let Some(tools_array) = tools_value.as_array() {
276                package.tools = tools_array
277                    .iter()
278                    .filter_map(|v| v.as_str())
279                    .map(|s| s.to_string())
280                    .collect();
281            }
282        }
283
284        // Validate the package
285        package.validate()?;
286
287        Ok(package)
288    }
289
290    /// Save a package to a file
291    pub fn save_to_file(
292        package: &Package,
293        path: &Path,
294        format: PackageFormat,
295    ) -> Result<(), RezCoreError> {
296        let content = Self::save_to_string(package, format)?;
297
298        fs::write(path, content).map_err(|e| {
299            RezCoreError::PackageParse(format!("Failed to write file {}: {}", path.display(), e))
300        })
301    }
302
303    /// Save a package to a string
304    pub fn save_to_string(
305        package: &Package,
306        format: PackageFormat,
307    ) -> Result<String, RezCoreError> {
308        match format {
309            PackageFormat::Yaml => Self::save_to_yaml(package),
310            PackageFormat::Json => Self::save_to_json(package),
311            PackageFormat::Python => Self::save_to_python(package),
312        }
313    }
314
315    /// Save a package to YAML format
316    pub fn save_to_yaml(package: &Package) -> Result<String, RezCoreError> {
317        serde_yaml::to_string(package)
318            .map_err(|e| RezCoreError::PackageParse(format!("Failed to serialize to YAML: {}", e)))
319    }
320
321    /// Save a package to JSON format
322    pub fn save_to_json(package: &Package) -> Result<String, RezCoreError> {
323        serde_json::to_string_pretty(package)
324            .map_err(|e| RezCoreError::PackageParse(format!("Failed to serialize to JSON: {}", e)))
325    }
326
327    /// Save a package to Python format (simplified)
328    pub fn save_to_python(package: &Package) -> Result<String, RezCoreError> {
329        let mut content = String::new();
330
331        content.push_str(&format!("name = \"{}\"\n", package.name));
332
333        if let Some(ref version) = package.version {
334            content.push_str(&format!("version = \"{}\"\n", version.as_str()));
335        }
336
337        if let Some(ref description) = package.description {
338            content.push_str(&format!("description = \"{}\"\n", description));
339        }
340
341        if !package.authors.is_empty() {
342            content.push_str("authors = [\n");
343            for author in &package.authors {
344                content.push_str(&format!("    \"{}\",\n", author));
345            }
346            content.push_str("]\n");
347        }
348
349        if !package.requires.is_empty() {
350            content.push_str("requires = [\n");
351            for req in &package.requires {
352                content.push_str(&format!("    \"{}\",\n", req));
353            }
354            content.push_str("]\n");
355        }
356
357        if !package.build_requires.is_empty() {
358            content.push_str("build_requires = [\n");
359            for req in &package.build_requires {
360                content.push_str(&format!("    \"{}\",\n", req));
361            }
362            content.push_str("]\n");
363        }
364
365        if !package.variants.is_empty() {
366            content.push_str("variants = [\n");
367            for variant in &package.variants {
368                content.push_str("    [");
369                for (i, req) in variant.iter().enumerate() {
370                    if i > 0 {
371                        content.push_str(", ");
372                    }
373                    content.push_str(&format!("\"{}\"", req));
374                }
375                content.push_str("],\n");
376            }
377            content.push_str("]\n");
378        }
379
380        if !package.tools.is_empty() {
381            content.push_str("tools = [\n");
382            for tool in &package.tools {
383                content.push_str(&format!("    \"{}\",\n", tool));
384            }
385            content.push_str("]\n");
386        }
387
388        Ok(content)
389    }
390}
391
392/// Convert YAML value to JSON value
393fn yaml_to_json_value(yaml_value: serde_yaml::Value) -> serde_json::Value {
394    match yaml_value {
395        serde_yaml::Value::Null => serde_json::Value::Null,
396        serde_yaml::Value::Bool(b) => serde_json::Value::Bool(b),
397        serde_yaml::Value::Number(n) => {
398            if let Some(i) = n.as_i64() {
399                serde_json::Value::Number(serde_json::Number::from(i))
400            } else if let Some(f) = n.as_f64() {
401                serde_json::Number::from_f64(f)
402                    .map(serde_json::Value::Number)
403                    .unwrap_or(serde_json::Value::Null)
404            } else {
405                serde_json::Value::Null
406            }
407        }
408        serde_yaml::Value::String(s) => serde_json::Value::String(s),
409        serde_yaml::Value::Sequence(seq) => {
410            let json_array: Vec<serde_json::Value> =
411                seq.into_iter().map(yaml_to_json_value).collect();
412            serde_json::Value::Array(json_array)
413        }
414        serde_yaml::Value::Mapping(map) => {
415            let mut json_object = serde_json::Map::new();
416            for (k, v) in map {
417                if let serde_yaml::Value::String(key) = k {
418                    json_object.insert(key, yaml_to_json_value(v));
419                }
420            }
421            serde_json::Value::Object(json_object)
422        }
423        serde_yaml::Value::Tagged(_) => serde_json::Value::Null, // Ignore tagged values
424    }
425}