systemprompt_security/policy/
governed.rs1use serde::{Deserialize, Serialize};
12use systemprompt_identifiers::McpToolName;
13
14pub const PROMPT_TARGET_NAME: &str = "user_prompt";
16
17pub const UNKNOWN_TARGET_NAME: &str = "unknown";
19
20#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
27#[serde(transparent)]
28pub struct McpToolInput(
29 serde_json::Value,
31);
32
33impl McpToolInput {
34 #[must_use]
35 pub const fn new(value: serde_json::Value) -> Self {
36 Self(value)
37 }
38
39 #[must_use]
40 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 { text: String },
96}
97
98#[derive(Debug, Clone, PartialEq, Eq)]
99pub struct GovernedString<'a> {
100 pub path: String,
101 pub value: &'a str,
102}
103
104impl GovernedInput {
105 #[must_use]
106 pub const fn tool_arguments(arguments: McpToolInput) -> Self {
107 Self::ToolArguments { arguments }
108 }
109
110 #[must_use]
111 pub const fn prompt(text: String) -> Self {
112 Self::Prompt { text }
113 }
114
115 #[must_use]
116 pub const fn location_kind(&self) -> &'static str {
117 match self {
118 Self::ToolArguments { .. } => "tool_input",
119 Self::Prompt { .. } => "prompt",
120 }
121 }
122
123 #[must_use]
124 pub const fn arguments(&self) -> Option<&McpToolInput> {
125 match self {
126 Self::ToolArguments { arguments } => Some(arguments),
127 Self::Prompt { .. } => None,
128 }
129 }
130
131 #[must_use]
136 pub fn strings(&self) -> Vec<GovernedString<'_>> {
137 match self {
138 Self::ToolArguments { arguments } => {
139 let mut out = Vec::new();
140 collect_strings(arguments.as_value(), &mut String::new(), &mut out);
141 out
142 },
143 Self::Prompt { text } => vec![GovernedString {
144 path: PROMPT_PATH.to_owned(),
145 value: text,
146 }],
147 }
148 }
149}
150
151const PROMPT_PATH: &str = "text";
152
153fn collect_strings<'a>(
154 value: &'a serde_json::Value,
155 path: &mut String,
156 out: &mut Vec<GovernedString<'a>>,
157) {
158 match value {
159 serde_json::Value::String(s) => out.push(GovernedString {
160 path: path.clone(),
161 value: s,
162 }),
163 serde_json::Value::Array(items) => {
164 for (index, item) in items.iter().enumerate() {
165 let parent = path.len();
166 path.push_str(&format!("[{index}]"));
167 collect_strings(item, path, out);
168 path.truncate(parent);
169 }
170 },
171 serde_json::Value::Object(map) => {
172 for (key, item) in map {
173 let parent = path.len();
174 if !path.is_empty() {
175 path.push('.');
176 }
177 path.push_str(key);
178 collect_strings(item, path, out);
179 path.truncate(parent);
180 }
181 },
182 _ => {},
183 }
184}