oxihuman_core/
role_map.rs1#![allow(dead_code)]
4
5use std::collections::{HashMap, HashSet};
8
9pub struct RoleMap {
11 entity_roles: HashMap<u64, HashSet<String>>,
12 role_perms: HashMap<String, HashSet<String>>,
13}
14
15#[allow(dead_code)]
16impl RoleMap {
17 pub fn new() -> Self {
18 RoleMap {
19 entity_roles: HashMap::new(),
20 role_perms: HashMap::new(),
21 }
22 }
23
24 pub fn assign_role(&mut self, entity: u64, role: &str) {
25 self.entity_roles
26 .entry(entity)
27 .or_default()
28 .insert(role.to_string());
29 }
30
31 pub fn remove_role(&mut self, entity: u64, role: &str) -> bool {
32 if let Some(roles) = self.entity_roles.get_mut(&entity) {
33 roles.remove(role)
34 } else {
35 false
36 }
37 }
38
39 pub fn has_role(&self, entity: u64, role: &str) -> bool {
40 self.entity_roles
41 .get(&entity)
42 .is_some_and(|r| r.contains(role))
43 }
44
45 pub fn roles_of(&self, entity: u64) -> Vec<String> {
46 self.entity_roles
47 .get(&entity)
48 .map(|r| r.iter().cloned().collect())
49 .unwrap_or_default()
50 }
51
52 pub fn define_permission(&mut self, role: &str, perm: &str) {
53 self.role_perms
54 .entry(role.to_string())
55 .or_default()
56 .insert(perm.to_string());
57 }
58
59 pub fn has_permission(&self, entity: u64, perm: &str) -> bool {
60 if let Some(roles) = self.entity_roles.get(&entity) {
61 roles.iter().any(|r| {
62 self.role_perms
63 .get(r)
64 .is_some_and(|perms| perms.contains(perm))
65 })
66 } else {
67 false
68 }
69 }
70
71 pub fn entity_count(&self) -> usize {
72 self.entity_roles.len()
73 }
74
75 pub fn role_count(&self) -> usize {
76 let mut all: HashSet<&str> = HashSet::new();
77 for roles in self.entity_roles.values() {
78 for r in roles {
79 all.insert(r.as_str());
80 }
81 }
82 all.len()
83 }
84
85 pub fn entities_with_role(&self, role: &str) -> Vec<u64> {
86 self.entity_roles
87 .iter()
88 .filter(|(_, roles)| roles.contains(role))
89 .map(|(id, _)| *id)
90 .collect()
91 }
92
93 pub fn clear_entity(&mut self, entity: u64) {
94 self.entity_roles.remove(&entity);
95 }
96
97 pub fn clear(&mut self) {
98 self.entity_roles.clear();
99 self.role_perms.clear();
100 }
101}
102
103impl Default for RoleMap {
104 fn default() -> Self {
105 Self::new()
106 }
107}
108
109pub fn new_role_map() -> RoleMap {
110 RoleMap::new()
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn assign_and_has_role() {
119 let mut m = new_role_map();
120 m.assign_role(1, "admin");
121 assert!(m.has_role(1, "admin"));
122 assert!(!m.has_role(1, "guest"));
123 }
124
125 #[test]
126 fn remove_role() {
127 let mut m = new_role_map();
128 m.assign_role(1, "admin");
129 assert!(m.remove_role(1, "admin"));
130 assert!(!m.has_role(1, "admin"));
131 }
132
133 #[test]
134 fn roles_of_entity() {
135 let mut m = new_role_map();
136 m.assign_role(1, "admin");
137 m.assign_role(1, "editor");
138 let roles = m.roles_of(1);
139 assert_eq!(roles.len(), 2);
140 }
141
142 #[test]
143 fn permissions() {
144 let mut m = new_role_map();
145 m.assign_role(1, "editor");
146 m.define_permission("editor", "write");
147 assert!(m.has_permission(1, "write"));
148 assert!(!m.has_permission(1, "delete"));
149 }
150
151 #[test]
152 fn entities_with_role() {
153 let mut m = new_role_map();
154 m.assign_role(1, "admin");
155 m.assign_role(2, "admin");
156 m.assign_role(3, "user");
157 let admins = m.entities_with_role("admin");
158 assert_eq!(admins.len(), 2);
159 }
160
161 #[test]
162 fn entity_count() {
163 let mut m = new_role_map();
164 m.assign_role(1, "a");
165 m.assign_role(2, "b");
166 assert_eq!(m.entity_count(), 2);
167 }
168
169 #[test]
170 fn clear_entity() {
171 let mut m = new_role_map();
172 m.assign_role(1, "admin");
173 m.clear_entity(1);
174 assert!(!m.has_role(1, "admin"));
175 }
176
177 #[test]
178 fn clear_all() {
179 let mut m = new_role_map();
180 m.assign_role(1, "admin");
181 m.clear();
182 assert_eq!(m.entity_count(), 0);
183 }
184
185 #[test]
186 fn permission_via_multiple_roles() {
187 let mut m = new_role_map();
188 m.assign_role(1, "viewer");
189 m.define_permission("viewer", "read");
190 assert!(m.has_permission(1, "read"));
191 }
192}