mod console_errors;
pub mod module;
mod vulnerable_libs;
pub use console_errors::{
prepare_console_collection, take_console_results, ConsoleErrorsAnalysis, ConsoleMessage,
};
pub use module::BestPracticesModule;
pub use vulnerable_libs::{
analyze_vulnerable_libraries, DetectedLibrary, VulnerableLibrariesAnalysis, VulnerableLibrary,
};
use chromiumoxide::Page;
use serde::{Deserialize, Serialize};
use tracing::warn;
use crate::error::Result;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BestPracticesAnalysis {
pub console_errors: ConsoleErrorsAnalysis,
pub vulnerable_libraries: VulnerableLibrariesAnalysis,
pub score: u32,
}
pub async fn analyze_best_practices(page: &Page) -> Result<BestPracticesAnalysis> {
let console_errors = match take_console_results(page).await {
Ok(c) => c,
Err(e) => {
warn!("Console error collection failed: {}", e);
ConsoleErrorsAnalysis {
errors: vec![],
warnings: vec![],
error_count: 0,
warning_count: 0,
}
}
};
let vulnerable_libraries = match analyze_vulnerable_libraries(page).await {
Ok(v) => v,
Err(e) => {
warn!("Vulnerable library detection failed: {}", e);
VulnerableLibrariesAnalysis {
detected: vec![],
vulnerable: vec![],
has_vulnerabilities: false,
}
}
};
let score = calculate_score(&console_errors, &vulnerable_libraries);
Ok(BestPracticesAnalysis {
console_errors,
vulnerable_libraries,
score,
})
}
fn calculate_score(console: &ConsoleErrorsAnalysis, libs: &VulnerableLibrariesAnalysis) -> u32 {
let mut score = 100u32;
let distinct_errors = {
let mut seen = std::collections::HashSet::new();
console
.errors
.iter()
.filter(|e| seen.insert(e.message.as_str()))
.count()
};
let error_penalty = (distinct_errors as u32 * 5).min(30);
score = score.saturating_sub(error_penalty);
let distinct_warnings = {
let mut seen = std::collections::HashSet::new();
console
.warnings
.iter()
.filter(|w| seen.insert(w.message.as_str()))
.count()
};
let warning_penalty = (distinct_warnings as u32 * 2).min(10);
score = score.saturating_sub(warning_penalty);
let vuln_penalty: u32 = libs
.vulnerable
.iter()
.map(|v| match v.severity.as_str() {
"high" => 20,
"medium" => 10,
_ => 5,
})
.sum::<u32>()
.min(40);
score = score.saturating_sub(vuln_penalty);
score
}