use std::collections::HashMap;
use logicaffeine_base::Symbol;
#[derive(Debug, Clone)]
pub enum PolicyCondition {
FieldEquals {
field: Symbol,
value: Symbol,
is_string_literal: bool,
},
FieldBool {
field: Symbol,
value: bool,
},
Predicate {
subject: Symbol,
predicate: Symbol,
},
ObjectFieldEquals {
subject: Symbol,
object: Symbol,
field: Symbol,
},
SubjectFieldEqualsObjectField {
subject_field: Symbol,
object: Symbol,
object_field: Symbol,
},
Or(Box<PolicyCondition>, Box<PolicyCondition>),
And(Box<PolicyCondition>, Box<PolicyCondition>),
}
#[derive(Debug, Clone)]
pub struct PredicateDef {
pub subject_type: Symbol,
pub predicate_name: Symbol,
pub condition: PolicyCondition,
}
#[derive(Debug, Clone)]
pub struct CapabilityDef {
pub subject_type: Symbol,
pub action: Symbol,
pub object_type: Symbol,
pub condition: PolicyCondition,
}
#[derive(Debug, Default, Clone)]
pub struct PolicyRegistry {
predicates: HashMap<Symbol, Vec<PredicateDef>>,
capabilities: HashMap<Symbol, Vec<CapabilityDef>>,
}
impl PolicyRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register_predicate(&mut self, def: PredicateDef) {
self.predicates
.entry(def.subject_type)
.or_insert_with(Vec::new)
.push(def);
}
pub fn register_capability(&mut self, def: CapabilityDef) {
self.capabilities
.entry(def.subject_type)
.or_insert_with(Vec::new)
.push(def);
}
pub fn get_predicates(&self, subject_type: Symbol) -> Option<&[PredicateDef]> {
self.predicates.get(&subject_type).map(|v| v.as_slice())
}
pub fn get_capabilities(&self, subject_type: Symbol) -> Option<&[CapabilityDef]> {
self.capabilities.get(&subject_type).map(|v| v.as_slice())
}
pub fn has_predicates(&self, subject_type: Symbol) -> bool {
self.predicates.contains_key(&subject_type)
}
pub fn has_capabilities(&self, subject_type: Symbol) -> bool {
self.capabilities.contains_key(&subject_type)
}
pub fn iter_predicates(&self) -> impl Iterator<Item = (&Symbol, &Vec<PredicateDef>)> {
self.predicates.iter()
}
pub fn iter_capabilities(&self) -> impl Iterator<Item = (&Symbol, &Vec<CapabilityDef>)> {
self.capabilities.iter()
}
pub fn is_empty(&self) -> bool {
self.predicates.is_empty() && self.capabilities.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
use logicaffeine_base::Interner;
#[test]
fn registry_stores_predicates() {
let mut interner = Interner::new();
let mut registry = PolicyRegistry::new();
let user = interner.intern("User");
let admin = interner.intern("admin");
let role = interner.intern("role");
let admin_val = interner.intern("admin");
let def = PredicateDef {
subject_type: user,
predicate_name: admin,
condition: PolicyCondition::FieldEquals {
field: role,
value: admin_val,
is_string_literal: true,
},
};
registry.register_predicate(def);
assert!(registry.has_predicates(user));
let preds = registry.get_predicates(user).unwrap();
assert_eq!(preds.len(), 1);
assert_eq!(preds[0].predicate_name, admin);
}
#[test]
fn registry_stores_capabilities() {
let mut interner = Interner::new();
let mut registry = PolicyRegistry::new();
let user = interner.intern("User");
let doc = interner.intern("Document");
let publish = interner.intern("publish");
let admin = interner.intern("admin");
let user_var = interner.intern("user");
let def = CapabilityDef {
subject_type: user,
action: publish,
object_type: doc,
condition: PolicyCondition::Predicate {
subject: user_var,
predicate: admin,
},
};
registry.register_capability(def);
assert!(registry.has_capabilities(user));
let caps = registry.get_capabilities(user).unwrap();
assert_eq!(caps.len(), 1);
assert_eq!(caps[0].action, publish);
assert_eq!(caps[0].object_type, doc);
}
}