sentinel_agent_protocol/v2/
control.rs

1//! Control plane messages for Protocol v2.
2
3use serde::{Deserialize, Serialize};
4
5/// Request to cancel an in-flight request.
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct CancelRequest {
8    pub correlation_id: String,
9    pub reason: CancelReason,
10    pub timestamp_ms: u64,
11}
12
13impl CancelRequest {
14    pub fn new(correlation_id: impl Into<String>, reason: CancelReason) -> Self {
15        Self {
16            correlation_id: correlation_id.into(),
17            reason,
18            timestamp_ms: now_ms(),
19        }
20    }
21
22    pub fn timeout(correlation_id: impl Into<String>) -> Self {
23        Self::new(correlation_id, CancelReason::Timeout)
24    }
25}
26
27/// Reason for request cancellation.
28#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
29#[serde(rename_all = "snake_case", tag = "type")]
30pub enum CancelReason {
31    ClientDisconnect,
32    Timeout,
33    BlockedByAgent { agent_id: String },
34    UpstreamError,
35    ProxyShutdown,
36    Manual { reason: String },
37}
38
39/// Configuration update request.
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct ConfigUpdateRequest {
42    pub update_type: ConfigUpdateType,
43    pub request_id: String,
44    pub timestamp_ms: u64,
45}
46
47/// Type of configuration update.
48#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
49#[serde(rename_all = "snake_case", tag = "type")]
50pub enum ConfigUpdateType {
51    RequestReload,
52    RuleUpdate { rule_set: String, rules: Vec<RuleDefinition>, remove_rules: Vec<String> },
53    ListUpdate { list_id: String, add: Vec<String>, remove: Vec<String> },
54    RestartRequired { reason: String, grace_period_ms: u64 },
55    ConfigError { error: String, field: Option<String> },
56}
57
58/// A rule definition.
59#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
60pub struct RuleDefinition {
61    pub id: String,
62    pub priority: i32,
63    pub definition: serde_json::Value,
64    pub enabled: bool,
65    pub description: Option<String>,
66    #[serde(default)]
67    pub tags: Vec<String>,
68}
69
70/// Response to a configuration update request.
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct ConfigUpdateResponse {
73    pub request_id: String,
74    pub accepted: bool,
75    pub error: Option<String>,
76    pub timestamp_ms: u64,
77}
78
79impl ConfigUpdateResponse {
80    pub fn success(request_id: impl Into<String>) -> Self {
81        Self { request_id: request_id.into(), accepted: true, error: None, timestamp_ms: now_ms() }
82    }
83
84    pub fn failure(request_id: impl Into<String>, error: impl Into<String>) -> Self {
85        Self { request_id: request_id.into(), accepted: false, error: Some(error.into()), timestamp_ms: now_ms() }
86    }
87}
88
89/// Shutdown request.
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct ShutdownRequest {
92    pub reason: ShutdownReason,
93    pub grace_period_ms: u64,
94    pub timestamp_ms: u64,
95}
96
97/// Reason for shutdown.
98#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
99#[serde(rename_all = "snake_case")]
100pub enum ShutdownReason {
101    Graceful,
102    Immediate,
103    ConfigReload,
104    Upgrade,
105}
106
107/// Drain request.
108#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct DrainRequest {
110    pub duration_ms: u64,
111    pub reason: DrainReason,
112    pub timestamp_ms: u64,
113}
114
115/// Reason for draining.
116#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
117#[serde(rename_all = "snake_case")]
118pub enum DrainReason {
119    ConfigReload,
120    Maintenance,
121    HealthCheckFailed,
122    Manual,
123}
124
125/// Log message from agent to proxy.
126#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct LogMessage {
128    pub level: LogLevel,
129    pub message: String,
130    pub correlation_id: Option<String>,
131    #[serde(default)]
132    pub fields: std::collections::HashMap<String, serde_json::Value>,
133    pub timestamp_ms: u64,
134}
135
136/// Log level.
137#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
138#[serde(rename_all = "lowercase")]
139pub enum LogLevel {
140    Debug,
141    Info,
142    Warn,
143    Error,
144}
145
146fn now_ms() -> u64 {
147    std::time::SystemTime::now()
148        .duration_since(std::time::UNIX_EPOCH)
149        .map(|d| d.as_millis() as u64)
150        .unwrap_or(0)
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn test_cancel_request() {
159        let cancel = CancelRequest::timeout("req-123");
160        assert_eq!(cancel.correlation_id, "req-123");
161        assert_eq!(cancel.reason, CancelReason::Timeout);
162    }
163
164    #[test]
165    fn test_config_update_response() {
166        let success = ConfigUpdateResponse::success("update-1");
167        assert!(success.accepted);
168
169        let failure = ConfigUpdateResponse::failure("update-2", "Error");
170        assert!(!failure.accepted);
171    }
172}