use crate::models::PolicyLocation;
pub const DEFAULT_GRAPH_BASE_URI: &str = "https://graph.microsoft.com/v1.0/";
pub const DEFAULT_BLOCKED_PROMPT_MESSAGE: &str = "Prompt blocked by policy";
pub const DEFAULT_BLOCKED_RESPONSE_MESSAGE: &str = "Response blocked by policy";
pub const DEFAULT_CACHE_TTL_SECONDS: u64 = 14_400;
pub const DEFAULT_MAX_CACHE_SIZE_BYTES: u64 = 200 * 1024 * 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PurviewLocationType {
Application,
Uri,
Domain,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PurviewAppLocation {
pub location_type: PurviewLocationType,
pub location_value: String,
}
impl PurviewAppLocation {
pub fn new(location_type: PurviewLocationType, location_value: impl Into<String>) -> Self {
Self {
location_type,
location_value: location_value.into(),
}
}
pub fn to_policy_location(&self) -> PolicyLocation {
let data_type = match self.location_type {
PurviewLocationType::Application => "microsoft.graph.policyLocationApplication",
PurviewLocationType::Uri => "microsoft.graph.policyLocationUrl",
PurviewLocationType::Domain => "microsoft.graph.policyLocationDomain",
};
PolicyLocation {
data_type: data_type.to_string(),
value: self.location_value.clone(),
}
}
}
#[derive(Debug, Clone)]
pub struct PurviewSettings {
pub app_name: String,
pub app_version: Option<String>,
pub tenant_id: Option<String>,
pub purview_app_location: Option<PurviewAppLocation>,
pub graph_base_uri: String,
pub blocked_prompt_message: String,
pub blocked_response_message: String,
pub ignore_exceptions: bool,
pub ignore_payment_required: bool,
pub cache_ttl_seconds: u64,
pub max_cache_size_bytes: u64,
}
impl PurviewSettings {
pub fn new(app_name: impl Into<String>) -> Self {
Self {
app_name: app_name.into(),
app_version: None,
tenant_id: None,
purview_app_location: None,
graph_base_uri: DEFAULT_GRAPH_BASE_URI.to_string(),
blocked_prompt_message: DEFAULT_BLOCKED_PROMPT_MESSAGE.to_string(),
blocked_response_message: DEFAULT_BLOCKED_RESPONSE_MESSAGE.to_string(),
ignore_exceptions: false,
ignore_payment_required: false,
cache_ttl_seconds: DEFAULT_CACHE_TTL_SECONDS,
max_cache_size_bytes: DEFAULT_MAX_CACHE_SIZE_BYTES,
}
}
pub fn with_app_version(mut self, version: impl Into<String>) -> Self {
self.app_version = Some(version.into());
self
}
pub fn with_tenant_id(mut self, tenant_id: impl Into<String>) -> Self {
self.tenant_id = Some(tenant_id.into());
self
}
pub fn with_purview_app_location(mut self, location: PurviewAppLocation) -> Self {
self.purview_app_location = Some(location);
self
}
pub fn with_graph_base_uri(mut self, uri: impl Into<String>) -> Self {
self.graph_base_uri = uri.into();
self
}
pub fn with_blocked_prompt_message(mut self, message: impl Into<String>) -> Self {
self.blocked_prompt_message = message.into();
self
}
pub fn with_blocked_response_message(mut self, message: impl Into<String>) -> Self {
self.blocked_response_message = message.into();
self
}
pub fn with_ignore_exceptions(mut self, value: bool) -> Self {
self.ignore_exceptions = value;
self
}
pub fn with_ignore_payment_required(mut self, value: bool) -> Self {
self.ignore_payment_required = value;
self
}
pub fn with_cache_ttl_seconds(mut self, ttl: u64) -> Self {
self.cache_ttl_seconds = ttl;
self
}
pub fn with_max_cache_size_bytes(mut self, bytes: u64) -> Self {
self.max_cache_size_bytes = bytes;
self
}
pub fn get_scopes(&self) -> Vec<String> {
let host =
extract_host(&self.graph_base_uri).unwrap_or_else(|| "graph.microsoft.com".to_string());
vec![format!("https://{host}/.default")]
}
}
fn extract_host(uri: &str) -> Option<String> {
let after_scheme = uri.split_once("://").map(|(_, rest)| rest).unwrap_or(uri);
let host = after_scheme.split(['/', '?', '#']).next()?;
if host.is_empty() {
None
} else {
Some(host.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_applies_python_documented_defaults() {
let settings = PurviewSettings::new("Test App");
assert_eq!(settings.app_name, "Test App");
assert_eq!(settings.graph_base_uri, "https://graph.microsoft.com/v1.0/");
assert!(settings.tenant_id.is_none());
assert!(settings.purview_app_location.is_none());
assert_eq!(settings.blocked_prompt_message, "Prompt blocked by policy");
assert_eq!(
settings.blocked_response_message,
"Response blocked by policy"
);
assert!(!settings.ignore_exceptions);
assert!(!settings.ignore_payment_required);
assert_eq!(settings.cache_ttl_seconds, 14_400);
assert_eq!(settings.max_cache_size_bytes, 200 * 1024 * 1024);
}
#[test]
fn builders_override_defaults() {
let location = PurviewAppLocation::new(PurviewLocationType::Application, "app-123");
let settings = PurviewSettings::new("Test App")
.with_graph_base_uri("https://graph.microsoft-ppe.com")
.with_tenant_id("test-tenant-id")
.with_purview_app_location(location.clone());
assert_eq!(settings.graph_base_uri, "https://graph.microsoft-ppe.com");
assert_eq!(settings.tenant_id.as_deref(), Some("test-tenant-id"));
assert_eq!(settings.purview_app_location, Some(location));
}
#[test]
fn get_scopes_derives_default_suffix_from_graph_base_uri() {
let settings = PurviewSettings::new("Test App");
assert_eq!(
settings.get_scopes(),
vec!["https://graph.microsoft.com/.default".to_string()]
);
}
#[test]
fn get_scopes_derives_suffix_from_custom_graph_base_uri() {
let settings = PurviewSettings::new("Test App")
.with_graph_base_uri("https://graph.microsoft-ppe.com/v1.0/");
assert_eq!(
settings.get_scopes(),
vec!["https://graph.microsoft-ppe.com/.default".to_string()]
);
}
#[test]
fn get_policy_location_maps_all_three_location_types() {
let cases = [
(
PurviewLocationType::Application,
"microsoft.graph.policyLocationApplication",
),
(
PurviewLocationType::Uri,
"microsoft.graph.policyLocationUrl",
),
(
PurviewLocationType::Domain,
"microsoft.graph.policyLocationDomain",
),
];
for (location_type, expected) in cases {
let location = PurviewAppLocation::new(location_type, "value-1");
let policy_location = location.to_policy_location();
assert_eq!(policy_location.data_type, expected);
assert_eq!(policy_location.value, "value-1");
}
}
#[test]
fn extract_host_handles_scheme_path_and_bare_host() {
assert_eq!(
extract_host("https://graph.microsoft.com/v1.0/"),
Some("graph.microsoft.com".to_string())
);
assert_eq!(
extract_host("https://graph.microsoft-ppe.com"),
Some("graph.microsoft-ppe.com".to_string())
);
assert_eq!(
extract_host("graph.microsoft.com/v1.0/"),
Some("graph.microsoft.com".to_string())
);
}
}