use serde::{Deserialize, Serialize};
use std::collections::HashSet;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Action {
Create,
Read,
Update,
Delete,
List,
Restore,
}
impl Action {
pub fn all() -> Vec<Self> {
vec![
Self::Create,
Self::Read,
Self::Update,
Self::Delete,
Self::List,
Self::Restore,
]
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum FieldRestriction {
All,
Only(HashSet<String>),
Except(HashSet<String>),
}
impl FieldRestriction {
pub fn is_allowed(&self, field: &str) -> bool {
match self {
Self::All => true,
Self::Only(fields) => fields.contains(field),
Self::Except(fields) => !fields.contains(field),
}
}
pub fn filter_fields<'a>(&self, fields: &[&'a str]) -> Vec<&'a str> {
fields.iter().copied().filter(|f| self.is_allowed(f)).collect()
}
}
#[derive(Debug, Clone)]
pub struct PermissionRule {
pub action: Action,
pub allowed: bool,
pub fields: Option<FieldRestriction>,
pub condition: Option<String>,
}
#[derive(Debug, Clone, thiserror::Error)]
pub enum PermissionError {
#[error("Action '{action:?}' not allowed for role '{role}'")]
ActionNotAllowed { action: Action, role: String },
#[error("Field '{field}' not accessible for action '{action:?}'")]
FieldNotAccessible { field: String, action: Action },
#[error("Condition not met for action '{action:?}'")]
ConditionNotMet { action: Action },
#[error("Role '{0}' not found")]
RoleNotFound(String),
}
pub type PermissionResult = Result<(), PermissionError>;
pub trait PermissionChecker {
fn can(&self, role: &str, action: Action) -> bool;
fn can_access_field(&self, role: &str, action: Action, field: &str) -> bool;
fn allowed_fields(&self, role: &str, action: Action) -> Vec<String>;
fn roles(&self) -> Vec<&str>;
}