systemprompt-models 0.2.1

Foundation data models for systemprompt.io AI governance infrastructure. Shared DTOs, config, and domain types consumed by every layer of the MCP governance pipeline.
Documentation
use anyhow::{Result, anyhow};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::str::FromStr;

use super::enums::UserType;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Permission {
    Admin,
    User,
    Anonymous,
    A2a,
    Mcp,
    Service,
}

impl Permission {
    pub const ALL_VARIANTS: &'static [&'static str] =
        &["admin", "user", "anonymous", "a2a", "mcp", "service"];

    pub const fn as_str(&self) -> &'static str {
        match self {
            Self::Admin => "admin",
            Self::User => "user",
            Self::Anonymous => "anonymous",
            Self::A2a => "a2a",
            Self::Mcp => "mcp",
            Self::Service => "service",
        }
    }

    pub fn is_valid_role(role: &str) -> bool {
        Self::ALL_VARIANTS.contains(&role)
    }

    pub fn validate_roles(roles: &[String]) -> std::result::Result<(), Vec<String>> {
        let invalid: Vec<String> = roles
            .iter()
            .filter(|r| !Self::is_valid_role(r))
            .cloned()
            .collect();

        if invalid.is_empty() {
            Ok(())
        } else {
            Err(invalid)
        }
    }

    pub const fn as_user_type(self) -> UserType {
        match self {
            Self::Admin => UserType::Admin,
            Self::User => UserType::User,
            Self::A2a => UserType::A2a,
            Self::Mcp => UserType::Mcp,
            Self::Service => UserType::Service,
            Self::Anonymous => UserType::Anon,
        }
    }

    pub const fn from_user_type(user_type: UserType) -> Self {
        match user_type {
            UserType::Admin => Self::Admin,
            UserType::User => Self::User,
            UserType::A2a => Self::A2a,
            UserType::Mcp => Self::Mcp,
            UserType::Service => Self::Service,
            UserType::Anon | UserType::Unknown => Self::Anonymous,
        }
    }

    pub const fn is_user_role(&self) -> bool {
        matches!(self, Self::Admin | Self::User | Self::Anonymous)
    }

    pub const fn is_service_scope(&self) -> bool {
        matches!(self, Self::A2a | Self::Mcp | Self::Service)
    }

    pub const fn hierarchy_level(&self) -> u8 {
        match self {
            Self::Admin => 100,
            Self::User => 50,
            Self::Service => 40,
            Self::A2a => 30,
            Self::Mcp => 20,
            Self::Anonymous => 10,
        }
    }

    pub const fn implies(&self, other: &Self) -> bool {
        self.hierarchy_level() >= other.hierarchy_level()
    }

    pub fn user_permissions() -> Vec<Self> {
        vec![Self::Admin, Self::User, Self::Anonymous]
    }

    pub fn service_permissions() -> Vec<Self> {
        vec![Self::A2a, Self::Mcp, Self::Service]
    }
}

impl fmt::Display for Permission {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.as_str())
    }
}

impl FromStr for Permission {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self> {
        match s {
            "admin" => Ok(Self::Admin),
            "user" => Ok(Self::User),
            "anonymous" => Ok(Self::Anonymous),
            "a2a" => Ok(Self::A2a),
            "mcp" => Ok(Self::Mcp),
            "service" => Ok(Self::Service),
            _ => Err(anyhow!("Invalid permission: {s}")),
        }
    }
}

pub fn permissions_to_string(permissions: &[Permission]) -> String {
    permissions
        .iter()
        .map(Permission::as_str)
        .collect::<Vec<_>>()
        .join(" ")
}

pub fn parse_permissions(s: &str) -> Result<Vec<Permission>> {
    s.split_whitespace().map(Permission::from_str).collect()
}