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 fn rank(self) -> u8 {
37 match self {
38 Role::User => 2,
39 Role::Staff => 3,
40 Role::Supervisor => 4,
41 Role::Administrator => 5,
42 Role::Developer => 6,
43 }
44 }
45
46 pub fn includes(self, other: Role) -> bool {
48 self.rank() >= other.rank()
49 }
50
51 pub fn as_str(self) -> &'static str {
53 match self {
54 Role::User => "user",
55 Role::Staff => "staff",
56 Role::Supervisor => "supervisor",
57 Role::Administrator => "administrator",
58 Role::Developer => "developer",
59 }
60 }
61
62 pub fn label(self) -> &'static str {
64 match self {
65 Role::User => "User",
66 Role::Staff => "Staff",
67 Role::Supervisor => "Supervisor",
68 Role::Administrator => "Administrator",
69 Role::Developer => "Developer",
70 }
71 }
72
73 pub fn parse(s: &str) -> Result<Self> {
76 match s {
77 "user" => Ok(Role::User),
78 "staff" => Ok(Role::Staff),
79 "supervisor" => Ok(Role::Supervisor),
80 "administrator" => Ok(Role::Administrator),
81 "developer" => Ok(Role::Developer),
82 other => Err(Error::BadRequest(format!("unknown role: {other}"))),
83 }
84 }
85
86 pub fn can_access_panel(self) -> bool {
89 self.rank() >= Role::Staff.rank()
90 }
91
92 pub fn bypasses_group_checks(self) -> bool {
96 matches!(self, Role::Administrator | Role::Developer)
97 }
98}
99
100impl serde::Serialize for Role {
101 fn serialize<S: serde::Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
102 s.serialize_str(self.as_str())
103 }
104}
105
106impl<'de> serde::Deserialize<'de> for Role {
107 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> std::result::Result<Self, D::Error> {
108 let s = <String as serde::Deserialize>::deserialize(d)?;
109 Role::parse(&s).map_err(serde::de::Error::custom)
110 }
111}
112
113impl std::fmt::Display for Role {
114 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
115 f.write_str(self.as_str())
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
125 fn includes_matrix_is_strict_ladder() {
126 let tiers = [
127 Role::User,
128 Role::Staff,
129 Role::Supervisor,
130 Role::Administrator,
131 Role::Developer,
132 ];
133 for (i, &a) in tiers.iter().enumerate() {
134 for (j, &b) in tiers.iter().enumerate() {
135 assert_eq!(
136 a.includes(b),
137 i >= j,
138 "{a:?}.includes({b:?}) should be {}",
139 i >= j
140 );
141 }
142 }
143 }
144
145 #[test]
146 fn parse_round_trips_for_every_variant() {
147 for &r in &[
148 Role::User,
149 Role::Staff,
150 Role::Supervisor,
151 Role::Administrator,
152 Role::Developer,
153 ] {
154 assert_eq!(Role::parse(r.as_str()).unwrap(), r);
155 }
156 }
157
158 #[test]
159 fn parse_rejects_unknown() {
160 assert!(Role::parse("admin").is_err());
161 assert!(Role::parse("root").is_err());
162 assert!(Role::parse("").is_err());
163 }
164
165 #[test]
166 fn can_access_panel_gates_at_staff() {
167 assert!(!Role::User.can_access_panel());
168 assert!(Role::Staff.can_access_panel());
169 assert!(Role::Supervisor.can_access_panel());
170 assert!(Role::Administrator.can_access_panel());
171 assert!(Role::Developer.can_access_panel());
172 }
173
174 #[test]
175 fn bypasses_group_checks_only_admin_and_dev() {
176 assert!(!Role::User.bypasses_group_checks());
177 assert!(!Role::Staff.bypasses_group_checks());
178 assert!(!Role::Supervisor.bypasses_group_checks());
179 assert!(Role::Administrator.bypasses_group_checks());
180 assert!(Role::Developer.bypasses_group_checks());
181 }
182
183 #[test]
184 fn label_is_capitalized_human_form() {
185 assert_eq!(Role::Administrator.label(), "Administrator");
186 assert_eq!(Role::Developer.label(), "Developer");
187 assert_eq!(Role::User.label(), "User");
188 }
189}