use std::collections::HashMap;
use zentinel_agent_protocol::{AgentResponse, AuditMetadata, BodyMutation, Decision, HeaderOp};
#[derive(Debug, Clone)]
pub struct AgentDecision {
pub action: AgentAction,
pub request_headers: Vec<HeaderOp>,
pub response_headers: Vec<HeaderOp>,
pub audit: Vec<AuditMetadata>,
pub routing_metadata: HashMap<String, String>,
pub needs_more: bool,
pub request_body_mutation: Option<BodyMutation>,
pub response_body_mutation: Option<BodyMutation>,
}
#[derive(Debug, Clone)]
pub enum AgentAction {
Allow,
Block {
status: u16,
body: Option<String>,
headers: Option<HashMap<String, String>>,
},
Redirect { url: String, status: u16 },
Challenge {
challenge_type: String,
params: HashMap<String, String>,
},
}
impl AgentDecision {
pub fn default_allow() -> Self {
Self {
action: AgentAction::Allow,
request_headers: Vec::new(),
response_headers: Vec::new(),
audit: Vec::new(),
routing_metadata: HashMap::new(),
needs_more: false,
request_body_mutation: None,
response_body_mutation: None,
}
}
pub fn block(status: u16, message: &str) -> Self {
Self {
action: AgentAction::Block {
status,
body: Some(message.to_string()),
headers: None,
},
request_headers: Vec::new(),
response_headers: Vec::new(),
audit: Vec::new(),
routing_metadata: HashMap::new(),
needs_more: false,
request_body_mutation: None,
response_body_mutation: None,
}
}
pub fn is_allow(&self) -> bool {
matches!(self.action, AgentAction::Allow)
}
pub fn merge(&mut self, other: AgentDecision) {
if !other.is_allow() {
self.action = other.action;
}
self.request_headers.extend(other.request_headers);
self.response_headers.extend(other.response_headers);
self.audit.extend(other.audit);
self.routing_metadata.extend(other.routing_metadata);
if other.needs_more {
self.needs_more = true;
}
if other.request_body_mutation.is_some() {
self.request_body_mutation = other.request_body_mutation;
}
if other.response_body_mutation.is_some() {
self.response_body_mutation = other.response_body_mutation;
}
}
}
impl From<AgentResponse> for AgentDecision {
fn from(response: AgentResponse) -> Self {
let action = match response.decision {
Decision::Allow => AgentAction::Allow,
Decision::Block {
status,
body,
headers,
} => AgentAction::Block {
status,
body,
headers,
},
Decision::Redirect { url, status } => AgentAction::Redirect { url, status },
Decision::Challenge {
challenge_type,
params,
} => AgentAction::Challenge {
challenge_type,
params,
},
};
Self {
action,
request_headers: response.request_headers,
response_headers: response.response_headers,
audit: vec![response.audit],
routing_metadata: response.routing_metadata,
needs_more: response.needs_more,
request_body_mutation: response.request_body_mutation,
response_body_mutation: response.response_body_mutation,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_agent_decision_merge() {
let mut decision1 = AgentDecision::default_allow();
decision1.request_headers.push(HeaderOp::Set {
name: "X-Test".to_string(),
value: "1".to_string(),
});
let decision2 = AgentDecision::block(403, "Forbidden");
decision1.merge(decision2);
assert!(!decision1.is_allow());
}
}