use std::collections::HashSet;
fn normalize(handle: &str) -> &str {
handle.rsplit(['/', ':']).next().unwrap_or(handle)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Admission {
Direct,
Broadcast,
Denied,
}
#[derive(Debug, Clone, Default)]
pub struct EffectiveGrant {
allow: HashSet<String>,
deny: HashSet<String>,
ceiling: HashSet<String>,
}
impl EffectiveGrant {
#[must_use]
pub fn new(allow: &[String], deny: &[String], ceiling: &[String]) -> Self {
let collect = |handles: &[String]| {
handles
.iter()
.map(|h| normalize(h).to_owned())
.collect::<HashSet<String>>()
};
Self {
allow: collect(allow),
deny: collect(deny),
ceiling: collect(ceiling),
}
}
#[must_use]
pub fn broadcast_only() -> Self {
Self::default()
}
#[must_use]
pub fn ceiling_only(ceiling: &[String]) -> Self {
Self::new(&[], &[], ceiling)
}
#[must_use]
pub fn admits(&self, connector: &str, default_enabled: bool) -> bool {
!matches!(
self.admission(connector, default_enabled),
Admission::Denied
)
}
#[must_use]
pub fn admission(&self, connector: &str, default_enabled: bool) -> Admission {
let connector = normalize(connector);
if !self.retains(connector) {
return Admission::Denied;
}
if self.allow.contains(connector) {
Admission::Direct
} else if default_enabled {
Admission::Broadcast
} else {
Admission::Denied
}
}
#[must_use]
pub fn retains(&self, connector: &str) -> bool {
let connector = normalize(connector);
if self.deny.contains(connector) {
return false;
}
self.ceiling.is_empty() || self.ceiling.contains(connector)
}
#[must_use]
pub fn admits_tool(&self, connector: &str, tool: &str, default_enabled: bool) -> bool {
let _ = tool;
self.admits(connector, default_enabled)
}
#[must_use]
#[allow(clippy::unused_self, clippy::missing_const_for_fn)]
pub fn admitted_tool_names(&self, connector: &str) -> Vec<String> {
let _ = connector;
Vec::new()
}
}
#[cfg(test)]
mod tests;