Skip to main content

webgates_core/
groups.rs

1//! Group identifiers for group-based authorization decisions.
2//!
3//! Groups model exact membership such as departments, teams, tenants, projects,
4//! or other organizational units. Unlike roles, groups do not imply privilege
5//! ordering. A user is either a member of the group or not.
6//!
7//! # Examples
8//!
9//! Create groups and use them in access policies:
10//!
11//! ```rust
12//! use webgates_core::authz::access_policy::AccessPolicy;
13//! use webgates_core::groups::Group;
14//! use webgates_core::roles::Role;
15//!
16//! let engineering = Group::new("engineering");
17//! let marketing = Group::new("marketing");
18//!
19//! let policy = AccessPolicy::<Role, Group>::require_group(engineering.clone())
20//!     .or_require_group(marketing.clone());
21//!
22//! assert_eq!(engineering.name(), "engineering");
23//! assert_eq!(marketing.name(), "marketing");
24//! assert!(!policy.denies_all());
25//! ```
26//!
27//! Common naming patterns:
28//!
29//! ```rust
30//! use webgates_core::groups::Group;
31//!
32//! let departments = vec![
33//!     Group::new("engineering"),
34//!     Group::new("marketing"),
35//!     Group::new("support"),
36//! ];
37//!
38//! let project_groups = vec![
39//!     Group::new("project-alpha"),
40//!     Group::new("project-beta"),
41//! ];
42//!
43//! let teams = vec![
44//!     Group::new("frontend-team"),
45//!     Group::new("backend-team"),
46//!     Group::new("qa-team"),
47//! ];
48//!
49//! assert_eq!(departments.len(), 3);
50//! assert_eq!(project_groups.len(), 2);
51//! assert_eq!(teams.len(), 3);
52//! ```
53
54use serde::{Deserialize, Serialize};
55
56/// A group identifier used for exact membership checks.
57///
58/// Groups are the non-hierarchical companion to roles. Use them when access is
59/// based on belonging to something, such as a department, project, tenant, or
60/// on-call rotation.
61///
62/// # Example
63/// ```rust
64/// use webgates_core::groups::Group;
65///
66/// let engineering = Group::new("engineering");
67/// let backend_team = Group::new("backend-team");
68///
69/// assert_eq!(engineering.name(), "engineering");
70/// assert_eq!(backend_team.name(), "backend-team");
71/// ```
72#[derive(Eq, PartialEq, Debug, Serialize, Deserialize, Clone)]
73#[serde(transparent)]
74pub struct Group(String);
75
76impl Group {
77    /// Creates a new group from its stable name.
78    ///
79    /// The name should be application-defined and stable over time.
80    ///
81    /// # Parameters
82    /// - `group`: Group identifier such as `"engineering"` or `"project-alpha"`.
83    ///
84    /// # Example
85    /// ```rust
86    /// use webgates_core::groups::Group;
87    ///
88    /// let engineering = Group::new("engineering");
89    /// let project_team = Group::new("project-alpha-team");
90    ///
91    /// assert_eq!(engineering.name(), "engineering");
92    /// assert_eq!(project_team.name(), "project-alpha-team");
93    /// ```
94    pub fn new(group: &str) -> Self {
95        Self(group.to_string())
96    }
97
98    /// Returns the stable group identifier.
99    ///
100    /// # Example
101    /// ```rust
102    /// use webgates_core::groups::Group;
103    ///
104    /// let group = Group::new("engineering");
105    ///
106    /// assert_eq!(group.name(), "engineering");
107    /// ```
108    pub fn name(&self) -> &str {
109        &self.0
110    }
111}
112
113/// Trait for types that expose a stable group identifier.
114///
115/// Implement this for your own group types when infrastructure code needs a
116/// canonical string identifier but you do not want to use the built-in [`Group`]
117/// type directly.
118pub trait GroupEntity {
119    /// Returns the unique identifier for this group as `&str`.
120    fn group_id(&self) -> &str;
121}
122
123/// Allows the built-in [`Group`] type to be used wherever a [`GroupEntity`]
124/// is required.
125impl GroupEntity for Group {
126    fn group_id(&self) -> &str {
127        self.name()
128    }
129}