Skip to main content

oxigdal_workflow/versioning/
migration.rs

1//! Workflow migration utilities.
2
3use crate::engine::WorkflowDefinition;
4use crate::error::{Result, WorkflowError};
5use serde::{Deserialize, Serialize};
6
7/// Workflow migration manager.
8pub struct WorkflowMigration;
9
10impl WorkflowMigration {
11    /// Create a new migration manager.
12    pub fn new() -> Self {
13        Self
14    }
15
16    /// Migrate a workflow from one version to another.
17    pub fn migrate(
18        &self,
19        from: WorkflowDefinition,
20        to: WorkflowDefinition,
21    ) -> Result<WorkflowDefinition> {
22        // Create migration plan
23        let plan = self.create_migration_plan(&from, &to)?;
24
25        // Execute migration
26        self.execute_migration_plan(from, plan)
27    }
28
29    /// Create a migration plan.
30    fn create_migration_plan(
31        &self,
32        from: &WorkflowDefinition,
33        to: &WorkflowDefinition,
34    ) -> Result<MigrationPlan> {
35        let mut steps = Vec::new();
36
37        // Compare versions
38        if from.version != to.version {
39            steps.push(MigrationStep::UpdateVersion {
40                from: from.version.clone(),
41                to: to.version.clone(),
42            });
43        }
44
45        // Compare task counts
46        if from.dag.task_count() != to.dag.task_count() {
47            steps.push(MigrationStep::UpdateTaskCount {
48                from: from.dag.task_count(),
49                to: to.dag.task_count(),
50            });
51        }
52
53        // Compare metadata
54        Ok(MigrationPlan {
55            steps,
56            requires_downtime: false,
57            estimated_duration_secs: 0,
58        })
59    }
60
61    /// Execute a migration plan.
62    fn execute_migration_plan(
63        &self,
64        mut workflow: WorkflowDefinition,
65        plan: MigrationPlan,
66    ) -> Result<WorkflowDefinition> {
67        for step in plan.steps {
68            workflow = self.execute_migration_step(workflow, step)?;
69        }
70
71        Ok(workflow)
72    }
73
74    /// Execute a single migration step.
75    fn execute_migration_step(
76        &self,
77        mut workflow: WorkflowDefinition,
78        step: MigrationStep,
79    ) -> Result<WorkflowDefinition> {
80        match step {
81            MigrationStep::UpdateVersion { to, .. } => {
82                workflow.version = to;
83            }
84            MigrationStep::UpdateTaskCount { .. } => {
85                // Task updates would be handled separately
86            }
87            MigrationStep::UpdateMetadata => {
88                // Metadata updates would be handled separately
89            }
90            MigrationStep::Custom { .. } => {
91                // Custom migration logic
92            }
93        }
94
95        Ok(workflow)
96    }
97
98    /// Validate migration compatibility.
99    pub fn validate_migration(
100        &self,
101        from: &WorkflowDefinition,
102        to: &WorkflowDefinition,
103    ) -> Result<()> {
104        // Check if migration is possible
105        if from.id != to.id {
106            return Err(WorkflowError::versioning(
107                "Cannot migrate between different workflows",
108            ));
109        }
110
111        Ok(())
112    }
113}
114
115impl Default for WorkflowMigration {
116    fn default() -> Self {
117        Self::new()
118    }
119}
120
121/// Migration plan.
122#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct MigrationPlan {
124    /// Migration steps to execute.
125    pub steps: Vec<MigrationStep>,
126    /// Whether the migration requires downtime.
127    pub requires_downtime: bool,
128    /// Estimated duration in seconds.
129    pub estimated_duration_secs: u64,
130}
131
132/// Migration step.
133#[derive(Debug, Clone, Serialize, Deserialize)]
134pub enum MigrationStep {
135    /// Update workflow version.
136    UpdateVersion {
137        /// From version.
138        from: String,
139        /// To version.
140        to: String,
141    },
142    /// Update task count.
143    UpdateTaskCount {
144        /// From count.
145        from: usize,
146        /// To count.
147        to: usize,
148    },
149    /// Update metadata.
150    UpdateMetadata,
151    /// Custom migration step.
152    Custom {
153        /// Step name.
154        name: String,
155        /// Step description.
156        description: String,
157    },
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163    use crate::dag::WorkflowDag;
164
165    #[test]
166    fn test_migration_creation() {
167        let migration = WorkflowMigration::new();
168
169        let from = WorkflowDefinition {
170            id: "test".to_string(),
171            name: "Test".to_string(),
172            description: None,
173            version: "1.0.0".to_string(),
174            dag: WorkflowDag::new(),
175        };
176
177        let to = WorkflowDefinition {
178            id: "test".to_string(),
179            name: "Test".to_string(),
180            description: None,
181            version: "2.0.0".to_string(),
182            dag: WorkflowDag::new(),
183        };
184
185        assert!(migration.validate_migration(&from, &to).is_ok());
186    }
187
188    #[test]
189    fn test_migration_plan() {
190        let migration = WorkflowMigration::new();
191
192        let from = WorkflowDefinition {
193            id: "test".to_string(),
194            name: "Test".to_string(),
195            description: None,
196            version: "1.0.0".to_string(),
197            dag: WorkflowDag::new(),
198        };
199
200        let to = WorkflowDefinition {
201            id: "test".to_string(),
202            name: "Test".to_string(),
203            description: None,
204            version: "2.0.0".to_string(),
205            dag: WorkflowDag::new(),
206        };
207
208        let plan = migration.create_migration_plan(&from, &to).expect("Failed");
209
210        assert!(!plan.steps.is_empty());
211    }
212
213    #[test]
214    fn test_invalid_migration() {
215        let migration = WorkflowMigration::new();
216
217        let from = WorkflowDefinition {
218            id: "test1".to_string(),
219            name: "Test1".to_string(),
220            description: None,
221            version: "1.0.0".to_string(),
222            dag: WorkflowDag::new(),
223        };
224
225        let to = WorkflowDefinition {
226            id: "test2".to_string(),
227            name: "Test2".to_string(),
228            description: None,
229            version: "2.0.0".to_string(),
230            dag: WorkflowDag::new(),
231        };
232
233        assert!(migration.validate_migration(&from, &to).is_err());
234    }
235}