systemprompt_security/authz/types/
kinds.rs1use std::borrow::Cow;
7use std::fmt;
8use std::str::FromStr;
9
10use serde::{Deserialize, Serialize};
11
12use crate::authz::error::AuthzError;
13
14#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
31#[serde(transparent)]
32pub struct RuleType(Cow<'static, str>);
33
34impl RuleType {
35 pub const USER: Self = Self(Cow::Borrowed("user"));
36 pub const ROLE: Self = Self(Cow::Borrowed("role"));
37
38 pub fn extension(slug: impl Into<Cow<'static, str>>) -> Result<Self, AuthzError> {
39 let slug = slug.into();
40 let well_formed = !slug.is_empty()
41 && !slug.starts_with('_')
42 && !slug.ends_with('_')
43 && slug
44 .chars()
45 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_');
46 if !well_formed || slug == Self::USER.as_str() || slug == Self::ROLE.as_str() {
47 return Err(AuthzError::InvalidRuleType(slug.into_owned()));
48 }
49 Ok(Self(slug))
50 }
51
52 #[must_use]
53 pub fn as_str(&self) -> &str {
54 &self.0
55 }
56}
57
58impl fmt::Display for RuleType {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 f.write_str(self.as_str())
61 }
62}
63
64impl From<&str> for RuleType {
65 fn from(s: &str) -> Self {
66 match s {
67 "user" => Self::USER,
68 "role" => Self::ROLE,
69 other => Self(Cow::Owned(other.to_owned())),
70 }
71 }
72}
73
74impl sqlx::Type<sqlx::Postgres> for RuleType {
75 fn type_info() -> <sqlx::Postgres as sqlx::Database>::TypeInfo {
76 <str as sqlx::Type<sqlx::Postgres>>::type_info()
77 }
78
79 fn compatible(ty: &<sqlx::Postgres as sqlx::Database>::TypeInfo) -> bool {
80 <str as sqlx::Type<sqlx::Postgres>>::compatible(ty)
81 }
82}
83
84impl<'q> sqlx::Encode<'q, sqlx::Postgres> for RuleType {
85 fn encode_by_ref(
86 &self,
87 buf: &mut <sqlx::Postgres as sqlx::Database>::ArgumentBuffer,
88 ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
89 <&str as sqlx::Encode<'q, sqlx::Postgres>>::encode(self.as_str(), buf)
90 }
91}
92
93impl<'r> sqlx::Decode<'r, sqlx::Postgres> for RuleType {
94 fn decode(
95 value: <sqlx::Postgres as sqlx::Database>::ValueRef<'r>,
96 ) -> Result<Self, sqlx::error::BoxDynError> {
97 let raw = <&str as sqlx::Decode<'r, sqlx::Postgres>>::decode(value)?;
98 Ok(Self::from(raw))
99 }
100}
101
102#[derive(
103 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, sqlx::Type,
104)]
105#[sqlx(type_name = "TEXT", rename_all = "lowercase")]
106#[serde(rename_all = "lowercase")]
107pub enum Access {
108 Allow,
109 Deny,
110}
111
112impl fmt::Display for Access {
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 f.write_str(match *self {
115 Self::Allow => "allow",
116 Self::Deny => "deny",
117 })
118 }
119}
120
121impl FromStr for Access {
122 type Err = AuthzError;
123
124 fn from_str(s: &str) -> Result<Self, Self::Err> {
125 match s {
126 "allow" => Ok(Self::Allow),
127 "deny" => Ok(Self::Deny),
128 other => Err(AuthzError::InvalidAccess(other.to_owned())),
129 }
130 }
131}
132
133#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
134#[serde(rename_all = "snake_case")]
135pub enum EntityKind {
136 GatewayRoute,
137 McpServer,
138 Plugin,
139 Agent,
140 Marketplace,
141 Skill,
142 Hook,
143 SlackWorkspace,
144 SlackChannel,
145 TeamsTenant,
146 TeamsConversation,
147}
148
149impl EntityKind {
150 pub const fn as_str(self) -> &'static str {
151 match self {
152 Self::GatewayRoute => "gateway_route",
153 Self::McpServer => "mcp_server",
154 Self::Plugin => "plugin",
155 Self::Agent => "agent",
156 Self::Marketplace => "marketplace",
157 Self::Skill => "skill",
158 Self::Hook => "hook",
159 Self::SlackWorkspace => "slack_workspace",
160 Self::SlackChannel => "slack_channel",
161 Self::TeamsTenant => "teams_tenant",
162 Self::TeamsConversation => "teams_conversation",
163 }
164 }
165}
166
167impl FromStr for EntityKind {
168 type Err = AuthzError;
169
170 fn from_str(s: &str) -> Result<Self, Self::Err> {
171 match s {
172 "gateway_route" => Ok(Self::GatewayRoute),
173 "mcp_server" => Ok(Self::McpServer),
174 "plugin" => Ok(Self::Plugin),
175 "agent" => Ok(Self::Agent),
176 "marketplace" => Ok(Self::Marketplace),
177 "skill" => Ok(Self::Skill),
178 "hook" => Ok(Self::Hook),
179 "slack_workspace" => Ok(Self::SlackWorkspace),
180 "slack_channel" => Ok(Self::SlackChannel),
181 "teams_tenant" => Ok(Self::TeamsTenant),
182 "teams_conversation" => Ok(Self::TeamsConversation),
183 other => Err(AuthzError::Validation(format!(
184 "unknown entity_type: {other}"
185 ))),
186 }
187 }
188}
189
190impl fmt::Display for EntityKind {
191 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192 f.write_str(self.as_str())
193 }
194}