adk_doc_audit/
validator.rs

1//! Example validator for testing code compilation and async patterns.
2//!
3//! This module provides functionality to validate that code examples in documentation
4//! compile correctly and follow proper patterns, especially for async code.
5
6use 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/// Validator for code examples that tests compilation and patterns.
15#[derive(Debug)]
16pub struct ExampleValidator {
17    /// Temporary directory for creating test projects
18    temp_dir: TempDir,
19    /// Current workspace version for dependency resolution
20    #[allow(dead_code)]
21    workspace_version: String,
22    /// Path to the workspace root for dependency resolution
23    workspace_path: PathBuf,
24    /// Cache of generated Cargo.toml templates
25    #[allow(dead_code)]
26    cargo_templates: HashMap<String, String>,
27}
28
29/// Result of validating a code example.
30#[derive(Debug, Clone, PartialEq)]
31pub struct ValidationResult {
32    /// Whether the validation succeeded
33    pub success: bool,
34    /// Compilation errors encountered
35    pub errors: Vec<CompilationError>,
36    /// Warnings from compilation
37    pub warnings: Vec<String>,
38    /// Suggested fixes for issues
39    pub suggestions: Vec<String>,
40    /// Additional metadata about the validation
41    pub metadata: ValidationMetadata,
42}
43
44/// Additional metadata about the validation process.
45#[derive(Debug, Clone, PartialEq, Default)]
46pub struct ValidationMetadata {
47    /// Time taken for validation in milliseconds
48    pub duration_ms: u64,
49    /// Whether a temporary project was created
50    pub used_temp_project: bool,
51    /// Cargo command that was executed
52    pub cargo_command: Option<String>,
53    /// Exit code from cargo command
54    pub exit_code: Option<i32>,
55}
56
57/// Represents a compilation error with detailed information.
58#[derive(Debug, Clone, PartialEq)]
59pub struct CompilationError {
60    /// Error message from the compiler
61    pub message: String,
62    /// Line number where the error occurred (if available)
63    pub line: Option<usize>,
64    /// Column number where the error occurred (if available)
65    pub column: Option<usize>,
66    /// Type of error encountered
67    pub error_type: ErrorType,
68    /// Suggested fix for the error (if available)
69    pub suggestion: Option<String>,
70    /// Code snippet that caused the error
71    pub code_snippet: Option<String>,
72}
73
74/// Types of compilation errors that can occur.
75#[derive(Debug, Clone, PartialEq, Eq)]
76pub enum ErrorType {
77    /// Syntax error in the code
78    SyntaxError,
79    /// Type mismatch or type checking error
80    TypeMismatch,
81    /// Unresolved import or module not found
82    UnresolvedImport,
83    /// Missing dependency in Cargo.toml
84    MissingDependency,
85    /// Use of deprecated API
86    DeprecatedApi,
87    /// Async/await pattern issue
88    AsyncPatternError,
89    /// Runtime setup issue (e.g., missing tokio runtime)
90    RuntimeSetupError,
91    /// Generic compilation error
92    CompilationFailure,
93}
94
95/// Configuration for async pattern validation.
96#[derive(Debug, Clone)]
97pub struct AsyncValidationConfig {
98    /// Whether to require explicit tokio runtime setup
99    pub require_runtime_setup: bool,
100    /// Whether to validate proper error handling in async code
101    pub validate_error_handling: bool,
102    /// Whether to check for proper async/await usage
103    pub check_await_patterns: bool,
104    /// Maximum allowed nesting depth for async blocks
105    pub max_async_nesting: usize,
106}
107
108impl ExampleValidator {
109    /// Creates a new example validator.
110    ///
111    /// # Arguments
112    ///
113    /// * `workspace_version` - Current version of the ADK-Rust workspace
114    /// * `workspace_path` - Path to the workspace root for dependency resolution
115    ///
116    /// # Returns
117    ///
118    /// A new `ExampleValidator` instance or an error if setup fails.
119    #[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    /// Validates a code example by attempting to compile it.
130    ///
131    /// # Arguments
132    ///
133    /// * `example` - The code example to validate
134    ///
135    /// # Returns
136    ///
137    /// A `ValidationResult` containing the outcome and any errors found.
138    #[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        // Only validate Rust examples for compilation
143        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        // Skip non-runnable examples
159        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        // Create temporary project and validate
176        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    /// Validates async patterns in a code example.
194    ///
195    /// # Arguments
196    ///
197    /// * `example` - The code example to validate for async patterns
198    /// * `config` - Configuration for async validation
199    ///
200    /// # Returns
201    ///
202    /// A `ValidationResult` containing async pattern validation results.
203    #[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        // Only validate Rust examples
215        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        // Check for async/await usage
233        if content.contains("async") || content.contains(".await") {
234            debug!("Found async code, validating patterns");
235
236            // Check for proper runtime setup
237            if config.require_runtime_setup {
238                self.validate_runtime_setup(content, &mut errors, &mut suggestions);
239            }
240
241            // Check for proper error handling
242            if config.validate_error_handling {
243                self.validate_async_error_handling(content, &mut errors, &mut suggestions);
244            }
245
246            // Check await patterns
247            if config.check_await_patterns {
248                self.validate_await_patterns(content, &mut errors, &mut warnings, &mut suggestions);
249            }
250
251            // Check nesting depth
252            self.validate_async_nesting(
253                content,
254                config.max_async_nesting,
255                &mut warnings,
256                &mut suggestions,
257            );
258
259            // Additional async pattern validations
260            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    /// Suggests fixes for compilation errors.
283    ///
284    /// # Arguments
285    ///
286    /// * `example` - The code example that failed compilation
287    /// * `errors` - The compilation errors encountered
288    ///
289    /// # Returns
290    ///
291    /// A vector of suggested fixes.
292    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                    // Generic suggestions based on error message
330                    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        // Add example-specific suggestions
343        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    /// Creates a temporary Rust project for testing the example.
356    #[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        // Create project directory structure
362        fs::create_dir_all(&project_path).await?;
363        fs::create_dir_all(project_path.join("src")).await?;
364
365        // Generate Cargo.toml
366        let cargo_toml = self.generate_cargo_toml(&project_name, example).await?;
367        fs::write(project_path.join("Cargo.toml"), cargo_toml).await?;
368
369        // Generate main.rs or lib.rs
370        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    /// Generates a Cargo.toml file for the temporary project.
380    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        // Add ADK dependencies based on code content
388        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        // Add tokio if async code is detected
414        if example.content.contains("async") || example.content.contains(".await") {
415            dependencies
416                .insert("tokio", "{ version = \"1.0\", features = [\"full\"] }".to_string());
417        }
418
419        // Add common dependencies based on imports
420        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    /// Prepares the Rust code for compilation, adding necessary boilerplate.
450    fn prepare_rust_code(&self, example: &CodeExample) -> Result<String> {
451        let mut code = example.content.clone();
452
453        // Add common imports if not present
454        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        // Add tokio main attribute if needed
473        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        // Wrap in a basic structure if it's just expressions
478        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    /// Compiles the example in the temporary project.
486    #[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, // Will be set by caller
526                used_temp_project: true,
527                cargo_command: Some(cargo_command.to_string()),
528                exit_code,
529            },
530        })
531    }
532
533    /// Parses cargo output to extract errors and warnings.
534    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        // Parse JSON messages from cargo
543        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        // Also parse stderr for any additional errors
554        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    /// Parses a compiler message from cargo JSON output.
575    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    /// Classifies the type of compilation error based on the message.
611    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    /// Extracts line and column information from a compiler message.
630    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    /// Validates runtime setup for async code.
643    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    /// Validates error handling patterns in async code.
663    fn validate_async_error_handling(
664        &self,
665        content: &str,
666        errors: &mut Vec<CompilationError>,
667        suggestions: &mut Vec<String>,
668    ) {
669        // Check for .await without proper error handling
670        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    /// Validates await patterns in async code.
690    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        // Check for missing .await on async calls
698        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    /// Validates async nesting depth.
712    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    /// Suggests fixes for import-related errors.
745    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    /// Suggests fixes for dependency-related errors.
758    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    /// Validates proper tokio usage patterns.
772    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        // Check for proper tokio runtime attributes
780        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        // Check for tokio::test usage in test functions
798        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        // Check for proper spawn usage
807        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    /// Validates async closure patterns.
814    fn validate_async_closures(
815        &self,
816        content: &str,
817        warnings: &mut Vec<String>,
818        suggestions: &mut Vec<String>,
819    ) {
820        // Check for async closures without proper handling
821        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        // Check for closure capture issues
832        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    /// Validates blocking calls in async context.
843    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    /// Validates async trait usage patterns.
889    fn validate_async_traits(
890        &self,
891        content: &str,
892        warnings: &mut Vec<String>,
893        suggestions: &mut Vec<String>,
894    ) {
895        // Check for async trait methods without async-trait
896        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        // Check for proper async trait implementation
905        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
938// Add uuid dependency for unique project names
939use 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        // Should detect missing #[tokio::main]
1033        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        // This should be a well-formed async example
1266        assert!(result.success);
1267        assert!(result.errors.is_empty());
1268    }
1269}