1use crate::{HatId, Topic};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct Event {
9 pub topic: Topic,
11
12 pub payload: String,
14
15 pub source: Option<HatId>,
17
18 pub target: Option<HatId>,
20
21 #[serde(skip_serializing_if = "Option::is_none")]
23 pub wave_id: Option<String>,
24
25 #[serde(skip_serializing_if = "Option::is_none")]
27 pub wave_index: Option<u32>,
28
29 #[serde(skip_serializing_if = "Option::is_none")]
31 pub wave_total: Option<u32>,
32}
33
34impl Event {
35 pub fn new(topic: impl Into<Topic>, payload: impl Into<String>) -> Self {
37 Self {
38 topic: topic.into(),
39 payload: payload.into(),
40 source: None,
41 target: None,
42 wave_id: None,
43 wave_index: None,
44 wave_total: None,
45 }
46 }
47
48 #[must_use]
50 pub fn with_source(mut self, source: impl Into<HatId>) -> Self {
51 self.source = Some(source.into());
52 self
53 }
54
55 #[must_use]
57 pub fn with_target(mut self, target: impl Into<HatId>) -> Self {
58 self.target = Some(target.into());
59 self
60 }
61
62 #[must_use]
64 pub fn with_wave(mut self, wave_id: impl Into<String>, index: u32, total: u32) -> Self {
65 self.wave_id = Some(wave_id.into());
66 self.wave_index = Some(index);
67 self.wave_total = Some(total);
68 self
69 }
70
71 pub fn is_wave_event(&self) -> bool {
73 self.wave_id.is_some()
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 #[test]
82 fn test_new_event_has_no_wave_metadata() {
83 let event = Event::new("test.topic", "payload");
84 assert!(!event.is_wave_event());
85 assert!(event.wave_id.is_none());
86 assert!(event.wave_index.is_none());
87 assert!(event.wave_total.is_none());
88 }
89
90 #[test]
91 fn test_with_wave_sets_metadata() {
92 let event = Event::new("review.file", "src/main.rs").with_wave("w-1a2b3c4d", 0, 3);
93 assert!(event.is_wave_event());
94 assert_eq!(event.wave_id.as_deref(), Some("w-1a2b3c4d"));
95 assert_eq!(event.wave_index, Some(0));
96 assert_eq!(event.wave_total, Some(3));
97 }
98
99 #[test]
100 fn test_wave_metadata_roundtrips_through_serde() {
101 let event = Event::new("review.file", "src/main.rs")
102 .with_source("dispatcher")
103 .with_wave("w-abcd1234", 2, 5);
104
105 let json = serde_json::to_string(&event).unwrap();
106 let deserialized: Event = serde_json::from_str(&json).unwrap();
107
108 assert_eq!(deserialized.wave_id.as_deref(), Some("w-abcd1234"));
109 assert_eq!(deserialized.wave_index, Some(2));
110 assert_eq!(deserialized.wave_total, Some(5));
111 assert_eq!(deserialized.topic.as_str(), "review.file");
112 assert_eq!(deserialized.payload, "src/main.rs");
113 }
114
115 #[test]
116 fn test_event_without_wave_serializes_without_wave_fields() {
117 let event = Event::new("test.topic", "payload");
118 let json = serde_json::to_string(&event).unwrap();
119 assert!(!json.contains("wave_id"));
120 assert!(!json.contains("wave_index"));
121 assert!(!json.contains("wave_total"));
122 }
123
124 #[test]
125 fn test_event_without_wave_fields_deserializes() {
126 let json = r#"{"topic":"test.topic","payload":"hello"}"#;
127 let event: Event = serde_json::from_str(json).unwrap();
128 assert!(!event.is_wave_event());
129 assert_eq!(event.topic.as_str(), "test.topic");
130 assert_eq!(event.payload, "hello");
131 }
132}