use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Action {
View,
Add,
Change,
Delete,
}
impl Action {
pub const ALL: [Self; 4] = [Self::View, Self::Add, Self::Change, Self::Delete];
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::View => "view",
Self::Add => "add",
Self::Change => "change",
Self::Delete => "delete",
}
}
#[must_use]
pub fn parse(name: &str) -> Option<Self> {
Self::ALL.into_iter().find(|a| a.as_str() == name)
}
fn bit(self) -> u8 {
match self {
Self::View => 1,
Self::Add => 2,
Self::Change => 4,
Self::Delete => 8,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct Access {
superuser: bool,
grants: HashMap<String, u8>,
}
impl Access {
#[must_use]
pub fn superuser() -> Self {
Self {
superuser: true,
grants: HashMap::new(),
}
}
#[must_use]
pub fn none() -> Self {
Self::default()
}
#[must_use]
pub fn grant(mut self, model: impl Into<String>, action: Action) -> Self {
*self.grants.entry(model.into()).or_default() |= action.bit();
self
}
#[must_use]
pub fn is_superuser(&self) -> bool {
self.superuser
}
#[must_use]
pub fn allows(&self, model: &str, action: Action) -> bool {
if self.superuser {
return true;
}
let granted = self.grants.get(model).copied().unwrap_or(0);
let implied = if action == Action::View {
Action::Change.bit()
} else {
0
};
granted & (action.bit() | implied) != 0
}
pub(crate) fn granted(&self, model: &str, action: Action) -> bool {
self.grants.get(model).copied().unwrap_or(0) & action.bit() != 0
}
}