rustio_admin/auth/
role.rs1use crate::error::{Error, Result};
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
25pub enum Role {
26 User,
27 Staff,
28 Supervisor,
29 Administrator,
30 Developer,
31}
32
33impl Role {
34 pub const fn rank(self) -> u32 {
41 match self {
42 Role::User => 100,
43 Role::Staff => 300,
44 Role::Supervisor => 600,
45 Role::Administrator => 900,
46 Role::Developer => 1000,
47 }
48 }
49
50 pub fn includes(self, other: Role) -> bool {
52 self.rank() >= other.rank()
53 }
54
55 pub fn as_str(self) -> &'static str {
57 match self {
58 Role::User => "user",
59 Role::Staff => "staff",
60 Role::Supervisor => "supervisor",
61 Role::Administrator => "administrator",
62 Role::Developer => "developer",
63 }
64 }
65
66 pub fn label(self) -> &'static str {
68 match self {
69 Role::User => "User",
70 Role::Staff => "Staff",
71 Role::Supervisor => "Supervisor",
72 Role::Administrator => "Administrator",
73 Role::Developer => "Developer",
74 }
75 }
76
77 pub fn parse(s: &str) -> Result<Self> {
80 match s {
81 "user" => Ok(Role::User),
82 "staff" => Ok(Role::Staff),
83 "supervisor" => Ok(Role::Supervisor),
84 "administrator" => Ok(Role::Administrator),
85 "developer" => Ok(Role::Developer),
86 other => Err(Error::BadRequest(format!("unknown role: {other}"))),
87 }
88 }
89
90 pub fn can_access_panel(self) -> bool {
93 self.rank() >= Role::Staff.rank()
94 }
95
96 pub fn bypasses_group_checks(self) -> bool {
100 matches!(self, Role::Administrator | Role::Developer)
101 }
102}
103
104pub const fn protected_roles() -> &'static [Role] {
117 &[Role::Administrator, Role::Developer]
118}
119
120impl serde::Serialize for Role {
121 fn serialize<S: serde::Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
122 s.serialize_str(self.as_str())
123 }
124}
125
126impl<'de> serde::Deserialize<'de> for Role {
127 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> std::result::Result<Self, D::Error> {
128 let s = <String as serde::Deserialize>::deserialize(d)?;
129 Role::parse(&s).map_err(serde::de::Error::custom)
130 }
131}
132
133impl std::fmt::Display for Role {
134 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
135 f.write_str(self.as_str())
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
145 fn includes_matrix_is_strict_ladder() {
146 let tiers = [
147 Role::User,
148 Role::Staff,
149 Role::Supervisor,
150 Role::Administrator,
151 Role::Developer,
152 ];
153 for (i, &a) in tiers.iter().enumerate() {
154 for (j, &b) in tiers.iter().enumerate() {
155 assert_eq!(
156 a.includes(b),
157 i >= j,
158 "{a:?}.includes({b:?}) should be {}",
159 i >= j
160 );
161 }
162 }
163 }
164
165 #[test]
166 fn parse_round_trips_for_every_variant() {
167 for &r in &[
168 Role::User,
169 Role::Staff,
170 Role::Supervisor,
171 Role::Administrator,
172 Role::Developer,
173 ] {
174 assert_eq!(Role::parse(r.as_str()).unwrap(), r);
175 }
176 }
177
178 #[test]
179 fn parse_rejects_unknown() {
180 assert!(Role::parse("admin").is_err());
181 assert!(Role::parse("root").is_err());
182 assert!(Role::parse("").is_err());
183 }
184
185 #[test]
186 fn can_access_panel_gates_at_staff() {
187 assert!(!Role::User.can_access_panel());
188 assert!(Role::Staff.can_access_panel());
189 assert!(Role::Supervisor.can_access_panel());
190 assert!(Role::Administrator.can_access_panel());
191 assert!(Role::Developer.can_access_panel());
192 }
193
194 #[test]
195 fn bypasses_group_checks_only_admin_and_dev() {
196 assert!(!Role::User.bypasses_group_checks());
197 assert!(!Role::Staff.bypasses_group_checks());
198 assert!(!Role::Supervisor.bypasses_group_checks());
199 assert!(Role::Administrator.bypasses_group_checks());
200 assert!(Role::Developer.bypasses_group_checks());
201 }
202
203 #[test]
204 fn label_is_capitalized_human_form() {
205 assert_eq!(Role::Administrator.label(), "Administrator");
206 assert_eq!(Role::Developer.label(), "Developer");
207 assert_eq!(Role::User.label(), "User");
208 }
209}