use super::atomic::AtomicPermission;
use super::composite::PermissionSet;
use crate::algebra::{Monoid, Semigroup};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PermissionDelta {
pub add: PermissionSet,
pub remove: PermissionSet,
}
impl PermissionDelta {
pub fn new(add: PermissionSet, remove: PermissionSet) -> Self {
Self { add, remove }
}
pub fn empty() -> Self {
Self {
add: PermissionSet::identity(),
remove: PermissionSet::identity(),
}
}
pub fn apply_to(&self, perms: PermissionSet) -> PermissionSet {
perms.combine(self.add.clone()).difference(&self.remove)
}
pub fn invert(self) -> Self {
Self {
add: self.remove,
remove: self.add,
}
}
pub fn builder() -> PermissionDeltaBuilder {
PermissionDeltaBuilder::default()
}
}
#[derive(Default)]
pub struct PermissionDeltaBuilder {
add: PermissionSet,
remove: PermissionSet,
}
impl PermissionDeltaBuilder {
pub fn grant(mut self, perm: AtomicPermission) -> Self {
self.add.extend([perm]);
self
}
pub fn grant_str(self, namespace: &str, action: &str) -> Self {
self.grant(AtomicPermission::new(namespace, action))
}
pub fn remove(mut self, perm: AtomicPermission) -> Self {
self.remove.extend([perm]);
self
}
pub fn remove_str(self, namespace: &str, action: &str) -> Self {
self.remove(AtomicPermission::new(namespace, action))
}
pub fn build(self) -> PermissionDelta {
PermissionDelta {
add: self.add,
remove: self.remove,
}
}
}
impl Semigroup for PermissionDelta {
fn combine(self, other: Self) -> Self {
Self {
add: self.add.combine(other.add),
remove: self.remove.combine(other.remove),
}
}
}
impl Monoid for PermissionDelta {
fn identity() -> Self {
Self::empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_apply_to() {
let perms = PermissionSet::from([AtomicPermission::new("file", "read")]);
let delta = PermissionDelta::builder()
.grant_str("file", "write")
.remove_str("file", "read")
.build();
let result = delta.apply_to(perms);
assert_eq!(result.len(), 1);
assert!(result.contains(&AtomicPermission::new("file", "write")));
assert!(!result.contains(&AtomicPermission::new("file", "read")));
}
#[test]
fn test_monoid_identity() {
let delta = PermissionDelta::builder().grant_str("file", "read").build();
let identity = PermissionDelta::identity();
assert_eq!(delta.clone().combine(identity), delta);
}
}