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
20pub 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 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 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; 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}