revelation_user/permissions.rs
1// SPDX-FileCopyrightText: 2025 Revelation Team
2// SPDX-License-Identifier: MIT
3
4//! Permission-based Role-Based Access Control (RBAC).
5//!
6//! This module provides a professional, production-ready permission system
7//! inspired by AWS IAM, Kubernetes RBAC, and Google Cloud IAM.
8//!
9//! # Architecture
10//!
11//! ```text
12//! ┌─────────────────────────────────────────────────────────────────┐
13//! │ User │
14//! │ │ │
15//! │ ▼ │
16//! │ ┌──────────┐ │
17//! │ │ Role │ ◄─── RUserRole, Custom roles │
18//! │ └────┬─────┘ │
19//! │ │ │
20//! │ ▼ │
21//! │ ┌─────────────────────┐ │
22//! │ │ Permissions │ ◄─── Bitflags (fast) │
23//! │ │ READ | WRITE | ... │ │
24//! │ └─────────────────────┘ │
25//! │ │ │
26//! │ ▼ │
27//! │ ┌─────────────────────┐ │
28//! │ │ Access Decision │ │
29//! │ │ ALLOW / DENY │ │
30//! │ └─────────────────────┘ │
31//! └─────────────────────────────────────────────────────────────────┘
32//! ```
33//!
34//! # Quick Start
35//!
36//! ```rust
37//! use revelation_user::{Permissions, RUserRole, Role};
38//!
39//! // Check permissions on a role
40//! let admin = RUserRole::Admin;
41//! assert!(admin.can(Permissions::DELETE));
42//! assert!(admin.can(Permissions::all()));
43//!
44//! // Combine permissions
45//! let editor_perms = Permissions::READ | Permissions::WRITE;
46//! assert!(editor_perms.contains(Permissions::READ));
47//!
48//! // Check multiple permissions at once
49//! let required = Permissions::READ | Permissions::WRITE;
50//! assert!(admin.permissions().contains(required));
51//! ```
52//!
53//! # Custom Roles
54//!
55//! Implement the [`Role`] trait for custom role types:
56//!
57//! ```rust
58//! use revelation_user::{Permissions, Role};
59//!
60//! #[derive(Debug, Clone, Copy)]
61//! enum CustomRole {
62//! Viewer,
63//! Editor,
64//! Moderator,
65//! SuperAdmin
66//! }
67//!
68//! impl Role for CustomRole {
69//! fn permissions(&self) -> Permissions {
70//! match self {
71//! Self::Viewer => Permissions::READ,
72//! Self::Editor => Permissions::READ | Permissions::WRITE,
73//! Self::Moderator => Permissions::READ | Permissions::WRITE | Permissions::DELETE,
74//! Self::SuperAdmin => Permissions::all()
75//! }
76//! }
77//!
78//! fn name(&self) -> &'static str {
79//! match self {
80//! Self::Viewer => "viewer",
81//! Self::Editor => "editor",
82//! Self::Moderator => "moderator",
83//! Self::SuperAdmin => "super_admin"
84//! }
85//! }
86//! }
87//!
88//! let mod_role = CustomRole::Moderator;
89//! assert!(mod_role.can(Permissions::DELETE));
90//! assert!(!mod_role.can(Permissions::ADMIN));
91//! ```
92//!
93//! # Permission Hierarchy
94//!
95//! | Permission | Bit | Description |
96//! |------------|-----|-------------|
97//! | `READ` | 0x0001 | View resources |
98//! | `WRITE` | 0x0002 | Create and modify resources |
99//! | `DELETE` | 0x0004 | Remove resources |
100//! | `ADMIN` | 0x0008 | Administrative operations |
101//! | `MANAGE_USERS` | 0x0010 | User management |
102//! | `MANAGE_ROLES` | 0x0020 | Role assignment |
103//! | `BILLING` | 0x0040 | Billing and payments |
104//! | `AUDIT` | 0x0080 | View audit logs |
105//! | `EXPORT` | 0x0100 | Export data |
106//! | `IMPORT` | 0x0200 | Import data |
107//! | `API_ACCESS` | 0x0400 | API access |
108//! | `PREMIUM` | 0x0800 | Premium features |
109//!
110//! # Preset Permission Sets
111//!
112//! | Preset | Permissions |
113//! |--------|-------------|
114//! | `VIEWER` | READ |
115//! | `EDITOR` | READ, WRITE |
116//! | `MANAGER` | READ, WRITE, DELETE, MANAGE_USERS |
117//!
118//! Use `Permissions::all()` for full access.
119//!
120//! # Serialization
121//!
122//! Permissions serialize to a numeric value for efficient storage:
123//!
124//! ```rust
125//! use revelation_user::Permissions;
126//!
127//! let perms = Permissions::READ | Permissions::WRITE;
128//! let json = serde_json::to_string(&perms).unwrap();
129//! assert_eq!(json, "3"); // 0b0011
130//!
131//! let restored: Permissions = serde_json::from_str(&json).unwrap();
132//! assert_eq!(perms, restored);
133//! ```
134
135bitflags::bitflags! {
136 /// Bitflag-based permissions for fine-grained access control.
137 ///
138 /// Permissions can be combined using bitwise operators (`|`, `&`, `^`)
139 /// and checked efficiently using the `contains` method.
140 ///
141 /// # Performance
142 ///
143 /// Permission checks are O(1) bitwise operations, making them
144 /// extremely fast for hot paths in request handlers.
145 ///
146 /// # Serialization
147 ///
148 /// - **Serialize**: Always outputs a number (compact for JWT/DB)
149 /// - **Deserialize**: Accepts both number and string formats
150 /// - **Display**: Human-readable format for logs
151 ///
152 /// ```rust
153 /// use revelation_user::Permissions;
154 ///
155 /// let perms = Permissions::READ | Permissions::WRITE;
156 ///
157 /// // Serializes to number
158 /// let json = serde_json::to_string(&perms).unwrap();
159 /// assert_eq!(json, "3");
160 ///
161 /// // Display is human-readable
162 /// assert_eq!(format!("{}", perms), "read, write");
163 /// ```
164 ///
165 /// # Examples
166 ///
167 /// ```rust
168 /// use revelation_user::Permissions;
169 ///
170 /// // Single permission
171 /// let read = Permissions::READ;
172 ///
173 /// // Combined permissions
174 /// let editor = Permissions::READ | Permissions::WRITE;
175 ///
176 /// // Check if permission is present
177 /// assert!(editor.contains(Permissions::READ));
178 /// assert!(!editor.contains(Permissions::DELETE));
179 ///
180 /// // Check multiple permissions
181 /// let required = Permissions::READ | Permissions::WRITE;
182 /// assert!(editor.contains(required));
183 /// ```
184 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
185 pub struct Permissions: u32 {
186 // ═══════════════════════════════════════════════════════════════
187 // Core CRUD Operations (0x000F)
188 // ═══════════════════════════════════════════════════════════════
189
190 /// Read/view resources.
191 ///
192 /// Basic permission for viewing content, user profiles,
193 /// and other read-only operations.
194 const READ = 0x0001;
195
196 /// Create and modify resources.
197 ///
198 /// Allows creating new content and editing existing content
199 /// owned by the user.
200 const WRITE = 0x0002;
201
202 /// Delete resources.
203 ///
204 /// Allows removing content. Usually combined with WRITE.
205 const DELETE = 0x0004;
206
207 /// Administrative operations.
208 ///
209 /// System-level operations, configuration changes,
210 /// and other administrative tasks.
211 const ADMIN = 0x0008;
212
213 // ═══════════════════════════════════════════════════════════════
214 // Management Operations (0x00F0)
215 // ═══════════════════════════════════════════════════════════════
216
217 /// Manage users.
218 ///
219 /// Create, modify, and delete user accounts.
220 /// View user details and activity.
221 const MANAGE_USERS = 0x0010;
222
223 /// Manage roles and permissions.
224 ///
225 /// Assign and revoke roles, modify permission sets.
226 const MANAGE_ROLES = 0x0020;
227
228 /// Billing and payment operations.
229 ///
230 /// View invoices, manage subscriptions, update payment methods.
231 const BILLING = 0x0040;
232
233 /// View audit logs.
234 ///
235 /// Access to security logs, activity history, and audit trails.
236 const AUDIT = 0x0080;
237
238 // ═══════════════════════════════════════════════════════════════
239 // Data Operations (0x0F00)
240 // ═══════════════════════════════════════════════════════════════
241
242 /// Export data.
243 ///
244 /// Download data in various formats (CSV, JSON, etc.).
245 const EXPORT = 0x0100;
246
247 /// Import data.
248 ///
249 /// Upload and process bulk data imports.
250 const IMPORT = 0x0200;
251
252 /// API access.
253 ///
254 /// Access to REST/GraphQL APIs for programmatic access.
255 const API_ACCESS = 0x0400;
256
257 /// Premium features.
258 ///
259 /// Access to premium/paid features.
260 const PREMIUM = 0x0800;
261
262 // ═══════════════════════════════════════════════════════════════
263 // Presets
264 // ═══════════════════════════════════════════════════════════════
265
266 /// Viewer preset: read-only access.
267 const VIEWER = Self::READ.bits();
268
269 /// Editor preset: read and write access.
270 const EDITOR = Self::READ.bits() | Self::WRITE.bits();
271
272 /// Manager preset: full content management.
273 const MANAGER = Self::READ.bits()
274 | Self::WRITE.bits()
275 | Self::DELETE.bits()
276 | Self::MANAGE_USERS.bits();
277 }
278}
279
280// Custom serialization: always serialize as number (compact for JWT)
281impl serde::Serialize for Permissions {
282 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
283 where
284 S: serde::Serializer
285 {
286 serializer.serialize_u32(self.bits())
287 }
288}
289
290// Custom deserialization: accept both number and string
291impl<'de> serde::Deserialize<'de> for Permissions {
292 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
293 where
294 D: serde::Deserializer<'de>
295 {
296 struct PermissionsVisitor;
297
298 impl serde::de::Visitor<'_> for PermissionsVisitor {
299 type Value = Permissions;
300
301 fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
302 formatter.write_str("a number or permission string")
303 }
304
305 fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
306 where
307 E: serde::de::Error
308 {
309 Permissions::from_bits(value as u32)
310 .ok_or_else(|| E::custom(format!("invalid permission bits: {value}")))
311 }
312
313 fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
314 where
315 E: serde::de::Error
316 {
317 if value < 0 {
318 return Err(E::custom("permissions cannot be negative"));
319 }
320 self.visit_u64(value as u64)
321 }
322
323 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
324 where
325 E: serde::de::Error
326 {
327 parse_permissions(value).map_err(E::custom)
328 }
329 }
330
331 deserializer.deserialize_any(PermissionsVisitor)
332 }
333}
334
335/// Parse permissions from a string like "read, write" or "READ | WRITE".
336fn parse_permissions(s: &str) -> Result<Permissions, String> {
337 let mut result = Permissions::empty();
338
339 for part in s.split([',', '|']) {
340 let name = part.trim().to_lowercase();
341 let perm = match name.as_str() {
342 "read" => Permissions::READ,
343 "write" => Permissions::WRITE,
344 "delete" => Permissions::DELETE,
345 "admin" => Permissions::ADMIN,
346 "manage_users" => Permissions::MANAGE_USERS,
347 "manage_roles" => Permissions::MANAGE_ROLES,
348 "billing" => Permissions::BILLING,
349 "audit" => Permissions::AUDIT,
350 "export" => Permissions::EXPORT,
351 "import" => Permissions::IMPORT,
352 "api_access" => Permissions::API_ACCESS,
353 "premium" => Permissions::PREMIUM,
354 "" => continue,
355 _ => return Err(format!("unknown permission: {name}"))
356 };
357 result |= perm;
358 }
359
360 Ok(result)
361}
362
363impl Permissions {
364 /// Check if these permissions satisfy the required permissions.
365 ///
366 /// Returns `true` if all bits in `required` are set in `self`.
367 ///
368 /// # Examples
369 ///
370 /// ```rust
371 /// use revelation_user::Permissions;
372 ///
373 /// let user_perms = Permissions::READ | Permissions::WRITE;
374 /// let required = Permissions::READ;
375 ///
376 /// assert!(user_perms.satisfies(required));
377 /// assert!(!user_perms.satisfies(Permissions::ADMIN));
378 /// ```
379 #[inline]
380 #[must_use]
381 pub const fn satisfies(self, required: Self) -> bool {
382 self.contains(required)
383 }
384
385 /// Create permissions from a raw bits value.
386 ///
387 /// Returns `None` if the bits contain invalid flags.
388 ///
389 /// # Examples
390 ///
391 /// ```rust
392 /// use revelation_user::Permissions;
393 ///
394 /// let perms = Permissions::from_bits_checked(0x0003);
395 /// assert_eq!(perms, Some(Permissions::READ | Permissions::WRITE));
396 ///
397 /// // Invalid bits return None
398 /// let invalid = Permissions::from_bits_checked(0xFFFF_FFFF);
399 /// assert!(invalid.is_none());
400 /// ```
401 #[inline]
402 #[must_use]
403 pub const fn from_bits_checked(bits: u32) -> Option<Self> {
404 Self::from_bits(bits)
405 }
406
407 /// Check if no permissions are set.
408 ///
409 /// # Examples
410 ///
411 /// ```rust
412 /// use revelation_user::Permissions;
413 ///
414 /// assert!(Permissions::empty().is_none());
415 /// assert!(!Permissions::READ.is_none());
416 /// ```
417 #[inline]
418 #[must_use]
419 pub const fn is_none(self) -> bool {
420 self.is_empty()
421 }
422
423 /// Get the raw bits value.
424 ///
425 /// Useful for database storage or serialization.
426 ///
427 /// # Examples
428 ///
429 /// ```rust
430 /// use revelation_user::Permissions;
431 ///
432 /// let perms = Permissions::READ | Permissions::WRITE;
433 /// assert_eq!(perms.as_u32(), 0x0003);
434 /// ```
435 #[inline]
436 #[must_use]
437 pub const fn as_u32(self) -> u32 {
438 self.bits()
439 }
440
441 /// Create permissions from raw bits, truncating invalid bits.
442 ///
443 /// Unlike `from_bits`, this never fails - it simply ignores
444 /// any bits that don't correspond to valid permissions.
445 ///
446 /// # Examples
447 ///
448 /// ```rust
449 /// use revelation_user::Permissions;
450 ///
451 /// // Invalid bits are ignored
452 /// let perms = Permissions::from_bits_truncating(0xFFFF_FFFF);
453 /// assert_eq!(perms, Permissions::all());
454 /// ```
455 #[inline]
456 #[must_use]
457 pub const fn from_bits_truncating(bits: u32) -> Self {
458 Self::from_bits_truncate(bits)
459 }
460}
461
462impl Default for Permissions {
463 /// Default permissions: READ only.
464 ///
465 /// New users get read access by default.
466 #[inline]
467 fn default() -> Self {
468 Self::READ
469 }
470}
471
472impl core::fmt::Display for Permissions {
473 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
474 if self.is_empty() {
475 return write!(f, "none");
476 }
477
478 let mut parts = Vec::new();
479
480 if self.contains(Self::READ) {
481 parts.push("read");
482 }
483 if self.contains(Self::WRITE) {
484 parts.push("write");
485 }
486 if self.contains(Self::DELETE) {
487 parts.push("delete");
488 }
489 if self.contains(Self::ADMIN) {
490 parts.push("admin");
491 }
492 if self.contains(Self::MANAGE_USERS) {
493 parts.push("manage_users");
494 }
495 if self.contains(Self::MANAGE_ROLES) {
496 parts.push("manage_roles");
497 }
498 if self.contains(Self::BILLING) {
499 parts.push("billing");
500 }
501 if self.contains(Self::AUDIT) {
502 parts.push("audit");
503 }
504 if self.contains(Self::EXPORT) {
505 parts.push("export");
506 }
507 if self.contains(Self::IMPORT) {
508 parts.push("import");
509 }
510 if self.contains(Self::API_ACCESS) {
511 parts.push("api_access");
512 }
513 if self.contains(Self::PREMIUM) {
514 parts.push("premium");
515 }
516
517 write!(f, "{}", parts.join(", "))
518 }
519}
520
521/// Trait for types that represent a role with permissions.
522///
523/// Implement this trait for custom role enums to integrate
524/// with the permission system.
525///
526/// # Examples
527///
528/// ```rust
529/// use revelation_user::{Permissions, Role};
530///
531/// #[derive(Debug, Clone, Copy)]
532/// enum AppRole {
533/// Guest,
534/// Member,
535/// Admin
536/// }
537///
538/// impl Role for AppRole {
539/// fn permissions(&self) -> Permissions {
540/// match self {
541/// Self::Guest => Permissions::READ,
542/// Self::Member => Permissions::READ | Permissions::WRITE | Permissions::PREMIUM,
543/// Self::Admin => Permissions::all()
544/// }
545/// }
546///
547/// fn name(&self) -> &'static str {
548/// match self {
549/// Self::Guest => "guest",
550/// Self::Member => "member",
551/// Self::Admin => "admin"
552/// }
553/// }
554/// }
555///
556/// let admin = AppRole::Admin;
557/// assert!(admin.can(Permissions::ADMIN));
558/// assert!(admin.can_all(Permissions::READ | Permissions::WRITE));
559/// ```
560pub trait Role: Send + Sync {
561 /// Get the permissions associated with this role.
562 fn permissions(&self) -> Permissions;
563
564 /// Get the role name for display/logging.
565 fn name(&self) -> &'static str;
566
567 /// Check if this role has the specified permission.
568 ///
569 /// # Examples
570 ///
571 /// ```rust
572 /// use revelation_user::{Permissions, RUserRole, Role};
573 ///
574 /// let admin = RUserRole::Admin;
575 /// assert!(admin.can(Permissions::DELETE));
576 /// ```
577 #[inline]
578 fn can(&self, permission: Permissions) -> bool {
579 self.permissions().contains(permission)
580 }
581
582 /// Check if this role has all the specified permissions.
583 ///
584 /// # Examples
585 ///
586 /// ```rust
587 /// use revelation_user::{Permissions, RUserRole, Role};
588 ///
589 /// let admin = RUserRole::Admin;
590 /// let required = Permissions::READ | Permissions::WRITE | Permissions::DELETE;
591 /// assert!(admin.can_all(required));
592 /// ```
593 #[inline]
594 fn can_all(&self, permissions: Permissions) -> bool {
595 self.permissions().contains(permissions)
596 }
597
598 /// Check if this role has any of the specified permissions.
599 ///
600 /// # Examples
601 ///
602 /// ```rust
603 /// use revelation_user::{Permissions, RUserRole, Role};
604 ///
605 /// let user = RUserRole::User;
606 /// let any_of = Permissions::ADMIN | Permissions::READ;
607 /// assert!(user.can_any(any_of)); // Has READ
608 /// ```
609 #[inline]
610 fn can_any(&self, permissions: Permissions) -> bool {
611 self.permissions().intersects(permissions)
612 }
613
614 /// Check if this role is an admin role.
615 ///
616 /// Default implementation checks for ADMIN permission.
617 #[inline]
618 fn is_admin(&self) -> bool {
619 self.can(Permissions::ADMIN)
620 }
621
622 /// Check if this role has premium access.
623 ///
624 /// Default implementation checks for PREMIUM permission.
625 #[inline]
626 fn is_premium(&self) -> bool {
627 self.can(Permissions::PREMIUM)
628 }
629}
630
631#[cfg(test)]
632mod tests {
633 use super::*;
634
635 #[test]
636 fn permissions_bitwise_operations() {
637 let read_write = Permissions::READ | Permissions::WRITE;
638 assert!(read_write.contains(Permissions::READ));
639 assert!(read_write.contains(Permissions::WRITE));
640 assert!(!read_write.contains(Permissions::DELETE));
641 }
642
643 #[test]
644 fn permissions_satisfies() {
645 let perms = Permissions::READ | Permissions::WRITE | Permissions::DELETE;
646 assert!(perms.satisfies(Permissions::READ));
647 assert!(perms.satisfies(Permissions::READ | Permissions::WRITE));
648 assert!(!perms.satisfies(Permissions::ADMIN));
649 }
650
651 #[test]
652 fn permissions_from_bits_checked() {
653 let valid = Permissions::from_bits_checked(0x0003);
654 assert_eq!(valid, Some(Permissions::READ | Permissions::WRITE));
655
656 let invalid = Permissions::from_bits_checked(0xFFFF_0000);
657 assert!(invalid.is_none());
658 }
659
660 #[test]
661 fn permissions_from_bits_truncating() {
662 let perms = Permissions::from_bits_truncating(0xFFFF_FFFF);
663 assert_eq!(perms, Permissions::all());
664 }
665
666 #[test]
667 fn permissions_is_none() {
668 assert!(Permissions::empty().is_none());
669 assert!(!Permissions::READ.is_none());
670 }
671
672 #[test]
673 fn permissions_as_u32() {
674 let perms = Permissions::READ | Permissions::WRITE;
675 assert_eq!(perms.as_u32(), 0x0003);
676 }
677
678 #[test]
679 fn permissions_default() {
680 assert_eq!(Permissions::default(), Permissions::READ);
681 }
682
683 #[test]
684 fn permissions_display_empty() {
685 assert_eq!(format!("{}", Permissions::empty()), "none");
686 }
687
688 #[test]
689 fn permissions_display_single() {
690 assert_eq!(format!("{}", Permissions::READ), "read");
691 assert_eq!(format!("{}", Permissions::ADMIN), "admin");
692 }
693
694 #[test]
695 fn permissions_display_multiple() {
696 let perms = Permissions::READ | Permissions::WRITE;
697 assert_eq!(format!("{}", perms), "read, write");
698 }
699
700 #[test]
701 fn permissions_display_all() {
702 let display = format!("{}", Permissions::all());
703 assert!(display.contains("read"));
704 assert!(display.contains("admin"));
705 assert!(display.contains("premium"));
706 }
707
708 #[test]
709 fn permissions_serializes_as_number() {
710 let perms = Permissions::READ | Permissions::WRITE;
711 let json = serde_json::to_string(&perms).unwrap();
712 assert_eq!(json, "3");
713 }
714
715 #[test]
716 fn permissions_deserializes_from_number() {
717 let perms: Permissions = serde_json::from_str("3").unwrap();
718 assert_eq!(perms, Permissions::READ | Permissions::WRITE);
719 }
720
721 #[test]
722 fn permissions_deserializes_from_string() {
723 let perms: Permissions = serde_json::from_str("\"read, write\"").unwrap();
724 assert_eq!(perms, Permissions::READ | Permissions::WRITE);
725 }
726
727 #[test]
728 fn permissions_deserializes_from_pipe_string() {
729 let perms: Permissions = serde_json::from_str("\"READ | WRITE\"").unwrap();
730 assert_eq!(perms, Permissions::READ | Permissions::WRITE);
731 }
732
733 #[test]
734 fn permissions_roundtrip() {
735 let original = Permissions::READ | Permissions::WRITE | Permissions::DELETE;
736 let json = serde_json::to_string(&original).unwrap();
737 let restored: Permissions = serde_json::from_str(&json).unwrap();
738 assert_eq!(original, restored);
739 }
740
741 #[test]
742 fn permissions_deserialize_all_names() {
743 let perms: Permissions =
744 serde_json::from_str("\"admin, manage_users, manage_roles, billing, audit\"").unwrap();
745 assert!(perms.contains(Permissions::ADMIN));
746 assert!(perms.contains(Permissions::MANAGE_USERS));
747 assert!(perms.contains(Permissions::MANAGE_ROLES));
748 assert!(perms.contains(Permissions::BILLING));
749 assert!(perms.contains(Permissions::AUDIT));
750
751 let perms2: Permissions =
752 serde_json::from_str("\"export, import, api_access, premium, delete\"").unwrap();
753 assert!(perms2.contains(Permissions::EXPORT));
754 assert!(perms2.contains(Permissions::IMPORT));
755 assert!(perms2.contains(Permissions::API_ACCESS));
756 assert!(perms2.contains(Permissions::PREMIUM));
757 assert!(perms2.contains(Permissions::DELETE));
758 }
759
760 #[test]
761 fn permissions_deserialize_empty_string() {
762 let perms: Permissions = serde_json::from_str("\"\"").unwrap();
763 assert!(perms.is_empty());
764 }
765
766 #[test]
767 fn permissions_deserialize_invalid_name() {
768 let result: Result<Permissions, _> = serde_json::from_str("\"invalid_perm\"");
769 assert!(result.is_err());
770 }
771
772 #[test]
773 fn permissions_deserialize_negative_number() {
774 let result: Result<Permissions, _> = serde_json::from_str("-1");
775 assert!(result.is_err());
776 }
777
778 #[test]
779 fn permissions_deserialize_invalid_bits() {
780 let result: Result<Permissions, _> = serde_json::from_str("4294967295");
781 assert!(result.is_err());
782 }
783
784 #[test]
785 fn permissions_deserialize_from_i64() {
786 // Test positive i64 path
787 let perms: Permissions = serde_json::from_value(serde_json::json!(3i64)).unwrap();
788 assert_eq!(perms, Permissions::READ | Permissions::WRITE);
789 }
790
791 #[test]
792 fn parse_permissions_with_write() {
793 let perms = super::parse_permissions("write").unwrap();
794 assert_eq!(perms, Permissions::WRITE);
795 }
796
797 #[test]
798 fn permissions_presets() {
799 assert_eq!(Permissions::VIEWER, Permissions::READ);
800 assert_eq!(Permissions::EDITOR, Permissions::READ | Permissions::WRITE);
801 assert!(Permissions::MANAGER.contains(Permissions::MANAGE_USERS));
802 assert!(Permissions::MANAGER.contains(Permissions::DELETE));
803 }
804
805 #[derive(Debug, Clone, Copy)]
806 enum TestRole {
807 Guest,
808 Member,
809 Admin
810 }
811
812 impl Role for TestRole {
813 fn permissions(&self) -> Permissions {
814 match self {
815 Self::Guest => Permissions::READ,
816 Self::Member => Permissions::READ | Permissions::WRITE | Permissions::PREMIUM,
817 Self::Admin => Permissions::all()
818 }
819 }
820
821 fn name(&self) -> &'static str {
822 match self {
823 Self::Guest => "guest",
824 Self::Member => "member",
825 Self::Admin => "admin"
826 }
827 }
828 }
829
830 #[test]
831 fn role_trait_can() {
832 let admin = TestRole::Admin;
833 assert!(admin.can(Permissions::DELETE));
834 assert!(admin.can(Permissions::ADMIN));
835
836 let guest = TestRole::Guest;
837 assert!(guest.can(Permissions::READ));
838 assert!(!guest.can(Permissions::WRITE));
839 }
840
841 #[test]
842 fn role_trait_can_all() {
843 let admin = TestRole::Admin;
844 let required = Permissions::READ | Permissions::WRITE | Permissions::DELETE;
845 assert!(admin.can_all(required));
846
847 let guest = TestRole::Guest;
848 assert!(!guest.can_all(required));
849 }
850
851 #[test]
852 fn role_trait_can_any() {
853 let guest = TestRole::Guest;
854 let any_of = Permissions::ADMIN | Permissions::READ;
855 assert!(guest.can_any(any_of));
856
857 let no_match = Permissions::ADMIN | Permissions::DELETE;
858 assert!(!guest.can_any(no_match));
859 }
860
861 #[test]
862 fn role_trait_is_admin() {
863 assert!(TestRole::Admin.is_admin());
864 assert!(!TestRole::Member.is_admin());
865 assert!(!TestRole::Guest.is_admin());
866 }
867
868 #[test]
869 fn role_trait_is_premium() {
870 assert!(TestRole::Admin.is_premium());
871 assert!(TestRole::Member.is_premium());
872 assert!(!TestRole::Guest.is_premium());
873 }
874
875 #[test]
876 fn role_trait_name() {
877 assert_eq!(TestRole::Guest.name(), "guest");
878 assert_eq!(TestRole::Member.name(), "member");
879 assert_eq!(TestRole::Admin.name(), "admin");
880 }
881
882 #[test]
883 fn permissions_deserialize_unexpected_type_triggers_expecting() {
884 // Boolean triggers the expecting() method for a better error message
885 let result: Result<Permissions, _> = serde_json::from_str("true");
886 assert!(result.is_err());
887 let err = result.unwrap_err().to_string();
888 assert!(err.contains("number") || err.contains("string"));
889 }
890
891 #[test]
892 fn permissions_deserialize_negative_i64_via_value() {
893 // Use serde_json::Value to ensure we go through visit_i64 path
894 use serde::Deserialize;
895 let negative = serde_json::json!(-42);
896 let result = Permissions::deserialize(&negative);
897 assert!(result.is_err());
898 let err = result.unwrap_err().to_string();
899 assert!(err.contains("negative"));
900 }
901
902 #[test]
903 fn permissions_deserialize_positive_i64_via_serde_test() {
904 // serde_test::Token::I64 forces the visit_i64 path with positive value
905 use serde_test::{Token, assert_de_tokens};
906 assert_de_tokens(&Permissions::READ, &[Token::I64(1)]);
907 assert_de_tokens(&(Permissions::READ | Permissions::WRITE), &[Token::I64(3)]);
908 // Zero i64 path
909 assert_de_tokens(&Permissions::empty(), &[Token::I64(0)]);
910 }
911}