use super::{PermissionCollision, PermissionId, ValidationReport};
use crate::errors::{Error, Result};
use crate::permissions::PermissionsError;
use std::collections::HashMap;
pub struct PermissionCollisionChecker {
permissions: Vec<String>,
collision_map: HashMap<u64, Vec<String>>,
}
impl PermissionCollisionChecker {
pub fn new(permissions: Vec<String>) -> Self {
Self {
permissions,
collision_map: HashMap::new(),
}
}
pub fn validate(&mut self) -> Result<ValidationReport> {
let mut report = ValidationReport::default();
self.check_hash_collisions(&mut report).map_err(|e| {
Error::Permissions(PermissionsError::collision(
0,
vec![format!("Failed to check for hash collisions: {}", e)],
))
})?;
self.build_collision_map();
Ok(report)
}
fn check_hash_collisions(&self, report: &mut ValidationReport) -> Result<()> {
let mut id_to_permissions: HashMap<u64, Vec<String>> = HashMap::new();
for permission in &self.permissions {
let id_raw = PermissionId::from(permission.as_str()).as_u64();
id_to_permissions
.entry(id_raw)
.or_default()
.push(permission.clone());
}
for (id, permissions) in id_to_permissions {
if permissions.len() > 1 {
report
.collisions
.push(PermissionCollision { id, permissions });
}
}
Ok(())
}
fn build_collision_map(&mut self) {
self.collision_map.clear();
for permission in &self.permissions {
let id = PermissionId::from(permission.as_str()).as_u64();
self.collision_map
.entry(id)
.or_default()
.push(permission.clone());
}
}
pub fn get_conflicting_permissions(&self, permission: &str) -> Vec<String> {
let id = PermissionId::from(permission).as_u64();
self.collision_map
.get(&id)
.map(|perms| perms.iter().filter(|p| *p != permission).cloned().collect())
.unwrap_or_default()
}
pub fn get_permission_summary(&self) -> HashMap<u64, Vec<String>> {
self.collision_map.clone()
}
pub fn permission_count(&self) -> usize {
self.permissions.len()
}
pub fn unique_id_count(&self) -> usize {
self.collision_map.len()
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn collision_checker_valid_permissions() {
let permissions = vec![
"user:read".to_string(),
"user:write".to_string(),
"admin:delete".to_string(),
];
let mut checker = PermissionCollisionChecker::new(permissions);
let report = checker.validate().unwrap();
assert!(report.is_valid());
assert!(report.duplicates().is_empty());
assert!(report.collisions.is_empty());
}
#[test]
fn collision_checker_duplicate_strings() {
let permissions = vec![
"user:read".to_string(),
"user:write".to_string(),
"user:read".to_string(), ];
let mut checker = PermissionCollisionChecker::new(permissions);
let report = checker.validate().unwrap();
assert!(!report.is_valid());
let duplicates = report.duplicates();
assert_eq!(duplicates.len(), 1);
assert_eq!(duplicates[0], "user:read");
assert_eq!(report.collisions.len(), 1);
}
#[test]
fn collision_checker_conflicting_permissions() {
let permissions = vec!["user:read".to_string(), "user:write".to_string()];
let mut checker = PermissionCollisionChecker::new(permissions);
checker.validate().unwrap();
let conflicts = checker.get_conflicting_permissions("user:read");
assert!(conflicts.is_empty());
}
#[test]
fn permission_collision_checker_summary() {
let permissions = vec![
"user:read".to_string(),
"user:write".to_string(),
"admin:delete".to_string(),
];
let mut checker = PermissionCollisionChecker::new(permissions);
checker.validate().unwrap();
assert_eq!(checker.permission_count(), 3);
assert_eq!(checker.unique_id_count(), 3);
let summary = checker.get_permission_summary();
assert_eq!(summary.len(), 3);
}
}