use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AccessLevel {
NotVisible,
ReadOnly,
Edit,
}
impl Default for AccessLevel {
fn default() -> Self {
AccessLevel::NotVisible
}
}
impl AccessLevel {
pub fn allows(self, required: AccessLevel) -> bool {
self >= required
}
pub fn can_view(self) -> bool {
matches!(self, AccessLevel::ReadOnly | AccessLevel::Edit)
}
pub fn can_edit(self) -> bool {
matches!(self, AccessLevel::Edit)
}
fn rank(self) -> u8 {
match self {
AccessLevel::NotVisible => 0,
AccessLevel::ReadOnly => 1,
AccessLevel::Edit => 2,
}
}
pub fn as_str(self) -> &'static str {
match self {
AccessLevel::NotVisible => "not_visible",
AccessLevel::ReadOnly => "read_only",
AccessLevel::Edit => "edit",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseAccessLevelError {
pub invalid_value: String,
}
impl std::fmt::Display for ParseAccessLevelError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"invalid access level '{}': expected 'not_visible', 'read_only', or 'edit'",
self.invalid_value
)
}
}
impl std::error::Error for ParseAccessLevelError {}
impl FromStr for AccessLevel {
type Err = ParseAccessLevelError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"not_visible" | "notvisible" | "hidden" => Ok(AccessLevel::NotVisible),
"read_only" | "readonly" | "read" | "view" => Ok(AccessLevel::ReadOnly),
"edit" | "write" | "full" => Ok(AccessLevel::Edit),
_ => Err(ParseAccessLevelError {
invalid_value: s.to_string(),
}),
}
}
}
impl PartialOrd for AccessLevel {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for AccessLevel {
fn cmp(&self, other: &Self) -> Ordering {
self.rank().cmp(&other.rank())
}
}
impl std::fmt::Display for AccessLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_access_level_ordering() {
assert!(AccessLevel::NotVisible < AccessLevel::ReadOnly);
assert!(AccessLevel::ReadOnly < AccessLevel::Edit);
assert!(AccessLevel::NotVisible < AccessLevel::Edit);
}
#[test]
fn test_allows() {
assert!(AccessLevel::Edit.allows(AccessLevel::Edit));
assert!(AccessLevel::Edit.allows(AccessLevel::ReadOnly));
assert!(AccessLevel::Edit.allows(AccessLevel::NotVisible));
assert!(AccessLevel::ReadOnly.allows(AccessLevel::ReadOnly));
assert!(AccessLevel::ReadOnly.allows(AccessLevel::NotVisible));
assert!(!AccessLevel::ReadOnly.allows(AccessLevel::Edit));
assert!(AccessLevel::NotVisible.allows(AccessLevel::NotVisible));
assert!(!AccessLevel::NotVisible.allows(AccessLevel::ReadOnly));
assert!(!AccessLevel::NotVisible.allows(AccessLevel::Edit));
}
#[test]
fn test_can_view_and_edit() {
assert!(!AccessLevel::NotVisible.can_view());
assert!(!AccessLevel::NotVisible.can_edit());
assert!(AccessLevel::ReadOnly.can_view());
assert!(!AccessLevel::ReadOnly.can_edit());
assert!(AccessLevel::Edit.can_view());
assert!(AccessLevel::Edit.can_edit());
}
#[test]
fn test_from_str() {
assert_eq!(
"not_visible".parse::<AccessLevel>().unwrap(),
AccessLevel::NotVisible
);
assert_eq!(
"read_only".parse::<AccessLevel>().unwrap(),
AccessLevel::ReadOnly
);
assert_eq!("edit".parse::<AccessLevel>().unwrap(), AccessLevel::Edit);
assert_eq!("EDIT".parse::<AccessLevel>().unwrap(), AccessLevel::Edit);
assert!("invalid".parse::<AccessLevel>().is_err());
}
#[test]
fn test_default() {
assert_eq!(AccessLevel::default(), AccessLevel::NotVisible);
}
#[test]
fn test_serialization() {
let level = AccessLevel::ReadOnly;
let json = serde_json::to_string(&level).unwrap();
assert_eq!(json, "\"read_only\"");
let parsed: AccessLevel = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, level);
}
#[test]
fn test_display() {
assert_eq!(format!("{}", AccessLevel::NotVisible), "not_visible");
assert_eq!(format!("{}", AccessLevel::ReadOnly), "read_only");
assert_eq!(format!("{}", AccessLevel::Edit), "edit");
}
#[test]
fn test_as_str() {
assert_eq!(AccessLevel::NotVisible.as_str(), "not_visible");
assert_eq!(AccessLevel::ReadOnly.as_str(), "read_only");
assert_eq!(AccessLevel::Edit.as_str(), "edit");
}
#[test]
fn test_parse_error_display() {
let err: Result<AccessLevel, _> = "invalid_level".parse();
assert!(err.is_err());
let err = err.unwrap_err();
assert!(err.to_string().contains("invalid_level"));
assert!(err.to_string().contains("not_visible"));
assert!(err.to_string().contains("read_only"));
assert!(err.to_string().contains("edit"));
}
#[test]
fn test_from_str_aliases() {
assert_eq!(
"hidden".parse::<AccessLevel>().unwrap(),
AccessLevel::NotVisible
);
assert_eq!(
"notvisible".parse::<AccessLevel>().unwrap(),
AccessLevel::NotVisible
);
assert_eq!(
"readonly".parse::<AccessLevel>().unwrap(),
AccessLevel::ReadOnly
);
assert_eq!(
"read".parse::<AccessLevel>().unwrap(),
AccessLevel::ReadOnly
);
assert_eq!(
"view".parse::<AccessLevel>().unwrap(),
AccessLevel::ReadOnly
);
assert_eq!("write".parse::<AccessLevel>().unwrap(), AccessLevel::Edit);
assert_eq!("full".parse::<AccessLevel>().unwrap(), AccessLevel::Edit);
}
#[test]
fn test_rank() {
assert!(AccessLevel::NotVisible < AccessLevel::ReadOnly);
assert!(AccessLevel::ReadOnly < AccessLevel::Edit);
assert!(AccessLevel::NotVisible < AccessLevel::Edit);
assert_eq!(
AccessLevel::Edit.cmp(&AccessLevel::Edit),
std::cmp::Ordering::Equal
);
assert_eq!(
AccessLevel::ReadOnly.cmp(&AccessLevel::NotVisible),
std::cmp::Ordering::Greater
);
}
#[test]
fn test_partial_ord() {
assert!(
AccessLevel::NotVisible.partial_cmp(&AccessLevel::ReadOnly)
== Some(std::cmp::Ordering::Less)
);
assert!(
AccessLevel::Edit.partial_cmp(&AccessLevel::ReadOnly)
== Some(std::cmp::Ordering::Greater)
);
assert!(
AccessLevel::ReadOnly.partial_cmp(&AccessLevel::ReadOnly)
== Some(std::cmp::Ordering::Equal)
);
}
#[test]
fn test_allows_not_visible() {
let level = AccessLevel::NotVisible;
assert!(level.allows(AccessLevel::NotVisible));
assert!(!level.allows(AccessLevel::ReadOnly));
assert!(!level.allows(AccessLevel::Edit));
}
}