sentinel_proxy/agents/mod.rs
1//! Agent integration module for Sentinel proxy.
2//!
3//! This module provides integration with external processing agents for WAF,
4//! auth, rate limiting, and custom logic. It implements the SPOE-inspired
5//! protocol with bounded behavior and failure isolation.
6//!
7//! # Architecture
8//!
9//! - [`AgentManager`]: Coordinates all agents, handles routing to appropriate agents
10//! - [`Agent`]: Individual agent with connection, circuit breaker, and metrics
11//! - [`AgentConnectionPool`]: Connection pooling for efficient connection reuse
12//! - [`AgentDecision`]: Combined result from processing through agents
13//! - [`AgentCallContext`]: Request context passed to agents
14//!
15//! # Example
16//!
17//! ```ignore
18//! use sentinel_proxy::agents::{AgentManager, AgentCallContext};
19//!
20//! let manager = AgentManager::new(agent_configs, 1000).await?;
21//! manager.initialize().await?;
22//!
23//! let decision = manager.process_request_headers(&ctx, &headers, &["waf", "auth"]).await?;
24//! if !decision.is_allow() {
25//! // Handle block/redirect/challenge
26//! }
27//! ```
28
29mod agent;
30mod context;
31mod decision;
32mod manager;
33mod metrics;
34mod pool;
35
36pub use agent::Agent;
37pub use context::AgentCallContext;
38pub use decision::{AgentAction, AgentDecision};
39pub use manager::AgentManager;
40pub use metrics::AgentMetrics;
41pub use pool::AgentConnectionPool;
42
43#[cfg(test)]
44mod tests {
45 use super::*;
46 use sentinel_agent_protocol::HeaderOp;
47 use sentinel_common::types::CircuitBreakerConfig;
48 use sentinel_common::CircuitBreaker;
49 use std::time::Duration;
50
51 #[tokio::test]
52 async fn test_agent_decision_merge() {
53 let mut decision1 = AgentDecision::default_allow();
54 decision1.request_headers.push(HeaderOp::Set {
55 name: "X-Test".to_string(),
56 value: "1".to_string(),
57 });
58
59 let decision2 = AgentDecision::block(403, "Forbidden");
60
61 decision1.merge(decision2);
62 assert!(!decision1.is_allow());
63 }
64
65 #[tokio::test]
66 async fn test_circuit_breaker() {
67 let config = CircuitBreakerConfig {
68 failure_threshold: 3,
69 success_threshold: 2,
70 timeout_seconds: 1,
71 half_open_max_requests: 1,
72 };
73
74 let breaker = CircuitBreaker::new(config);
75 assert!(breaker.is_closed().await);
76
77 // Record failures to open
78 for _ in 0..3 {
79 breaker.record_failure().await;
80 }
81 assert!(!breaker.is_closed().await);
82
83 // Wait for timeout
84 tokio::time::sleep(Duration::from_secs(2)).await;
85 assert!(breaker.is_closed().await); // Should be half-open now
86 }
87}