data_modelling_core/import/
sketch.rs1use super::ImportError;
7use crate::models::sketch::{Sketch, SketchIndex};
8
9#[cfg(feature = "schema-validation")]
10use crate::validation::schema::validate_sketch_internal;
11
12pub struct SketchImporter;
14
15impl SketchImporter {
16 pub fn new() -> Self {
18 Self
19 }
20
21 pub fn import(&self, yaml_content: &str) -> Result<Sketch, ImportError> {
53 #[cfg(feature = "schema-validation")]
55 {
56 validate_sketch_internal(yaml_content).map_err(ImportError::ValidationError)?;
57 }
58
59 Sketch::from_yaml(yaml_content)
61 .map_err(|e| ImportError::ParseError(format!("Failed to parse sketch YAML: {}", e)))
62 }
63
64 pub fn import_without_validation(&self, yaml_content: &str) -> Result<Sketch, ImportError> {
77 Sketch::from_yaml(yaml_content)
78 .map_err(|e| ImportError::ParseError(format!("Failed to parse sketch YAML: {}", e)))
79 }
80
81 pub fn import_index(&self, yaml_content: &str) -> Result<SketchIndex, ImportError> {
106 SketchIndex::from_yaml(yaml_content).map_err(|e| {
107 ImportError::ParseError(format!("Failed to parse sketch index YAML: {}", e))
108 })
109 }
110
111 pub fn import_from_directory(
123 &self,
124 dir_path: &std::path::Path,
125 ) -> Result<(Vec<Sketch>, Vec<ImportError>), ImportError> {
126 let mut sketches = Vec::new();
127 let mut errors = Vec::new();
128
129 if !dir_path.exists() {
130 return Err(ImportError::IoError(format!(
131 "Directory does not exist: {}",
132 dir_path.display()
133 )));
134 }
135
136 if !dir_path.is_dir() {
137 return Err(ImportError::IoError(format!(
138 "Path is not a directory: {}",
139 dir_path.display()
140 )));
141 }
142
143 let entries = std::fs::read_dir(dir_path)
145 .map_err(|e| ImportError::IoError(format!("Failed to read directory: {}", e)))?;
146
147 for entry in entries.flatten() {
148 let path = entry.path();
149 if path.extension().and_then(|s| s.to_str()) == Some("yaml")
150 && path
151 .file_name()
152 .and_then(|s| s.to_str())
153 .is_some_and(|name| name.ends_with(".sketch.yaml"))
154 {
155 match std::fs::read_to_string(&path) {
156 Ok(content) => match self.import(&content) {
157 Ok(sketch) => sketches.push(sketch),
158 Err(e) => errors.push(ImportError::ParseError(format!(
159 "Failed to import {}: {}",
160 path.display(),
161 e
162 ))),
163 },
164 Err(e) => errors.push(ImportError::IoError(format!(
165 "Failed to read {}: {}",
166 path.display(),
167 e
168 ))),
169 }
170 }
171 }
172
173 sketches.sort_by(|a, b| a.number.cmp(&b.number));
175
176 Ok((sketches, errors))
177 }
178
179 pub fn import_by_domain(
190 &self,
191 dir_path: &std::path::Path,
192 domain: &str,
193 ) -> Result<(Vec<Sketch>, Vec<ImportError>), ImportError> {
194 let (sketches, errors) = self.import_from_directory(dir_path)?;
195
196 let filtered: Vec<Sketch> = sketches
197 .into_iter()
198 .filter(|s| s.domain.as_deref() == Some(domain))
199 .collect();
200
201 Ok((filtered, errors))
202 }
203}
204
205impl Default for SketchImporter {
206 fn default() -> Self {
207 Self::new()
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 #[test]
216 fn test_import_sketch() {
217 let importer = SketchImporter::new();
218 let yaml = r#"
219id: 770e8400-e29b-41d4-a716-446655440001
220number: 1
221title: "Architecture Diagram"
222sketchType: architecture
223status: published
224excalidrawData: "{}"
225createdAt: "2024-01-15T10:00:00Z"
226updatedAt: "2024-01-15T10:00:00Z"
227"#;
228 let result = importer.import_without_validation(yaml);
229 assert!(result.is_ok());
230 let sketch = result.unwrap();
231 assert_eq!(sketch.title, "Architecture Diagram");
232 assert_eq!(sketch.number, 1);
233 }
234
235 #[test]
236 fn test_import_sketch_index() {
237 let importer = SketchImporter::new();
238 let yaml = r#"
239schemaVersion: "1.0"
240sketches: []
241nextNumber: 1
242"#;
243 let result = importer.import_index(yaml);
244 assert!(result.is_ok());
245 let index = result.unwrap();
246 assert_eq!(index.next_number, 1);
247 assert_eq!(index.schema_version, "1.0");
248 }
249
250 #[test]
251 fn test_import_invalid_yaml() {
252 let importer = SketchImporter::new();
253 let yaml = "not: valid: yaml: at: all";
254 let result = importer.import_without_validation(yaml);
255 assert!(result.is_err());
256 }
257
258 #[test]
259 fn test_import_sketch_with_all_fields() {
260 let importer = SketchImporter::new();
261 let yaml = r#"
262id: 770e8400-e29b-41d4-a716-446655440001
263number: 1
264title: "Sales Domain Architecture"
265sketchType: architecture
266status: published
267domain: sales
268description: "High-level architecture diagram"
269excalidrawData: '{"elements":[]}'
270thumbnailPath: thumbnails/sketch-0001.png
271authors:
272 - architect@company.com
273tags:
274 - architecture
275 - sales
276createdAt: "2024-01-15T10:00:00Z"
277updatedAt: "2024-01-15T10:00:00Z"
278"#;
279 let result = importer.import_without_validation(yaml);
280 assert!(result.is_ok());
281 let sketch = result.unwrap();
282 assert_eq!(sketch.title, "Sales Domain Architecture");
283 assert_eq!(sketch.domain, Some("sales".to_string()));
284 assert_eq!(
285 sketch.thumbnail_path,
286 Some("thumbnails/sketch-0001.png".to_string())
287 );
288 assert_eq!(sketch.authors.len(), 1);
289 }
290}