Skip to main content

systemprompt_models/auth/enums/
caller.rs

1//! Caller classification and the rate-limit tier it maps to.
2//!
3//! [`UserType`] is the privilege class derived from a permission set;
4//! [`RateLimitTier`] is the throughput band it resolves to; [`TokenType`]
5//! is the bearer-scheme marker. [`UserType::from_permissions`] is the single
6//! source of truth for the permission → type mapping.
7//!
8//! Copyright (c) systemprompt.io — Business Source License 1.1.
9//! See <https://systemprompt.io> for licensing details.
10
11use serde::{Deserialize, Serialize};
12use std::fmt;
13use std::str::FromStr;
14
15use crate::auth::permission::Permission;
16use crate::errors::ParseEnumError;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
19#[serde(rename_all = "lowercase")]
20pub enum UserType {
21    Admin,
22    User,
23    A2a,
24    Mcp,
25    Service,
26    Anon,
27    Unknown,
28}
29
30impl UserType {
31    pub fn from_permissions(permissions: &[Permission]) -> Self {
32        let has = |p: Permission| permissions.contains(&p);
33        if has(Permission::Admin) {
34            Self::Admin
35        } else if has(Permission::User) {
36            Self::User
37        } else if has(Permission::A2a) {
38            Self::A2a
39        } else if has(Permission::Mcp) {
40            Self::Mcp
41        } else if has(Permission::Service)
42            || has(Permission::HookGovern)
43            || has(Permission::HookTrack)
44        {
45            Self::Service
46        } else {
47            Self::Anon
48        }
49    }
50
51    pub const fn as_str(&self) -> &'static str {
52        match self {
53            Self::Admin => "admin",
54            Self::User => "user",
55            Self::A2a => "a2a",
56            Self::Mcp => "mcp",
57            Self::Service => "service",
58            Self::Anon => "anon",
59            Self::Unknown => "unknown",
60        }
61    }
62
63    pub const fn rate_tier(&self) -> RateLimitTier {
64        match self {
65            Self::Admin => RateLimitTier::Admin,
66            Self::User => RateLimitTier::User,
67            Self::A2a => RateLimitTier::A2a,
68            Self::Mcp => RateLimitTier::Mcp,
69            Self::Service => RateLimitTier::Service,
70            Self::Anon | Self::Unknown => RateLimitTier::Anon,
71        }
72    }
73
74    // Why: Human types (Admin/User) are authoritative on the users row, not the
75    // JWT: an Admin-claimed token whose user row is no longer in the admin role
76    // gets downgraded here. Machine types (Service/A2a/Mcp/Anon) are not
77    // reflected in users.roles — they are minted by the OAuth layer and trusted
78    // as claimed.
79    #[must_use]
80    pub const fn reconcile_with(self, user_is_admin: bool) -> Self {
81        match self {
82            Self::Admin if !user_is_admin => Self::User,
83            other => other,
84        }
85    }
86}
87
88impl fmt::Display for UserType {
89    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90        write!(f, "{}", self.as_str())
91    }
92}
93
94impl FromStr for UserType {
95    type Err = ParseEnumError;
96    fn from_str(s: &str) -> Result<Self, Self::Err> {
97        match s {
98            "admin" => Ok(Self::Admin),
99            "user" => Ok(Self::User),
100            "a2a" => Ok(Self::A2a),
101            "mcp" => Ok(Self::Mcp),
102            "service" => Ok(Self::Service),
103            "anon" => Ok(Self::Anon),
104            "unknown" => Ok(Self::Unknown),
105            _ => Err(ParseEnumError::new("user_type", s)),
106        }
107    }
108}
109
110#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
111pub enum TokenType {
112    #[default]
113    Bearer,
114}
115
116impl TokenType {
117    pub const fn as_str(self) -> &'static str {
118        match self {
119            Self::Bearer => "Bearer",
120        }
121    }
122}
123
124impl fmt::Display for TokenType {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        write!(f, "Bearer")
127    }
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
131#[serde(rename_all = "lowercase")]
132pub enum RateLimitTier {
133    Admin,
134    User,
135    A2a,
136    Mcp,
137    Service,
138    Anon,
139}
140
141impl RateLimitTier {
142    pub const fn as_str(&self) -> &'static str {
143        match self {
144            Self::Admin => "admin",
145            Self::User => "user",
146            Self::A2a => "a2a",
147            Self::Mcp => "mcp",
148            Self::Service => "service",
149            Self::Anon => "anon",
150        }
151    }
152}
153
154impl fmt::Display for RateLimitTier {
155    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156        write!(f, "{}", self.as_str())
157    }
158}
159
160impl FromStr for RateLimitTier {
161    type Err = ParseEnumError;
162    fn from_str(s: &str) -> Result<Self, Self::Err> {
163        match s {
164            "admin" => Ok(Self::Admin),
165            "user" => Ok(Self::User),
166            "a2a" => Ok(Self::A2a),
167            "mcp" => Ok(Self::Mcp),
168            "service" => Ok(Self::Service),
169            "anon" => Ok(Self::Anon),
170            _ => Err(ParseEnumError::new("rate_limit_tier", s)),
171        }
172    }
173}