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