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}