1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(rename_all = "camelCase")]
8pub enum SubscriptionMode {
9 Live,
10 Replay,
11}
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
15#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
16pub enum BreakpointType {
17 TransitionEnabled,
18 TransitionStart,
19 TransitionComplete,
20 TransitionFail,
21 TokenAdded,
22 TokenRemoved,
23}
24
25#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
27#[serde(rename_all = "camelCase")]
28pub struct BreakpointConfig {
29 pub id: String,
30 #[serde(rename = "type")]
31 pub bp_type: BreakpointType,
32 pub target: Option<String>,
33 pub enabled: bool,
34}
35
36#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
38#[serde(rename_all = "camelCase")]
39pub struct EventFilter {
40 pub event_types: Option<Vec<String>>,
41 pub transition_names: Option<Vec<String>>,
42 pub place_names: Option<Vec<String>>,
43}
44
45impl EventFilter {
46 pub fn all() -> Self {
48 Self::default()
49 }
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
54#[serde(tag = "type", rename_all = "camelCase")]
55pub enum DebugCommand {
56 ListSessions {
57 limit: Option<usize>,
58 active_only: Option<bool>,
59 },
60 Subscribe {
61 session_id: String,
62 mode: SubscriptionMode,
63 from_index: Option<usize>,
64 },
65 Unsubscribe {
66 session_id: String,
67 },
68 Seek {
69 session_id: String,
70 timestamp: String,
71 },
72 PlaybackSpeed {
73 session_id: String,
74 speed: f64,
75 },
76 Filter {
77 session_id: String,
78 filter: EventFilter,
79 },
80 Pause {
81 session_id: String,
82 },
83 Resume {
84 session_id: String,
85 },
86 StepForward {
87 session_id: String,
88 },
89 StepBackward {
90 session_id: String,
91 },
92 SetBreakpoint {
93 session_id: String,
94 breakpoint: BreakpointConfig,
95 },
96 ClearBreakpoint {
97 session_id: String,
98 breakpoint_id: String,
99 },
100 ListBreakpoints {
101 session_id: String,
102 },
103 ListArchives {
104 limit: Option<usize>,
105 prefix: Option<String>,
106 },
107 ImportArchive {
108 session_id: String,
109 },
110 UploadArchive {
111 file_name: String,
112 data: String,
113 },
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn serde_round_trip_subscribe() {
122 let cmd = DebugCommand::Subscribe {
123 session_id: "s1".into(),
124 mode: SubscriptionMode::Live,
125 from_index: Some(10),
126 };
127 let json = serde_json::to_string(&cmd).unwrap();
128 assert!(json.contains("\"type\":\"subscribe\""));
129 let back: DebugCommand = serde_json::from_str(&json).unwrap();
130 match back {
131 DebugCommand::Subscribe {
132 session_id,
133 mode,
134 from_index,
135 } => {
136 assert_eq!(session_id, "s1");
137 assert_eq!(mode, SubscriptionMode::Live);
138 assert_eq!(from_index, Some(10));
139 }
140 _ => panic!("wrong variant"),
141 }
142 }
143
144 #[test]
145 fn serde_round_trip_list_sessions() {
146 let cmd = DebugCommand::ListSessions {
147 limit: None,
148 active_only: Some(true),
149 };
150 let json = serde_json::to_string(&cmd).unwrap();
151 assert!(json.contains("\"type\":\"listSessions\""));
152 let back: DebugCommand = serde_json::from_str(&json).unwrap();
153 match back {
154 DebugCommand::ListSessions { limit, active_only } => {
155 assert!(limit.is_none());
156 assert_eq!(active_only, Some(true));
157 }
158 _ => panic!("wrong variant"),
159 }
160 }
161
162 #[test]
163 fn serde_breakpoint_config() {
164 let bp = BreakpointConfig {
165 id: "bp1".into(),
166 bp_type: BreakpointType::TransitionStart,
167 target: Some("t1".into()),
168 enabled: true,
169 };
170 let json = serde_json::to_string(&bp).unwrap();
171 assert!(json.contains("\"type\":\"TRANSITION_START\""));
172 let back: BreakpointConfig = serde_json::from_str(&json).unwrap();
173 assert_eq!(back.bp_type, BreakpointType::TransitionStart);
174 }
175
176 #[test]
177 fn serde_event_filter_all() {
178 let filter = EventFilter::all();
179 let json = serde_json::to_string(&filter).unwrap();
180 let back: EventFilter = serde_json::from_str(&json).unwrap();
181 assert!(back.event_types.is_none());
182 assert!(back.transition_names.is_none());
183 assert!(back.place_names.is_none());
184 }
185
186 #[test]
187 fn serde_all_command_variants() {
188 let cmds = vec![
189 DebugCommand::ListSessions {
190 limit: Some(10),
191 active_only: None,
192 },
193 DebugCommand::Subscribe {
194 session_id: "s1".into(),
195 mode: SubscriptionMode::Replay,
196 from_index: None,
197 },
198 DebugCommand::Unsubscribe {
199 session_id: "s1".into(),
200 },
201 DebugCommand::Seek {
202 session_id: "s1".into(),
203 timestamp: "2025-01-01T00:00:00Z".into(),
204 },
205 DebugCommand::PlaybackSpeed {
206 session_id: "s1".into(),
207 speed: 2.0,
208 },
209 DebugCommand::Filter {
210 session_id: "s1".into(),
211 filter: EventFilter::all(),
212 },
213 DebugCommand::Pause {
214 session_id: "s1".into(),
215 },
216 DebugCommand::Resume {
217 session_id: "s1".into(),
218 },
219 DebugCommand::StepForward {
220 session_id: "s1".into(),
221 },
222 DebugCommand::StepBackward {
223 session_id: "s1".into(),
224 },
225 DebugCommand::SetBreakpoint {
226 session_id: "s1".into(),
227 breakpoint: BreakpointConfig {
228 id: "bp1".into(),
229 bp_type: BreakpointType::TokenAdded,
230 target: None,
231 enabled: true,
232 },
233 },
234 DebugCommand::ClearBreakpoint {
235 session_id: "s1".into(),
236 breakpoint_id: "bp1".into(),
237 },
238 DebugCommand::ListBreakpoints {
239 session_id: "s1".into(),
240 },
241 DebugCommand::ListArchives {
242 limit: None,
243 prefix: None,
244 },
245 DebugCommand::ImportArchive {
246 session_id: "s1".into(),
247 },
248 DebugCommand::UploadArchive {
249 file_name: "test.gz".into(),
250 data: "base64data".into(),
251 },
252 ];
253 for cmd in cmds {
254 let json = serde_json::to_string(&cmd).unwrap();
255 let _back: DebugCommand = serde_json::from_str(&json).unwrap();
256 }
257 }
258}