Skip to main content

oxigdal_workflow/templates/
library.rs

1//! Built-in workflow template library.
2
3use crate::error::{Result, WorkflowError};
4use crate::templates::{
5    Parameter, ParameterConstraints, ParameterType, ParameterValue, TemplateCategory,
6    WorkflowTemplate,
7};
8use dashmap::DashMap;
9use std::sync::Arc;
10
11/// Workflow template library.
12pub struct WorkflowTemplateLibrary {
13    templates: Arc<DashMap<String, WorkflowTemplate>>,
14}
15
16impl WorkflowTemplateLibrary {
17    /// Create a new template library.
18    pub fn new() -> Self {
19        let library = Self {
20            templates: Arc::new(DashMap::new()),
21        };
22
23        // Add built-in templates
24        library.register_builtin_templates();
25
26        library
27    }
28
29    /// Register all built-in templates.
30    fn register_builtin_templates(&self) {
31        // Satellite processing template
32        if let Ok(template) = Self::create_satellite_processing_template() {
33            let _ = self.add_template(template);
34        }
35
36        // Change detection template
37        if let Ok(template) = Self::create_change_detection_template() {
38            let _ = self.add_template(template);
39        }
40
41        // Batch processing template
42        if let Ok(template) = Self::create_batch_processing_template() {
43            let _ = self.add_template(template);
44        }
45
46        // Terrain analysis template
47        if let Ok(template) = Self::create_terrain_analysis_template() {
48            let _ = self.add_template(template);
49        }
50    }
51
52    /// Add a template to the library.
53    pub fn add_template(&self, template: WorkflowTemplate) -> Result<()> {
54        if self.templates.contains_key(&template.id) {
55            return Err(WorkflowError::already_exists(&template.id));
56        }
57
58        self.templates.insert(template.id.clone(), template);
59        Ok(())
60    }
61
62    /// Get a template by ID.
63    pub fn get_template(&self, id: &str) -> Option<WorkflowTemplate> {
64        self.templates.get(id).map(|entry| entry.clone())
65    }
66
67    /// Remove a template.
68    pub fn remove_template(&self, id: &str) -> Option<WorkflowTemplate> {
69        self.templates.remove(id).map(|(_, template)| template)
70    }
71
72    /// List all templates.
73    pub fn list_templates(&self) -> Vec<WorkflowTemplate> {
74        self.templates
75            .iter()
76            .map(|entry| entry.value().clone())
77            .collect()
78    }
79
80    /// List templates by category.
81    pub fn list_by_category(&self, category: &TemplateCategory) -> Vec<WorkflowTemplate> {
82        self.templates
83            .iter()
84            .filter(|entry| &entry.value().metadata.category == category)
85            .map(|entry| entry.value().clone())
86            .collect()
87    }
88
89    /// Search templates by tag.
90    pub fn search_by_tag(&self, tag: &str) -> Vec<WorkflowTemplate> {
91        self.templates
92            .iter()
93            .filter(|entry| entry.value().tags.contains(&tag.to_string()))
94            .map(|entry| entry.value().clone())
95            .collect()
96    }
97
98    /// Create satellite processing template.
99    fn create_satellite_processing_template() -> Result<WorkflowTemplate> {
100        let mut template = WorkflowTemplate::new(
101            "satellite-processing",
102            "Satellite Image Processing",
103            "Process satellite imagery with atmospheric correction and cloud masking",
104        );
105
106        template.set_category(TemplateCategory::SatelliteProcessing);
107        template.metadata.complexity = 3;
108        template.add_tag("satellite");
109        template.add_tag("processing");
110        template.add_tag("imagery");
111
112        template.add_parameter(Parameter {
113            name: "input_path".to_string(),
114            param_type: ParameterType::FilePath,
115            description: "Input satellite image path".to_string(),
116            required: true,
117            default_value: None,
118            constraints: None,
119        });
120
121        template.add_parameter(Parameter {
122            name: "output_path".to_string(),
123            param_type: ParameterType::FilePath,
124            description: "Output processed image path".to_string(),
125            required: true,
126            default_value: None,
127            constraints: None,
128        });
129
130        template.add_parameter(Parameter {
131            name: "apply_cloud_mask".to_string(),
132            param_type: ParameterType::Boolean,
133            description: "Apply cloud masking".to_string(),
134            required: false,
135            default_value: Some(ParameterValue::Boolean(true)),
136            constraints: None,
137        });
138
139        template.set_template(
140            r#"{
141                "id": "satellite-processing-workflow",
142                "name": "Satellite Processing",
143                "description": "Process satellite imagery",
144                "version": "1.0.0",
145                "tasks": [],
146                "dependencies": [],
147                "metadata": {}
148            }"#,
149        );
150
151        Ok(template)
152    }
153
154    /// Create change detection template.
155    fn create_change_detection_template() -> Result<WorkflowTemplate> {
156        let mut template = WorkflowTemplate::new(
157            "change-detection",
158            "Change Detection Workflow",
159            "Detect changes between two time periods using multi-temporal imagery",
160        );
161
162        template.set_category(TemplateCategory::ChangeDetection);
163        template.metadata.complexity = 4;
164        template.add_tag("change-detection");
165        template.add_tag("temporal");
166
167        template.add_parameter(Parameter {
168            name: "before_image".to_string(),
169            param_type: ParameterType::FilePath,
170            description: "Image from before period".to_string(),
171            required: true,
172            default_value: None,
173            constraints: None,
174        });
175
176        template.add_parameter(Parameter {
177            name: "after_image".to_string(),
178            param_type: ParameterType::FilePath,
179            description: "Image from after period".to_string(),
180            required: true,
181            default_value: None,
182            constraints: None,
183        });
184
185        template.add_parameter(Parameter {
186            name: "threshold".to_string(),
187            param_type: ParameterType::Float,
188            description: "Change detection threshold".to_string(),
189            required: false,
190            default_value: Some(ParameterValue::Float(0.5)),
191            constraints: Some(ParameterConstraints {
192                min: Some(0.0),
193                max: Some(1.0),
194                min_length: None,
195                max_length: None,
196                pattern: None,
197            }),
198        });
199
200        template.set_template(
201            r#"{
202                "id": "change-detection-workflow",
203                "name": "Change Detection",
204                "description": "Detect changes between imagery",
205                "version": "1.0.0",
206                "tasks": [],
207                "dependencies": [],
208                "metadata": {}
209            }"#,
210        );
211
212        Ok(template)
213    }
214
215    /// Create batch processing template.
216    fn create_batch_processing_template() -> Result<WorkflowTemplate> {
217        let mut template = WorkflowTemplate::new(
218            "batch-processing",
219            "Batch Processing Workflow",
220            "Process multiple files in parallel",
221        );
222
223        template.set_category(TemplateCategory::BatchProcessing);
224        template.metadata.complexity = 2;
225        template.add_tag("batch");
226        template.add_tag("parallel");
227
228        template.add_parameter(Parameter {
229            name: "input_directory".to_string(),
230            param_type: ParameterType::FilePath,
231            description: "Input directory containing files to process".to_string(),
232            required: true,
233            default_value: None,
234            constraints: None,
235        });
236
237        template.add_parameter(Parameter {
238            name: "output_directory".to_string(),
239            param_type: ParameterType::FilePath,
240            description: "Output directory for processed files".to_string(),
241            required: true,
242            default_value: None,
243            constraints: None,
244        });
245
246        template.add_parameter(Parameter {
247            name: "parallel_tasks".to_string(),
248            param_type: ParameterType::Integer,
249            description: "Number of parallel tasks".to_string(),
250            required: false,
251            default_value: Some(ParameterValue::Integer(4)),
252            constraints: Some(ParameterConstraints {
253                min: Some(1.0),
254                max: Some(32.0),
255                min_length: None,
256                max_length: None,
257                pattern: None,
258            }),
259        });
260
261        template.set_template(
262            r#"{
263                "id": "batch-processing-workflow",
264                "name": "Batch Processing",
265                "description": "Process files in batch",
266                "version": "1.0.0",
267                "tasks": [],
268                "dependencies": [],
269                "metadata": {}
270            }"#,
271        );
272
273        Ok(template)
274    }
275
276    /// Create terrain analysis template.
277    fn create_terrain_analysis_template() -> Result<WorkflowTemplate> {
278        let mut template = WorkflowTemplate::new(
279            "terrain-analysis",
280            "Terrain Analysis Workflow",
281            "Analyze terrain from DEM data",
282        );
283
284        template.set_category(TemplateCategory::TerrainAnalysis);
285        template.metadata.complexity = 3;
286        template.add_tag("terrain");
287        template.add_tag("dem");
288        template.add_tag("analysis");
289
290        template.add_parameter(Parameter {
291            name: "dem_path".to_string(),
292            param_type: ParameterType::FilePath,
293            description: "Digital Elevation Model file path".to_string(),
294            required: true,
295            default_value: None,
296            constraints: None,
297        });
298
299        template.add_parameter(Parameter {
300            name: "analysis_type".to_string(),
301            param_type: ParameterType::Enum {
302                allowed_values: vec![
303                    "slope".to_string(),
304                    "aspect".to_string(),
305                    "hillshade".to_string(),
306                    "all".to_string(),
307                ],
308            },
309            description: "Type of terrain analysis to perform".to_string(),
310            required: false,
311            default_value: Some(ParameterValue::String("all".to_string())),
312            constraints: None,
313        });
314
315        template.set_template(
316            r#"{
317                "id": "terrain-analysis-workflow",
318                "name": "Terrain Analysis",
319                "description": "Analyze terrain from DEM",
320                "version": "1.0.0",
321                "tasks": [],
322                "dependencies": [],
323                "metadata": {}
324            }"#,
325        );
326
327        Ok(template)
328    }
329
330    /// Export library to JSON.
331    pub fn export_to_json(&self) -> Result<String> {
332        let templates = self.list_templates();
333        serde_json::to_string_pretty(&templates)
334            .map_err(|e| WorkflowError::template(format!("Failed to export library: {}", e)))
335    }
336
337    /// Import library from JSON.
338    pub fn import_from_json(&self, json: &str) -> Result<usize> {
339        let templates: Vec<WorkflowTemplate> = serde_json::from_str(json)
340            .map_err(|e| WorkflowError::template(format!("Failed to import library: {}", e)))?;
341
342        let mut count = 0;
343        for template in templates {
344            if self.add_template(template).is_ok() {
345                count += 1;
346            }
347        }
348
349        Ok(count)
350    }
351}
352
353impl Default for WorkflowTemplateLibrary {
354    fn default() -> Self {
355        Self::new()
356    }
357}
358
359#[cfg(test)]
360mod tests {
361    use super::*;
362
363    #[test]
364    fn test_library_creation() {
365        let library = WorkflowTemplateLibrary::new();
366        let templates = library.list_templates();
367
368        // Should have built-in templates
369        assert!(!templates.is_empty());
370    }
371
372    #[test]
373    fn test_add_get_template() {
374        let library = WorkflowTemplateLibrary::new();
375
376        let template = WorkflowTemplate::new("test", "Test", "Test template");
377
378        assert!(library.add_template(template).is_ok());
379        assert!(library.get_template("test").is_some());
380    }
381
382    #[test]
383    fn test_remove_template() {
384        let library = WorkflowTemplateLibrary::new();
385
386        let template = WorkflowTemplate::new("test", "Test", "Test template");
387        library.add_template(template).expect("Failed to add");
388
389        assert!(library.remove_template("test").is_some());
390        assert!(library.get_template("test").is_none());
391    }
392
393    #[test]
394    fn test_list_by_category() {
395        let library = WorkflowTemplateLibrary::new();
396
397        let satellite_templates = library.list_by_category(&TemplateCategory::SatelliteProcessing);
398
399        assert!(!satellite_templates.is_empty());
400    }
401
402    #[test]
403    fn test_search_by_tag() {
404        let library = WorkflowTemplateLibrary::new();
405
406        let batch_templates = library.search_by_tag("batch");
407
408        assert!(!batch_templates.is_empty());
409    }
410
411    #[test]
412    fn test_duplicate_template() {
413        let library = WorkflowTemplateLibrary::new();
414
415        let template1 = WorkflowTemplate::new("dup", "Dup", "Duplicate");
416        let template2 = WorkflowTemplate::new("dup", "Dup2", "Duplicate 2");
417
418        assert!(library.add_template(template1).is_ok());
419        assert!(library.add_template(template2).is_err());
420    }
421
422    #[test]
423    fn test_export_import() {
424        let library1 = WorkflowTemplateLibrary::new();
425
426        let json = library1.export_to_json().expect("Failed to export");
427
428        let library2 = WorkflowTemplateLibrary::default();
429        library2.templates.clear(); // Clear built-ins for test
430
431        let count = library2.import_from_json(&json).expect("Failed to import");
432
433        assert!(count > 0);
434    }
435}