Skip to main content

data_modelling_core/export/
sketch.rs

1//! Sketch exporter
2//!
3//! Exports Sketch models to YAML format.
4
5use crate::export::ExportError;
6use crate::models::sketch::{Sketch, SketchIndex};
7
8/// Sketch exporter for generating YAML from Sketch models
9pub struct SketchExporter;
10
11impl SketchExporter {
12    /// Create a new Sketch exporter instance
13    pub fn new() -> Self {
14        Self
15    }
16
17    /// Export a sketch to YAML format
18    ///
19    /// # Arguments
20    ///
21    /// * `sketch` - The Sketch to export
22    ///
23    /// # Returns
24    ///
25    /// A Result containing the YAML string, or an ExportError
26    pub fn export(&self, sketch: &Sketch) -> Result<String, ExportError> {
27        let yaml = sketch.to_yaml().map_err(|e| {
28            ExportError::SerializationError(format!("Failed to serialize sketch: {}", e))
29        })?;
30
31        // Validate exported YAML against sketch schema (if feature enabled)
32        #[cfg(feature = "schema-validation")]
33        {
34            use crate::validation::schema::validate_sketch_internal;
35            validate_sketch_internal(&yaml).map_err(ExportError::ValidationError)?;
36        }
37
38        Ok(yaml)
39    }
40
41    /// Export a sketch without validation
42    ///
43    /// Use this when you want to skip schema validation for performance
44    /// or when exporting to a trusted destination.
45    pub fn export_without_validation(&self, sketch: &Sketch) -> Result<String, ExportError> {
46        sketch.to_yaml().map_err(|e| {
47            ExportError::SerializationError(format!("Failed to serialize sketch: {}", e))
48        })
49    }
50
51    /// Export a sketch index to YAML format
52    ///
53    /// # Arguments
54    ///
55    /// * `index` - The SketchIndex to export
56    ///
57    /// # Returns
58    ///
59    /// A Result containing the YAML string, or an ExportError
60    pub fn export_index(&self, index: &SketchIndex) -> Result<String, ExportError> {
61        index.to_yaml().map_err(|e| {
62            ExportError::SerializationError(format!("Failed to serialize sketch index: {}", e))
63        })
64    }
65
66    /// Export multiple sketches to a directory
67    ///
68    /// # Arguments
69    ///
70    /// * `sketches` - The sketches to export
71    /// * `dir_path` - Directory to export to
72    /// * `workspace_name` - Workspace name for filename generation
73    ///
74    /// # Returns
75    ///
76    /// A Result with the number of files exported, or an ExportError
77    pub fn export_to_directory(
78        &self,
79        sketches: &[Sketch],
80        dir_path: &std::path::Path,
81        workspace_name: &str,
82    ) -> Result<usize, ExportError> {
83        // Create directory if it doesn't exist
84        if !dir_path.exists() {
85            std::fs::create_dir_all(dir_path)
86                .map_err(|e| ExportError::IoError(format!("Failed to create directory: {}", e)))?;
87        }
88
89        let mut count = 0;
90        for sketch in sketches {
91            let filename = sketch.filename(workspace_name);
92            let path = dir_path.join(&filename);
93            let yaml = self.export(sketch)?;
94            std::fs::write(&path, yaml).map_err(|e| {
95                ExportError::IoError(format!("Failed to write {}: {}", filename, e))
96            })?;
97            count += 1;
98        }
99
100        Ok(count)
101    }
102
103    /// Export sketches filtered by domain to a directory
104    ///
105    /// # Arguments
106    ///
107    /// * `sketches` - The sketches to export
108    /// * `dir_path` - Directory to export to
109    /// * `workspace_name` - Workspace name for filename generation
110    /// * `domain` - Domain to filter by
111    ///
112    /// # Returns
113    ///
114    /// A Result with the number of files exported, or an ExportError
115    pub fn export_domain_to_directory(
116        &self,
117        sketches: &[Sketch],
118        dir_path: &std::path::Path,
119        workspace_name: &str,
120        domain: &str,
121    ) -> Result<usize, ExportError> {
122        let filtered: Vec<&Sketch> = sketches
123            .iter()
124            .filter(|s| s.domain.as_deref() == Some(domain))
125            .collect();
126
127        // Create domain subdirectory
128        let domain_dir = dir_path.join(domain);
129        if !domain_dir.exists() {
130            std::fs::create_dir_all(&domain_dir)
131                .map_err(|e| ExportError::IoError(format!("Failed to create directory: {}", e)))?;
132        }
133
134        let mut count = 0;
135        for sketch in filtered {
136            let filename = sketch.filename(workspace_name);
137            let path = domain_dir.join(&filename);
138            let yaml = self.export(sketch)?;
139            std::fs::write(&path, yaml).map_err(|e| {
140                ExportError::IoError(format!("Failed to write {}: {}", filename, e))
141            })?;
142            count += 1;
143        }
144
145        Ok(count)
146    }
147}
148
149impl Default for SketchExporter {
150    fn default() -> Self {
151        Self::new()
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158    use crate::models::sketch::SketchStatus;
159
160    #[test]
161    fn test_export_sketch() {
162        let sketch = Sketch::new(1, "Architecture Diagram", r#"{"elements":[]}"#)
163            .with_status(SketchStatus::Published);
164
165        let exporter = SketchExporter::new();
166        let result = exporter.export_without_validation(&sketch);
167        assert!(result.is_ok());
168        let yaml = result.unwrap();
169        assert!(yaml.contains("title: Architecture Diagram"));
170        assert!(yaml.contains("status: published"));
171    }
172
173    #[test]
174    fn test_export_sketch_index() {
175        let index = SketchIndex::new();
176        let exporter = SketchExporter::new();
177        let result = exporter.export_index(&index);
178        assert!(result.is_ok());
179        let yaml = result.unwrap();
180        assert!(yaml.contains("schemaVersion"));
181        assert!(yaml.contains("nextNumber: 1"));
182    }
183
184    #[test]
185    fn test_export_sketch_with_all_fields() {
186        let sketch = Sketch::new(1, "Sales Domain Architecture", r#"{"elements":[]}"#)
187            .with_status(SketchStatus::Published)
188            .with_domain("sales")
189            .with_description("High-level architecture diagram")
190            .with_thumbnail("thumbnails/sketch-0001.png")
191            .add_author("architect@company.com");
192
193        let exporter = SketchExporter::new();
194        let result = exporter.export_without_validation(&sketch);
195        assert!(result.is_ok());
196        let yaml = result.unwrap();
197        assert!(yaml.contains("domain: sales"));
198        assert!(yaml.contains("thumbnailPath: thumbnails/sketch-0001.png"));
199    }
200}