use crate::types::{AgentId, CommunicationError, MessageType};
#[derive(Debug, Clone)]
pub struct CommunicationRequest {
pub sender: AgentId,
pub recipient: AgentId,
pub message_type: MessageType,
pub topic: Option<String>,
}
#[derive(Debug, Clone)]
pub enum CommunicationCondition {
SenderIs(AgentId),
RecipientIs(AgentId),
Always,
All(Vec<CommunicationCondition>),
Any(Vec<CommunicationCondition>),
}
#[derive(Debug, Clone)]
pub enum CommunicationEffect {
Allow,
Deny { reason: String },
}
#[derive(Debug, Clone)]
pub struct CommunicationPolicyRule {
pub id: String,
pub name: String,
pub condition: CommunicationCondition,
pub effect: CommunicationEffect,
pub priority: u32,
}
#[derive(Debug, Clone)]
pub struct CommunicationPolicyGate {
rules: Vec<CommunicationPolicyRule>,
default_allow: bool,
}
impl Default for CommunicationPolicyGate {
fn default() -> Self {
Self {
rules: Vec::new(),
default_allow: true,
}
}
}
impl CommunicationPolicyGate {
pub fn new(mut rules: Vec<CommunicationPolicyRule>) -> Self {
rules.sort_by(|a, b| b.priority.cmp(&a.priority));
Self {
rules,
default_allow: true,
}
}
pub fn permissive() -> Self {
Self::default()
}
pub fn deny_by_default(rules: Vec<CommunicationPolicyRule>) -> Self {
let mut gate = Self::new(rules);
gate.default_allow = false;
gate
}
pub fn evaluate(&self, request: &CommunicationRequest) -> Result<(), CommunicationError> {
for rule in &self.rules {
if self.matches_condition(&rule.condition, request) {
return match &rule.effect {
CommunicationEffect::Allow => Ok(()),
CommunicationEffect::Deny { reason } => Err(CommunicationError::PolicyDenied {
reason: format!("[{}] {}", rule.name, reason),
}),
};
}
}
if self.default_allow {
Ok(())
} else {
Err(CommunicationError::PolicyDenied {
reason: "No matching rule and default is deny".to_string(),
})
}
}
fn matches_condition(
&self,
condition: &CommunicationCondition,
request: &CommunicationRequest,
) -> bool {
match condition {
CommunicationCondition::SenderIs(id) => request.sender == *id,
CommunicationCondition::RecipientIs(id) => request.recipient == *id,
CommunicationCondition::Always => true,
CommunicationCondition::All(conditions) => conditions
.iter()
.all(|c| self.matches_condition(c, request)),
CommunicationCondition::Any(conditions) => conditions
.iter()
.any(|c| self.matches_condition(c, request)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::RequestId;
fn make_request(sender: AgentId, recipient: AgentId) -> CommunicationRequest {
CommunicationRequest {
sender,
recipient,
message_type: MessageType::Request(RequestId::new()),
topic: None,
}
}
#[test]
fn test_permissive_gate_allows_all() {
let gate = CommunicationPolicyGate::permissive();
let req = make_request(AgentId::new(), AgentId::new());
assert!(gate.evaluate(&req).is_ok());
}
#[test]
fn test_deny_by_default_denies_without_rules() {
let gate = CommunicationPolicyGate::deny_by_default(vec![]);
let req = make_request(AgentId::new(), AgentId::new());
let err = gate.evaluate(&req).unwrap_err();
assert!(matches!(err, CommunicationError::PolicyDenied { .. }));
}
#[test]
fn test_deny_rule_blocks_sender() {
let blocked = AgentId::new();
let allowed = AgentId::new();
let recipient = AgentId::new();
let gate = CommunicationPolicyGate::new(vec![CommunicationPolicyRule {
id: "r1".into(),
name: "block-sender".into(),
condition: CommunicationCondition::SenderIs(blocked),
effect: CommunicationEffect::Deny {
reason: "blocked".into(),
},
priority: 10,
}]);
let blocked_req = make_request(blocked, recipient);
assert!(gate.evaluate(&blocked_req).is_err());
let allowed_req = make_request(allowed, recipient);
assert!(gate.evaluate(&allowed_req).is_ok());
}
#[test]
fn test_priority_ordering() {
let agent = AgentId::new();
let recipient = AgentId::new();
let gate = CommunicationPolicyGate::new(vec![
CommunicationPolicyRule {
id: "allow".into(),
name: "low-allow".into(),
condition: CommunicationCondition::SenderIs(agent),
effect: CommunicationEffect::Allow,
priority: 1,
},
CommunicationPolicyRule {
id: "deny".into(),
name: "high-deny".into(),
condition: CommunicationCondition::SenderIs(agent),
effect: CommunicationEffect::Deny {
reason: "denied".into(),
},
priority: 100,
},
]);
let req = make_request(agent, recipient);
assert!(gate.evaluate(&req).is_err());
}
#[test]
fn test_all_condition() {
let sender = AgentId::new();
let recipient = AgentId::new();
let other_recipient = AgentId::new();
let gate = CommunicationPolicyGate::new(vec![CommunicationPolicyRule {
id: "r1".into(),
name: "all-match".into(),
condition: CommunicationCondition::All(vec![
CommunicationCondition::SenderIs(sender),
CommunicationCondition::RecipientIs(recipient),
]),
effect: CommunicationEffect::Deny {
reason: "both match".into(),
},
priority: 10,
}]);
let req = make_request(sender, recipient);
assert!(gate.evaluate(&req).is_err());
let partial_req = make_request(sender, other_recipient);
assert!(gate.evaluate(&partial_req).is_ok());
}
#[test]
fn test_any_condition() {
let agent_a = AgentId::new();
let agent_b = AgentId::new();
let agent_c = AgentId::new();
let recipient = AgentId::new();
let gate = CommunicationPolicyGate::new(vec![CommunicationPolicyRule {
id: "r1".into(),
name: "any-match".into(),
condition: CommunicationCondition::Any(vec![
CommunicationCondition::SenderIs(agent_a),
CommunicationCondition::SenderIs(agent_b),
]),
effect: CommunicationEffect::Deny {
reason: "either match".into(),
},
priority: 10,
}]);
assert!(gate.evaluate(&make_request(agent_a, recipient)).is_err());
assert!(gate.evaluate(&make_request(agent_b, recipient)).is_err());
assert!(gate.evaluate(&make_request(agent_c, recipient)).is_ok());
}
}