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