1use std::collections::HashMap;
4use uuid::Uuid;
5
6#[derive(Debug, Clone, PartialEq, Eq)]
8#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))]
9pub struct Subject {
10 id: String,
12 subject_type: SubjectType,
14 display_name: Option<String>,
16 attributes: HashMap<String, String>,
18}
19
20#[derive(Debug, Clone, PartialEq, Eq, Hash)]
22#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))]
23pub enum SubjectType {
24 User,
26 Group,
28 Service,
30 Device,
32 Custom(String),
34}
35
36impl Subject {
37 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 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 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 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 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 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 pub fn id(&self) -> &str {
99 &self.id
100 }
101
102 pub fn subject_type(&self) -> &SubjectType {
104 &self.subject_type
105 }
106
107 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 pub fn display_name(&self) -> Option<&str> {
115 self.display_name.as_deref()
116 }
117
118 pub fn set_display_name(&mut self, display_name: impl Into<String>) {
120 self.display_name = Some(display_name.into());
121 }
122
123 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 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 pub fn attribute(&self, key: &str) -> Option<&str> {
136 self.attributes.get(key).map(|s| s.as_str())
137 }
138
139 pub fn attributes(&self) -> &HashMap<String, String> {
141 &self.attributes
142 }
143
144 pub fn remove_attribute(&mut self, key: &str) -> Option<String> {
146 self.attributes.remove(key)
147 }
148
149 pub fn has_attribute(&self, key: &str) -> bool {
151 self.attributes.contains_key(key)
152 }
153
154 pub fn effective_name(&self) -> &str {
156 self.display_name.as_deref().unwrap_or(&self.id)
157 }
158}
159
160impl SubjectType {
161 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 pub fn is_user(&self) -> bool {
174 matches!(self, SubjectType::User)
175 }
176
177 pub fn is_group(&self) -> bool {
179 matches!(self, SubjectType::Group)
180 }
181
182 pub fn is_service(&self) -> bool {
184 matches!(self, SubjectType::Service)
185 }
186
187 pub fn is_device(&self) -> bool {
189 matches!(self, SubjectType::Device)
190 }
191
192 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#[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 pub fn new() -> Self {
239 Self::default()
240 }
241
242 pub fn id(mut self, id: impl Into<String>) -> Self {
244 self.id = Some(id.into());
245 self
246 }
247
248 pub fn generate_id(mut self) -> Self {
250 self.id = Some(Uuid::new_v4().to_string());
251 self
252 }
253
254 pub fn subject_type(mut self, subject_type: SubjectType) -> Self {
256 self.subject_type = Some(subject_type);
257 self
258 }
259
260 pub fn user(mut self) -> Self {
262 self.subject_type = Some(SubjectType::User);
263 self
264 }
265
266 pub fn group(mut self) -> Self {
268 self.subject_type = Some(SubjectType::Group);
269 self
270 }
271
272 pub fn service(mut self) -> Self {
274 self.subject_type = Some(SubjectType::Service);
275 self
276 }
277
278 pub fn device(mut self) -> Self {
280 self.subject_type = Some(SubjectType::Device);
281 self
282 }
283
284 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 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 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 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}