Skip to main content

oxigdal_workflow/templates/
mod.rs

1//! Workflow template system.
2//!
3//! Provides reusable workflow templates with parameterization,
4//! validation, and a built-in library of common geospatial workflows.
5
6pub mod library;
7pub mod parameterization;
8pub mod validation;
9
10use crate::engine::WorkflowDefinition;
11use crate::error::{Result, WorkflowError};
12use chrono::{DateTime, Utc};
13use serde::{Deserialize, Serialize};
14use std::collections::HashMap;
15
16pub use library::WorkflowTemplateLibrary;
17pub use parameterization::{
18    Parameter, ParameterConstraints, ParameterType, ParameterValue, TemplateParameterizer,
19};
20pub use validation::TemplateValidator;
21
22/// Workflow template definition.
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct WorkflowTemplate {
25    /// Template ID.
26    pub id: String,
27    /// Template name.
28    pub name: String,
29    /// Template description.
30    pub description: String,
31    /// Template version.
32    pub version: String,
33    /// Template author.
34    pub author: String,
35    /// Template tags.
36    pub tags: Vec<String>,
37    /// Template parameters.
38    pub parameters: Vec<Parameter>,
39    /// Base workflow definition (with parameter placeholders).
40    pub workflow_template: String,
41    /// Template metadata.
42    pub metadata: TemplateMetadata,
43    /// Example parameter values.
44    pub examples: Vec<HashMap<String, ParameterValue>>,
45}
46
47/// Template metadata.
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct TemplateMetadata {
50    /// Creation timestamp.
51    pub created_at: DateTime<Utc>,
52    /// Last update timestamp.
53    pub updated_at: DateTime<Utc>,
54    /// Template category.
55    pub category: TemplateCategory,
56    /// Complexity rating (1-5).
57    pub complexity: u8,
58    /// Estimated execution time description.
59    pub estimated_duration: Option<String>,
60    /// Required resources.
61    pub required_resources: Vec<String>,
62    /// Compatible versions.
63    pub compatible_versions: Vec<String>,
64}
65
66/// Template category.
67#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
68pub enum TemplateCategory {
69    /// Satellite image processing.
70    SatelliteProcessing,
71    /// Change detection.
72    ChangeDetection,
73    /// Terrain analysis.
74    TerrainAnalysis,
75    /// Vector processing.
76    VectorProcessing,
77    /// Batch processing.
78    BatchProcessing,
79    /// ETL (Extract, Transform, Load).
80    Etl,
81    /// Quality control.
82    QualityControl,
83    /// Machine learning.
84    MachineLearning,
85    /// Custom template.
86    Custom,
87}
88
89impl WorkflowTemplate {
90    /// Create a new workflow template.
91    pub fn new<S: Into<String>>(id: S, name: S, description: S) -> Self {
92        Self {
93            id: id.into(),
94            name: name.into(),
95            description: description.into(),
96            version: "1.0.0".to_string(),
97            author: "unknown".to_string(),
98            tags: Vec::new(),
99            parameters: Vec::new(),
100            workflow_template: String::new(),
101            metadata: TemplateMetadata {
102                created_at: Utc::now(),
103                updated_at: Utc::now(),
104                category: TemplateCategory::Custom,
105                complexity: 1,
106                estimated_duration: None,
107                required_resources: Vec::new(),
108                compatible_versions: Vec::new(),
109            },
110            examples: Vec::new(),
111        }
112    }
113
114    /// Add a parameter to the template.
115    pub fn add_parameter(&mut self, parameter: Parameter) {
116        self.parameters.push(parameter);
117    }
118
119    /// Set the workflow template string.
120    pub fn set_template<S: Into<String>>(&mut self, template: S) {
121        self.workflow_template = template.into();
122    }
123
124    /// Set the template category.
125    pub fn set_category(&mut self, category: TemplateCategory) {
126        self.metadata.category = category;
127    }
128
129    /// Add a tag.
130    pub fn add_tag<S: Into<String>>(&mut self, tag: S) {
131        self.tags.push(tag.into());
132    }
133
134    /// Add an example parameter set.
135    pub fn add_example(&mut self, example: HashMap<String, ParameterValue>) {
136        self.examples.push(example);
137    }
138
139    /// Instantiate the template with parameter values.
140    pub fn instantiate(
141        &self,
142        params: HashMap<String, ParameterValue>,
143    ) -> Result<WorkflowDefinition> {
144        // Validate parameters
145        let validator = TemplateValidator::new();
146        validator.validate_parameters(&self.parameters, &params)?;
147
148        // Instantiate template
149        let parameterizer = TemplateParameterizer::new();
150        let workflow_json = parameterizer.apply_parameters(&self.workflow_template, &params)?;
151
152        // Parse workflow definition
153        let workflow: WorkflowDefinition = serde_json::from_str(&workflow_json)
154            .map_err(|e| WorkflowError::template(format!("Failed to parse workflow: {}", e)))?;
155
156        Ok(workflow)
157    }
158
159    /// Get required parameters.
160    pub fn get_required_parameters(&self) -> Vec<&Parameter> {
161        self.parameters.iter().filter(|p| p.required).collect()
162    }
163
164    /// Get optional parameters.
165    pub fn get_optional_parameters(&self) -> Vec<&Parameter> {
166        self.parameters.iter().filter(|p| !p.required).collect()
167    }
168
169    /// Validate the template itself.
170    pub fn validate(&self) -> Result<()> {
171        let validator = TemplateValidator::new();
172        validator.validate_template(self)
173    }
174
175    /// Clone the template with a new ID and version.
176    pub fn clone_as(&self, new_id: String, new_version: String) -> Self {
177        let mut cloned = self.clone();
178        cloned.id = new_id;
179        cloned.version = new_version;
180        cloned.metadata.created_at = Utc::now();
181        cloned.metadata.updated_at = Utc::now();
182        cloned
183    }
184}
185
186/// Template builder for fluent API.
187pub struct TemplateBuilder {
188    template: WorkflowTemplate,
189}
190
191impl TemplateBuilder {
192    /// Create a new template builder.
193    pub fn new<S1: Into<String>, S2: Into<String>>(id: S1, name: S2) -> Self {
194        Self {
195            template: WorkflowTemplate::new(id.into(), name.into(), "".to_string()),
196        }
197    }
198
199    /// Set the description.
200    pub fn description<S: Into<String>>(mut self, description: S) -> Self {
201        self.template.description = description.into();
202        self
203    }
204
205    /// Set the version.
206    pub fn version<S: Into<String>>(mut self, version: S) -> Self {
207        self.template.version = version.into();
208        self
209    }
210
211    /// Set the author.
212    pub fn author<S: Into<String>>(mut self, author: S) -> Self {
213        self.template.author = author.into();
214        self
215    }
216
217    /// Add a tag.
218    pub fn tag<S: Into<String>>(mut self, tag: S) -> Self {
219        self.template.tags.push(tag.into());
220        self
221    }
222
223    /// Add a parameter.
224    pub fn parameter(mut self, parameter: Parameter) -> Self {
225        self.template.parameters.push(parameter);
226        self
227    }
228
229    /// Set the template string.
230    pub fn template<S: Into<String>>(mut self, template: S) -> Self {
231        self.template.workflow_template = template.into();
232        self
233    }
234
235    /// Set the category.
236    pub fn category(mut self, category: TemplateCategory) -> Self {
237        self.template.metadata.category = category;
238        self
239    }
240
241    /// Set the complexity.
242    pub fn complexity(mut self, complexity: u8) -> Self {
243        self.template.metadata.complexity = complexity.min(5);
244        self
245    }
246
247    /// Build the template.
248    pub fn build(self) -> Result<WorkflowTemplate> {
249        self.template.validate()?;
250        Ok(self.template)
251    }
252}
253
254#[cfg(test)]
255mod tests {
256    use super::*;
257
258    #[test]
259    fn test_template_creation() {
260        let template = WorkflowTemplate::new("test-template", "Test Template", "A test template");
261
262        assert_eq!(template.id, "test-template");
263        assert_eq!(template.name, "Test Template");
264    }
265
266    #[test]
267    fn test_template_builder() {
268        let template = TemplateBuilder::new("test", "Test")
269            .description("Description")
270            .version("1.0.0")
271            .author("Test Author")
272            .tag("test")
273            .complexity(3)
274            .build();
275
276        // Will fail validation without proper template
277        assert!(template.is_err());
278    }
279
280    #[test]
281    fn test_parameter_filtering() {
282        let mut template = WorkflowTemplate::new("test", "Test", "Description");
283
284        let param1 = Parameter {
285            name: "required_param".to_string(),
286            param_type: ParameterType::String,
287            description: "A required parameter".to_string(),
288            required: true,
289            default_value: None,
290            constraints: None,
291        };
292
293        let param2 = Parameter {
294            name: "optional_param".to_string(),
295            param_type: ParameterType::String,
296            description: "An optional parameter".to_string(),
297            required: false,
298            default_value: Some(ParameterValue::String("default".to_string())),
299            constraints: None,
300        };
301
302        template.add_parameter(param1);
303        template.add_parameter(param2);
304
305        assert_eq!(template.get_required_parameters().len(), 1);
306        assert_eq!(template.get_optional_parameters().len(), 1);
307    }
308
309    #[test]
310    fn test_template_categories() {
311        assert_eq!(
312            TemplateCategory::SatelliteProcessing,
313            TemplateCategory::SatelliteProcessing
314        );
315    }
316}