use serde::{Deserialize, Serialize};
use std::str::FromStr;
use std::fmt;
use tracing::{error, instrument};
use super::{AppPermission, FieldPermission, PermissionType};
#[derive(Debug, Clone, PartialEq)]
pub struct HRolesError {
message: String
}
impl fmt::Display for HRolesError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message)
}
}
impl std::error::Error for HRolesError {}
#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[serde(rename_all = "lowercase")]
pub enum HRoles {
#[serde(rename = "0")]
Owner,
#[serde(rename = "1")]
Manager,
#[serde(rename = "2")]
Editor,
#[serde(rename = "3")]
UpdateOnly,
#[serde(rename = "4")]
ReadOnly,
}
impl Default for HRoles {
fn default() -> Self {
Self::ReadOnly
}
}
impl HRoles {
pub const fn variants() -> &'static [HRoles] {
&[
HRoles::Owner,
HRoles::Manager,
HRoles::Editor,
HRoles::UpdateOnly,
HRoles::ReadOnly,
]
}
#[inline]
pub const fn to_i8(self) -> i8 {
match self {
HRoles::Owner => 0,
HRoles::Manager => 1,
HRoles::Editor => 2,
HRoles::UpdateOnly => 3,
HRoles::ReadOnly => 4,
}
}
#[inline]
pub const fn as_str(self) -> &'static str {
match self {
HRoles::Owner => "0",
HRoles::Manager => "1",
HRoles::Editor => "2",
HRoles::UpdateOnly => "3",
HRoles::ReadOnly => "4",
}
}
#[inline]
pub const fn display_name(self) -> &'static str {
match self {
HRoles::Owner => "Owner",
HRoles::Manager => "Manager",
HRoles::Editor => "Editor",
HRoles::UpdateOnly => "Update Only",
HRoles::ReadOnly => "Read Only",
}
}
#[instrument]
pub fn try_from_i8(value: i8) -> Result<Self, HRolesError> {
match value {
0 => Ok(HRoles::Owner),
1 => Ok(HRoles::Manager),
2 => Ok(HRoles::Editor),
3 => Ok(HRoles::UpdateOnly),
4 => Ok(HRoles::ReadOnly),
invalid => {
error!(value = invalid, "Invalid role value");
Err(HRolesError {
message: format!("Invalid role value: {}", invalid)
})
}
}
}
#[instrument]
pub fn get_permission(&self, node: Option<String>) -> AppPermission {
AppPermission::get_permission(node, *self)
}
#[instrument]
pub fn have_permission(&self, permission: PermissionType) -> bool {
self.get_permission(None).has_permission(permission)
}
#[instrument]
pub fn get_field_permission(&self) -> FieldPermission {
match self {
HRoles::Owner | HRoles::Manager => FieldPermission {
can_edit: true,
can_read: true,
can_remove: true,
is_admin: true,
is_owner: matches!(self, HRoles::Owner),
node_manageable: true,
permission_extend: true,
..Default::default()
},
HRoles::Editor => FieldPermission {
can_edit: true,
can_read: true,
can_remove: true,
node_manageable: true,
permission_extend: true,
..Default::default()
},
HRoles::UpdateOnly => FieldPermission {
can_edit: true,
can_read: true,
..Default::default()
},
HRoles::ReadOnly => FieldPermission {
can_read: true,
..Default::default()
},
}
}
#[inline]
pub fn max(self, other: HRoles) -> HRoles {
if self <= other { self } else { other }
}
#[instrument]
pub fn is_admin(&self) -> bool {
matches!(self, HRoles::Owner | HRoles::Manager)
}
#[instrument]
pub fn can_edit(&self) -> bool {
matches!(self, HRoles::Owner | HRoles::Manager | HRoles::Editor | HRoles::UpdateOnly)
}
#[instrument]
pub fn can_manage_permissions(&self) -> bool {
matches!(self, HRoles::Owner | HRoles::Manager)
}
#[instrument]
pub fn permission_level(&self) -> &'static str {
match self {
HRoles::Owner => "full",
HRoles::Manager => "admin",
HRoles::Editor => "edit",
HRoles::UpdateOnly => "update",
HRoles::ReadOnly => "read",
}
}
}
impl From<i8> for HRoles {
#[inline]
fn from(value: i8) -> Self {
match value {
0 => HRoles::Owner,
1 => HRoles::Manager,
2 => HRoles::Editor,
3 => HRoles::UpdateOnly,
_ => HRoles::ReadOnly,
}
}
}
impl From<HRoles> for i8 {
#[inline]
fn from(role: HRoles) -> Self {
role.to_i8()
}
}
impl TryFrom<i32> for HRoles {
type Error = HRolesError;
#[instrument]
fn try_from(value: i32) -> Result<Self, Self::Error> {
if value < i8::MIN as i32 || value > i8::MAX as i32 {
error!(value = value, "Role value out of i8 range");
return Err(HRolesError {
message: format!("Role value out of range: {}", value)
});
}
Self::try_from_i8(value as i8)
}
}
impl FromStr for HRoles {
type Err = HRolesError;
#[instrument]
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"0" | "owner" => Ok(HRoles::Owner),
"1" | "manager" => Ok(HRoles::Manager),
"2" | "editor" => Ok(HRoles::Editor),
"3" | "updateonly" => Ok(HRoles::UpdateOnly),
"4" | "readonly" => Ok(HRoles::ReadOnly),
invalid => {
error!(value = invalid, "Invalid role string");
Err(HRolesError {
message: format!("Invalid role string: {}", invalid)
})
}
}
}
}
impl ToString for HRoles {
#[inline]
fn to_string(&self) -> String {
self.as_str().to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_role_permissions() {
let owner = HRoles::Owner;
let permission = owner.get_permission(None);
assert!(permission.has_permission(PermissionType::Manageable));
assert!(permission.has_permission(PermissionType::Editable));
let reader = HRoles::ReadOnly;
let permission = reader.get_permission(None);
assert!(!permission.has_permission(PermissionType::Editable));
assert!(permission.has_permission(PermissionType::Readable));
}
#[test]
fn test_field_permissions() {
let owner = HRoles::Owner;
let field_perms = owner.get_field_permission();
assert!(field_perms.can_edit);
assert!(field_perms.can_read);
assert!(field_perms.can_remove);
assert!(field_perms.is_owner);
let reader = HRoles::ReadOnly;
let field_perms = reader.get_field_permission();
assert!(!field_perms.can_edit);
assert!(field_perms.can_read);
assert!(!field_perms.can_remove);
}
}