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
113impl GovernedInput {
114 #[must_use]
115 pub const fn tool_arguments(arguments: McpToolInput) -> Self {
116 Self::ToolArguments { arguments }
117 }
118
119 #[must_use]
120 pub fn prompt_parts(parts: impl IntoIterator<Item = (String, String)>) -> Self {
121 Self::Prompt {
122 parts: parts
123 .into_iter()
124 .map(|(path, value)| PromptPart { path, value })
125 .collect(),
126 }
127 }
128
129 #[must_use]
130 pub fn prompt_text(text: String) -> Self {
131 Self::Prompt {
132 parts: vec![PromptPart {
133 path: PROMPT_PATH.to_owned(),
134 value: text,
135 }],
136 }
137 }
138
139 #[must_use]
140 pub const fn location_kind(&self) -> &'static str {
141 match self {
142 Self::ToolArguments { .. } => "tool_input",
143 Self::Prompt { .. } => "prompt",
144 }
145 }
146
147 #[must_use]
148 pub const fn arguments(&self) -> Option<&McpToolInput> {
149 match self {
150 Self::ToolArguments { arguments } => Some(arguments),
151 Self::Prompt { .. } => None,
152 }
153 }
154
155 #[must_use]
156 pub fn strings(&self) -> Vec<GovernedString<'_>> {
157 match self {
158 Self::ToolArguments { arguments } => {
159 let mut out = Vec::new();
160 collect_strings(arguments.as_value(), &mut String::new(), &mut out);
161 out
162 },
163 Self::Prompt { parts } => parts
164 .iter()
165 .map(|part| GovernedString {
166 path: part.path.clone(),
167 value: &part.value,
168 })
169 .collect(),
170 }
171 }
172}
173
174const PROMPT_PATH: &str = "text";
175
176fn collect_strings<'a>(
177 value: &'a serde_json::Value,
178 path: &mut String,
179 out: &mut Vec<GovernedString<'a>>,
180) {
181 match value {
182 serde_json::Value::String(s) => out.push(GovernedString {
183 path: path.clone(),
184 value: s,
185 }),
186 serde_json::Value::Array(items) => {
187 for (index, item) in items.iter().enumerate() {
188 let parent = path.len();
189 path.push_str(&format!("[{index}]"));
190 collect_strings(item, path, out);
191 path.truncate(parent);
192 }
193 },
194 serde_json::Value::Object(map) => {
195 for (key, item) in map {
196 let parent = path.len();
197 if !path.is_empty() {
198 path.push('.');
199 }
200 path.push_str(key);
201 collect_strings(item, path, out);
202 path.truncate(parent);
203 }
204 },
205 _ => {},
206 }
207}