use super::atomic::AtomicPermission;
use crate::algebra::{JoinSemilattice, MeetSemilattice, Monoid, Semigroup};
use std::collections::BTreeSet;
use std::fmt;
use std::iter::FromIterator;
use std::ops::{BitAnd, BitOr, Sub};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PermissionSet(pub(crate) BTreeSet<AtomicPermission>);
impl PermissionSet {
pub fn new() -> Self {
Self(BTreeSet::new())
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn contains(&self, perm: &AtomicPermission) -> bool {
self.0.contains(perm)
}
pub fn is_superset_of(&self, other: &Self) -> bool {
self.0.is_superset(&other.0)
}
pub fn is_subset_of(&self, other: &Self) -> bool {
self.0.is_subset(&other.0)
}
pub fn is_disjoint(&self, other: &Self) -> bool {
self.0.is_disjoint(&other.0)
}
pub fn insert(&mut self, perm: AtomicPermission) -> bool {
self.0.insert(perm)
}
pub fn remove(&mut self, perm: &AtomicPermission) -> bool {
self.0.remove(perm)
}
pub fn iter(&self) -> impl Iterator<Item = &AtomicPermission> {
self.0.iter()
}
pub fn difference(&self, other: &Self) -> Self {
Self(self.0.difference(&other.0).cloned().collect())
}
pub fn symmetric_difference(&self, other: &Self) -> Self {
Self(self.0.symmetric_difference(&other.0).cloned().collect())
}
pub fn builder() -> PermissionSetBuilder {
PermissionSetBuilder::new()
}
}
#[derive(Default)]
pub struct PermissionSetBuilder {
permissions: BTreeSet<AtomicPermission>,
}
impl PermissionSetBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn insert(mut self, perm: AtomicPermission) -> Self {
self.permissions.insert(perm);
self
}
pub fn build(self) -> PermissionSet {
PermissionSet(self.permissions)
}
}
impl Default for PermissionSet {
fn default() -> Self {
Self::new()
}
}
impl Semigroup for PermissionSet {
#[inline]
fn combine(self, other: Self) -> Self {
Self(self.0.union(&other.0).cloned().collect())
}
}
impl Monoid for PermissionSet {
#[inline]
fn identity() -> Self {
Self::new()
}
}
impl PartialOrd for PermissionSet {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
use std::cmp::Ordering;
if self == other {
Some(Ordering::Equal)
} else if self.is_subset_of(other) {
Some(Ordering::Less)
} else if self.is_superset_of(other) {
Some(Ordering::Greater)
} else {
None }
}
}
impl MeetSemilattice for PermissionSet {
#[inline]
fn meet(self, other: Self) -> Self {
Self(self.0.intersection(&other.0).cloned().collect())
}
}
impl JoinSemilattice for PermissionSet {
#[inline]
fn join(self, other: Self) -> Self {
self.combine(other) }
}
impl BitAnd for PermissionSet {
type Output = Self;
fn bitand(self, rhs: Self) -> Self::Output {
self.meet(rhs)
}
}
impl BitOr for PermissionSet {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
self.join(rhs)
}
}
impl Sub for PermissionSet {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
self.difference(&rhs)
}
}
impl From<BTreeSet<AtomicPermission>> for PermissionSet {
fn from(set: BTreeSet<AtomicPermission>) -> Self {
Self(set)
}
}
impl From<Vec<AtomicPermission>> for PermissionSet {
fn from(vec: Vec<AtomicPermission>) -> Self {
Self(vec.into_iter().collect())
}
}
impl<const N: usize> From<[AtomicPermission; N]> for PermissionSet {
fn from(arr: [AtomicPermission; N]) -> Self {
Self(arr.into_iter().collect())
}
}
impl FromIterator<AtomicPermission> for PermissionSet {
fn from_iter<T: IntoIterator<Item = AtomicPermission>>(iter: T) -> Self {
Self(iter.into_iter().collect())
}
}
impl IntoIterator for PermissionSet {
type Item = AtomicPermission;
type IntoIter = std::collections::btree_set::IntoIter<AtomicPermission>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl Extend<AtomicPermission> for PermissionSet {
fn extend<T: IntoIterator<Item = AtomicPermission>>(&mut self, iter: T) {
self.0.extend(iter);
}
}
impl fmt::Display for PermissionSet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{{")?;
for (i, perm) in self.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", perm)?;
}
write!(f, "}}")
}
}
#[cfg(test)]
mod tests {
use super::*;
fn perm(ns: &str, action: &str) -> AtomicPermission {
AtomicPermission::new(ns, action)
}
#[test]
fn test_new() {
let perms = PermissionSet::new();
assert!(perms.is_empty());
assert_eq!(perms.len(), 0);
}
#[test]
fn test_from_array() {
let perms = PermissionSet::from([perm("file", "read"), perm("file", "write")]);
assert_eq!(perms.len(), 2);
assert!(perms.contains(&perm("file", "read")));
}
#[test]
fn test_semigroup_combine() {
let p1 = PermissionSet::from([perm("file", "read")]);
let p2 = PermissionSet::from([perm("file", "write")]);
let combined = p1.combine(p2);
assert_eq!(combined.len(), 2);
}
#[test]
fn test_monoid_identity() {
let p = PermissionSet::from([perm("file", "read")]);
let e = PermissionSet::identity();
assert_eq!(p.clone().combine(e.clone()), p);
assert_eq!(e.combine(p.clone()), p);
}
#[test]
fn test_meet() {
let p1 = PermissionSet::from([perm("file", "read"), perm("file", "write")]);
let p2 = PermissionSet::from([perm("file", "read")]);
let meet = p1.meet(p2);
assert_eq!(meet.len(), 1);
assert!(meet.contains(&perm("file", "read")));
}
#[test]
fn test_join() {
let p1 = PermissionSet::from([perm("file", "read")]);
let p2 = PermissionSet::from([perm("file", "write")]);
let join = p1.join(p2);
assert_eq!(join.len(), 2);
}
#[test]
fn test_partial_ord() {
let subset = PermissionSet::from([perm("file", "read")]);
let superset = PermissionSet::from([perm("file", "read"), perm("file", "write")]);
assert!(subset < superset);
assert!(superset > subset);
}
#[test]
fn test_operators() {
let p1 = PermissionSet::from([perm("file", "read"), perm("file", "write")]);
let p2 = PermissionSet::from([perm("file", "read")]);
let meet = p1.clone() & p2.clone();
assert_eq!(meet.len(), 1);
let join = p1.clone() | p2.clone();
assert_eq!(join.len(), 2);
let diff = p1 - p2;
assert_eq!(diff.len(), 1);
assert!(diff.contains(&perm("file", "write")));
}
#[test]
fn test_display() {
let perms = PermissionSet::from([perm("file", "read")]);
let s = perms.to_string();
assert!(s.contains("file:read"));
}
}