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 std::fmt::Display;
55use std::str::FromStr;
56
57use serde::{Deserialize, Serialize};
58
59/// A group identifier used for exact membership checks.
60///
61/// Groups are the non-hierarchical companion to roles. Use them when access is
62/// based on belonging to something, such as a department, project, tenant, or
63/// on-call rotation.
64///
65/// # Example
66/// ```rust
67/// use webgates_core::groups::Group;
68///
69/// let engineering = Group::new("engineering");
70/// let backend_team = Group::new("backend-team");
71///
72/// assert_eq!(engineering.name(), "engineering");
73/// assert_eq!(backend_team.name(), "backend-team");
74/// ```
75#[derive(Eq, PartialEq, Debug, Serialize, Deserialize, Clone)]
76#[serde(transparent)]
77pub struct Group(String);
78
79impl Group {
80    /// Creates a new group from its stable name.
81    ///
82    /// The name should be application-defined and stable over time.
83    ///
84    /// # Parameters
85    /// - `group`: Group identifier such as `"engineering"` or `"project-alpha"`.
86    ///
87    /// # Example
88    /// ```rust
89    /// use webgates_core::groups::Group;
90    ///
91    /// let engineering = Group::new("engineering");
92    /// let project_team = Group::new("project-alpha-team");
93    ///
94    /// assert_eq!(engineering.name(), "engineering");
95    /// assert_eq!(project_team.name(), "project-alpha-team");
96    /// ```
97    pub fn new(group: &str) -> Self {
98        Self(group.to_string())
99    }
100
101    /// Returns the stable group identifier.
102    ///
103    /// # Example
104    /// ```rust
105    /// use webgates_core::groups::Group;
106    ///
107    /// let group = Group::new("engineering");
108    ///
109    /// assert_eq!(group.name(), "engineering");
110    /// ```
111    pub fn name(&self) -> &str {
112        &self.0
113    }
114}
115
116impl FromStr for Group {
117    type Err = String;
118    fn from_str(s: &str) -> Result<Self, Self::Err> {
119        Ok(Group::new(s))
120    }
121}
122
123impl Display for Group {
124    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125        write!(f, "{}", self.name())
126    }
127}
128
129/// Trait for types that expose a stable group identifier.
130///
131/// Implement this for your own group types when infrastructure code needs a
132/// canonical string identifier but you do not want to use the built-in [`Group`]
133/// type directly.
134pub trait GroupEntity {
135    /// Returns the unique identifier for this group as `&str`.
136    fn group_id(&self) -> &str;
137}
138
139/// Allows the built-in [`Group`] type to be used wherever a [`GroupEntity`]
140/// is required.
141impl GroupEntity for Group {
142    fn group_id(&self) -> &str {
143        self.name()
144    }
145}