ldap-acis 0.2.1

LDAP Access Control Instructions (ACI) system built on acls-rs
Documentation
//! ACI scope — how deep the ACI applies in the directory tree.

use acls_rs::algebra::{BoundedJoinSemilattice, BoundedMeetSemilattice};
use acls_rs::prelude::*;

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

use std::cmp::Ordering;

/// ACI scope - how deep the ACI applies in the directory tree.
///
/// Ordered by breadth: `Base < OneLevel < Subtree`.
/// Forms a bounded lattice where meet = min (narrower) and join = max (wider).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub enum Scope {
    /// Only the target entry itself
    Base,
    /// One level below the target
    OneLevel,
    /// Entire subtree below the target
    Subtree,
}

impl Scope {
    fn rank(self) -> u8 {
        match self {
            Self::Base => 0,
            Self::OneLevel => 1,
            Self::Subtree => 2,
        }
    }
}

impl Ord for Scope {
    fn cmp(&self, other: &Self) -> Ordering {
        self.rank().cmp(&other.rank())
    }
}

impl PartialOrd for Scope {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Semigroup for Scope {
    fn combine(self, other: Self) -> Self {
        self.max(other)
    }
}

impl Monoid for Scope {
    fn identity() -> Self {
        Self::Base
    }
}

impl MeetSemilattice for Scope {
    fn meet(self, other: Self) -> Self {
        self.min(other)
    }
}

impl JoinSemilattice for Scope {
    fn join(self, other: Self) -> Self {
        self.max(other)
    }
}

impl BoundedMeetSemilattice for Scope {
    fn top() -> Self {
        Self::Subtree
    }
}

impl BoundedJoinSemilattice for Scope {
    fn bottom() -> Self {
        Self::Base
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn total_order() {
        assert!(Scope::Base < Scope::OneLevel);
        assert!(Scope::OneLevel < Scope::Subtree);
        assert!(Scope::Base < Scope::Subtree);
    }

    #[test]
    fn reflexive() {
        for s in [Scope::Base, Scope::OneLevel, Scope::Subtree] {
            assert_eq!(s.partial_cmp(&s), Some(Ordering::Equal));
        }
    }

    #[test]
    fn lattice_meet_is_min() {
        assert_eq!(Scope::Base.meet(Scope::Subtree), Scope::Base);
        assert_eq!(Scope::Subtree.meet(Scope::OneLevel), Scope::OneLevel);
        assert_eq!(Scope::OneLevel.meet(Scope::OneLevel), Scope::OneLevel);
    }

    #[test]
    fn lattice_join_is_max() {
        assert_eq!(Scope::Base.join(Scope::Subtree), Scope::Subtree);
        assert_eq!(Scope::Base.join(Scope::OneLevel), Scope::OneLevel);
        assert_eq!(Scope::Subtree.join(Scope::Subtree), Scope::Subtree);
    }

    #[test]
    fn monoid_identity() {
        for s in [Scope::Base, Scope::OneLevel, Scope::Subtree] {
            assert_eq!(s.combine(Scope::identity()), s);
            assert_eq!(Scope::identity().combine(s), s);
        }
    }

    #[test]
    fn bounded_top_bottom() {
        for s in [Scope::Base, Scope::OneLevel, Scope::Subtree] {
            assert_eq!(s.meet(Scope::top()), s);
            assert_eq!(s.join(Scope::bottom()), s);
        }
    }

    #[test]
    fn absorption() {
        for a in [Scope::Base, Scope::OneLevel, Scope::Subtree] {
            for b in [Scope::Base, Scope::OneLevel, Scope::Subtree] {
                assert_eq!(a.meet(a.join(b)), a);
                assert_eq!(a.join(a.meet(b)), a);
            }
        }
    }
}