sentinel_proxy/agents/
decision.rs1use std::collections::HashMap;
4
5use sentinel_agent_protocol::{AgentResponse, AuditMetadata, Decision, HeaderOp};
6
7#[derive(Debug, Clone)]
9pub struct AgentDecision {
10 pub action: AgentAction,
12 pub request_headers: Vec<HeaderOp>,
14 pub response_headers: Vec<HeaderOp>,
16 pub audit: Vec<AuditMetadata>,
18 pub routing_metadata: HashMap<String, String>,
20}
21
22#[derive(Debug, Clone)]
24pub enum AgentAction {
25 Allow,
27 Block {
29 status: u16,
30 body: Option<String>,
31 headers: Option<HashMap<String, String>>,
32 },
33 Redirect { url: String, status: u16 },
35 Challenge {
37 challenge_type: String,
38 params: HashMap<String, String>,
39 },
40}
41
42impl AgentDecision {
43 pub fn default_allow() -> Self {
45 Self {
46 action: AgentAction::Allow,
47 request_headers: Vec::new(),
48 response_headers: Vec::new(),
49 audit: Vec::new(),
50 routing_metadata: HashMap::new(),
51 }
52 }
53
54 pub fn block(status: u16, message: &str) -> Self {
56 Self {
57 action: AgentAction::Block {
58 status,
59 body: Some(message.to_string()),
60 headers: None,
61 },
62 request_headers: Vec::new(),
63 response_headers: Vec::new(),
64 audit: Vec::new(),
65 routing_metadata: HashMap::new(),
66 }
67 }
68
69 pub fn is_allow(&self) -> bool {
71 matches!(self.action, AgentAction::Allow)
72 }
73
74 pub fn merge(&mut self, other: AgentDecision) {
79 if !other.is_allow() {
81 self.action = other.action;
82 }
83
84 self.request_headers.extend(other.request_headers);
86 self.response_headers.extend(other.response_headers);
87
88 self.audit.extend(other.audit);
90
91 self.routing_metadata.extend(other.routing_metadata);
93 }
94}
95
96impl From<AgentResponse> for AgentDecision {
97 fn from(response: AgentResponse) -> Self {
98 let action = match response.decision {
99 Decision::Allow => AgentAction::Allow,
100 Decision::Block {
101 status,
102 body,
103 headers,
104 } => AgentAction::Block {
105 status,
106 body,
107 headers,
108 },
109 Decision::Redirect { url, status } => AgentAction::Redirect { url, status },
110 Decision::Challenge {
111 challenge_type,
112 params,
113 } => AgentAction::Challenge {
114 challenge_type,
115 params,
116 },
117 };
118
119 Self {
120 action,
121 request_headers: response.request_headers,
122 response_headers: response.response_headers,
123 audit: vec![response.audit],
124 routing_metadata: response.routing_metadata,
125 }
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn test_agent_decision_merge() {
135 let mut decision1 = AgentDecision::default_allow();
136 decision1.request_headers.push(HeaderOp::Set {
137 name: "X-Test".to_string(),
138 value: "1".to_string(),
139 });
140
141 let decision2 = AgentDecision::block(403, "Forbidden");
142
143 decision1.merge(decision2);
144 assert!(!decision1.is_allow());
145 }
146}