1use alloc::string::String;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6#[serde(tag = "t")]
7pub enum WireMsg {
8 #[serde(rename = "hello")]
11 Hello(HelloPayload),
12
13 #[serde(rename = "ping")]
15 Ping {
16 #[serde(default)]
18 ts: Option<u64>,
19 },
20
21 #[serde(rename = "pong")]
23 Pong {
24 #[serde(default)]
26 ts: Option<u64>,
27 },
28
29 #[serde(rename = "cmd")]
33 Cmd(CmdPayload),
34
35 #[serde(rename = "ack")]
37 Ack(AckPayload),
38
39 #[serde(rename = "event")]
42 Event(EventPayload),
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct HelloPayload {
47 #[serde(default)]
49 pub client: Option<String>,
50
51 #[serde(default)]
53 pub aircraft: Option<String>,
54
55 #[serde(default)]
57 pub tail: Option<String>,
58
59 #[serde(default)]
61 pub session: Option<String>,
62
63 #[serde(default)]
65 pub v: Option<u32>,
66
67 #[serde(default, skip_serializing_if = "Option::is_none")]
69 pub meta: Option<Value>,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct CmdPayload {
74 pub id: String,
76
77 #[serde(default, skip_serializing_if = "Option::is_none")]
79 pub name: Option<String>,
80
81 pub payload: Value,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct AckPayload {
87 pub id: String,
89
90 pub ok: bool,
92
93 #[serde(default, skip_serializing_if = "Option::is_none")]
95 pub error: Option<String>,
96
97 #[serde(default, skip_serializing_if = "Option::is_none")]
99 pub response: Option<Value>,
100
101 #[serde(default, skip_serializing_if = "Option::is_none")]
103 pub duplicate: Option<bool>,
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct EventPayload {
108 pub name: String,
110
111 pub data: Value,
113}
114
115impl AckPayload {
116 pub fn ok(id: String, response: Value) -> Self {
117 Self {
118 id,
119 ok: true,
120 error: None,
121 response: Some(response),
122 duplicate: None,
123 }
124 }
125
126 pub fn err(id: String, error: String) -> Self {
127 Self {
128 id,
129 ok: false,
130 error: Some(error),
131 response: None,
132 duplicate: None,
133 }
134 }
135
136 pub fn duplicate(id: String) -> Self {
137 Self {
138 id,
139 ok: true,
140 error: None,
141 response: None,
142 duplicate: Some(true),
143 }
144 }
145}
146
147impl CmdPayload {
148 pub fn new(id: String, payload: Value) -> Self {
149 Self {
150 id,
151 name: None,
152 payload,
153 }
154 }
155
156 pub fn named(id: String, name: impl Into<String>, payload: Value) -> Self {
157 Self {
158 id,
159 name: Some(name.into()),
160 payload,
161 }
162 }
163}
164
165impl EventPayload {
166 pub fn new(name: impl Into<String>, data: Value) -> Self {
167 Self {
168 name: name.into(),
169 data,
170 }
171 }
172}
173
174impl WireMsg {
175 pub fn to_json(&self) -> Result<String, serde_json::Error> {
176 serde_json::to_string(self)
177 }
178
179 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
180 serde_json::from_str(json)
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187 use serde_json::json;
188
189 #[test]
190 fn round_trip_cmd() {
191 let cmd = WireMsg::Cmd(CmdPayload::named(
192 "abc-123".into(),
193 "get_state",
194 json!({"key": "value"}),
195 ));
196 let json = cmd.to_json().unwrap();
197 let parsed = WireMsg::from_json(&json).unwrap();
198
199 match parsed {
200 WireMsg::Cmd(c) => {
201 assert_eq!(c.id, "abc-123");
202 assert_eq!(c.name.as_deref(), Some("get_state"));
203 assert_eq!(c.payload, json!({"key": "value"}));
204 }
205 other => panic!("expected Cmd, got {:?}", other),
206 }
207 }
208
209 #[test]
210 fn round_trip_ack_ok() {
211 let ack = WireMsg::Ack(AckPayload::ok("abc-123".into(), json!(42)));
212 let json = ack.to_json().unwrap();
213 let parsed = WireMsg::from_json(&json).unwrap();
214
215 match parsed {
216 WireMsg::Ack(a) => {
217 assert!(a.ok);
218 assert_eq!(a.response, Some(json!(42)));
219 assert!(a.error.is_none());
220 }
221 other => panic!("expected Ack, got {:?}", other),
222 }
223 }
224
225 #[test]
226 fn round_trip_ack_err() {
227 let ack = WireMsg::Ack(AckPayload::err("abc-123".into(), "boom".into()));
228 let json = ack.to_json().unwrap();
229 let parsed = WireMsg::from_json(&json).unwrap();
230
231 match parsed {
232 WireMsg::Ack(a) => {
233 assert!(!a.ok);
234 assert_eq!(a.error.as_deref(), Some("boom"));
235 }
236 other => panic!("expected Ack, got {:?}", other),
237 }
238 }
239
240 #[test]
241 fn round_trip_event() {
242 let evt = WireMsg::Event(EventPayload::new(
243 "state_changed",
244 json!({"phase": "cruise"}),
245 ));
246 let json = evt.to_json().unwrap();
247 let parsed = WireMsg::from_json(&json).unwrap();
248
249 match parsed {
250 WireMsg::Event(e) => {
251 assert_eq!(e.name, "state_changed");
252 assert_eq!(e.data, json!({"phase": "cruise"}));
253 }
254 other => panic!("expected Event, got {:?}", other),
255 }
256 }
257
258 #[test]
259 fn round_trip_hello() {
260 let hello = WireMsg::Hello(HelloPayload {
261 client: Some("msfs-gauge".into()),
262 aircraft: Some("DC-10".into()),
263 tail: None,
264 session: Some("12345".into()),
265 v: Some(1),
266 meta: None,
267 });
268 let json = hello.to_json().unwrap();
269 let parsed = WireMsg::from_json(&json).unwrap();
270
271 match parsed {
272 WireMsg::Hello(h) => {
273 assert_eq!(h.client.as_deref(), Some("msfs-gauge"));
274 assert_eq!(h.v, Some(1));
275 }
276 other => panic!("expected Hello, got {:?}", other),
277 }
278 }
279
280 #[test]
281 fn round_trip_ping_pong() {
282 let ping = WireMsg::Ping {
283 ts: Some(1234567890),
284 };
285 let json = ping.to_json().unwrap();
286 assert!(json.contains("\"t\":\"ping\""));
287
288 let pong = WireMsg::Pong {
289 ts: Some(1234567890),
290 };
291 let json = pong.to_json().unwrap();
292 let parsed = WireMsg::from_json(&json).unwrap();
293 match parsed {
294 WireMsg::Pong { ts } => assert_eq!(ts, Some(1234567890)),
295 other => panic!("expected Pong, got {:?}", other),
296 }
297 }
298
299 #[test]
300 fn duplicate_ack() {
301 let ack = WireMsg::Ack(AckPayload::duplicate("abc-123".into()));
302 let json = ack.to_json().unwrap();
303 let parsed = WireMsg::from_json(&json).unwrap();
304
305 match parsed {
306 WireMsg::Ack(a) => {
307 assert!(a.ok);
308 assert_eq!(a.duplicate, Some(true));
309 }
310 other => panic!("expected Ack, got {:?}", other),
311 }
312 }
313
314 #[test]
315 fn unknown_type_fails() {
316 let bad = r#"{"t":"banana","stuff":42}"#;
317 assert!(WireMsg::from_json(bad).is_err());
318 }
319
320 #[test]
321 fn skip_serializing_none_fields() {
322 let ack = AckPayload::ok("id".into(), json!(null));
323 let json = serde_json::to_string(&ack).unwrap();
324 assert!(!json.contains("\"error\""));
326 assert!(!json.contains("\"duplicate\""));
327 }
328}