Skip to main content

pysentry/
lib.rs

1// SPDX-License-Identifier: MIT
2
3pub use cache::{AuditCache, Cache, CacheBucket, CacheEntry, DatabaseMetadata, Freshness};
4pub use config::{
5    CacheConfig, CiConfig, Config, ConfigLoader, DefaultConfig, IgnoreConfig, MaintenanceConfig,
6    OutputConfig, PackageIgnoreRule, ProjectConfig, ResolverConfig, SourcesConfig,
7};
8pub use dependency::scanner::{DependencyScanner, DependencyStats};
9pub use maintenance::{
10    MaintenanceCheckConfig, MaintenanceIssue, MaintenanceIssueType, MaintenanceSummary,
11    ProjectState, ProjectStatus, SimpleIndexClient,
12};
13pub use output::report::{AuditReport, AuditSummary, ReportGenerator};
14pub use providers::{VulnerabilityProvider, VulnerabilitySource};
15pub use types::{
16    AuditFormat, PackageName, ResolutionCacheEntry, ResolvedDependency, ResolverType,
17    SeverityLevel, Version, VulnerabilitySourceType,
18};
19pub use vulnerability::{
20    database::{Severity, VersionRange, Vulnerability, VulnerabilityDatabase, VulnerabilityMatch},
21    matcher::{DatabaseStats, FixAnalysis, FixSuggestion, MatcherConfig, VulnerabilityMatcher},
22};
23
24pub mod cache;
25pub mod cli;
26pub mod config;
27pub mod dependency;
28pub mod maintenance;
29pub mod output;
30pub mod parsers;
31pub mod providers;
32pub mod types;
33pub mod vulnerability;
34
35mod error;
36
37pub use error::{AuditError, Result};
38
39#[cfg(feature = "python")]
40mod python;
41
42/// Main entry point for performing audits
43///
44/// This is a high-level API that coordinates the entire audit process:
45/// 1. Dependency scanning
46/// 2. Vulnerability database fetching
47/// 3. Vulnerability matching
48/// 4. Report generation
49pub struct AuditEngine {
50    scanner: DependencyScanner,
51    cache: Option<AuditCache>,
52}
53
54impl AuditEngine {
55    /// Create a new audit engine with default configuration
56    pub fn new() -> Self {
57        Self {
58            scanner: DependencyScanner::default(),
59            cache: None,
60        }
61    }
62
63    /// Create a new audit engine with custom scanner configuration
64    pub fn with_scanner(scanner: DependencyScanner) -> Self {
65        Self {
66            scanner,
67            cache: None,
68        }
69    }
70
71    /// Set cache for the audit engine
72    pub fn with_cache(mut self, cache: AuditCache) -> Self {
73        self.cache = Some(cache);
74        self
75    }
76
77    /// Perform a complete audit of a Python project
78    pub async fn audit_project<P: AsRef<std::path::Path>>(
79        &self,
80        project_path: P,
81        source_type: VulnerabilitySourceType,
82        min_severity: SeverityLevel,
83        ignore_ids: &[String],
84        direct_only: bool,
85        include_withdrawn: bool,
86    ) -> Result<AuditReport> {
87        let project_path = project_path.as_ref();
88
89        // 1. Scan dependencies
90        let (dependencies, skipped_packages, parser_name) =
91            self.scanner.scan_project(project_path).await?;
92        let dependency_stats = self.scanner.get_stats(&dependencies);
93        let warnings =
94            self.scanner
95                .validate_dependencies(&dependencies, &skipped_packages, &parser_name);
96
97        // 2. Create vulnerability source
98        let cache = self.cache.as_ref().cloned().unwrap_or_else(|| {
99            let temp_dir = std::env::temp_dir().join("pysentry-cache");
100            AuditCache::new(temp_dir)
101        });
102
103        let vuln_source = VulnerabilitySource::new(
104            source_type,
105            cache,
106            false,
107            crate::config::HttpConfig::default(),
108        );
109
110        // 3. Fetch vulnerabilities
111        let packages: Vec<(String, String)> = dependencies
112            .iter()
113            .map(|dep| (dep.name.to_string(), dep.version.to_string()))
114            .collect();
115
116        let database = vuln_source.fetch_vulnerabilities(&packages).await?;
117
118        // 4. Match vulnerabilities
119        let matcher_config = MatcherConfig::new(
120            min_severity,
121            ignore_ids.to_vec(),
122            vec![],
123            direct_only,
124            include_withdrawn,
125        );
126        let matcher = VulnerabilityMatcher::new(database, matcher_config);
127
128        let matches = matcher.find_vulnerabilities(&dependencies)?;
129        let filtered_matches = matcher.filter_matches(matches);
130
131        let database_stats = matcher.get_database_stats();
132        let fix_analysis = matcher.analyze_fixes(&filtered_matches);
133
134        // 5. Create report
135        let report = AuditReport::new(
136            dependency_stats,
137            database_stats,
138            filtered_matches,
139            fix_analysis,
140            warnings,
141            Vec::new(), // No maintenance checks in simple audit
142        );
143
144        Ok(report)
145    }
146}
147
148impl Default for AuditEngine {
149    fn default() -> Self {
150        Self::new()
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn test_audit_engine_creation() {
160        let engine = AuditEngine::new();
161        assert!(engine.cache.is_none());
162    }
163
164    #[test]
165    fn test_audit_engine_with_cache() {
166        let cache = AuditCache::new(std::env::temp_dir().join("test-cache"));
167        let engine = AuditEngine::new().with_cache(cache);
168        assert!(engine.cache.is_some());
169    }
170}