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}