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 { text: String },
94}
95
96#[derive(Debug, Clone, PartialEq, Eq)]
97pub struct GovernedString<'a> {
98 pub path: String,
99 pub value: &'a str,
100}
101
102impl GovernedInput {
103 #[must_use]
104 pub const fn tool_arguments(arguments: McpToolInput) -> Self {
105 Self::ToolArguments { arguments }
106 }
107
108 #[must_use]
109 pub const fn prompt(text: String) -> Self {
110 Self::Prompt { text }
111 }
112
113 #[must_use]
114 pub const fn location_kind(&self) -> &'static str {
115 match self {
116 Self::ToolArguments { .. } => "tool_input",
117 Self::Prompt { .. } => "prompt",
118 }
119 }
120
121 #[must_use]
122 pub const fn arguments(&self) -> Option<&McpToolInput> {
123 match self {
124 Self::ToolArguments { arguments } => Some(arguments),
125 Self::Prompt { .. } => None,
126 }
127 }
128
129 #[must_use]
130 pub fn strings(&self) -> Vec<GovernedString<'_>> {
131 match self {
132 Self::ToolArguments { arguments } => {
133 let mut out = Vec::new();
134 collect_strings(arguments.as_value(), &mut String::new(), &mut out);
135 out
136 },
137 Self::Prompt { text } => vec![GovernedString {
138 path: PROMPT_PATH.to_owned(),
139 value: text,
140 }],
141 }
142 }
143}
144
145const PROMPT_PATH: &str = "text";
146
147fn collect_strings<'a>(
148 value: &'a serde_json::Value,
149 path: &mut String,
150 out: &mut Vec<GovernedString<'a>>,
151) {
152 match value {
153 serde_json::Value::String(s) => out.push(GovernedString {
154 path: path.clone(),
155 value: s,
156 }),
157 serde_json::Value::Array(items) => {
158 for (index, item) in items.iter().enumerate() {
159 let parent = path.len();
160 path.push_str(&format!("[{index}]"));
161 collect_strings(item, path, out);
162 path.truncate(parent);
163 }
164 },
165 serde_json::Value::Object(map) => {
166 for (key, item) in map {
167 let parent = path.len();
168 if !path.is_empty() {
169 path.push('.');
170 }
171 path.push_str(key);
172 collect_strings(item, path, out);
173 path.truncate(parent);
174 }
175 },
176 _ => {},
177 }
178}