1use anyhow::Result;
4use serde::{Deserialize, Serialize};
5use std::path::PathBuf;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct PipelineContext {
10 pub input_path: PathBuf,
12
13 pub output_path: PathBuf,
15
16 pub primary_language: Option<crate::types::Language>,
18
19 pub file_mappings: Vec<(PathBuf, PathBuf)>,
21
22 pub optimizations: Vec<String>,
24
25 pub validation_results: Vec<ValidationResult>,
27
28 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 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#[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#[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub enum ValidationStrategy {
77 StopOnError,
79 ContinueOnError,
81 None,
83}
84
85#[async_trait::async_trait]
87pub trait PipelineStage: Send + Sync {
88 fn name(&self) -> &str;
90
91 async fn execute(&self, ctx: PipelineContext) -> Result<PipelineContext>;
93
94 fn validate(&self, _ctx: &PipelineContext) -> Result<ValidationResult> {
96 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); }
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}