Skip to main content

batuta/pipeline/
types.rs

1//! Pipeline types and trait definitions.
2
3use anyhow::Result;
4use serde::{Deserialize, Serialize};
5use std::path::PathBuf;
6
7/// Context passed between pipeline stages
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct PipelineContext {
10    /// Input path (source project)
11    pub input_path: PathBuf,
12
13    /// Output path (transpiled Rust project)
14    pub output_path: PathBuf,
15
16    /// Primary language detected
17    pub primary_language: Option<crate::types::Language>,
18
19    /// Transpiled file mappings (source -> output)
20    pub file_mappings: Vec<(PathBuf, PathBuf)>,
21
22    /// Optimization passes applied
23    pub optimizations: Vec<String>,
24
25    /// Validation results
26    pub validation_results: Vec<ValidationResult>,
27
28    /// Metadata accumulated during pipeline
29    pub metadata: std::collections::HashMap<String, serde_json::Value>,
30}
31
32impl PipelineContext {
33    pub fn new(input_path: PathBuf, output_path: PathBuf) -> Self {
34        Self {
35            input_path,
36            output_path,
37            primary_language: None,
38            file_mappings: Vec::new(),
39            optimizations: Vec::new(),
40            validation_results: Vec::new(),
41            metadata: std::collections::HashMap::new(),
42        }
43    }
44
45    /// Get final output artifacts
46    pub fn output(&self) -> PipelineOutput {
47        PipelineOutput {
48            output_path: self.output_path.clone(),
49            file_mappings: self.file_mappings.clone(),
50            optimizations: self.optimizations.clone(),
51            validation_passed: self.validation_results.iter().all(|v| v.passed),
52        }
53    }
54}
55
56/// Validation result from a pipeline stage
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct ValidationResult {
59    pub stage: String,
60    pub passed: bool,
61    pub message: String,
62    pub details: Option<serde_json::Value>,
63}
64
65/// Final output from the pipeline
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct PipelineOutput {
68    pub output_path: PathBuf,
69    pub file_mappings: Vec<(PathBuf, PathBuf)>,
70    pub optimizations: Vec<String>,
71    pub validation_passed: bool,
72}
73
74/// Validation strategy for pipeline stages
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub enum ValidationStrategy {
77    /// Stop on first error (Jidoka principle)
78    StopOnError,
79    /// Continue on errors but collect them
80    ContinueOnError,
81    /// Skip validation
82    None,
83}
84
85/// Trait for pipeline stages
86#[async_trait::async_trait]
87pub trait PipelineStage: Send + Sync {
88    /// Name of this stage
89    fn name(&self) -> &str;
90
91    /// Execute this stage
92    async fn execute(&self, ctx: PipelineContext) -> Result<PipelineContext>;
93
94    /// Validate the output of this stage
95    fn validate(&self, _ctx: &PipelineContext) -> Result<ValidationResult> {
96        // Default: always pass
97        Ok(ValidationResult {
98            stage: self.name().to_string(),
99            passed: true,
100            message: "No validation configured".to_string(),
101            details: None,
102        })
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    #[test]
111    fn test_pipeline_context_new() {
112        let ctx = PipelineContext::new(PathBuf::from("/input"), PathBuf::from("/output"));
113        assert_eq!(ctx.input_path, PathBuf::from("/input"));
114        assert_eq!(ctx.output_path, PathBuf::from("/output"));
115        assert!(ctx.primary_language.is_none());
116        assert!(ctx.file_mappings.is_empty());
117        assert!(ctx.optimizations.is_empty());
118        assert!(ctx.validation_results.is_empty());
119        assert!(ctx.metadata.is_empty());
120    }
121
122    #[test]
123    fn test_pipeline_context_output() {
124        let ctx = PipelineContext::new(PathBuf::from("/input"), PathBuf::from("/output"));
125        let output = ctx.output();
126        assert_eq!(output.output_path, PathBuf::from("/output"));
127        assert!(output.validation_passed);
128    }
129
130    #[test]
131    fn test_pipeline_context_output_with_failed_validation() {
132        let mut ctx = PipelineContext::new(PathBuf::from("/input"), PathBuf::from("/output"));
133        ctx.validation_results.push(ValidationResult {
134            stage: "test".to_string(),
135            passed: false,
136            message: "Failed".to_string(),
137            details: None,
138        });
139        let output = ctx.output();
140        assert!(!output.validation_passed);
141    }
142
143    #[test]
144    fn test_pipeline_context_output_with_mixed_validations() {
145        let mut ctx = PipelineContext::new(PathBuf::from("/input"), PathBuf::from("/output"));
146        ctx.validation_results.push(ValidationResult {
147            stage: "stage1".to_string(),
148            passed: true,
149            message: "OK".to_string(),
150            details: None,
151        });
152        ctx.validation_results.push(ValidationResult {
153            stage: "stage2".to_string(),
154            passed: false,
155            message: "Failed".to_string(),
156            details: None,
157        });
158        let output = ctx.output();
159        assert!(!output.validation_passed); // One failure means overall failure
160    }
161
162    #[test]
163    fn test_validation_result_serialization() {
164        let result = ValidationResult {
165            stage: "test".to_string(),
166            passed: true,
167            message: "Success".to_string(),
168            details: Some(serde_json::json!({"key": "value"})),
169        };
170        let json = serde_json::to_string(&result).expect("json serialize failed");
171        let deserialized: ValidationResult =
172            serde_json::from_str(&json).expect("json deserialize failed");
173        assert_eq!(deserialized.stage, "test");
174        assert!(deserialized.passed);
175    }
176
177    #[test]
178    fn test_pipeline_output_serialization() {
179        let output = PipelineOutput {
180            output_path: PathBuf::from("/out"),
181            file_mappings: vec![(PathBuf::from("a.py"), PathBuf::from("a.rs"))],
182            optimizations: vec!["opt1".to_string()],
183            validation_passed: true,
184        };
185        let json = serde_json::to_string(&output).expect("json serialize failed");
186        let deserialized: PipelineOutput =
187            serde_json::from_str(&json).expect("json deserialize failed");
188        assert_eq!(deserialized.file_mappings.len(), 1);
189    }
190
191    #[test]
192    fn test_validation_strategy_equality() {
193        assert_eq!(ValidationStrategy::StopOnError, ValidationStrategy::StopOnError);
194        assert_ne!(ValidationStrategy::StopOnError, ValidationStrategy::ContinueOnError);
195        assert_ne!(ValidationStrategy::ContinueOnError, ValidationStrategy::None);
196    }
197
198    #[test]
199    fn test_pipeline_context_serialization() {
200        let ctx = PipelineContext::new(PathBuf::from("/input"), PathBuf::from("/output"));
201        let json = serde_json::to_string(&ctx).expect("json serialize failed");
202        let deserialized: PipelineContext =
203            serde_json::from_str(&json).expect("json deserialize failed");
204        assert_eq!(deserialized.input_path, PathBuf::from("/input"));
205    }
206
207    #[test]
208    fn test_pipeline_context_with_file_mappings() {
209        let mut ctx = PipelineContext::new(PathBuf::from("/input"), PathBuf::from("/output"));
210        ctx.file_mappings.push((PathBuf::from("src/main.py"), PathBuf::from("src/main.rs")));
211        let output = ctx.output();
212        assert_eq!(output.file_mappings.len(), 1);
213    }
214
215    #[test]
216    fn test_pipeline_context_with_optimizations() {
217        let mut ctx = PipelineContext::new(PathBuf::from("/input"), PathBuf::from("/output"));
218        ctx.optimizations.push("dead_code_elimination".to_string());
219        ctx.optimizations.push("inlining".to_string());
220        let output = ctx.output();
221        assert_eq!(output.optimizations.len(), 2);
222    }
223}