hoicko_lib 0.1.16

Hoicko library
Documentation
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,
}

// Default implementation
impl Default for HRoles {
    fn default() -> Self {
        Self::ReadOnly
    }
}

impl HRoles {
    /// Get all possible roles
    pub const fn variants() -> &'static [HRoles] {
        &[
            HRoles::Owner,
            HRoles::Manager,
            HRoles::Editor,
            HRoles::UpdateOnly,
            HRoles::ReadOnly,
        ]
    }

    /// Convert to i8
    #[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,
        }
    }

    /// Convert to string representation
    #[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",
        }
    }

    /// Get human readable name
    #[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",
        }
    }

    /// Safe conversion from i8
    #[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()
            },
        }
    }

    /// Get the highest role between two roles
    #[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)
    }

    /// Checks if the role can edit content
    #[instrument]
    pub fn can_edit(&self) -> bool {
        matches!(self, HRoles::Owner | HRoles::Manager | HRoles::Editor | HRoles::UpdateOnly)
    }

    /// Checks if the role can manage permissions
    #[instrument]
    pub fn can_manage_permissions(&self) -> bool {
        matches!(self, HRoles::Owner | HRoles::Manager)
    }

    /// Gets the permission level as a string
    #[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",
        }
    }
}

// Implement conversion traits
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);
    }
}