agent_framework_purview/
settings.rs1use crate::models::PolicyLocation;
5
6pub const DEFAULT_GRAPH_BASE_URI: &str = "https://graph.microsoft.com/v1.0/";
9
10pub const DEFAULT_BLOCKED_PROMPT_MESSAGE: &str = "Prompt blocked by policy";
12
13pub const DEFAULT_BLOCKED_RESPONSE_MESSAGE: &str = "Response blocked by policy";
15
16pub const DEFAULT_CACHE_TTL_SECONDS: u64 = 14_400;
20
21pub const DEFAULT_MAX_CACHE_SIZE_BYTES: u64 = 200 * 1024 * 1024;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum PurviewLocationType {
29 Application,
30 Uri,
31 Domain,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct PurviewAppLocation {
39 pub location_type: PurviewLocationType,
40 pub location_value: String,
41}
42
43impl PurviewAppLocation {
44 pub fn new(location_type: PurviewLocationType, location_value: impl Into<String>) -> Self {
45 Self {
46 location_type,
47 location_value: location_value.into(),
48 }
49 }
50
51 pub fn to_policy_location(&self) -> PolicyLocation {
54 let data_type = match self.location_type {
55 PurviewLocationType::Application => "microsoft.graph.policyLocationApplication",
56 PurviewLocationType::Uri => "microsoft.graph.policyLocationUrl",
57 PurviewLocationType::Domain => "microsoft.graph.policyLocationDomain",
58 };
59 PolicyLocation {
60 data_type: data_type.to_string(),
61 value: self.location_value.clone(),
62 }
63 }
64}
65
66#[derive(Debug, Clone)]
72pub struct PurviewSettings {
73 pub app_name: String,
76 pub app_version: Option<String>,
77 pub tenant_id: Option<String>,
82 pub purview_app_location: Option<PurviewAppLocation>,
87 pub graph_base_uri: String,
88 pub blocked_prompt_message: String,
89 pub blocked_response_message: String,
90 pub ignore_exceptions: bool,
95 pub ignore_payment_required: bool,
101 pub cache_ttl_seconds: u64,
105 pub max_cache_size_bytes: u64,
107}
108
109impl PurviewSettings {
110 pub fn new(app_name: impl Into<String>) -> Self {
113 Self {
114 app_name: app_name.into(),
115 app_version: None,
116 tenant_id: None,
117 purview_app_location: None,
118 graph_base_uri: DEFAULT_GRAPH_BASE_URI.to_string(),
119 blocked_prompt_message: DEFAULT_BLOCKED_PROMPT_MESSAGE.to_string(),
120 blocked_response_message: DEFAULT_BLOCKED_RESPONSE_MESSAGE.to_string(),
121 ignore_exceptions: false,
122 ignore_payment_required: false,
123 cache_ttl_seconds: DEFAULT_CACHE_TTL_SECONDS,
124 max_cache_size_bytes: DEFAULT_MAX_CACHE_SIZE_BYTES,
125 }
126 }
127
128 pub fn with_app_version(mut self, version: impl Into<String>) -> Self {
129 self.app_version = Some(version.into());
130 self
131 }
132 pub fn with_tenant_id(mut self, tenant_id: impl Into<String>) -> Self {
133 self.tenant_id = Some(tenant_id.into());
134 self
135 }
136 pub fn with_purview_app_location(mut self, location: PurviewAppLocation) -> Self {
137 self.purview_app_location = Some(location);
138 self
139 }
140 pub fn with_graph_base_uri(mut self, uri: impl Into<String>) -> Self {
141 self.graph_base_uri = uri.into();
142 self
143 }
144 pub fn with_blocked_prompt_message(mut self, message: impl Into<String>) -> Self {
145 self.blocked_prompt_message = message.into();
146 self
147 }
148 pub fn with_blocked_response_message(mut self, message: impl Into<String>) -> Self {
149 self.blocked_response_message = message.into();
150 self
151 }
152 pub fn with_ignore_exceptions(mut self, value: bool) -> Self {
153 self.ignore_exceptions = value;
154 self
155 }
156 pub fn with_ignore_payment_required(mut self, value: bool) -> Self {
157 self.ignore_payment_required = value;
158 self
159 }
160 pub fn with_cache_ttl_seconds(mut self, ttl: u64) -> Self {
161 self.cache_ttl_seconds = ttl;
162 self
163 }
164 pub fn with_max_cache_size_bytes(mut self, bytes: u64) -> Self {
165 self.max_cache_size_bytes = bytes;
166 self
167 }
168
169 pub fn get_scopes(&self) -> Vec<String> {
172 let host =
173 extract_host(&self.graph_base_uri).unwrap_or_else(|| "graph.microsoft.com".to_string());
174 vec![format!("https://{host}/.default")]
175 }
176}
177
178fn extract_host(uri: &str) -> Option<String> {
182 let after_scheme = uri.split_once("://").map(|(_, rest)| rest).unwrap_or(uri);
183 let host = after_scheme.split(['/', '?', '#']).next()?;
184 if host.is_empty() {
185 None
186 } else {
187 Some(host.to_string())
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194
195 #[test]
196 fn new_applies_python_documented_defaults() {
197 let settings = PurviewSettings::new("Test App");
198 assert_eq!(settings.app_name, "Test App");
199 assert_eq!(settings.graph_base_uri, "https://graph.microsoft.com/v1.0/");
200 assert!(settings.tenant_id.is_none());
201 assert!(settings.purview_app_location.is_none());
202 assert_eq!(settings.blocked_prompt_message, "Prompt blocked by policy");
203 assert_eq!(
204 settings.blocked_response_message,
205 "Response blocked by policy"
206 );
207 assert!(!settings.ignore_exceptions);
208 assert!(!settings.ignore_payment_required);
209 assert_eq!(settings.cache_ttl_seconds, 14_400);
210 assert_eq!(settings.max_cache_size_bytes, 200 * 1024 * 1024);
211 }
212
213 #[test]
214 fn builders_override_defaults() {
215 let location = PurviewAppLocation::new(PurviewLocationType::Application, "app-123");
216 let settings = PurviewSettings::new("Test App")
217 .with_graph_base_uri("https://graph.microsoft-ppe.com")
218 .with_tenant_id("test-tenant-id")
219 .with_purview_app_location(location.clone());
220 assert_eq!(settings.graph_base_uri, "https://graph.microsoft-ppe.com");
221 assert_eq!(settings.tenant_id.as_deref(), Some("test-tenant-id"));
222 assert_eq!(settings.purview_app_location, Some(location));
223 }
224
225 #[test]
226 fn get_scopes_derives_default_suffix_from_graph_base_uri() {
227 let settings = PurviewSettings::new("Test App");
228 assert_eq!(
229 settings.get_scopes(),
230 vec!["https://graph.microsoft.com/.default".to_string()]
231 );
232 }
233
234 #[test]
235 fn get_scopes_derives_suffix_from_custom_graph_base_uri() {
236 let settings = PurviewSettings::new("Test App")
237 .with_graph_base_uri("https://graph.microsoft-ppe.com/v1.0/");
238 assert_eq!(
239 settings.get_scopes(),
240 vec!["https://graph.microsoft-ppe.com/.default".to_string()]
241 );
242 }
243
244 #[test]
245 fn get_policy_location_maps_all_three_location_types() {
246 let cases = [
247 (
248 PurviewLocationType::Application,
249 "microsoft.graph.policyLocationApplication",
250 ),
251 (
252 PurviewLocationType::Uri,
253 "microsoft.graph.policyLocationUrl",
254 ),
255 (
256 PurviewLocationType::Domain,
257 "microsoft.graph.policyLocationDomain",
258 ),
259 ];
260 for (location_type, expected) in cases {
261 let location = PurviewAppLocation::new(location_type, "value-1");
262 let policy_location = location.to_policy_location();
263 assert_eq!(policy_location.data_type, expected);
264 assert_eq!(policy_location.value, "value-1");
265 }
266 }
267
268 #[test]
269 fn extract_host_handles_scheme_path_and_bare_host() {
270 assert_eq!(
271 extract_host("https://graph.microsoft.com/v1.0/"),
272 Some("graph.microsoft.com".to_string())
273 );
274 assert_eq!(
275 extract_host("https://graph.microsoft-ppe.com"),
276 Some("graph.microsoft-ppe.com".to_string())
277 );
278 assert_eq!(
279 extract_host("graph.microsoft.com/v1.0/"),
280 Some("graph.microsoft.com".to_string())
281 );
282 }
283}