Skip to main content

depyler_mcp/
lib.rs

1pub mod error;
2pub mod pmat_integration;
3pub mod server;
4pub mod tools;
5pub mod transport;
6pub mod validator;
7
8#[cfg(test)]
9mod tests;
10
11pub use error::DepylerMcpError;
12pub use pmat_integration::{PmatIntegration, PmatQualityReport};
13pub use server::{AnalyzeTool, DepylerMcpServer, PmatQualityTool, TranspileTool, VerifyTool};
14pub use tools::{
15    AnalyzeRequest, CrateRecommendation, MigrationPhase, PerformanceComparison, SafetyGuarantees,
16    TestResults, TranspileMetrics, TranspileRequest, TranspileResponse, VerifyRequest,
17};
18pub use transport::TransportFactory;
19
20// Re-export pmcp types for convenience
21pub use pmcp::{
22    Error as McpError, RequestHandlerExtra, Server, ServerBuilder, ToolHandler, Transport,
23};
24
25use anyhow::Result;
26use serde::{Deserialize, Serialize};
27
28#[derive(Debug)]
29pub struct McpClient {
30    client: Option<Box<dyn std::any::Any + Send>>,
31}
32
33#[derive(Debug, Serialize)]
34pub struct McpTranspilationRequest {
35    pub version: &'static str,
36    pub python_ast: serde_json::Value,
37    pub error_context: ErrorContext,
38    pub quality_hints: QualityHints,
39}
40
41#[derive(Debug, Serialize)]
42pub struct ErrorContext {
43    pub error_message: String,
44    pub error_location: Option<Location>,
45    pub attempted_approach: String,
46}
47
48#[derive(Debug, Serialize)]
49pub struct Location {
50    pub line: usize,
51    pub column: usize,
52}
53
54#[derive(Debug, Serialize)]
55pub struct QualityHints {
56    pub target_complexity: u32,
57    pub preferred_types: Vec<String>,
58    pub style_level: StyleLevel,
59}
60
61#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
62pub enum StyleLevel {
63    Basic,
64    Idiomatic,
65    Optimized,
66}
67
68#[derive(Debug, Deserialize)]
69pub struct McpTranspilationResponse {
70    pub rust_code: String,
71    pub explanation: String,
72    pub test_cases: Vec<TestCase>,
73    pub confidence: f64,
74    pub alternative_approaches: Vec<AlternativeApproach>,
75}
76
77#[derive(Debug, Deserialize)]
78pub struct TestCase {
79    pub name: String,
80    pub input: serde_json::Value,
81    pub expected_output: serde_json::Value,
82}
83
84#[derive(Debug, Deserialize)]
85pub struct AlternativeApproach {
86    pub name: String,
87    pub description: String,
88    pub trade_offs: String,
89}
90
91impl McpClient {
92    pub fn new() -> Result<Self> {
93        Ok(Self { client: None })
94    }
95
96    pub async fn with_stdio() -> Result<Self> {
97        // Initialize with stdio transport when needed
98        Ok(Self { client: None })
99    }
100
101    pub fn is_available(&self) -> bool {
102        self.client.is_some()
103    }
104
105    pub async fn transpile_fallback(
106        &mut self,
107        _request: McpTranspilationRequest,
108    ) -> Result<McpTranspilationResponse> {
109        // For now, always return fallback response since client integration needs API updates
110        self.fallback_response().await
111    }
112
113    async fn fallback_response(&self) -> Result<McpTranspilationResponse> {
114        let mock_response = McpTranspilationResponse {
115            rust_code: "// MCP client not initialized - fallback response".to_string(),
116            explanation: "This construct requires MCP assistance for proper transpilation"
117                .to_string(),
118            test_cases: vec![],
119            confidence: 0.1,
120            alternative_approaches: vec![],
121        };
122
123        Ok(mock_response)
124    }
125
126    pub async fn transpile_fallback_sync(
127        &mut self,
128        request: McpTranspilationRequest,
129    ) -> Result<McpTranspilationResponse> {
130        self.transpile_fallback(request).await
131    }
132}
133
134impl Default for McpClient {
135    fn default() -> Self {
136        Self::new().unwrap_or_else(|e| {
137            eprintln!("Warning: Failed to create MCP client: {}", e);
138            McpClient { client: None }
139        })
140    }
141}
142
143#[cfg(test)]
144mod lib_tests {
145    use super::*;
146
147    #[test]
148    fn test_mcp_transpilation_request_creation() {
149        let request = McpTranspilationRequest {
150            version: "1.0",
151            python_ast: serde_json::json!({"type": "Module"}),
152            error_context: ErrorContext {
153                error_message: "test error".to_string(),
154                error_location: None,
155                attempted_approach: "direct transpilation".to_string(),
156            },
157            quality_hints: QualityHints {
158                target_complexity: 10,
159                preferred_types: vec!["String".to_string()],
160                style_level: StyleLevel::Idiomatic,
161            },
162        };
163
164        assert_eq!(request.version, "1.0");
165        assert_eq!(request.quality_hints.target_complexity, 10);
166    }
167
168    #[test]
169    fn test_error_context_with_location() {
170        let ctx = ErrorContext {
171            error_message: "type mismatch".to_string(),
172            error_location: Some(Location {
173                line: 10,
174                column: 5,
175            }),
176            attempted_approach: "type inference".to_string(),
177        };
178
179        assert_eq!(ctx.error_message, "type mismatch");
180        assert!(ctx.error_location.is_some());
181        let loc = ctx.error_location.unwrap();
182        assert_eq!(loc.line, 10);
183        assert_eq!(loc.column, 5);
184    }
185
186    #[test]
187    fn test_location_creation() {
188        let loc = Location { line: 1, column: 0 };
189        assert_eq!(loc.line, 1);
190        assert_eq!(loc.column, 0);
191    }
192
193    #[test]
194    fn test_quality_hints_creation() {
195        let hints = QualityHints {
196            target_complexity: 5,
197            preferred_types: vec!["i64".to_string(), "String".to_string()],
198            style_level: StyleLevel::Basic,
199        };
200
201        assert_eq!(hints.target_complexity, 5);
202        assert_eq!(hints.preferred_types.len(), 2);
203    }
204
205    #[test]
206    fn test_style_level_variants() {
207        assert!(matches!(StyleLevel::Basic, StyleLevel::Basic));
208        assert!(matches!(StyleLevel::Idiomatic, StyleLevel::Idiomatic));
209        assert!(matches!(StyleLevel::Optimized, StyleLevel::Optimized));
210    }
211
212    #[test]
213    fn test_style_level_clone() {
214        let level = StyleLevel::Idiomatic;
215        let cloned = level; // Copy type, clone() unnecessary
216        assert!(matches!(cloned, StyleLevel::Idiomatic));
217    }
218
219    #[test]
220    fn test_style_level_copy() {
221        let level = StyleLevel::Optimized;
222        let copied: StyleLevel = level;
223        assert!(matches!(copied, StyleLevel::Optimized));
224        assert!(matches!(level, StyleLevel::Optimized));
225    }
226
227    #[test]
228    fn test_style_level_serialization() {
229        let level = StyleLevel::Basic;
230        let json = serde_json::to_string(&level).unwrap();
231        assert!(!json.is_empty());
232
233        let deserialized: StyleLevel = serde_json::from_str(&json).unwrap();
234        assert!(matches!(deserialized, StyleLevel::Basic));
235    }
236
237    #[test]
238    fn test_mcp_transpilation_response_creation() {
239        let response = McpTranspilationResponse {
240            rust_code: "fn test() {}".to_string(),
241            explanation: "A test function".to_string(),
242            test_cases: vec![TestCase {
243                name: "test_one".to_string(),
244                input: serde_json::json!(1),
245                expected_output: serde_json::json!(1),
246            }],
247            confidence: 0.9,
248            alternative_approaches: vec![],
249        };
250
251        assert_eq!(response.rust_code, "fn test() {}");
252        assert_eq!(response.confidence, 0.9);
253        assert_eq!(response.test_cases.len(), 1);
254    }
255
256    #[test]
257    fn test_test_case_creation() {
258        let test_case = TestCase {
259            name: "test_add".to_string(),
260            input: serde_json::json!({"a": 1, "b": 2}),
261            expected_output: serde_json::json!(3),
262        };
263
264        assert_eq!(test_case.name, "test_add");
265    }
266
267    #[test]
268    fn test_alternative_approach_creation() {
269        let approach = AlternativeApproach {
270            name: "Functional style".to_string(),
271            description: "Uses map/filter instead of loops".to_string(),
272            trade_offs: "More idiomatic but potentially slower".to_string(),
273        };
274
275        assert_eq!(approach.name, "Functional style");
276        assert!(!approach.description.is_empty());
277    }
278
279    #[test]
280    fn test_mcp_client_new() {
281        let client = McpClient::new();
282        assert!(client.is_ok());
283    }
284
285    #[test]
286    fn test_mcp_client_default() {
287        let client = McpClient::default();
288        assert!(!client.is_available());
289    }
290
291    #[test]
292    fn test_mcp_client_is_available() {
293        let client = McpClient::new().unwrap();
294        assert!(!client.is_available());
295    }
296
297    #[tokio::test]
298    async fn test_mcp_client_with_stdio() {
299        let client = McpClient::with_stdio().await;
300        assert!(client.is_ok());
301    }
302
303    #[tokio::test]
304    async fn test_mcp_client_transpile_fallback() {
305        let mut client = McpClient::new().unwrap();
306        let request = McpTranspilationRequest {
307            version: "1.0",
308            python_ast: serde_json::json!({}),
309            error_context: ErrorContext {
310                error_message: "test".to_string(),
311                error_location: None,
312                attempted_approach: "test".to_string(),
313            },
314            quality_hints: QualityHints {
315                target_complexity: 10,
316                preferred_types: vec![],
317                style_level: StyleLevel::Basic,
318            },
319        };
320
321        let result = client.transpile_fallback(request).await;
322        assert!(result.is_ok());
323
324        let response = result.unwrap();
325        assert!(response.rust_code.contains("MCP client not initialized"));
326        assert!(response.confidence < 0.5);
327    }
328
329    #[tokio::test]
330    async fn test_mcp_client_transpile_fallback_sync() {
331        let mut client = McpClient::new().unwrap();
332        let request = McpTranspilationRequest {
333            version: "1.0",
334            python_ast: serde_json::json!({}),
335            error_context: ErrorContext {
336                error_message: "test".to_string(),
337                error_location: None,
338                attempted_approach: "test".to_string(),
339            },
340            quality_hints: QualityHints {
341                target_complexity: 10,
342                preferred_types: vec![],
343                style_level: StyleLevel::Basic,
344            },
345        };
346
347        let result = client.transpile_fallback_sync(request).await;
348        assert!(result.is_ok());
349    }
350
351    #[test]
352    fn test_mcp_client_debug() {
353        let client = McpClient::new().unwrap();
354        let debug_str = format!("{:?}", client);
355        assert!(debug_str.contains("McpClient"));
356    }
357}