oxigdal_workflow/templates/
library.rs1use crate::error::{Result, WorkflowError};
4use crate::templates::{
5 Parameter, ParameterConstraints, ParameterType, ParameterValue, TemplateCategory,
6 WorkflowTemplate,
7};
8use dashmap::DashMap;
9use std::sync::Arc;
10
11pub struct WorkflowTemplateLibrary {
13 templates: Arc<DashMap<String, WorkflowTemplate>>,
14}
15
16impl WorkflowTemplateLibrary {
17 pub fn new() -> Self {
19 let library = Self {
20 templates: Arc::new(DashMap::new()),
21 };
22
23 library.register_builtin_templates();
25
26 library
27 }
28
29 fn register_builtin_templates(&self) {
31 if let Ok(template) = Self::create_satellite_processing_template() {
33 let _ = self.add_template(template);
34 }
35
36 if let Ok(template) = Self::create_change_detection_template() {
38 let _ = self.add_template(template);
39 }
40
41 if let Ok(template) = Self::create_batch_processing_template() {
43 let _ = self.add_template(template);
44 }
45
46 if let Ok(template) = Self::create_terrain_analysis_template() {
48 let _ = self.add_template(template);
49 }
50 }
51
52 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 pub fn get_template(&self, id: &str) -> Option<WorkflowTemplate> {
64 self.templates.get(id).map(|entry| entry.clone())
65 }
66
67 pub fn remove_template(&self, id: &str) -> Option<WorkflowTemplate> {
69 self.templates.remove(id).map(|(_, template)| template)
70 }
71
72 pub fn list_templates(&self) -> Vec<WorkflowTemplate> {
74 self.templates
75 .iter()
76 .map(|entry| entry.value().clone())
77 .collect()
78 }
79
80 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 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 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 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 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 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 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 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 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(); let count = library2.import_from_json(&json).expect("Failed to import");
432
433 assert!(count > 0);
434 }
435}