role_system/
subject.rs

1//! Subject definitions (users, groups, or entities that can have roles).
2
3use std::collections::HashMap;
4use uuid::Uuid;
5
6/// A subject represents an entity that can be assigned roles (user, group, service, etc.).
7#[derive(Debug, Clone, PartialEq, Eq)]
8#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))]
9pub struct Subject {
10    /// Unique identifier for the subject.
11    id: String,
12    /// Type of subject (e.g., "user", "group", "service").
13    subject_type: SubjectType,
14    /// Display name for the subject.
15    display_name: Option<String>,
16    /// Additional attributes for the subject.
17    attributes: HashMap<String, String>,
18}
19
20/// Types of subjects that can be assigned roles.
21#[derive(Debug, Clone, PartialEq, Eq, Hash)]
22#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))]
23pub enum SubjectType {
24    /// A human user.
25    User,
26    /// A group of users.
27    Group,
28    /// A service or application.
29    Service,
30    /// A device or system.
31    Device,
32    /// Custom subject type.
33    Custom(String),
34}
35
36impl Subject {
37    /// Create a new subject with a generated UUID.
38    pub fn new(id: impl Into<String>) -> Self {
39        Self {
40            id: id.into(),
41            subject_type: SubjectType::User,
42            display_name: None,
43            attributes: HashMap::new(),
44        }
45    }
46
47    /// Create a new user subject.
48    pub fn user(id: impl Into<String>) -> Self {
49        Self {
50            id: id.into(),
51            subject_type: SubjectType::User,
52            display_name: None,
53            attributes: HashMap::new(),
54        }
55    }
56
57    /// Create a new group subject.
58    pub fn group(id: impl Into<String>) -> Self {
59        Self {
60            id: id.into(),
61            subject_type: SubjectType::Group,
62            display_name: None,
63            attributes: HashMap::new(),
64        }
65    }
66
67    /// Create a new service subject.
68    pub fn service(id: impl Into<String>) -> Self {
69        Self {
70            id: id.into(),
71            subject_type: SubjectType::Service,
72            display_name: None,
73            attributes: HashMap::new(),
74        }
75    }
76
77    /// Create a new device subject.
78    pub fn device(id: impl Into<String>) -> Self {
79        Self {
80            id: id.into(),
81            subject_type: SubjectType::Device,
82            display_name: None,
83            attributes: HashMap::new(),
84        }
85    }
86
87    /// Create a new custom subject type.
88    pub fn custom(id: impl Into<String>, custom_type: impl Into<String>) -> Self {
89        Self {
90            id: id.into(),
91            subject_type: SubjectType::Custom(custom_type.into()),
92            display_name: None,
93            attributes: HashMap::new(),
94        }
95    }
96
97    /// Get the subject's unique identifier.
98    pub fn id(&self) -> &str {
99        &self.id
100    }
101
102    /// Get the subject's type.
103    pub fn subject_type(&self) -> &SubjectType {
104        &self.subject_type
105    }
106
107    /// Set the display name for the subject.
108    pub fn with_display_name(mut self, display_name: impl Into<String>) -> Self {
109        self.display_name = Some(display_name.into());
110        self
111    }
112
113    /// Get the subject's display name.
114    pub fn display_name(&self) -> Option<&str> {
115        self.display_name.as_deref()
116    }
117
118    /// Set the display name.
119    pub fn set_display_name(&mut self, display_name: impl Into<String>) {
120        self.display_name = Some(display_name.into());
121    }
122
123    /// Add an attribute to the subject.
124    pub fn with_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
125        self.attributes.insert(key.into(), value.into());
126        self
127    }
128
129    /// Set an attribute on the subject.
130    pub fn set_attribute(&mut self, key: impl Into<String>, value: impl Into<String>) {
131        self.attributes.insert(key.into(), value.into());
132    }
133
134    /// Get an attribute value.
135    pub fn attribute(&self, key: &str) -> Option<&str> {
136        self.attributes.get(key).map(|s| s.as_str())
137    }
138
139    /// Get all attributes.
140    pub fn attributes(&self) -> &HashMap<String, String> {
141        &self.attributes
142    }
143
144    /// Remove an attribute.
145    pub fn remove_attribute(&mut self, key: &str) -> Option<String> {
146        self.attributes.remove(key)
147    }
148
149    /// Check if the subject has a specific attribute.
150    pub fn has_attribute(&self, key: &str) -> bool {
151        self.attributes.contains_key(key)
152    }
153
154    /// Get the effective name for display purposes.
155    pub fn effective_name(&self) -> &str {
156        self.display_name.as_deref().unwrap_or(&self.id)
157    }
158}
159
160impl SubjectType {
161    /// Get the string representation of the subject type.
162    pub fn as_str(&self) -> &str {
163        match self {
164            SubjectType::User => "user",
165            SubjectType::Group => "group",
166            SubjectType::Service => "service",
167            SubjectType::Device => "device",
168            SubjectType::Custom(custom) => custom,
169        }
170    }
171
172    /// Check if this is a user subject type.
173    pub fn is_user(&self) -> bool {
174        matches!(self, SubjectType::User)
175    }
176
177    /// Check if this is a group subject type.
178    pub fn is_group(&self) -> bool {
179        matches!(self, SubjectType::Group)
180    }
181
182    /// Check if this is a service subject type.
183    pub fn is_service(&self) -> bool {
184        matches!(self, SubjectType::Service)
185    }
186
187    /// Check if this is a device subject type.
188    pub fn is_device(&self) -> bool {
189        matches!(self, SubjectType::Device)
190    }
191
192    /// Check if this is a custom subject type.
193    pub fn is_custom(&self) -> bool {
194        matches!(self, SubjectType::Custom(_))
195    }
196}
197
198impl std::fmt::Display for SubjectType {
199    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
200        write!(f, "{}", self.as_str())
201    }
202}
203
204impl std::str::FromStr for SubjectType {
205    type Err = ();
206
207    fn from_str(s: &str) -> Result<Self, Self::Err> {
208        match s.to_lowercase().as_str() {
209            "user" => Ok(SubjectType::User),
210            "group" => Ok(SubjectType::Group),
211            "service" => Ok(SubjectType::Service),
212            "device" => Ok(SubjectType::Device),
213            custom => Ok(SubjectType::Custom(custom.to_string())),
214        }
215    }
216}
217
218impl std::fmt::Display for Subject {
219    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
220        match &self.display_name {
221            Some(name) => write!(f, "{} ({}:{})", name, self.subject_type, self.id),
222            None => write!(f, "{}:{}", self.subject_type, self.id),
223        }
224    }
225}
226
227/// Builder for creating subjects with a fluent API.
228#[derive(Debug, Default)]
229pub struct SubjectBuilder {
230    id: Option<String>,
231    subject_type: Option<SubjectType>,
232    display_name: Option<String>,
233    attributes: HashMap<String, String>,
234}
235
236impl SubjectBuilder {
237    /// Create a new subject builder.
238    pub fn new() -> Self {
239        Self::default()
240    }
241
242    /// Set the subject ID.
243    pub fn id(mut self, id: impl Into<String>) -> Self {
244        self.id = Some(id.into());
245        self
246    }
247
248    /// Generate a random UUID for the subject ID.
249    pub fn generate_id(mut self) -> Self {
250        self.id = Some(Uuid::new_v4().to_string());
251        self
252    }
253
254    /// Set the subject type.
255    pub fn subject_type(mut self, subject_type: SubjectType) -> Self {
256        self.subject_type = Some(subject_type);
257        self
258    }
259
260    /// Set as a user subject.
261    pub fn user(mut self) -> Self {
262        self.subject_type = Some(SubjectType::User);
263        self
264    }
265
266    /// Set as a group subject.
267    pub fn group(mut self) -> Self {
268        self.subject_type = Some(SubjectType::Group);
269        self
270    }
271
272    /// Set as a service subject.
273    pub fn service(mut self) -> Self {
274        self.subject_type = Some(SubjectType::Service);
275        self
276    }
277
278    /// Set as a device subject.
279    pub fn device(mut self) -> Self {
280        self.subject_type = Some(SubjectType::Device);
281        self
282    }
283
284    /// Set as a custom subject type.
285    pub fn custom(mut self, custom_type: impl Into<String>) -> Self {
286        self.subject_type = Some(SubjectType::Custom(custom_type.into()));
287        self
288    }
289
290    /// Set the display name.
291    pub fn display_name(mut self, display_name: impl Into<String>) -> Self {
292        self.display_name = Some(display_name.into());
293        self
294    }
295
296    /// Add an attribute.
297    pub fn attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
298        self.attributes.insert(key.into(), value.into());
299        self
300    }
301
302    /// Build the subject.
303    pub fn build(self) -> Result<Subject, String> {
304        let id = self.id.ok_or("Subject ID is required")?;
305        let subject_type = self.subject_type.unwrap_or(SubjectType::User);
306
307        let subject = Subject {
308            id,
309            subject_type,
310            display_name: self.display_name,
311            attributes: self.attributes,
312        };
313
314        Ok(subject)
315    }
316}
317
318#[cfg(test)]
319mod tests {
320    use super::*;
321
322    #[test]
323    fn test_subject_creation() {
324        let subject = Subject::user("user123")
325            .with_display_name("John Doe")
326            .with_attribute("email", "john@example.com")
327            .with_attribute("department", "Engineering");
328
329        assert_eq!(subject.id(), "user123");
330        assert!(subject.subject_type().is_user());
331        assert_eq!(subject.display_name(), Some("John Doe"));
332        assert_eq!(subject.attribute("email"), Some("john@example.com"));
333        assert_eq!(subject.attribute("department"), Some("Engineering"));
334        assert_eq!(subject.effective_name(), "John Doe");
335    }
336
337    #[test]
338    fn test_subject_types() {
339        let user = Subject::user("u1");
340        let group = Subject::group("g1");
341        let service = Subject::service("s1");
342        let device = Subject::device("d1");
343        let custom = Subject::custom("c1", "robot");
344
345        assert!(user.subject_type().is_user());
346        assert!(group.subject_type().is_group());
347        assert!(service.subject_type().is_service());
348        assert!(device.subject_type().is_device());
349        assert!(custom.subject_type().is_custom());
350    }
351
352    #[test]
353    fn test_subject_builder() {
354        let subject = SubjectBuilder::new()
355            .id("test-id")
356            .user()
357            .display_name("Test User")
358            .attribute("role", "developer")
359            .build()
360            .unwrap();
361
362        assert_eq!(subject.id(), "test-id");
363        assert!(subject.subject_type().is_user());
364        assert_eq!(subject.display_name(), Some("Test User"));
365        assert_eq!(subject.attribute("role"), Some("developer"));
366    }
367
368    #[test]
369    fn test_subject_type_parsing() {
370        assert!(matches!("user".parse::<SubjectType>().unwrap(), SubjectType::User));
371        assert!(matches!("group".parse::<SubjectType>().unwrap(), SubjectType::Group));
372        assert!(matches!("service".parse::<SubjectType>().unwrap(), SubjectType::Service));
373        assert!(matches!("device".parse::<SubjectType>().unwrap(), SubjectType::Device));
374        
375        match "custom_type".parse::<SubjectType>().unwrap() {
376            SubjectType::Custom(s) => assert_eq!(s, "custom_type"),
377            _ => panic!("Expected custom type"),
378        }
379    }
380
381    #[test]
382    fn test_effective_name() {
383        let subject_with_name = Subject::user("u1").with_display_name("John");
384        let subject_without_name = Subject::user("u2");
385
386        assert_eq!(subject_with_name.effective_name(), "John");
387        assert_eq!(subject_without_name.effective_name(), "u2");
388    }
389}