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 {
33 subject: Symbol,
34 object: Symbol,
35 field: Symbol,
36 },
37 Or(Box<PolicyCondition>, Box<PolicyCondition>),
39 And(Box<PolicyCondition>, Box<PolicyCondition>),
41}
42
43#[derive(Debug, Clone)]
45pub struct PredicateDef {
46 pub subject_type: Symbol,
48 pub predicate_name: Symbol,
50 pub condition: PolicyCondition,
52}
53
54#[derive(Debug, Clone)]
56pub struct CapabilityDef {
57 pub subject_type: Symbol,
59 pub action: Symbol,
61 pub object_type: Symbol,
63 pub condition: PolicyCondition,
65}
66
67#[derive(Debug, Default, Clone)]
69pub struct PolicyRegistry {
70 predicates: HashMap<Symbol, Vec<PredicateDef>>,
72 capabilities: HashMap<Symbol, Vec<CapabilityDef>>,
74}
75
76impl PolicyRegistry {
77 pub fn new() -> Self {
78 Self::default()
79 }
80
81 pub fn register_predicate(&mut self, def: PredicateDef) {
83 self.predicates
84 .entry(def.subject_type)
85 .or_insert_with(Vec::new)
86 .push(def);
87 }
88
89 pub fn register_capability(&mut self, def: CapabilityDef) {
91 self.capabilities
92 .entry(def.subject_type)
93 .or_insert_with(Vec::new)
94 .push(def);
95 }
96
97 pub fn get_predicates(&self, subject_type: Symbol) -> Option<&[PredicateDef]> {
99 self.predicates.get(&subject_type).map(|v| v.as_slice())
100 }
101
102 pub fn get_capabilities(&self, subject_type: Symbol) -> Option<&[CapabilityDef]> {
104 self.capabilities.get(&subject_type).map(|v| v.as_slice())
105 }
106
107 pub fn has_predicates(&self, subject_type: Symbol) -> bool {
109 self.predicates.contains_key(&subject_type)
110 }
111
112 pub fn has_capabilities(&self, subject_type: Symbol) -> bool {
114 self.capabilities.contains_key(&subject_type)
115 }
116
117 pub fn iter_predicates(&self) -> impl Iterator<Item = (&Symbol, &Vec<PredicateDef>)> {
119 self.predicates.iter()
120 }
121
122 pub fn iter_capabilities(&self) -> impl Iterator<Item = (&Symbol, &Vec<CapabilityDef>)> {
124 self.capabilities.iter()
125 }
126
127 pub fn is_empty(&self) -> bool {
129 self.predicates.is_empty() && self.capabilities.is_empty()
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136 use logicaffeine_base::Interner;
137
138 #[test]
139 fn registry_stores_predicates() {
140 let mut interner = Interner::new();
141 let mut registry = PolicyRegistry::new();
142
143 let user = interner.intern("User");
144 let admin = interner.intern("admin");
145 let role = interner.intern("role");
146 let admin_val = interner.intern("admin");
147
148 let def = PredicateDef {
149 subject_type: user,
150 predicate_name: admin,
151 condition: PolicyCondition::FieldEquals {
152 field: role,
153 value: admin_val,
154 is_string_literal: true,
155 },
156 };
157
158 registry.register_predicate(def);
159
160 assert!(registry.has_predicates(user));
161 let preds = registry.get_predicates(user).unwrap();
162 assert_eq!(preds.len(), 1);
163 assert_eq!(preds[0].predicate_name, admin);
164 }
165
166 #[test]
167 fn registry_stores_capabilities() {
168 let mut interner = Interner::new();
169 let mut registry = PolicyRegistry::new();
170
171 let user = interner.intern("User");
172 let doc = interner.intern("Document");
173 let publish = interner.intern("publish");
174 let admin = interner.intern("admin");
175 let user_var = interner.intern("user");
176
177 let def = CapabilityDef {
178 subject_type: user,
179 action: publish,
180 object_type: doc,
181 condition: PolicyCondition::Predicate {
182 subject: user_var,
183 predicate: admin,
184 },
185 };
186
187 registry.register_capability(def);
188
189 assert!(registry.has_capabilities(user));
190 let caps = registry.get_capabilities(user).unwrap();
191 assert_eq!(caps.len(), 1);
192 assert_eq!(caps[0].action, publish);
193 assert_eq!(caps[0].object_type, doc);
194 }
195}