Skip to main content

ant_core/node/
events.rs

1use std::path::PathBuf;
2
3use serde::{Deserialize, Serialize};
4
5/// Structured events emitted by the daemon supervisor and long-running operations.
6///
7/// Serialized to JSON for SSE streaming at `GET /api/v1/events`.
8#[derive(Debug, Clone, Serialize, Deserialize)]
9#[serde(tag = "type", rename_all = "snake_case")]
10pub enum NodeEvent {
11    NodeStarting {
12        node_id: u32,
13    },
14    NodeStarted {
15        node_id: u32,
16        pid: u32,
17    },
18    NodeStopping {
19        node_id: u32,
20    },
21    NodeStopped {
22        node_id: u32,
23    },
24    NodeCrashed {
25        node_id: u32,
26        exit_code: Option<i32>,
27    },
28    NodeRestarting {
29        node_id: u32,
30        attempt: u32,
31    },
32    NodeErrored {
33        node_id: u32,
34        message: String,
35    },
36    DownloadStarted {
37        version: String,
38    },
39    DownloadProgress {
40        bytes: u64,
41        total: u64,
42    },
43    DownloadComplete {
44        version: String,
45        path: PathBuf,
46    },
47    /// Emitted when the supervisor detects that a node's on-disk binary has been
48    /// replaced by its auto-upgrade, ahead of the node process restarting.
49    UpgradeScheduled {
50        node_id: u32,
51        pending_version: String,
52    },
53    /// Emitted after the supervisor has respawned a node against its replaced binary and
54    /// observed the new version.
55    NodeUpgraded {
56        node_id: u32,
57        old_version: String,
58        new_version: String,
59    },
60}
61
62impl NodeEvent {
63    /// Returns the SSE event type name for this event.
64    pub fn event_type(&self) -> &'static str {
65        match self {
66            NodeEvent::NodeStarting { .. } => "node_starting",
67            NodeEvent::NodeStarted { .. } => "node_started",
68            NodeEvent::NodeStopping { .. } => "node_stopping",
69            NodeEvent::NodeStopped { .. } => "node_stopped",
70            NodeEvent::NodeCrashed { .. } => "node_crashed",
71            NodeEvent::NodeRestarting { .. } => "node_restarting",
72            NodeEvent::NodeErrored { .. } => "node_errored",
73            NodeEvent::DownloadStarted { .. } => "download_started",
74            NodeEvent::DownloadProgress { .. } => "download_progress",
75            NodeEvent::DownloadComplete { .. } => "download_complete",
76            NodeEvent::UpgradeScheduled { .. } => "upgrade_scheduled",
77            NodeEvent::NodeUpgraded { .. } => "node_upgraded",
78        }
79    }
80}
81
82/// Trait for receiving node lifecycle events.
83pub trait EventListener: Send + Sync {
84    fn on_event(&self, event: NodeEvent);
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn event_serializes_with_type_tag() {
93        let event = NodeEvent::NodeStarted {
94            node_id: 1,
95            pid: 1234,
96        };
97        let json = serde_json::to_string(&event).unwrap();
98        assert!(json.contains("\"type\":\"node_started\""));
99        assert!(json.contains("\"node_id\":1"));
100        assert!(json.contains("\"pid\":1234"));
101    }
102
103    #[test]
104    fn event_type_matches_serde_tag() {
105        let event = NodeEvent::NodeCrashed {
106            node_id: 2,
107            exit_code: Some(1),
108        };
109        assert_eq!(event.event_type(), "node_crashed");
110
111        // Verify the serde tag matches
112        let json = serde_json::to_string(&event).unwrap();
113        assert!(json.contains(&format!("\"type\":\"{}\"", event.event_type())));
114    }
115
116    #[test]
117    fn event_roundtrips() {
118        let event = NodeEvent::DownloadProgress {
119            bytes: 1024,
120            total: 4096,
121        };
122        let json = serde_json::to_string(&event).unwrap();
123        let deserialized: NodeEvent = serde_json::from_str(&json).unwrap();
124        assert_eq!(deserialized.event_type(), "download_progress");
125    }
126
127    #[test]
128    fn upgrade_scheduled_event_serializes() {
129        let event = NodeEvent::UpgradeScheduled {
130            node_id: 2,
131            pending_version: "0.10.11-rc.1".to_string(),
132        };
133        let json = serde_json::to_string(&event).unwrap();
134        assert!(json.contains("\"type\":\"upgrade_scheduled\""));
135        assert!(json.contains("\"node_id\":2"));
136        assert!(json.contains("\"pending_version\":\"0.10.11-rc.1\""));
137        assert_eq!(event.event_type(), "upgrade_scheduled");
138    }
139
140    #[test]
141    fn node_upgraded_event_serializes() {
142        let event = NodeEvent::NodeUpgraded {
143            node_id: 3,
144            old_version: "0.10.1".to_string(),
145            new_version: "0.10.11-rc.1".to_string(),
146        };
147        let json = serde_json::to_string(&event).unwrap();
148        assert!(json.contains("\"type\":\"node_upgraded\""));
149        assert!(json.contains("\"old_version\":\"0.10.1\""));
150        assert!(json.contains("\"new_version\":\"0.10.11-rc.1\""));
151        assert_eq!(event.event_type(), "node_upgraded");
152    }
153}