use crate::audit::issue::{AuditIssue, IssueSeverity};
use crate::audit::sections::{
BreakingChangesAuditSection, DependencyAuditSection, DependencyCategorization,
UpgradeAuditSection, VersionConsistencyAuditSection,
};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditReport {
pub audited_at: DateTime<Utc>,
pub workspace_root: PathBuf,
pub is_monorepo: bool,
pub sections: AuditSections,
pub summary: AuditSummary,
pub health_score: u8,
}
impl AuditReport {
#[must_use]
pub fn new(
workspace_root: PathBuf,
is_monorepo: bool,
sections: AuditSections,
health_score: u8,
) -> Self {
let summary = AuditSummary::from_sections(§ions);
Self {
audited_at: Utc::now(),
workspace_root,
is_monorepo,
sections,
summary,
health_score,
}
}
#[must_use]
pub fn issues_by_severity(&self, severity: IssueSeverity) -> Vec<&AuditIssue> {
self.all_issues().into_iter().filter(|issue| issue.severity == severity).collect()
}
#[must_use]
pub fn critical_issues(&self) -> Vec<&AuditIssue> {
self.issues_by_severity(IssueSeverity::Critical)
}
#[must_use]
pub fn warnings(&self) -> Vec<&AuditIssue> {
self.issues_by_severity(IssueSeverity::Warning)
}
#[must_use]
pub fn info_items(&self) -> Vec<&AuditIssue> {
self.issues_by_severity(IssueSeverity::Info)
}
#[must_use]
pub fn passed(&self) -> bool {
self.summary.critical_issues == 0
}
#[must_use]
pub fn all_issues(&self) -> Vec<&AuditIssue> {
let mut issues = Vec::new();
issues.extend(&self.sections.upgrades.issues);
issues.extend(&self.sections.dependencies.issues);
issues.extend(&self.sections.breaking_changes.issues);
issues.extend(&self.sections.version_consistency.issues);
issues
}
#[must_use]
pub fn total_issues(&self) -> usize {
self.summary.total_issues
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditSections {
pub upgrades: UpgradeAuditSection,
pub dependencies: DependencyAuditSection,
pub breaking_changes: BreakingChangesAuditSection,
pub categorization: DependencyCategorization,
pub version_consistency: VersionConsistencyAuditSection,
}
impl AuditSections {
#[must_use]
pub fn new(
upgrades: UpgradeAuditSection,
dependencies: DependencyAuditSection,
breaking_changes: BreakingChangesAuditSection,
categorization: DependencyCategorization,
version_consistency: VersionConsistencyAuditSection,
) -> Self {
Self { upgrades, dependencies, breaking_changes, categorization, version_consistency }
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditSummary {
pub packages_analyzed: usize,
pub dependencies_analyzed: usize,
pub total_issues: usize,
pub critical_issues: usize,
pub warnings: usize,
pub info_items: usize,
pub suggested_actions: Vec<String>,
}
impl AuditSummary {
#[must_use]
pub fn from_sections(sections: &AuditSections) -> Self {
let mut critical_issues = 0;
let mut warnings = 0;
let mut info_items = 0;
for issue in §ions.upgrades.issues {
match issue.severity {
IssueSeverity::Critical => critical_issues += 1,
IssueSeverity::Warning => warnings += 1,
IssueSeverity::Info => info_items += 1,
}
}
for issue in §ions.dependencies.issues {
match issue.severity {
IssueSeverity::Critical => critical_issues += 1,
IssueSeverity::Warning => warnings += 1,
IssueSeverity::Info => info_items += 1,
}
}
for issue in §ions.breaking_changes.issues {
match issue.severity {
IssueSeverity::Critical => critical_issues += 1,
IssueSeverity::Warning => warnings += 1,
IssueSeverity::Info => info_items += 1,
}
}
for issue in §ions.version_consistency.issues {
match issue.severity {
IssueSeverity::Critical => critical_issues += 1,
IssueSeverity::Warning => warnings += 1,
IssueSeverity::Info => info_items += 1,
}
}
let total_issues = critical_issues + warnings + info_items;
let packages_analyzed = sections.categorization.stats.total_packages;
let dependencies_analyzed = sections.categorization.stats.internal_packages
+ sections.categorization.stats.external_packages;
let suggested_actions = Self::generate_suggestions(sections);
Self {
packages_analyzed,
dependencies_analyzed,
total_issues,
critical_issues,
warnings,
info_items,
suggested_actions,
}
}
fn generate_suggestions(sections: &AuditSections) -> Vec<String> {
let mut suggestions = Vec::new();
if sections.upgrades.major_upgrades > 0 {
suggestions.push(format!(
"Review {} major upgrade(s) for breaking changes",
sections.upgrades.major_upgrades
));
}
if !sections.upgrades.deprecated_packages.is_empty() {
suggestions.push(format!(
"Replace {} deprecated package(s)",
sections.upgrades.deprecated_packages.len()
));
}
if !sections.dependencies.circular_dependencies.is_empty() {
suggestions.push(format!(
"Resolve {} circular dependenc(y/ies)",
sections.dependencies.circular_dependencies.len()
));
}
if !sections.dependencies.version_conflicts.is_empty() {
suggestions.push(format!(
"Fix {} version conflict(s)",
sections.dependencies.version_conflicts.len()
));
}
if sections.breaking_changes.total_breaking_changes > 0 {
suggestions.push(format!(
"Review {} breaking change(s) before release",
sections.breaking_changes.total_breaking_changes
));
}
if !sections.version_consistency.inconsistencies.is_empty() {
suggestions.push(format!(
"Align {} version inconsistenc(y/ies)",
sections.version_consistency.inconsistencies.len()
));
}
if suggestions.is_empty() {
suggestions.push("No immediate actions required".to_string());
}
suggestions
}
#[must_use]
pub fn empty() -> Self {
Self {
packages_analyzed: 0,
dependencies_analyzed: 0,
total_issues: 0,
critical_issues: 0,
warnings: 0,
info_items: 0,
suggested_actions: Vec::new(),
}
}
}