mod common;
use common::LogRecord;
use policy_rs::proto::tero::policy::v1::{
LogField, LogMatcher, LogTarget, Policy as ProtoPolicy, log_matcher,
};
use policy_rs::{
EvaluateResult, Policy, PolicyCallback, PolicyEngine, PolicyError, PolicyProvider,
PolicyRegistry,
};
use std::sync::{Arc, RwLock};
struct ApiPolicyProvider {
endpoint: String,
policies: RwLock<Vec<Policy>>,
subscribers: RwLock<Vec<PolicyCallback>>,
}
impl ApiPolicyProvider {
fn new(endpoint: &str) -> Self {
Self {
endpoint: endpoint.to_string(),
policies: RwLock::new(Vec::new()),
subscribers: RwLock::new(Vec::new()),
}
}
fn fetch_from_api(&self) -> Result<Vec<Policy>, PolicyError> {
println!("Fetching policies from API: {}", self.endpoint);
let policies = vec![
self.create_policy("api-drop-debug", "DEBUG", "none"),
self.create_policy("api-sample-info", "INFO", "50%"),
self.create_policy("api-keep-error", "ERROR", "all"),
];
Ok(policies)
}
fn create_policy(&self, id: &str, severity: &str, keep: &str) -> Policy {
let matcher = LogMatcher {
field: Some(log_matcher::Field::LogField(LogField::SeverityText.into())),
r#match: Some(log_matcher::Match::Exact(severity.to_string())),
negate: false,
case_insensitive: false,
};
let log_target = LogTarget {
r#match: vec![matcher],
keep: keep.to_string(),
transform: None,
sample_key: None,
};
let proto = ProtoPolicy {
id: id.to_string(),
name: format!("Policy from {}", self.endpoint),
enabled: true,
target: Some(policy_rs::proto::tero::policy::v1::policy::Target::Log(
log_target,
)),
..Default::default()
};
Policy::new(proto)
}
pub fn simulate_update(&self, new_policies: Vec<Policy>) {
println!("API update received: {} policies", new_policies.len());
{
let mut policies = self.policies.write().unwrap();
*policies = new_policies.clone();
}
let subscribers = self.subscribers.read().unwrap();
for callback in subscribers.iter() {
callback(new_policies.clone());
}
}
}
impl ApiPolicyProvider {
fn load(&self) -> Result<Vec<Policy>, PolicyError> {
let policies = self.fetch_from_api()?;
let mut cached = self.policies.write().unwrap();
*cached = policies.clone();
Ok(policies)
}
}
impl PolicyProvider for ApiPolicyProvider {
fn subscribe(&self, callback: PolicyCallback) -> Result<(), PolicyError> {
let policies = self.load()?;
callback(policies);
let mut subscribers = self.subscribers.write().unwrap();
subscribers.push(callback);
Ok(())
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let registry = PolicyRegistry::new();
let provider = Arc::new(ApiPolicyProvider::new("https://api.example.com/policies"));
registry.subscribe(provider.as_ref())?;
println!("Subscribed to API provider");
let engine = PolicyEngine::new();
println!("\n--- Initial Evaluation ---");
let snapshot = registry.snapshot();
println!("Policies loaded: {}", snapshot.len());
let logs = vec![
LogRecord::new("Debug trace", "DEBUG"),
LogRecord::new("User action", "INFO"),
LogRecord::new("Something failed", "ERROR"),
];
for log in &logs {
let result = engine.evaluate(&snapshot, log)?;
print!(
"[{}] {}: ",
log.severity.as_deref().unwrap_or(""),
log.body.as_deref().unwrap_or("")
);
match result {
EvaluateResult::NoMatch => println!("pass through"),
EvaluateResult::Keep { policy_id, .. } => println!("KEEP ({})", policy_id),
EvaluateResult::Drop { policy_id } => println!("DROP ({})", policy_id),
EvaluateResult::Sample {
policy_id,
percentage,
keep,
..
} => {
println!(
"SAMPLE {}% ({}) -> {}",
percentage,
policy_id,
if keep { "kept" } else { "dropped" }
)
}
EvaluateResult::RateLimit {
policy_id, allowed, ..
} => {
println!(
"RATE LIMIT ({}) -> {}",
policy_id,
if allowed { "allowed" } else { "throttled" }
)
}
}
}
println!("\n--- Simulating API Update ---");
provider.simulate_update(vec![
provider.create_policy("api-drop-debug", "DEBUG", "none"),
provider.create_policy("api-drop-info", "INFO", "none"), provider.create_policy("api-keep-error", "ERROR", "all"),
provider.create_policy("api-keep-warn", "WARN", "all"), ]);
let new_snapshot = registry.snapshot();
println!("Policies after update: {}", new_snapshot.len());
println!("\n--- Evaluation After Update ---");
for log in &logs {
let result = engine.evaluate(&new_snapshot, log)?;
print!(
"[{}] {}: ",
log.severity.as_deref().unwrap_or(""),
log.body.as_deref().unwrap_or("")
);
match result {
EvaluateResult::NoMatch => println!("pass through"),
EvaluateResult::Keep { policy_id, .. } => println!("KEEP ({})", policy_id),
EvaluateResult::Drop { policy_id } => println!("DROP ({})", policy_id),
EvaluateResult::Sample {
policy_id,
percentage,
keep,
..
} => {
println!(
"SAMPLE {}% ({}) -> {}",
percentage,
policy_id,
if keep { "kept" } else { "dropped" }
)
}
EvaluateResult::RateLimit {
policy_id, allowed, ..
} => {
println!(
"RATE LIMIT ({}) -> {}",
policy_id,
if allowed { "allowed" } else { "throttled" }
)
}
}
}
Ok(())
}