1use crate::{AuditError, CodeExample, Result};
7use std::collections::HashMap;
8use std::path::{Path, PathBuf};
9use std::process::Command;
10use tempfile::TempDir;
11use tokio::fs;
12use tracing::{debug, info, instrument};
13
14#[derive(Debug)]
16pub struct ExampleValidator {
17 temp_dir: TempDir,
19 #[allow(dead_code)]
21 workspace_version: String,
22 workspace_path: PathBuf,
24 #[allow(dead_code)]
26 cargo_templates: HashMap<String, String>,
27}
28
29#[derive(Debug, Clone, PartialEq)]
31pub struct ValidationResult {
32 pub success: bool,
34 pub errors: Vec<CompilationError>,
36 pub warnings: Vec<String>,
38 pub suggestions: Vec<String>,
40 pub metadata: ValidationMetadata,
42}
43
44#[derive(Debug, Clone, PartialEq, Default)]
46pub struct ValidationMetadata {
47 pub duration_ms: u64,
49 pub used_temp_project: bool,
51 pub cargo_command: Option<String>,
53 pub exit_code: Option<i32>,
55}
56
57#[derive(Debug, Clone, PartialEq)]
59pub struct CompilationError {
60 pub message: String,
62 pub line: Option<usize>,
64 pub column: Option<usize>,
66 pub error_type: ErrorType,
68 pub suggestion: Option<String>,
70 pub code_snippet: Option<String>,
72}
73
74#[derive(Debug, Clone, PartialEq, Eq)]
76pub enum ErrorType {
77 SyntaxError,
79 TypeMismatch,
81 UnresolvedImport,
83 MissingDependency,
85 DeprecatedApi,
87 AsyncPatternError,
89 RuntimeSetupError,
91 CompilationFailure,
93}
94
95#[derive(Debug, Clone)]
97pub struct AsyncValidationConfig {
98 pub require_runtime_setup: bool,
100 pub validate_error_handling: bool,
102 pub check_await_patterns: bool,
104 pub max_async_nesting: usize,
106}
107
108impl ExampleValidator {
109 #[instrument(skip(workspace_path))]
120 pub async fn new(workspace_version: String, workspace_path: PathBuf) -> Result<Self> {
121 let temp_dir =
122 TempDir::new().map_err(|e| AuditError::TempDirError { details: e.to_string() })?;
123
124 info!("Created temporary directory for example validation: {:?}", temp_dir.path());
125
126 Ok(Self { temp_dir, workspace_version, workspace_path, cargo_templates: HashMap::new() })
127 }
128
129 #[instrument(skip(self, example), fields(language = %example.language, runnable = %example.is_runnable))]
139 pub async fn validate_example(&self, example: &CodeExample) -> Result<ValidationResult> {
140 let start_time = std::time::Instant::now();
141
142 if example.language != "rust" {
144 return Ok(ValidationResult {
145 success: true,
146 errors: Vec::new(),
147 warnings: vec!["Non-Rust code not validated for compilation".to_string()],
148 suggestions: Vec::new(),
149 metadata: ValidationMetadata {
150 duration_ms: start_time.elapsed().as_millis() as u64,
151 used_temp_project: false,
152 cargo_command: None,
153 exit_code: None,
154 },
155 });
156 }
157
158 if !example.is_runnable {
160 debug!("Skipping non-runnable example");
161 return Ok(ValidationResult {
162 success: true,
163 errors: Vec::new(),
164 warnings: vec!["Example marked as non-runnable, skipping compilation".to_string()],
165 suggestions: Vec::new(),
166 metadata: ValidationMetadata {
167 duration_ms: start_time.elapsed().as_millis() as u64,
168 used_temp_project: false,
169 cargo_command: None,
170 exit_code: None,
171 },
172 });
173 }
174
175 let project_path = self.create_temp_project(example).await?;
177 let result = self.compile_example(&project_path, example).await?;
178
179 Ok(ValidationResult {
180 success: result.success,
181 errors: result.errors,
182 warnings: result.warnings,
183 suggestions: result.suggestions,
184 metadata: ValidationMetadata {
185 duration_ms: start_time.elapsed().as_millis() as u64,
186 used_temp_project: true,
187 cargo_command: result.metadata.cargo_command,
188 exit_code: result.metadata.exit_code,
189 },
190 })
191 }
192
193 #[instrument(skip(self, example, config))]
204 pub async fn validate_async_patterns(
205 &self,
206 example: &CodeExample,
207 config: &AsyncValidationConfig,
208 ) -> Result<ValidationResult> {
209 let start_time = std::time::Instant::now();
210 let mut errors = Vec::new();
211 let mut warnings = Vec::new();
212 let mut suggestions = Vec::new();
213
214 if example.language != "rust" {
216 return Ok(ValidationResult {
217 success: true,
218 errors,
219 warnings: vec!["Non-Rust code not validated for async patterns".to_string()],
220 suggestions,
221 metadata: ValidationMetadata {
222 duration_ms: start_time.elapsed().as_millis() as u64,
223 used_temp_project: false,
224 cargo_command: None,
225 exit_code: None,
226 },
227 });
228 }
229
230 let content = &example.content;
231
232 if content.contains("async") || content.contains(".await") {
234 debug!("Found async code, validating patterns");
235
236 if config.require_runtime_setup {
238 self.validate_runtime_setup(content, &mut errors, &mut suggestions);
239 }
240
241 if config.validate_error_handling {
243 self.validate_async_error_handling(content, &mut errors, &mut suggestions);
244 }
245
246 if config.check_await_patterns {
248 self.validate_await_patterns(content, &mut errors, &mut warnings, &mut suggestions);
249 }
250
251 self.validate_async_nesting(
253 content,
254 config.max_async_nesting,
255 &mut warnings,
256 &mut suggestions,
257 );
258
259 self.validate_tokio_usage(content, &mut errors, &mut warnings, &mut suggestions);
261 self.validate_async_closures(content, &mut warnings, &mut suggestions);
262 self.validate_blocking_calls(content, &mut warnings, &mut suggestions);
263 self.validate_async_traits(content, &mut warnings, &mut suggestions);
264 }
265
266 let success = errors.is_empty();
267
268 Ok(ValidationResult {
269 success,
270 errors,
271 warnings,
272 suggestions,
273 metadata: ValidationMetadata {
274 duration_ms: start_time.elapsed().as_millis() as u64,
275 used_temp_project: false,
276 cargo_command: None,
277 exit_code: None,
278 },
279 })
280 }
281
282 pub async fn suggest_fixes(
293 &self,
294 example: &CodeExample,
295 errors: &[CompilationError],
296 ) -> Result<Vec<String>> {
297 let mut suggestions = Vec::new();
298
299 for error in errors {
300 match error.error_type {
301 ErrorType::UnresolvedImport => {
302 if let Some(suggestion) = self.suggest_import_fix(&error.message) {
303 suggestions.push(suggestion);
304 }
305 }
306 ErrorType::MissingDependency => {
307 if let Some(suggestion) = self.suggest_dependency_fix(&error.message) {
308 suggestions.push(suggestion);
309 }
310 }
311 ErrorType::AsyncPatternError => {
312 suggestions
313 .push("Consider using #[tokio::main] for async main functions".to_string());
314 suggestions.push("Ensure all async calls use .await".to_string());
315 }
316 ErrorType::RuntimeSetupError => {
317 suggestions.push(
318 "Add tokio runtime setup: #[tokio::main] or tokio::runtime::Runtime::new()"
319 .to_string(),
320 );
321 }
322 ErrorType::DeprecatedApi => {
323 suggestions.push(
324 "Update to use the current API - check the latest documentation"
325 .to_string(),
326 );
327 }
328 _ => {
329 if error.message.contains("cannot find") {
331 suggestions
332 .push("Check if the module or type is properly imported".to_string());
333 }
334 if error.message.contains("async") {
335 suggestions
336 .push("Ensure async functions are called with .await".to_string());
337 }
338 }
339 }
340 }
341
342 if example.content.contains("adk_") && !example.content.contains("use adk_") {
344 suggestions.push("Add appropriate use statements for ADK crates".to_string());
345 }
346
347 if example.content.contains("async fn main") && !example.content.contains("#[tokio::main]")
348 {
349 suggestions.push("Add #[tokio::main] attribute to async main function".to_string());
350 }
351
352 Ok(suggestions)
353 }
354
355 #[instrument(skip(self, example))]
357 async fn create_temp_project(&self, example: &CodeExample) -> Result<PathBuf> {
358 let project_name = format!("example_test_{}", uuid::Uuid::new_v4().simple());
359 let project_path = self.temp_dir.path().join(&project_name);
360
361 fs::create_dir_all(&project_path).await?;
363 fs::create_dir_all(project_path.join("src")).await?;
364
365 let cargo_toml = self.generate_cargo_toml(&project_name, example).await?;
367 fs::write(project_path.join("Cargo.toml"), cargo_toml).await?;
368
369 let rust_code = self.prepare_rust_code(example)?;
371 let target_file =
372 if example.content.contains("fn main") { "src/main.rs" } else { "src/lib.rs" };
373 fs::write(project_path.join(target_file), rust_code).await?;
374
375 debug!("Created temporary project at: {:?}", project_path);
376 Ok(project_path)
377 }
378
379 async fn generate_cargo_toml(
381 &self,
382 project_name: &str,
383 example: &CodeExample,
384 ) -> Result<String> {
385 let mut dependencies = HashMap::new();
386
387 if example.content.contains("adk_core") {
389 dependencies.insert(
390 "adk-core",
391 format!("{{ path = \"{}\" }}", self.workspace_path.join("adk-core").display()),
392 );
393 }
394 if example.content.contains("adk_model") {
395 dependencies.insert(
396 "adk-model",
397 format!("{{ path = \"{}\" }}", self.workspace_path.join("adk-model").display()),
398 );
399 }
400 if example.content.contains("adk_agent") {
401 dependencies.insert(
402 "adk-agent",
403 format!("{{ path = \"{}\" }}", self.workspace_path.join("adk-agent").display()),
404 );
405 }
406 if example.content.contains("adk_tool") {
407 dependencies.insert(
408 "adk-tool",
409 format!("{{ path = \"{}\" }}", self.workspace_path.join("adk-tool").display()),
410 );
411 }
412
413 if example.content.contains("async") || example.content.contains(".await") {
415 dependencies
416 .insert("tokio", "{ version = \"1.0\", features = [\"full\"] }".to_string());
417 }
418
419 if example.content.contains("serde") {
421 dependencies
422 .insert("serde", "{ version = \"1.0\", features = [\"derive\"] }".to_string());
423 }
424 if example.content.contains("anyhow") {
425 dependencies.insert("anyhow", "\"1.0\"".to_string());
426 }
427 if example.content.contains("thiserror") {
428 dependencies.insert("thiserror", "\"1.0\"".to_string());
429 }
430
431 let mut cargo_toml = format!(
432 r#"[package]
433name = "{}"
434version = "0.1.0"
435edition = "2021"
436
437[dependencies]
438"#,
439 project_name
440 );
441
442 for (name, version) in dependencies {
443 cargo_toml.push_str(&format!("{} = {}\n", name, version));
444 }
445
446 Ok(cargo_toml)
447 }
448
449 fn prepare_rust_code(&self, example: &CodeExample) -> Result<String> {
451 let mut code = example.content.clone();
452
453 if !code.contains("use ") && (code.contains("adk_") || code.contains("tokio")) {
455 let mut imports = Vec::new();
456
457 if code.contains("adk_core") {
458 imports.push("use adk_core::*;");
459 }
460 if code.contains("adk_model") {
461 imports.push("use adk_model::*;");
462 }
463 if code.contains("tokio") && code.contains("async") {
464 imports.push("use tokio;");
465 }
466
467 if !imports.is_empty() {
468 code = format!("{}\n\n{}", imports.join("\n"), code);
469 }
470 }
471
472 if code.contains("async fn main") && !code.contains("#[tokio::main]") {
474 code = code.replace("async fn main", "#[tokio::main]\nasync fn main");
475 }
476
477 if !code.contains("fn ") && !code.contains("struct ") && !code.contains("impl ") {
479 code = format!("fn main() {{\n{}\n}}", code);
480 }
481
482 Ok(code)
483 }
484
485 #[instrument(skip(self, example))]
487 async fn compile_example(
488 &self,
489 project_path: &Path,
490 example: &CodeExample,
491 ) -> Result<ValidationResult> {
492 let cargo_command = "cargo check";
493
494 debug!("Running cargo check in: {:?}", project_path);
495
496 let output = Command::new("cargo")
497 .arg("check")
498 .arg("--message-format=json")
499 .current_dir(project_path)
500 .output()
501 .map_err(|e| AuditError::CargoError {
502 command: cargo_command.to_string(),
503 output: e.to_string(),
504 })?;
505
506 let exit_code = output.status.code();
507 let success = output.status.success();
508
509 let stdout = String::from_utf8_lossy(&output.stdout);
510 let stderr = String::from_utf8_lossy(&output.stderr);
511
512 debug!("Cargo check exit code: {:?}", exit_code);
513 debug!("Cargo check stdout: {}", stdout);
514 debug!("Cargo check stderr: {}", stderr);
515
516 let (errors, warnings) = self.parse_cargo_output(&stdout, &stderr)?;
517 let suggestions = self.suggest_fixes(example, &errors).await?;
518
519 Ok(ValidationResult {
520 success,
521 errors,
522 warnings,
523 suggestions,
524 metadata: ValidationMetadata {
525 duration_ms: 0, used_temp_project: true,
527 cargo_command: Some(cargo_command.to_string()),
528 exit_code,
529 },
530 })
531 }
532
533 fn parse_cargo_output(
535 &self,
536 stdout: &str,
537 stderr: &str,
538 ) -> Result<(Vec<CompilationError>, Vec<String>)> {
539 let mut errors = Vec::new();
540 let mut warnings = Vec::new();
541
542 for line in stdout.lines() {
544 if let Ok(message) = serde_json::from_str::<serde_json::Value>(line) {
545 if let Some("compiler-message") = message.get("reason").and_then(|r| r.as_str()) {
546 if let Some(msg) = message.get("message") {
547 self.parse_compiler_message(msg, &mut errors, &mut warnings)?;
548 }
549 }
550 }
551 }
552
553 if !stderr.is_empty() {
555 for line in stderr.lines() {
556 if line.contains("error:") {
557 errors.push(CompilationError {
558 message: line.to_string(),
559 line: None,
560 column: None,
561 error_type: ErrorType::CompilationFailure,
562 suggestion: None,
563 code_snippet: None,
564 });
565 } else if line.contains("warning:") {
566 warnings.push(line.to_string());
567 }
568 }
569 }
570
571 Ok((errors, warnings))
572 }
573
574 fn parse_compiler_message(
576 &self,
577 message: &serde_json::Value,
578 errors: &mut Vec<CompilationError>,
579 warnings: &mut Vec<String>,
580 ) -> Result<()> {
581 let level = message.get("level").and_then(|l| l.as_str()).unwrap_or("error");
582 let text = message.get("message").and_then(|m| m.as_str()).unwrap_or("Unknown error");
583
584 if level == "error" {
585 let error_type = self.classify_error_type(text);
586 let (line, column) = self.extract_location(message);
587 let suggestion = message
588 .get("children")
589 .and_then(|c| c.as_array())
590 .and_then(|arr| arr.first())
591 .and_then(|child| child.get("message"))
592 .and_then(|m| m.as_str())
593 .map(|s| s.to_string());
594
595 errors.push(CompilationError {
596 message: text.to_string(),
597 line,
598 column,
599 error_type,
600 suggestion,
601 code_snippet: None,
602 });
603 } else if level == "warning" {
604 warnings.push(text.to_string());
605 }
606
607 Ok(())
608 }
609
610 fn classify_error_type(&self, message: &str) -> ErrorType {
612 if message.contains("cannot find") || message.contains("unresolved import") {
613 ErrorType::UnresolvedImport
614 } else if message.contains("mismatched types") || message.contains("type mismatch") {
615 ErrorType::TypeMismatch
616 } else if message.contains("deprecated") {
617 ErrorType::DeprecatedApi
618 } else if message.contains("async") || message.contains("await") {
619 ErrorType::AsyncPatternError
620 } else if message.contains("runtime") || message.contains("tokio") {
621 ErrorType::RuntimeSetupError
622 } else if message.contains("syntax") || message.contains("unexpected token") {
623 ErrorType::SyntaxError
624 } else {
625 ErrorType::CompilationFailure
626 }
627 }
628
629 fn extract_location(&self, message: &serde_json::Value) -> (Option<usize>, Option<usize>) {
631 let spans = message.get("spans").and_then(|s| s.as_array());
632 if let Some(spans) = spans {
633 if let Some(span) = spans.first() {
634 let line = span.get("line_start").and_then(|l| l.as_u64()).map(|l| l as usize);
635 let column = span.get("column_start").and_then(|c| c.as_u64()).map(|c| c as usize);
636 return (line, column);
637 }
638 }
639 (None, None)
640 }
641
642 fn validate_runtime_setup(
644 &self,
645 content: &str,
646 errors: &mut Vec<CompilationError>,
647 suggestions: &mut Vec<String>,
648 ) {
649 if content.contains("async fn main") && !content.contains("#[tokio::main]") {
650 errors.push(CompilationError {
651 message: "Async main function requires runtime setup".to_string(),
652 line: None,
653 column: None,
654 error_type: ErrorType::RuntimeSetupError,
655 suggestion: Some("Add #[tokio::main] attribute".to_string()),
656 code_snippet: None,
657 });
658 suggestions.push("Add #[tokio::main] attribute to async main function".to_string());
659 }
660 }
661
662 fn validate_async_error_handling(
664 &self,
665 content: &str,
666 errors: &mut Vec<CompilationError>,
667 suggestions: &mut Vec<String>,
668 ) {
669 if content.contains(".await")
671 && !content.contains("?")
672 && !content.contains("unwrap")
673 && !content.contains("expect")
674 {
675 errors.push(CompilationError {
676 message: "Async calls should handle errors properly".to_string(),
677 line: None,
678 column: None,
679 error_type: ErrorType::AsyncPatternError,
680 suggestion: Some("Use ? operator or explicit error handling".to_string()),
681 code_snippet: None,
682 });
683 suggestions.push(
684 "Consider using the ? operator for error propagation in async code".to_string(),
685 );
686 }
687 }
688
689 fn validate_await_patterns(
691 &self,
692 content: &str,
693 _errors: &mut [CompilationError],
694 warnings: &mut Vec<String>,
695 suggestions: &mut Vec<String>,
696 ) {
697 let lines: Vec<&str> = content.lines().collect();
699 for (i, line) in lines.iter().enumerate() {
700 if line.contains("async")
701 && line.contains("(")
702 && !line.contains(".await")
703 && !line.contains("fn ")
704 {
705 warnings.push(format!("Line {}: Possible missing .await on async call", i + 1));
706 suggestions.push("Ensure async function calls use .await".to_string());
707 }
708 }
709 }
710
711 fn validate_async_nesting(
713 &self,
714 content: &str,
715 max_depth: usize,
716 warnings: &mut Vec<String>,
717 suggestions: &mut Vec<String>,
718 ) {
719 let mut depth = 0;
720 let mut max_found = 0;
721
722 for line in content.lines() {
723 if line.contains("async {") || line.contains("async move {") {
724 depth += 1;
725 max_found = max_found.max(depth);
726 }
727 if line.contains('}') && depth > 0 {
728 depth -= 1;
729 }
730 }
731
732 if max_found > max_depth {
733 warnings.push(format!(
734 "Async nesting depth {} exceeds recommended maximum {}",
735 max_found, max_depth
736 ));
737 suggestions.push(
738 "Consider refactoring deeply nested async blocks into separate functions"
739 .to_string(),
740 );
741 }
742 }
743
744 fn suggest_import_fix(&self, error_message: &str) -> Option<String> {
746 if error_message.contains("adk_core") {
747 Some("Add: use adk_core::*; or specific imports".to_string())
748 } else if error_message.contains("tokio") {
749 Some("Add: use tokio; and ensure tokio is in dependencies".to_string())
750 } else if error_message.contains("serde") {
751 Some("Add: use serde::{Serialize, Deserialize}; and serde dependency".to_string())
752 } else {
753 None
754 }
755 }
756
757 fn suggest_dependency_fix(&self, error_message: &str) -> Option<String> {
759 if error_message.contains("adk") {
760 Some("Add the appropriate ADK crate to Cargo.toml dependencies".to_string())
761 } else if error_message.contains("tokio") {
762 Some(
763 "Add tokio = { version = \"1.0\", features = [\"full\"] } to dependencies"
764 .to_string(),
765 )
766 } else {
767 None
768 }
769 }
770
771 fn validate_tokio_usage(
773 &self,
774 content: &str,
775 errors: &mut Vec<CompilationError>,
776 warnings: &mut Vec<String>,
777 suggestions: &mut Vec<String>,
778 ) {
779 if content.contains("async fn main")
781 && !content.contains("#[tokio::main]")
782 && !content.contains("Runtime::new()")
783 {
784 errors.push(CompilationError {
785 message: "Async main function requires tokio runtime setup".to_string(),
786 line: None,
787 column: None,
788 error_type: ErrorType::RuntimeSetupError,
789 suggestion: Some(
790 "Add #[tokio::main] attribute or create runtime manually".to_string(),
791 ),
792 code_snippet: None,
793 });
794 suggestions.push("Use #[tokio::main] for simple async main functions".to_string());
795 }
796
797 if content.contains("#[test]") && content.contains("async fn") {
799 warnings.push(
800 "Async test functions should use #[tokio::test] instead of #[test]".to_string(),
801 );
802 suggestions
803 .push("Replace #[test] with #[tokio::test] for async test functions".to_string());
804 }
805
806 if content.contains("tokio::spawn") && !content.contains(".await") {
808 warnings.push("Spawned tasks should typically be awaited or joined".to_string());
809 suggestions.push("Consider awaiting spawned tasks or using JoinHandle".to_string());
810 }
811 }
812
813 fn validate_async_closures(
815 &self,
816 content: &str,
817 warnings: &mut Vec<String>,
818 suggestions: &mut Vec<String>,
819 ) {
820 if (content.contains("async move |") || content.contains("async |"))
822 && !content.contains("Box::pin")
823 && !content.contains("futures::")
824 {
825 warnings.push("Async closures may need special handling for compilation".to_string());
826 suggestions.push(
827 "Consider using Box::pin for async closures or futures utilities".to_string(),
828 );
829 }
830
831 if content.contains("move |") && content.contains(".await") {
833 warnings.push(
834 "Be careful with move closures and async - ensure proper lifetime management"
835 .to_string(),
836 );
837 suggestions
838 .push("Verify that moved values live long enough for async operations".to_string());
839 }
840 }
841
842 fn validate_blocking_calls(
844 &self,
845 content: &str,
846 warnings: &mut Vec<String>,
847 suggestions: &mut Vec<String>,
848 ) {
849 let blocking_patterns = [
850 "std::thread::sleep",
851 "std::fs::",
852 "std::net::",
853 ".read_to_string()",
854 ".write_all(",
855 "reqwest::blocking::",
856 ];
857
858 for pattern in &blocking_patterns {
859 if content.contains(pattern) && content.contains("async") {
860 warnings.push(format!("Potentially blocking call '{}' in async context", pattern));
861 match *pattern {
862 "std::thread::sleep" => {
863 suggestions.push(
864 "Use tokio::time::sleep instead of std::thread::sleep".to_string(),
865 );
866 }
867 "std::fs::" => {
868 suggestions.push("Use tokio::fs for async file operations".to_string());
869 }
870 "std::net::" => {
871 suggestions.push("Use tokio::net for async networking".to_string());
872 }
873 "reqwest::blocking::" => {
874 suggestions.push(
875 "Use async reqwest client instead of blocking client".to_string(),
876 );
877 }
878 _ => {
879 suggestions.push(
880 "Consider using async alternatives for blocking operations".to_string(),
881 );
882 }
883 }
884 }
885 }
886 }
887
888 fn validate_async_traits(
890 &self,
891 content: &str,
892 warnings: &mut Vec<String>,
893 suggestions: &mut Vec<String>,
894 ) {
895 if content.contains("trait ")
897 && content.contains("async fn")
898 && !content.contains("#[async_trait]")
899 {
900 warnings.push("Async methods in traits require the async-trait crate".to_string());
901 suggestions.push("Add #[async_trait] attribute and use async-trait crate".to_string());
902 }
903
904 if content.contains("impl ")
906 && content.contains("async fn")
907 && !content.contains("#[async_trait]")
908 {
909 let lines: Vec<&str> = content.lines().collect();
910 for (i, line) in lines.iter().enumerate() {
911 if line.contains("impl ")
912 && i + 1 < lines.len()
913 && lines[i + 1].contains("async fn")
914 {
915 warnings.push(
916 "Implementing async trait methods requires #[async_trait]".to_string(),
917 );
918 suggestions
919 .push("Add #[async_trait] to impl blocks with async methods".to_string());
920 break;
921 }
922 }
923 }
924 }
925}
926
927impl Default for AsyncValidationConfig {
928 fn default() -> Self {
929 Self {
930 require_runtime_setup: true,
931 validate_error_handling: true,
932 check_await_patterns: true,
933 max_async_nesting: 3,
934 }
935 }
936}
937
938use uuid;
940
941#[cfg(test)]
942mod tests {
943 use super::*;
944 use std::env;
945
946 async fn create_test_validator() -> ExampleValidator {
947 let temp_workspace = env::temp_dir().join("test_workspace");
948 tokio::fs::create_dir_all(&temp_workspace).await.unwrap();
949
950 ExampleValidator::new("0.1.0".to_string(), temp_workspace).await.unwrap()
951 }
952
953 #[tokio::test]
954 async fn test_validator_creation() {
955 let validator = create_test_validator().await;
956 assert_eq!(validator.workspace_version, "0.1.0");
957 }
958
959 #[tokio::test]
960 async fn test_simple_rust_example_validation() {
961 let validator = create_test_validator().await;
962
963 let example = CodeExample {
964 content: "fn main() { println!(\"Hello, world!\"); }".to_string(),
965 language: "rust".to_string(),
966 line_number: 1,
967 is_runnable: true,
968 attributes: Vec::new(),
969 };
970
971 let result = validator.validate_example(&example).await.unwrap();
972 assert!(result.success);
973 assert!(result.errors.is_empty());
974 }
975
976 #[tokio::test]
977 async fn test_non_rust_example_skipped() {
978 let validator = create_test_validator().await;
979
980 let example = CodeExample {
981 content: "console.log('Hello, world!');".to_string(),
982 language: "javascript".to_string(),
983 line_number: 1,
984 is_runnable: true,
985 attributes: Vec::new(),
986 };
987
988 let result = validator.validate_example(&example).await.unwrap();
989 assert!(result.success);
990 assert!(!result.warnings.is_empty());
991 assert!(!result.metadata.used_temp_project);
992 }
993
994 #[tokio::test]
995 async fn test_non_runnable_example_skipped() {
996 let validator = create_test_validator().await;
997
998 let example = CodeExample {
999 content: "fn main() { println!(\"Hello, world!\"); }".to_string(),
1000 language: "rust".to_string(),
1001 line_number: 1,
1002 is_runnable: false,
1003 attributes: vec!["ignore".to_string()],
1004 };
1005
1006 let result = validator.validate_example(&example).await.unwrap();
1007 assert!(result.success);
1008 assert!(!result.warnings.is_empty());
1009 assert!(!result.metadata.used_temp_project);
1010 }
1011
1012 #[tokio::test]
1013 async fn test_async_pattern_validation() {
1014 let validator = create_test_validator().await;
1015 let config = AsyncValidationConfig::default();
1016
1017 let example = CodeExample {
1018 content: r#"
1019async fn main() {
1020 println!("Hello, async world!");
1021}
1022"#
1023 .to_string(),
1024 language: "rust".to_string(),
1025 line_number: 1,
1026 is_runnable: true,
1027 attributes: Vec::new(),
1028 };
1029
1030 let result = validator.validate_async_patterns(&example, &config).await.unwrap();
1031
1032 assert!(!result.success);
1034 assert!(!result.errors.is_empty());
1035 assert!(result.errors.iter().any(|e| e.error_type == ErrorType::RuntimeSetupError));
1036 }
1037
1038 #[tokio::test]
1039 async fn test_proper_async_example() {
1040 let validator = create_test_validator().await;
1041 let config = AsyncValidationConfig::default();
1042
1043 let example = CodeExample {
1044 content: r#"
1045#[tokio::main]
1046async fn main() -> Result<(), Box<dyn std::error::Error>> {
1047 println!("Hello, async world!");
1048 Ok(())
1049}
1050"#
1051 .to_string(),
1052 language: "rust".to_string(),
1053 line_number: 1,
1054 is_runnable: true,
1055 attributes: Vec::new(),
1056 };
1057
1058 let result = validator.validate_async_patterns(&example, &config).await.unwrap();
1059 assert!(result.success);
1060 assert!(result.errors.is_empty());
1061 }
1062
1063 #[tokio::test]
1064 async fn test_error_classification() {
1065 let validator = create_test_validator().await;
1066
1067 assert_eq!(
1068 validator.classify_error_type("cannot find type `UnknownType`"),
1069 ErrorType::UnresolvedImport
1070 );
1071 assert_eq!(validator.classify_error_type("mismatched types"), ErrorType::TypeMismatch);
1072 assert_eq!(
1073 validator.classify_error_type("use of deprecated function"),
1074 ErrorType::DeprecatedApi
1075 );
1076 assert_eq!(
1077 validator.classify_error_type("async function in sync context"),
1078 ErrorType::AsyncPatternError
1079 );
1080 }
1081
1082 #[tokio::test]
1083 async fn test_suggestion_generation() {
1084 let validator = create_test_validator().await;
1085
1086 let errors = vec![CompilationError {
1087 message: "cannot find adk_core in scope".to_string(),
1088 line: None,
1089 column: None,
1090 error_type: ErrorType::UnresolvedImport,
1091 suggestion: None,
1092 code_snippet: None,
1093 }];
1094
1095 let example = CodeExample {
1096 content: "use adk_core::Agent;".to_string(),
1097 language: "rust".to_string(),
1098 line_number: 1,
1099 is_runnable: true,
1100 attributes: Vec::new(),
1101 };
1102
1103 let suggestions = validator.suggest_fixes(&example, &errors).await.unwrap();
1104 assert!(!suggestions.is_empty());
1105 assert!(suggestions.iter().any(|s| s.contains("adk_core")));
1106 }
1107
1108 #[tokio::test]
1109 async fn test_cargo_toml_generation() {
1110 let validator = create_test_validator().await;
1111
1112 let example = CodeExample {
1113 content: r#"
1114use adk_core::Agent;
1115use tokio;
1116
1117#[tokio::main]
1118async fn main() {
1119 println!("Hello!");
1120}
1121"#
1122 .to_string(),
1123 language: "rust".to_string(),
1124 line_number: 1,
1125 is_runnable: true,
1126 attributes: Vec::new(),
1127 };
1128
1129 let cargo_toml = validator.generate_cargo_toml("test_project", &example).await.unwrap();
1130
1131 assert!(cargo_toml.contains("adk-core"));
1132 assert!(cargo_toml.contains("tokio"));
1133 assert!(cargo_toml.contains("[package]"));
1134 assert!(cargo_toml.contains("[dependencies]"));
1135 }
1136
1137 #[tokio::test]
1138 async fn test_rust_code_preparation() {
1139 let validator = create_test_validator().await;
1140
1141 let example = CodeExample {
1142 content: "async fn main() { println!(\"Hello!\"); }".to_string(),
1143 language: "rust".to_string(),
1144 line_number: 1,
1145 is_runnable: true,
1146 attributes: Vec::new(),
1147 };
1148
1149 let prepared = validator.prepare_rust_code(&example).unwrap();
1150 assert!(prepared.contains("#[tokio::main]"));
1151 assert!(prepared.contains("async fn main"));
1152 }
1153
1154 #[tokio::test]
1155 async fn test_tokio_usage_validation() {
1156 let validator = create_test_validator().await;
1157 let config = AsyncValidationConfig::default();
1158
1159 let example = CodeExample {
1160 content: r#"
1161#[test]
1162async fn test_something() {
1163 // This should trigger a warning about using #[test] with async
1164}
1165"#
1166 .to_string(),
1167 language: "rust".to_string(),
1168 line_number: 1,
1169 is_runnable: true,
1170 attributes: Vec::new(),
1171 };
1172
1173 let result = validator.validate_async_patterns(&example, &config).await.unwrap();
1174 assert!(!result.warnings.is_empty());
1175 assert!(result.warnings.iter().any(|w| w.contains("tokio::test")));
1176 }
1177
1178 #[tokio::test]
1179 async fn test_blocking_calls_validation() {
1180 let validator = create_test_validator().await;
1181 let config = AsyncValidationConfig::default();
1182
1183 let example = CodeExample {
1184 content: r#"
1185async fn read_file() {
1186 let content = std::fs::read_to_string("file.txt");
1187 println!("{}", content);
1188}
1189"#
1190 .to_string(),
1191 language: "rust".to_string(),
1192 line_number: 1,
1193 is_runnable: true,
1194 attributes: Vec::new(),
1195 };
1196
1197 let result = validator.validate_async_patterns(&example, &config).await.unwrap();
1198 assert!(!result.warnings.is_empty());
1199 assert!(result.warnings.iter().any(|w| w.contains("blocking call")));
1200 assert!(result.suggestions.iter().any(|s| s.contains("tokio::fs")));
1201 }
1202
1203 #[tokio::test]
1204 async fn test_async_trait_validation() {
1205 let validator = create_test_validator().await;
1206 let config = AsyncValidationConfig::default();
1207
1208 let example = CodeExample {
1209 content: r#"
1210trait MyTrait {
1211 async fn do_something(&self) -> Result<(), Error>;
1212}
1213"#
1214 .to_string(),
1215 language: "rust".to_string(),
1216 line_number: 1,
1217 is_runnable: true,
1218 attributes: Vec::new(),
1219 };
1220
1221 let result = validator.validate_async_patterns(&example, &config).await.unwrap();
1222 assert!(!result.warnings.is_empty());
1223 assert!(result.warnings.iter().any(|w| w.contains("async-trait")));
1224 }
1225
1226 #[tokio::test]
1227 async fn test_comprehensive_async_validation() {
1228 let validator = create_test_validator().await;
1229 let config = AsyncValidationConfig {
1230 require_runtime_setup: true,
1231 validate_error_handling: true,
1232 check_await_patterns: true,
1233 max_async_nesting: 2,
1234 };
1235
1236 let example = CodeExample {
1237 content: r#"
1238#[tokio::main]
1239async fn main() -> Result<(), Box<dyn std::error::Error>> {
1240 let result = async_operation().await?;
1241
1242 tokio::spawn(async move {
1243 async_nested_operation().await
1244 }).await??;
1245
1246 Ok(())
1247}
1248
1249async fn async_operation() -> Result<String, std::io::Error> {
1250 tokio::fs::read_to_string("file.txt").await
1251}
1252
1253async fn async_nested_operation() -> Result<(), std::io::Error> {
1254 Ok(())
1255}
1256"#
1257 .to_string(),
1258 language: "rust".to_string(),
1259 line_number: 1,
1260 is_runnable: true,
1261 attributes: Vec::new(),
1262 };
1263
1264 let result = validator.validate_async_patterns(&example, &config).await.unwrap();
1265 assert!(result.success);
1267 assert!(result.errors.is_empty());
1268 }
1269}