pub mod analyzer;
pub mod cache;
pub mod config;
pub mod domain;
pub mod patterns;
pub mod report;
pub use domain::violations::{
GuardianError, GuardianResult, Severity, ValidationReport, ValidationSummary, Violation,
};
pub use config::{GuardianConfig, PatternCategory, PatternRule, RuleType};
pub use analyzer::{AnalysisOptions, Analyzer, PatternStats};
pub use report::{OutputFormat, ReportFormatter, ReportOptions};
pub use cache::{CacheStatistics, FileCache};
use std::path::{Path, PathBuf};
pub struct GuardianValidator {
analyzer: Analyzer,
cache: Option<FileCache>,
report_formatter: ReportFormatter,
}
#[derive(Debug, Clone)]
pub struct ValidationOptions {
pub use_cache: bool,
pub cache_path: Option<PathBuf>,
pub continue_on_error: bool,
pub output_format: OutputFormat,
pub report_options: ReportOptions,
pub analysis_options: AnalysisOptions,
}
impl Default for ValidationOptions {
fn default() -> Self {
Self {
use_cache: true,
cache_path: None,
continue_on_error: true,
output_format: OutputFormat::Human,
report_options: ReportOptions::default(),
analysis_options: AnalysisOptions::default(),
}
}
}
impl GuardianValidator {
pub fn new_with_config(config: GuardianConfig) -> GuardianResult<Self> {
let analyzer = Analyzer::new(config)?;
let report_formatter = ReportFormatter::default();
Ok(Self {
analyzer,
cache: None,
report_formatter,
})
}
pub fn new() -> GuardianResult<Self> {
Self::new_with_config(GuardianConfig::default())
}
pub fn from_config_file<P: AsRef<Path>>(path: P) -> GuardianResult<Self> {
let config = GuardianConfig::load_from_file(path)?;
Self::new_with_config(config)
}
pub fn with_cache<P: AsRef<Path>>(mut self, cache_path: P) -> GuardianResult<Self> {
let mut cache = FileCache::new(cache_path);
cache.load()?;
cache.set_config_fingerprint(self.analyzer.config_fingerprint());
self.cache = Some(cache);
Ok(self)
}
pub fn with_report_formatter(mut self, formatter: ReportFormatter) -> Self {
self.report_formatter = formatter;
self
}
pub async fn validate_for_agent<P: AsRef<Path>>(
&mut self,
paths: Vec<P>,
) -> GuardianResult<ValidationReport> {
self.validate_with_options(paths, &ValidationOptions::default())
.await
}
pub async fn validate_with_options<P: AsRef<Path>>(
&mut self,
paths: Vec<P>,
options: &ValidationOptions,
) -> GuardianResult<ValidationReport> {
let paths: Vec<PathBuf> = paths.iter().map(|p| p.as_ref().to_path_buf()).collect();
let report = if options.use_cache && self.cache.is_some() {
self.analyze_with_cache(&paths, &options.analysis_options)
.await?
} else {
self.analyzer.analyze_paths(
&paths.iter().map(|p| p.as_path()).collect::<Vec<_>>(),
&options.analysis_options,
)?
};
Ok(report)
}
pub fn validate_file<P: AsRef<Path>>(&self, file_path: P) -> GuardianResult<ValidationReport> {
let violations = self.analyzer.analyze_file(file_path)?;
let mut report = ValidationReport::new();
for violation in violations {
report.add_violation(violation);
}
report.set_files_analyzed(1);
Ok(report)
}
pub fn validate_directory<P: AsRef<Path>>(
&self,
root: P,
options: &AnalysisOptions,
) -> GuardianResult<ValidationReport> {
self.analyzer.analyze_directory(root, options)
}
pub fn format_report(
&self,
report: &ValidationReport,
format: OutputFormat,
) -> GuardianResult<String> {
self.report_formatter.format_report(report, format)
}
pub fn pattern_statistics(&self) -> PatternStats {
self.analyzer.pattern_stats()
}
pub fn cache_statistics(&self) -> Option<CacheStatistics> {
self.cache.as_ref().map(|c| c.statistics())
}
pub fn clear_cache(&mut self) -> GuardianResult<()> {
if let Some(cache) = &mut self.cache {
cache.clear()?;
}
Ok(())
}
pub fn save_cache(&mut self) -> GuardianResult<()> {
if let Some(cache) = &mut self.cache {
cache.save()?;
}
Ok(())
}
pub fn cleanup_cache(&mut self) -> GuardianResult<Option<usize>> {
if let Some(cache) = &mut self.cache {
Ok(Some(cache.cleanup()?))
} else {
Ok(None)
}
}
async fn analyze_with_cache(
&mut self,
paths: &[PathBuf],
options: &AnalysisOptions,
) -> GuardianResult<ValidationReport> {
let mut all_violations = Vec::new();
let files_analyzed: usize;
let start_time = std::time::Instant::now();
let config_fingerprint = self.analyzer.config_fingerprint();
let mut all_files = Vec::new();
for path in paths {
if path.is_file() {
all_files.push(path.clone());
} else if path.is_dir() {
let temp_report = self.analyzer.analyze_directory(path, options)?;
let discovered_files: std::collections::HashSet<PathBuf> = temp_report
.violations
.iter()
.map(|v| v.file_path.clone())
.collect();
all_files.extend(discovered_files);
}
}
if let Some(cache) = &mut self.cache {
let mut files_to_analyze = Vec::new();
let mut _cached_violation_count = 0;
for file_path in &all_files {
match cache.needs_analysis(file_path, &config_fingerprint) {
Ok(needs_analysis) => {
if needs_analysis {
files_to_analyze.push(file_path.clone());
} else {
_cached_violation_count += 1; }
}
Err(e) => {
tracing::warn!("Cache check failed for {}: {}", file_path.display(), e);
files_to_analyze.push(file_path.clone());
}
}
}
if !files_to_analyze.is_empty() {
let fresh_report = self.analyzer.analyze_paths(
&files_to_analyze
.iter()
.map(|p| p.as_path())
.collect::<Vec<_>>(),
options,
)?;
all_violations.extend(fresh_report.violations);
for file_path in &files_to_analyze {
let file_violations: Vec<_> = all_violations
.iter()
.filter(|v| v.file_path == *file_path)
.collect();
if let Err(e) =
cache.update_entry(file_path, file_violations.len(), &config_fingerprint)
{
tracing::warn!("Failed to update cache for {}: {}", file_path.display(), e);
}
}
}
files_analyzed = all_files.len(); } else {
let report = self.analyzer.analyze_paths(
&all_files.iter().map(|p| p.as_path()).collect::<Vec<_>>(),
options,
)?;
all_violations.extend(report.violations);
files_analyzed = report.summary.total_files;
}
let mut report = ValidationReport::new();
for violation in all_violations {
report.add_violation(violation);
}
report.set_files_analyzed(files_analyzed);
report.set_execution_time(start_time.elapsed().as_millis() as u64);
report.set_config_fingerprint(config_fingerprint);
report.sort_violations();
Ok(report)
}
}
pub fn create_validator() -> GuardianResult<GuardianValidator> {
GuardianValidator::new()
}
pub async fn validate_files<P: AsRef<Path>>(files: Vec<P>) -> GuardianResult<ValidationReport> {
let mut validator = GuardianValidator::new()?;
validator.validate_for_agent(files).await
}
pub fn validate_directory<P: AsRef<Path>>(directory: P) -> GuardianResult<ValidationReport> {
let validator = GuardianValidator::new()?;
validator.validate_directory(directory, &AnalysisOptions::default())
}
pub mod agent {
use super::*;
pub async fn pre_commit_check<P: AsRef<Path>>(modified_files: Vec<P>) -> GuardianResult<()> {
let mut validator = GuardianValidator::new()?;
let report = validator.validate_for_agent(modified_files).await?;
if report.has_errors() {
let error_count = report.summary.violations_by_severity.error;
return Err(GuardianError::config(format!(
"Pre-commit check failed: {} blocking violation{} found",
error_count,
if error_count == 1 { "" } else { "s" }
)));
}
Ok(())
}
pub async fn development_check<P: AsRef<Path>>(
files: Vec<P>,
) -> GuardianResult<ValidationReport> {
let options = ValidationOptions {
analysis_options: AnalysisOptions {
fail_fast: false,
parallel: true,
..Default::default()
},
report_options: ReportOptions {
min_severity: Some(Severity::Warning),
..Default::default()
},
..Default::default()
};
let mut validator = GuardianValidator::new()?;
validator.validate_with_options(files, &options).await
}
pub async fn production_check<P: AsRef<Path>>(
files: Vec<P>,
) -> GuardianResult<ValidationReport> {
let options = ValidationOptions {
analysis_options: AnalysisOptions {
fail_fast: true,
parallel: true,
..Default::default()
},
report_options: ReportOptions {
min_severity: Some(Severity::Warning),
show_suggestions: true,
..Default::default()
},
..Default::default()
};
let mut validator = GuardianValidator::new()?;
let report = validator.validate_with_options(files, &options).await?;
if report.has_violations() {
return Err(GuardianError::config(format!(
"Production validation failed: {} violations found",
report.violations.len()
)));
}
Ok(report)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[tokio::test]
async fn test_validator_creation() {
let validator = GuardianValidator::new().unwrap();
let stats = validator.pattern_statistics();
assert!(stats.enabled_rules > 0);
}
#[tokio::test]
async fn test_validate_for_agent() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("test.rs");
fs::write(&test_file, "// TODO: implement this\nfn main() {}").unwrap();
let mut validator = GuardianValidator::new().unwrap();
let report = validator.validate_for_agent(vec![test_file]).await.unwrap();
assert!(report.has_violations());
assert!(report.violations.iter().any(|v| v.rule_id.contains("todo")));
}
#[test]
fn test_single_file_validation() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("test.rs");
fs::write(&test_file, "fn main() { unimplemented!() }").unwrap();
let validator = GuardianValidator::new().unwrap();
let report = validator.validate_file(&test_file).unwrap();
assert!(report.has_violations());
assert_eq!(report.summary.total_files, 1);
}
#[test]
fn test_directory_validation() {
let temp_dir = TempDir::new().unwrap();
let root = temp_dir.path();
fs::create_dir_all(root.join("src")).unwrap();
fs::write(root.join("src/lib.rs"), "// TODO: implement").unwrap();
fs::write(root.join("src/main.rs"), "fn main() {}").unwrap();
let validator = GuardianValidator::new().unwrap();
let report = validator
.validate_directory(root, &AnalysisOptions::default())
.unwrap();
assert!(report.has_violations());
assert!(report.summary.total_files > 0);
}
#[test]
fn test_report_formatting() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("test.rs");
fs::write(&test_file, "// TODO: test").unwrap();
let validator = GuardianValidator::new().unwrap();
let report = validator.validate_file(&test_file).unwrap();
let human = validator
.format_report(&report, OutputFormat::Human)
.unwrap();
assert!(human.contains("Code Quality Violations Found"));
let json = validator
.format_report(&report, OutputFormat::Json)
.unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert!(parsed["violations"].is_array());
}
#[tokio::test]
async fn test_agent_pre_commit_check() {
let temp_dir = TempDir::new().unwrap();
let clean_file = temp_dir.path().join("clean.rs");
let dirty_file = temp_dir.path().join("dirty.rs");
fs::write(&clean_file, "fn main() { println!(\"Hello\"); }").unwrap();
fs::write(&dirty_file, "fn main() { TODO: implement }").unwrap();
assert!(agent::pre_commit_check(vec![clean_file]).await.is_ok());
assert!(agent::pre_commit_check(vec![dirty_file]).await.is_err());
}
#[tokio::test]
async fn test_development_vs_production_checks() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("test.rs");
fs::write(&test_file, "fn main() { /* temporary implementation */ }").unwrap();
let dev_result = agent::development_check(vec![&test_file]).await;
assert!(dev_result.is_ok());
let prod_result = agent::production_check(vec![&test_file]).await;
let _ = prod_result; }
#[test]
fn test_convenience_functions() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("test.rs");
fs::write(&test_file, "fn main() {}").unwrap();
let validator = create_validator().unwrap();
assert!(validator.pattern_statistics().enabled_rules > 0);
let report = validate_directory(temp_dir.path()).unwrap();
assert_eq!(report.summary.total_files, 1);
}
}