systemprompt_identifiers/
actor.rs1use std::fmt;
9
10use serde::{Deserialize, Serialize};
11
12use crate::UserId;
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub struct Actor {
16 pub user_id: UserId,
17 pub kind: ActorKind,
18}
19
20impl Actor {
21 #[must_use]
22 pub const fn user(user_id: UserId) -> Self {
23 Self {
24 user_id,
25 kind: ActorKind::User,
26 }
27 }
28
29 #[must_use]
33 pub const fn anonymous(user_id: UserId) -> Self {
34 Self {
35 user_id,
36 kind: ActorKind::Anonymous,
37 }
38 }
39
40 #[must_use]
44 pub const fn system(user_id: UserId) -> Self {
45 Self {
46 user_id,
47 kind: ActorKind::System,
48 }
49 }
50
51 #[must_use]
52 pub fn job(user_id: UserId, job_name: impl Into<String>) -> Self {
53 Self {
54 user_id,
55 kind: ActorKind::Job {
56 job_name: job_name.into(),
57 },
58 }
59 }
60
61 #[must_use]
62 pub fn mcp(user_id: UserId, server_name: impl Into<String>) -> Self {
63 Self {
64 user_id,
65 kind: ActorKind::Mcp {
66 server_name: server_name.into(),
67 },
68 }
69 }
70
71 #[must_use]
75 pub fn agent(user_id: UserId, agent_id: impl Into<String>) -> Self {
76 Self {
77 user_id,
78 kind: ActorKind::Agent {
79 agent_id: agent_id.into(),
80 },
81 }
82 }
83
84 #[must_use]
85 pub fn audit_columns(&self) -> (&str, &str) {
86 (self.kind.as_str(), self.kind.actor_id(&self.user_id))
87 }
88
89 #[must_use]
90 pub fn from_tool_name(user_id: UserId, agent_id: Option<&str>, tool_name: &str) -> Self {
91 if let Some(rest) = tool_name.strip_prefix("mcp__") {
92 if let Some(server) = rest.split("__").next() {
93 if !server.is_empty() {
94 return Self::mcp(user_id, server);
95 }
96 }
97 }
98 match agent_id {
99 Some(id) if !id.is_empty() => Self::agent(user_id, id),
100 _ => Self::user(user_id),
101 }
102 }
103}
104
105#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
106#[serde(tag = "kind", rename_all = "snake_case")]
107pub enum ActorKind {
108 User,
109 Anonymous,
110 System,
111 Job { job_name: String },
112 Mcp { server_name: String },
113 Agent { agent_id: String },
114}
115
116impl ActorKind {
117 #[must_use]
118 pub const fn as_str(&self) -> &'static str {
119 match self {
120 Self::User => "user",
121 Self::Anonymous => "anonymous",
122 Self::System => "system",
123 Self::Job { .. } => "job",
124 Self::Mcp { .. } => "mcp",
125 Self::Agent { .. } => "agent",
126 }
127 }
128
129 #[must_use]
130 pub fn actor_id<'a>(&'a self, user_id: &'a UserId) -> &'a str {
131 match self {
132 Self::User | Self::Anonymous | Self::System => user_id.as_str(),
133 Self::Job { job_name } => job_name.as_str(),
134 Self::Mcp { server_name } => server_name.as_str(),
135 Self::Agent { agent_id } => agent_id.as_str(),
136 }
137 }
138}
139
140impl ActorKind {
141 #[must_use]
142 pub const fn tag(&self) -> ActorKindTag {
143 match self {
144 Self::User => ActorKindTag::User,
145 Self::Anonymous => ActorKindTag::Anonymous,
146 Self::System => ActorKindTag::System,
147 Self::Job { .. } => ActorKindTag::Job,
148 Self::Mcp { .. } => ActorKindTag::Mcp,
149 Self::Agent { .. } => ActorKindTag::Agent,
150 }
151 }
152}
153
154impl fmt::Display for ActorKind {
155 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156 f.write_str(self.as_str())
157 }
158}
159
160#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
167#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
168#[cfg_attr(feature = "sqlx", sqlx(type_name = "TEXT", rename_all = "snake_case"))]
169#[serde(rename_all = "snake_case")]
170pub enum ActorKindTag {
171 User,
172 Anonymous,
173 System,
174 Job,
175 Mcp,
176 Agent,
177}
178
179impl ActorKindTag {
180 #[must_use]
181 pub const fn as_str(self) -> &'static str {
182 match self {
183 Self::User => "user",
184 Self::Anonymous => "anonymous",
185 Self::System => "system",
186 Self::Job => "job",
187 Self::Mcp => "mcp",
188 Self::Agent => "agent",
189 }
190 }
191}
192
193impl fmt::Display for ActorKindTag {
194 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195 f.write_str(self.as_str())
196 }
197}