logicaffeine_language/analysis/
policy.rs1use std::collections::HashMap;
8use logicaffeine_base::Symbol;
9
10#[derive(Debug, Clone)]
13pub enum PolicyCondition {
14 FieldEquals {
16 field: Symbol,
17 value: Symbol,
18 is_string_literal: bool,
20 },
21 FieldBool {
23 field: Symbol,
24 value: bool,
25 },
26 Predicate {
28 subject: Symbol,
29 predicate: Symbol,
30 },
31 ObjectFieldEquals {
34 subject: Symbol,
35 object: Symbol,
36 field: Symbol,
37 },
38 SubjectFieldEqualsObjectField {
41 subject_field: Symbol,
42 object: Symbol,
43 object_field: Symbol,
44 },
45 Or(Box<PolicyCondition>, Box<PolicyCondition>),
47 And(Box<PolicyCondition>, Box<PolicyCondition>),
49}
50
51#[derive(Debug, Clone)]
53pub struct PredicateDef {
54 pub subject_type: Symbol,
56 pub predicate_name: Symbol,
58 pub condition: PolicyCondition,
60}
61
62#[derive(Debug, Clone)]
64pub struct CapabilityDef {
65 pub subject_type: Symbol,
67 pub action: Symbol,
69 pub object_type: Symbol,
71 pub condition: PolicyCondition,
73}
74
75#[derive(Debug, Default, Clone)]
77pub struct PolicyRegistry {
78 predicates: HashMap<Symbol, Vec<PredicateDef>>,
80 capabilities: HashMap<Symbol, Vec<CapabilityDef>>,
82}
83
84impl PolicyRegistry {
85 pub fn new() -> Self {
86 Self::default()
87 }
88
89 pub fn register_predicate(&mut self, def: PredicateDef) {
91 self.predicates
92 .entry(def.subject_type)
93 .or_insert_with(Vec::new)
94 .push(def);
95 }
96
97 pub fn register_capability(&mut self, def: CapabilityDef) {
99 self.capabilities
100 .entry(def.subject_type)
101 .or_insert_with(Vec::new)
102 .push(def);
103 }
104
105 pub fn get_predicates(&self, subject_type: Symbol) -> Option<&[PredicateDef]> {
107 self.predicates.get(&subject_type).map(|v| v.as_slice())
108 }
109
110 pub fn get_capabilities(&self, subject_type: Symbol) -> Option<&[CapabilityDef]> {
112 self.capabilities.get(&subject_type).map(|v| v.as_slice())
113 }
114
115 pub fn has_predicates(&self, subject_type: Symbol) -> bool {
117 self.predicates.contains_key(&subject_type)
118 }
119
120 pub fn has_capabilities(&self, subject_type: Symbol) -> bool {
122 self.capabilities.contains_key(&subject_type)
123 }
124
125 pub fn iter_predicates(&self) -> impl Iterator<Item = (&Symbol, &Vec<PredicateDef>)> {
127 self.predicates.iter()
128 }
129
130 pub fn iter_capabilities(&self) -> impl Iterator<Item = (&Symbol, &Vec<CapabilityDef>)> {
132 self.capabilities.iter()
133 }
134
135 pub fn is_empty(&self) -> bool {
137 self.predicates.is_empty() && self.capabilities.is_empty()
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use logicaffeine_base::Interner;
145
146 #[test]
147 fn registry_stores_predicates() {
148 let mut interner = Interner::new();
149 let mut registry = PolicyRegistry::new();
150
151 let user = interner.intern("User");
152 let admin = interner.intern("admin");
153 let role = interner.intern("role");
154 let admin_val = interner.intern("admin");
155
156 let def = PredicateDef {
157 subject_type: user,
158 predicate_name: admin,
159 condition: PolicyCondition::FieldEquals {
160 field: role,
161 value: admin_val,
162 is_string_literal: true,
163 },
164 };
165
166 registry.register_predicate(def);
167
168 assert!(registry.has_predicates(user));
169 let preds = registry.get_predicates(user).unwrap();
170 assert_eq!(preds.len(), 1);
171 assert_eq!(preds[0].predicate_name, admin);
172 }
173
174 #[test]
175 fn registry_stores_capabilities() {
176 let mut interner = Interner::new();
177 let mut registry = PolicyRegistry::new();
178
179 let user = interner.intern("User");
180 let doc = interner.intern("Document");
181 let publish = interner.intern("publish");
182 let admin = interner.intern("admin");
183 let user_var = interner.intern("user");
184
185 let def = CapabilityDef {
186 subject_type: user,
187 action: publish,
188 object_type: doc,
189 condition: PolicyCondition::Predicate {
190 subject: user_var,
191 predicate: admin,
192 },
193 };
194
195 registry.register_capability(def);
196
197 assert!(registry.has_capabilities(user));
198 let caps = registry.get_capabilities(user).unwrap();
199 assert_eq!(caps.len(), 1);
200 assert_eq!(caps[0].action, publish);
201 assert_eq!(caps[0].object_type, doc);
202 }
203}