Skip to main content

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 {
53        rule_set: String,
54        rules: Vec<RuleDefinition>,
55        remove_rules: Vec<String>,
56    },
57    ListUpdate {
58        list_id: String,
59        add: Vec<String>,
60        remove: Vec<String>,
61    },
62    RestartRequired {
63        reason: String,
64        grace_period_ms: u64,
65    },
66    ConfigError {
67        error: String,
68        field: Option<String>,
69    },
70}
71
72/// A rule definition.
73#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
74pub struct RuleDefinition {
75    pub id: String,
76    pub priority: i32,
77    pub definition: serde_json::Value,
78    pub enabled: bool,
79    pub description: Option<String>,
80    #[serde(default)]
81    pub tags: Vec<String>,
82}
83
84/// Response to a configuration update request.
85#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct ConfigUpdateResponse {
87    pub request_id: String,
88    pub accepted: bool,
89    pub error: Option<String>,
90    pub timestamp_ms: u64,
91}
92
93impl ConfigUpdateResponse {
94    pub fn success(request_id: impl Into<String>) -> Self {
95        Self {
96            request_id: request_id.into(),
97            accepted: true,
98            error: None,
99            timestamp_ms: now_ms(),
100        }
101    }
102
103    pub fn failure(request_id: impl Into<String>, error: impl Into<String>) -> Self {
104        Self {
105            request_id: request_id.into(),
106            accepted: false,
107            error: Some(error.into()),
108            timestamp_ms: now_ms(),
109        }
110    }
111}
112
113/// Shutdown request.
114#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct ShutdownRequest {
116    pub reason: ShutdownReason,
117    pub grace_period_ms: u64,
118    pub timestamp_ms: u64,
119}
120
121/// Reason for shutdown.
122#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
123#[serde(rename_all = "snake_case")]
124pub enum ShutdownReason {
125    Graceful,
126    Immediate,
127    ConfigReload,
128    Upgrade,
129}
130
131/// Drain request.
132#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct DrainRequest {
134    pub duration_ms: u64,
135    pub reason: DrainReason,
136    pub timestamp_ms: u64,
137}
138
139/// Reason for draining.
140#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
141#[serde(rename_all = "snake_case")]
142pub enum DrainReason {
143    ConfigReload,
144    Maintenance,
145    HealthCheckFailed,
146    Manual,
147}
148
149/// Log message from agent to proxy.
150#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct LogMessage {
152    pub level: LogLevel,
153    pub message: String,
154    pub correlation_id: Option<String>,
155    #[serde(default)]
156    pub fields: std::collections::HashMap<String, serde_json::Value>,
157    pub timestamp_ms: u64,
158}
159
160/// Log level.
161#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
162#[serde(rename_all = "lowercase")]
163pub enum LogLevel {
164    Debug,
165    Info,
166    Warn,
167    Error,
168}
169
170fn now_ms() -> u64 {
171    std::time::SystemTime::now()
172        .duration_since(std::time::UNIX_EPOCH)
173        .map(|d| d.as_millis() as u64)
174        .unwrap_or(0)
175}
176
177#[cfg(test)]
178mod tests {
179    use super::*;
180
181    #[test]
182    fn test_cancel_request() {
183        let cancel = CancelRequest::timeout("req-123");
184        assert_eq!(cancel.correlation_id, "req-123");
185        assert_eq!(cancel.reason, CancelReason::Timeout);
186    }
187
188    #[test]
189    fn test_config_update_response() {
190        let success = ConfigUpdateResponse::success("update-1");
191        assert!(success.accepted);
192
193        let failure = ConfigUpdateResponse::failure("update-2", "Error");
194        assert!(!failure.accepted);
195    }
196}