Skip to main content

libgrite_ipc/
notifications.rs

1//! Notification types for pub/sub
2//!
3//! The daemon emits these notifications asynchronously.
4//! Clients should treat unknown variants as ignorable.
5
6use rkyv::{Archive, Deserialize, Serialize};
7
8/// Notifications emitted by the daemon
9#[derive(Archive, Serialize, Deserialize, Debug, Clone)]
10#[rkyv(derive(Debug))]
11pub enum Notification {
12    /// An event was applied to an issue
13    EventApplied {
14        /// Issue ID (hex-encoded)
15        issue_id: String,
16        /// Event ID (hex-encoded)
17        event_id: String,
18        /// Timestamp in milliseconds since Unix epoch
19        ts_unix_ms: u64,
20    },
21
22    /// WAL was synced with a remote
23    WalSynced {
24        /// New WAL head commit hash
25        wal_head: String,
26        /// Remote name (e.g., "origin")
27        remote: String,
28    },
29
30    /// A lock changed state
31    LockChanged {
32        /// Resource being locked (e.g., "path:docs/")
33        resource: String,
34        /// Lock owner identifier
35        owner: String,
36        /// When the lock expires (0 = released)
37        expires_unix_ms: u64,
38    },
39
40    /// A snapshot was created
41    SnapshotCreated {
42        /// Snapshot ref (e.g., "refs/grite/snapshots/1700000000000")
43        snapshot_ref: String,
44    },
45
46    /// Worker started for a repository
47    WorkerStarted {
48        /// Repository root path
49        repo_root: String,
50        /// Actor ID (hex-encoded)
51        actor_id: String,
52    },
53
54    /// Worker stopped for a repository
55    WorkerStopped {
56        /// Repository root path
57        repo_root: String,
58        /// Actor ID (hex-encoded)
59        actor_id: String,
60        /// Reason for stopping
61        reason: String,
62    },
63}
64
65impl Notification {
66    /// Get the notification type as a string (for filtering)
67    pub fn notification_type(&self) -> &'static str {
68        match self {
69            Notification::EventApplied { .. } => "EventApplied",
70            Notification::WalSynced { .. } => "WalSynced",
71            Notification::LockChanged { .. } => "LockChanged",
72            Notification::SnapshotCreated { .. } => "SnapshotCreated",
73            Notification::WorkerStarted { .. } => "WorkerStarted",
74            Notification::WorkerStopped { .. } => "WorkerStopped",
75        }
76    }
77
78    /// Create an EventApplied notification
79    pub fn event_applied(issue_id: String, event_id: String, ts_unix_ms: u64) -> Self {
80        Notification::EventApplied {
81            issue_id,
82            event_id,
83            ts_unix_ms,
84        }
85    }
86
87    /// Create a WalSynced notification
88    pub fn wal_synced(wal_head: String, remote: String) -> Self {
89        Notification::WalSynced { wal_head, remote }
90    }
91
92    /// Create a LockChanged notification
93    pub fn lock_changed(resource: String, owner: String, expires_unix_ms: u64) -> Self {
94        Notification::LockChanged {
95            resource,
96            owner,
97            expires_unix_ms,
98        }
99    }
100
101    /// Create a SnapshotCreated notification
102    pub fn snapshot_created(snapshot_ref: String) -> Self {
103        Notification::SnapshotCreated { snapshot_ref }
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn test_notification_type() {
113        let n = Notification::event_applied("issue1".to_string(), "event1".to_string(), 1000);
114        assert_eq!(n.notification_type(), "EventApplied");
115
116        let n = Notification::wal_synced("abc123".to_string(), "origin".to_string());
117        assert_eq!(n.notification_type(), "WalSynced");
118    }
119
120    #[test]
121    fn test_rkyv_roundtrip() {
122        let notification = Notification::EventApplied {
123            issue_id: "issue123".to_string(),
124            event_id: "event456".to_string(),
125            ts_unix_ms: 1700000000000,
126        };
127
128        let bytes = rkyv::to_bytes::<rkyv::rancor::Error>(&notification).unwrap();
129        let archived = rkyv::access::<ArchivedNotification, rkyv::rancor::Error>(&bytes).unwrap();
130
131        match archived {
132            ArchivedNotification::EventApplied {
133                issue_id,
134                event_id,
135                ts_unix_ms,
136            } => {
137                assert_eq!(issue_id.as_str(), "issue123");
138                assert_eq!(event_id.as_str(), "event456");
139                assert_eq!(*ts_unix_ms, 1700000000000);
140            }
141            _ => panic!("Wrong variant"),
142        }
143    }
144}