systemprompt_security/policy/
governed.rs1use serde::{Deserialize, Serialize};
12use systemprompt_identifiers::McpToolName;
13
14pub const PROMPT_TARGET_NAME: &str = "user_prompt";
15
16pub const UNKNOWN_TARGET_NAME: &str = "unknown";
17
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
25#[serde(transparent)]
26pub struct McpToolInput(
27 serde_json::Value,
29);
30
31impl McpToolInput {
32 #[must_use]
33 pub const fn new(value: serde_json::Value) -> Self {
35 Self(value)
36 }
37
38 #[must_use]
39 pub const fn as_value(&self) -> &serde_json::Value {
41 &self.0
42 }
43
44 #[must_use]
45 pub fn as_str(&self, field: &str) -> Option<&str> {
46 self.0.get(field).and_then(serde_json::Value::as_str)
47 }
48
49 #[must_use]
50 pub fn as_path(&self, field: &str) -> Option<&str> {
51 self.as_str(field)
52 }
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
60#[serde(tag = "kind", rename_all = "snake_case")]
61pub enum GovernedTarget {
62 Tool { tool: McpToolName },
63 Prompt,
64 Unknown,
65}
66
67impl GovernedTarget {
68 #[must_use]
69 pub fn as_str(&self) -> &str {
70 match self {
71 Self::Tool { tool } => tool.as_str(),
72 Self::Prompt => PROMPT_TARGET_NAME,
73 Self::Unknown => UNKNOWN_TARGET_NAME,
74 }
75 }
76
77 #[must_use]
78 pub const fn tool(&self) -> Option<&McpToolName> {
79 match self {
80 Self::Tool { tool } => Some(tool),
81 Self::Prompt | Self::Unknown => None,
82 }
83 }
84}
85
86#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
92#[serde(tag = "kind", rename_all = "snake_case")]
93pub enum GovernedInput {
94 ToolArguments { arguments: McpToolInput },
95 Prompt { parts: Vec<PromptPart> },
96}
97
98#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
104pub struct PromptPart {
105 pub path: String,
106 pub value: String,
107}
108
109#[derive(Debug, Clone, PartialEq, Eq)]
110pub struct GovernedString<'a> {
111 pub path: String,
112 pub value: &'a str,
113}
114
115#[derive(Debug, Clone, PartialEq)]
118pub struct GovernedScalar<'a> {
119 pub path: String,
120 pub value: &'a serde_json::Value,
122}
123
124impl GovernedInput {
125 #[must_use]
126 pub const fn tool_arguments(arguments: McpToolInput) -> Self {
127 Self::ToolArguments { arguments }
128 }
129
130 #[must_use]
131 pub fn prompt_parts(parts: impl IntoIterator<Item = (String, String)>) -> Self {
132 Self::Prompt {
133 parts: parts
134 .into_iter()
135 .map(|(path, value)| PromptPart { path, value })
136 .collect(),
137 }
138 }
139
140 #[must_use]
141 pub fn prompt_text(text: String) -> Self {
142 Self::Prompt {
143 parts: vec![PromptPart {
144 path: PROMPT_PATH.to_owned(),
145 value: text,
146 }],
147 }
148 }
149
150 #[must_use]
151 pub const fn location_kind(&self) -> &'static str {
152 match self {
153 Self::ToolArguments { .. } => "tool_input",
154 Self::Prompt { .. } => "prompt",
155 }
156 }
157
158 #[must_use]
159 pub const fn arguments(&self) -> Option<&McpToolInput> {
160 match self {
161 Self::ToolArguments { arguments } => Some(arguments),
162 Self::Prompt { .. } => None,
163 }
164 }
165
166 #[must_use]
167 pub fn strings(&self) -> Vec<GovernedString<'_>> {
168 match self {
169 Self::ToolArguments { .. } => self
170 .scalars()
171 .into_iter()
172 .filter_map(|scalar| {
173 scalar.value.as_str().map(|value| GovernedString {
174 path: scalar.path,
175 value,
176 })
177 })
178 .collect(),
179 Self::Prompt { parts } => parts
180 .iter()
181 .map(|part| GovernedString {
182 path: part.path.clone(),
183 value: &part.value,
184 })
185 .collect(),
186 }
187 }
188
189 #[must_use]
190 pub fn scalars(&self) -> Vec<GovernedScalar<'_>> {
191 match self {
192 Self::ToolArguments { arguments } => {
193 let mut out = Vec::new();
194 collect_scalars(arguments.as_value(), &mut String::new(), &mut out);
195 out
196 },
197 Self::Prompt { .. } => Vec::new(),
198 }
199 }
200}
201
202const PROMPT_PATH: &str = "text";
203
204fn collect_scalars<'a>(
206 value: &'a serde_json::Value,
207 path: &mut String,
208 out: &mut Vec<GovernedScalar<'a>>,
209) {
210 match value {
211 serde_json::Value::Array(items) => {
212 for (index, item) in items.iter().enumerate() {
213 let parent = path.len();
214 path.push_str(&format!("[{index}]"));
215 collect_scalars(item, path, out);
216 path.truncate(parent);
217 }
218 },
219 serde_json::Value::Object(map) => {
220 for (key, item) in map {
221 let parent = path.len();
222 if !path.is_empty() {
223 path.push('.');
224 }
225 path.push_str(key);
226 collect_scalars(item, path, out);
227 path.truncate(parent);
228 }
229 },
230 scalar => out.push(GovernedScalar {
231 path: path.clone(),
232 value: scalar,
233 }),
234 }
235}