1use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(rename_all = "snake_case")]
15pub enum LifecycleEvent {
16 ChannelPollStarted,
17 ChannelPollSucceeded,
18 ChannelPollFailed,
19 ChannelBackoffArmed,
20 ChannelBackoffReset,
21 ChannelHeartbeatPersistFailed,
22 ConfigLocked,
23 CheckpointOutcomeRecorded,
24 PhaseStarted,
25 PhaseCompleted,
26 PhaseCancelled,
27}
28
29#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
31pub struct ChannelPollStartedPayload {
32 pub channel_kind: String,
33 pub channel_slug: String,
34 pub since_rfc3339: String,
35}
36
37#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
39pub struct ChannelPollSucceededPayload {
40 pub channel_kind: String,
41 pub channel_slug: String,
42 pub envelope_count: usize,
43 pub previous_backoff_attempt: u32,
44}
45
46#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
48pub struct ChannelPollFailedPayload {
49 pub channel_kind: String,
50 pub channel_slug: String,
51 pub error_class: String,
52 pub error_message: String,
53}
54
55#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
57pub struct ChannelBackoffArmedPayload {
58 pub channel_kind: String,
59 pub channel_slug: String,
60 pub attempt: u32,
61 pub step_ms: u64,
62 pub delay_ms: u64,
63}
64
65#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
67pub struct ChannelBackoffResetPayload {
68 pub channel_kind: String,
69 pub channel_slug: String,
70 pub previous_backoff_attempt: u32,
71}
72
73#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
75pub struct ChannelHeartbeatPersistFailedPayload {
76 pub channel_kind: String,
77 pub channel_slug: String,
78 pub error: String,
79}
80
81#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
83pub struct ConfigLockedPayload {
84 pub key: String,
85 pub value: String,
86}
87
88#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
90pub struct CheckpointOutcomeRecordedPayload {
91 pub wal_pages: u64,
92 pub warn_pages: u64,
93 pub high_water_pages: u64,
94 pub truncate_high_water_pages: u64,
95 pub above_warn: bool,
96 pub above_high_water: bool,
97 pub above_truncate_high_water: bool,
98}
99
100#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
109pub struct PhaseStartedPayload {
110 pub work_class: String,
111 pub phase: String,
112 pub corpus_size: Option<u64>,
113}
114
115#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
121pub struct PhaseCompletedPayload {
122 pub work_class: String,
123 pub phase: String,
124 pub wall_us: i64,
125 pub cpu_us: Option<i64>,
126}
127
128#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
132pub struct PhaseCancelledPayload {
133 pub work_class: String,
134 pub phase: String,
135 pub wall_us: i64,
136 pub cpu_us: Option<i64>,
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn lifecycle_event_roundtrips_through_json() {
145 for kind in [
146 LifecycleEvent::ChannelPollStarted,
147 LifecycleEvent::ChannelPollSucceeded,
148 LifecycleEvent::ChannelPollFailed,
149 LifecycleEvent::ChannelBackoffArmed,
150 LifecycleEvent::ChannelBackoffReset,
151 LifecycleEvent::ChannelHeartbeatPersistFailed,
152 LifecycleEvent::ConfigLocked,
153 LifecycleEvent::CheckpointOutcomeRecorded,
154 LifecycleEvent::PhaseStarted,
155 LifecycleEvent::PhaseCompleted,
156 LifecycleEvent::PhaseCancelled,
157 ] {
158 let json = serde_json::to_string(&kind).expect("serialize");
159 let parsed: LifecycleEvent = serde_json::from_str(&json).expect("deserialize");
160 assert_eq!(parsed, kind);
161 }
162 }
163
164 #[test]
165 fn checkpoint_outcome_recorded_payload_roundtrips() {
166 let payload = CheckpointOutcomeRecordedPayload {
167 wal_pages: 2500,
168 warn_pages: 2000,
169 high_water_pages: 6000,
170 truncate_high_water_pages: 20_000,
171 above_warn: true,
172 above_high_water: false,
173 above_truncate_high_water: false,
174 };
175 let json = serde_json::to_value(&payload).expect("serialize");
176 let parsed: CheckpointOutcomeRecordedPayload =
177 serde_json::from_value(json).expect("deserialize");
178 assert_eq!(parsed, payload);
179 }
180
181 #[test]
182 fn phase_started_payload_roundtrips() {
183 let payload = PhaseStartedPayload {
184 work_class: "warm".into(),
185 phase: "ann_warm".into(),
186 corpus_size: Some(553_000),
187 };
188 let json = serde_json::to_value(&payload).expect("serialize");
189 let parsed: PhaseStartedPayload = serde_json::from_value(json).expect("deserialize");
190 assert_eq!(parsed, payload);
191 }
192
193 #[test]
194 fn phase_completed_payload_roundtrips_with_absent_cpu_us() {
195 let payload = PhaseCompletedPayload {
196 work_class: "warm".into(),
197 phase: "ann_warm".into(),
198 wall_us: 41_000_000,
199 cpu_us: None,
200 };
201 let json = serde_json::to_value(&payload).expect("serialize");
202 let parsed: PhaseCompletedPayload = serde_json::from_value(json).expect("deserialize");
203 assert_eq!(parsed, payload);
204 }
205
206 #[test]
207 fn phase_cancelled_payload_roundtrips() {
208 let payload = PhaseCancelledPayload {
209 work_class: "warm".into(),
210 phase: "ann_warm".into(),
211 wall_us: 12_000,
212 cpu_us: Some(9_500),
213 };
214 let json = serde_json::to_value(&payload).expect("serialize");
215 let parsed: PhaseCancelledPayload = serde_json::from_value(json).expect("deserialize");
216 assert_eq!(parsed, payload);
217 }
218}