communitas_core/permissions/
access_level.rs1use serde::{Deserialize, Serialize};
4use std::cmp::Ordering;
5use std::str::FromStr;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
15#[serde(rename_all = "snake_case")]
16pub enum AccessLevel {
17 NotVisible,
22
23 ReadOnly,
35
36 Edit,
42}
43
44impl Default for AccessLevel {
45 fn default() -> Self {
47 AccessLevel::NotVisible
48 }
49}
50
51impl AccessLevel {
52 pub fn allows(self, required: AccessLevel) -> bool {
67 self >= required
68 }
69
70 pub fn can_view(self) -> bool {
74 matches!(self, AccessLevel::ReadOnly | AccessLevel::Edit)
75 }
76
77 pub fn can_edit(self) -> bool {
81 matches!(self, AccessLevel::Edit)
82 }
83
84 fn rank(self) -> u8 {
86 match self {
87 AccessLevel::NotVisible => 0,
88 AccessLevel::ReadOnly => 1,
89 AccessLevel::Edit => 2,
90 }
91 }
92
93 pub fn as_str(self) -> &'static str {
95 match self {
96 AccessLevel::NotVisible => "not_visible",
97 AccessLevel::ReadOnly => "read_only",
98 AccessLevel::Edit => "edit",
99 }
100 }
101}
102
103#[derive(Debug, Clone, PartialEq, Eq)]
105pub struct ParseAccessLevelError {
106 pub invalid_value: String,
108}
109
110impl std::fmt::Display for ParseAccessLevelError {
111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112 write!(
113 f,
114 "invalid access level '{}': expected 'not_visible', 'read_only', or 'edit'",
115 self.invalid_value
116 )
117 }
118}
119
120impl std::error::Error for ParseAccessLevelError {}
121
122impl FromStr for AccessLevel {
123 type Err = ParseAccessLevelError;
124
125 fn from_str(s: &str) -> Result<Self, Self::Err> {
129 match s.to_lowercase().as_str() {
130 "not_visible" | "notvisible" | "hidden" => Ok(AccessLevel::NotVisible),
131 "read_only" | "readonly" | "read" | "view" => Ok(AccessLevel::ReadOnly),
132 "edit" | "write" | "full" => Ok(AccessLevel::Edit),
133 _ => Err(ParseAccessLevelError {
134 invalid_value: s.to_string(),
135 }),
136 }
137 }
138}
139
140impl PartialOrd for AccessLevel {
141 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
142 Some(self.cmp(other))
143 }
144}
145
146impl Ord for AccessLevel {
147 fn cmp(&self, other: &Self) -> Ordering {
148 self.rank().cmp(&other.rank())
149 }
150}
151
152impl std::fmt::Display for AccessLevel {
153 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154 write!(f, "{}", self.as_str())
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161
162 #[test]
163 fn test_access_level_ordering() {
164 assert!(AccessLevel::NotVisible < AccessLevel::ReadOnly);
165 assert!(AccessLevel::ReadOnly < AccessLevel::Edit);
166 assert!(AccessLevel::NotVisible < AccessLevel::Edit);
167 }
168
169 #[test]
170 fn test_allows() {
171 assert!(AccessLevel::Edit.allows(AccessLevel::Edit));
173 assert!(AccessLevel::Edit.allows(AccessLevel::ReadOnly));
174 assert!(AccessLevel::Edit.allows(AccessLevel::NotVisible));
175
176 assert!(AccessLevel::ReadOnly.allows(AccessLevel::ReadOnly));
178 assert!(AccessLevel::ReadOnly.allows(AccessLevel::NotVisible));
179 assert!(!AccessLevel::ReadOnly.allows(AccessLevel::Edit));
180
181 assert!(AccessLevel::NotVisible.allows(AccessLevel::NotVisible));
183 assert!(!AccessLevel::NotVisible.allows(AccessLevel::ReadOnly));
184 assert!(!AccessLevel::NotVisible.allows(AccessLevel::Edit));
185 }
186
187 #[test]
188 fn test_can_view_and_edit() {
189 assert!(!AccessLevel::NotVisible.can_view());
190 assert!(!AccessLevel::NotVisible.can_edit());
191
192 assert!(AccessLevel::ReadOnly.can_view());
193 assert!(!AccessLevel::ReadOnly.can_edit());
194
195 assert!(AccessLevel::Edit.can_view());
196 assert!(AccessLevel::Edit.can_edit());
197 }
198
199 #[test]
200 fn test_from_str() {
201 assert_eq!(
202 "not_visible".parse::<AccessLevel>().unwrap(),
203 AccessLevel::NotVisible
204 );
205 assert_eq!(
206 "read_only".parse::<AccessLevel>().unwrap(),
207 AccessLevel::ReadOnly
208 );
209 assert_eq!("edit".parse::<AccessLevel>().unwrap(), AccessLevel::Edit);
210 assert_eq!("EDIT".parse::<AccessLevel>().unwrap(), AccessLevel::Edit);
211 assert!("invalid".parse::<AccessLevel>().is_err());
212 }
213
214 #[test]
215 fn test_default() {
216 assert_eq!(AccessLevel::default(), AccessLevel::NotVisible);
217 }
218
219 #[test]
220 fn test_serialization() {
221 let level = AccessLevel::ReadOnly;
222 let json = serde_json::to_string(&level).unwrap();
223 assert_eq!(json, "\"read_only\"");
224
225 let parsed: AccessLevel = serde_json::from_str(&json).unwrap();
226 assert_eq!(parsed, level);
227 }
228
229 #[test]
230 fn test_display() {
231 assert_eq!(format!("{}", AccessLevel::NotVisible), "not_visible");
232 assert_eq!(format!("{}", AccessLevel::ReadOnly), "read_only");
233 assert_eq!(format!("{}", AccessLevel::Edit), "edit");
234 }
235
236 #[test]
237 fn test_as_str() {
238 assert_eq!(AccessLevel::NotVisible.as_str(), "not_visible");
239 assert_eq!(AccessLevel::ReadOnly.as_str(), "read_only");
240 assert_eq!(AccessLevel::Edit.as_str(), "edit");
241 }
242
243 #[test]
244 fn test_parse_error_display() {
245 let err: Result<AccessLevel, _> = "invalid_level".parse();
246 assert!(err.is_err());
247 let err = err.unwrap_err();
248 assert!(err.to_string().contains("invalid_level"));
249 assert!(err.to_string().contains("not_visible"));
250 assert!(err.to_string().contains("read_only"));
251 assert!(err.to_string().contains("edit"));
252 }
253
254 #[test]
255 fn test_from_str_aliases() {
256 assert_eq!(
258 "hidden".parse::<AccessLevel>().unwrap(),
259 AccessLevel::NotVisible
260 );
261 assert_eq!(
262 "notvisible".parse::<AccessLevel>().unwrap(),
263 AccessLevel::NotVisible
264 );
265
266 assert_eq!(
267 "readonly".parse::<AccessLevel>().unwrap(),
268 AccessLevel::ReadOnly
269 );
270 assert_eq!(
271 "read".parse::<AccessLevel>().unwrap(),
272 AccessLevel::ReadOnly
273 );
274 assert_eq!(
275 "view".parse::<AccessLevel>().unwrap(),
276 AccessLevel::ReadOnly
277 );
278
279 assert_eq!("write".parse::<AccessLevel>().unwrap(), AccessLevel::Edit);
280 assert_eq!("full".parse::<AccessLevel>().unwrap(), AccessLevel::Edit);
281 }
282
283 #[test]
284 fn test_rank() {
285 assert!(AccessLevel::NotVisible < AccessLevel::ReadOnly);
287 assert!(AccessLevel::ReadOnly < AccessLevel::Edit);
288 assert!(AccessLevel::NotVisible < AccessLevel::Edit);
289
290 assert_eq!(
292 AccessLevel::Edit.cmp(&AccessLevel::Edit),
293 std::cmp::Ordering::Equal
294 );
295 assert_eq!(
296 AccessLevel::ReadOnly.cmp(&AccessLevel::NotVisible),
297 std::cmp::Ordering::Greater
298 );
299 }
300
301 #[test]
302 fn test_partial_ord() {
303 assert!(
304 AccessLevel::NotVisible.partial_cmp(&AccessLevel::ReadOnly)
305 == Some(std::cmp::Ordering::Less)
306 );
307 assert!(
308 AccessLevel::Edit.partial_cmp(&AccessLevel::ReadOnly)
309 == Some(std::cmp::Ordering::Greater)
310 );
311 assert!(
312 AccessLevel::ReadOnly.partial_cmp(&AccessLevel::ReadOnly)
313 == Some(std::cmp::Ordering::Equal)
314 );
315 }
316
317 #[test]
318 fn test_allows_not_visible() {
319 let level = AccessLevel::NotVisible;
320 assert!(level.allows(AccessLevel::NotVisible));
321 assert!(!level.allows(AccessLevel::ReadOnly));
322 assert!(!level.allows(AccessLevel::Edit));
323 }
324}