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