portalis_orchestration/
lib.rs1pub mod workspace_generator;
6
7use portalis_core::{Agent, Error, Phase, PipelineState, Result};
8pub use workspace_generator::{WorkspaceGenerator, WorkspaceConfig, CrateInfo, ExternalDependency};
9use portalis_ingest::{IngestAgent, IngestInput};
10use portalis_analysis::{AnalysisAgent, AnalysisInput};
11use portalis_transpiler::{TranspilerAgent, TranspilerInput};
12use portalis_build::{BuildAgent, BuildInput};
13use portalis_test::{TestAgent, TestInput};
14use portalis_packaging::{PackagingAgent, PackagingInput};
15use serde::{Deserialize, Serialize};
16use std::path::PathBuf;
17
18pub struct Pipeline {
20 ingest: IngestAgent,
21 analysis: AnalysisAgent,
22 transpiler: TranspilerAgent,
23 build: BuildAgent,
24 test: TestAgent,
25 packaging: PackagingAgent,
26 state: PipelineState,
27}
28
29impl Pipeline {
30 pub fn new() -> Self {
32 Self {
33 ingest: IngestAgent::new(),
34 analysis: AnalysisAgent::new(),
35 transpiler: TranspilerAgent::new(),
36 build: BuildAgent::new(),
37 test: TestAgent::new(),
38 packaging: PackagingAgent::new(),
39 state: PipelineState::new(),
40 }
41 }
42
43 pub async fn translate(&mut self, source_path: PathBuf, source_code: String) -> Result<PipelineOutput> {
45 tracing::info!("Starting translation pipeline for {:?}", source_path);
46
47 self.state.transition(Phase::Ingesting);
49 let ingest_output = self.ingest.execute(IngestInput {
50 source_path: source_path.clone(),
51 source_code,
52 }).await?;
53
54 self.state.transition(Phase::Analyzing);
56 let ast_json = serde_json::to_value(&ingest_output.ast)
57 .map_err(|e| Error::Pipeline(format!("Failed to serialize AST: {}", e)))?;
58
59 let analysis_output = self.analysis.execute(AnalysisInput {
60 ast: ast_json,
61 }).await?;
62
63 self.state.transition(Phase::Transpiling);
65 let typed_functions = serde_json::to_value(&analysis_output.typed_functions)
66 .map_err(|e| Error::Pipeline(format!("Failed to serialize functions: {}", e)))?;
67
68 let api_contract = serde_json::to_value(&analysis_output.api_contract)
69 .map_err(|e| Error::Pipeline(format!("Failed to serialize API: {}", e)))?;
70
71 let transpiler_output = self.transpiler.execute(TranspilerInput {
72 typed_functions: typed_functions.as_array().cloned().unwrap_or_default(),
73 typed_classes: vec![],
74 use_statements: vec![],
75 cargo_dependencies: vec![],
76 api_contract,
77 }).await?;
78
79 self.state.transition(Phase::Building);
81 let build_output = self.build.execute(BuildInput {
82 rust_code: transpiler_output.rust_code.clone(),
83 }).await?;
84
85 self.state.transition(Phase::Testing);
87 let test_output = self.test.execute(TestInput {
88 wasm_bytes: build_output.wasm_bytes.clone(),
89 }).await?;
90
91 self.state.transition(Phase::Packaging);
93 let test_results = serde_json::to_value(&test_output)
94 .map_err(|e| Error::Pipeline(format!("Failed to serialize tests: {}", e)))?;
95
96 let packaging_output = self.packaging.execute(PackagingInput {
97 wasm_bytes: build_output.wasm_bytes.clone(),
98 test_results,
99 }).await?;
100
101 self.state.transition(Phase::Complete);
103
104 Ok(PipelineOutput {
105 source_path,
106 rust_code: transpiler_output.rust_code,
107 wasm_bytes: packaging_output.package_bytes,
108 manifest: packaging_output.manifest,
109 test_passed: test_output.passed,
110 test_failed: test_output.failed,
111 })
112 }
113
114 pub fn state(&self) -> &PipelineState {
116 &self.state
117 }
118}
119
120impl Default for Pipeline {
121 fn default() -> Self {
122 Self::new()
123 }
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct PipelineOutput {
129 pub source_path: PathBuf,
130 pub rust_code: String,
131 pub wasm_bytes: Vec<u8>,
132 pub manifest: serde_json::Value,
133 pub test_passed: usize,
134 pub test_failed: usize,
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[tokio::test]
142 async fn test_pipeline_simple_function() {
143 let mut pipeline = Pipeline::new();
144
145 let source = r#"
146def add(a: int, b: int) -> int:
147 return a + b
148"#;
149
150 let result = pipeline.translate(
151 PathBuf::from("test.py"),
152 source.to_string(),
153 ).await;
154
155 match result {
158 Ok(output) => {
159 assert!(!output.rust_code.is_empty());
160 println!("Generated Rust:\n{}", output.rust_code);
161 }
162 Err(e) => {
163 println!("Pipeline error (expected if wasm32 target not installed): {}", e);
164 }
165 }
166 }
167
168 #[tokio::test]
169 async fn test_pipeline_fibonacci() {
170 let mut pipeline = Pipeline::new();
171
172 let source = r#"
173def fibonacci(n: int) -> int:
174 if n <= 1:
175 return n
176 return fibonacci(n - 1) + fibonacci(n - 2)
177"#;
178
179 let result = pipeline.translate(
180 PathBuf::from("fibonacci.py"),
181 source.to_string(),
182 ).await;
183
184 match result {
185 Ok(output) => {
186 assert!(!output.rust_code.is_empty());
187 assert!(output.rust_code.contains("fibonacci"));
188 }
189 Err(e) => {
190 println!("Pipeline error (expected if wasm32 target not installed): {}", e);
191 }
192 }
193 }
194
195 #[tokio::test]
196 async fn test_pipeline_multiple_functions() {
197 let mut pipeline = Pipeline::new();
198
199 let source = r#"
200def add(a: int, b: int) -> int:
201 return a + b
202
203def multiply(x: int, y: int) -> int:
204 return x * y
205"#;
206
207 let result = pipeline.translate(
208 PathBuf::from("multi.py"),
209 source.to_string(),
210 ).await;
211
212 match result {
213 Ok(output) => {
214 assert!(output.rust_code.contains("add"));
215 assert!(output.rust_code.contains("multiply"));
216 }
217 Err(e) => {
218 println!("Pipeline error (expected if wasm32 target not installed): {}", e);
219 }
220 }
221 }
222
223 #[test]
224 fn test_pipeline_state_tracking() {
225 let pipeline = Pipeline::new();
226 assert_eq!(pipeline.state().phase, Phase::Idle);
227 }
228}