use super::PermissionCollision;
use tracing::{info, warn};
#[derive(Debug, Default)]
pub struct ValidationReport {
pub collisions: Vec<PermissionCollision>,
}
impl ValidationReport {
pub fn is_valid(&self) -> bool {
self.collisions.is_empty()
}
pub fn duplicates(&self) -> Vec<String> {
self.collisions
.iter()
.filter(|collision| {
collision.permissions.len() > 1
&& collision.permissions.windows(2).all(|w| w[0] == w[1])
})
.map(|collision| collision.permissions[0].clone())
.collect()
}
pub fn summary(&self) -> String {
if self.is_valid() {
return "All permissions are valid and collision-free".to_string();
}
let mut parts = Vec::new();
let duplicates = self.duplicates();
if !duplicates.is_empty() {
parts.push(format!(
"{} duplicate permission string(s)",
duplicates.len()
));
}
let non_duplicate_collisions = self
.collisions
.iter()
.filter(|collision| {
!(collision.permissions.len() > 1
&& collision.permissions.windows(2).all(|w| w[0] == w[1]))
})
.count();
if non_duplicate_collisions > 0 {
let total_colliding = self
.collisions
.iter()
.filter(|collision| {
!(collision.permissions.len() > 1
&& collision.permissions.windows(2).all(|w| w[0] == w[1]))
})
.map(|c| c.permissions.len())
.sum::<usize>();
parts.push(format!(
"{} hash collision(s) affecting {} permission(s)",
non_duplicate_collisions, total_colliding
));
}
parts.join(", ")
}
pub fn log_results(&self) {
if self.is_valid() {
info!("Permission validation passed: all permissions are valid");
return;
}
let duplicates = self.duplicates();
for duplicate in &duplicates {
warn!("Duplicate permission string found: '{}'", duplicate);
}
for collision in &self.collisions {
let is_duplicate = collision.permissions.len() > 1
&& collision.permissions.windows(2).all(|w| w[0] == w[1]);
if !is_duplicate {
warn!(
"Hash collision detected (ID: {}): permissions {:?} all hash to the same value",
collision.id, collision.permissions
);
}
}
}
pub fn detailed_errors(&self) -> Vec<String> {
let mut errors = Vec::new();
let duplicates = self.duplicates();
for duplicate in &duplicates {
errors.push(format!("Duplicate permission: '{}'", duplicate));
}
for collision in &self.collisions {
let is_duplicate = collision.permissions.len() > 1
&& collision.permissions.windows(2).all(|w| w[0] == w[1]);
if !is_duplicate {
errors.push(format!(
"Hash collision (ID {}): {} -> {:?}",
collision.id,
collision.permissions.join(", "),
collision.permissions
));
}
}
errors
}
pub fn total_issues(&self) -> usize {
self.collisions.len()
}
}
#[cfg(test)]
mod tests {
use super::super::PermissionCollision;
use super::*;
#[test]
fn validation_report_summary() {
let mut report = ValidationReport::default();
assert!(report.is_valid());
assert!(report.summary().contains("valid"));
report.collisions.push(PermissionCollision {
id: 12345,
permissions: vec!["test".to_string(), "test".to_string()],
});
assert!(!report.is_valid());
assert!(report.summary().contains("duplicate"));
report.collisions.push(PermissionCollision {
id: 12345,
permissions: vec!["perm1".to_string(), "perm2".to_string()],
});
assert!(report.summary().contains("collision"));
}
#[test]
fn validation_report_detailed_errors() {
let mut report = ValidationReport::default();
report.collisions.push(PermissionCollision {
id: 54321,
permissions: vec!["test:duplicate".to_string(), "test:duplicate".to_string()],
});
report.collisions.push(PermissionCollision {
id: 12345,
permissions: vec!["perm1".to_string(), "perm2".to_string()],
});
let errors = report.detailed_errors();
assert_eq!(errors.len(), 2);
assert!(errors[0].contains("Duplicate"));
assert!(errors[1].contains("Hash collision"));
}
}