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