Skip to main content

oxihuman_core/
role_map.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Role-based permission map: entities carry named roles.
6
7use std::collections::{HashMap, HashSet};
8
9/// Assigns roles to entity IDs and checks permissions.
10pub 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}