1use core::fmt;
13
14use serde::de::value::MapAccessDeserializer;
15use serde::de::{DeserializeSeed, Error as _, IgnoredAny, IntoDeserializer, MapAccess, Visitor};
16use serde::{Deserialize, Deserializer, Serialize};
17
18use crate::protocol::{BusEvent, Reply};
19
20#[derive(Debug, Clone, PartialEq, Serialize)]
26#[serde(untagged)]
27#[non_exhaustive]
44pub enum ServerFrame {
45 Reply(Reply),
47 Event(BusEvent),
49}
50
51enum Kind {
56 Reply,
57 Event,
58}
59
60impl Kind {
61 fn of(key: &str) -> Option<(Self, &'static str)> {
63 match key {
64 "id" => Some((Self::Reply, "id")),
65 "result" => Some((Self::Reply, "result")),
66 "event" => Some((Self::Event, "event")),
67 "data" => Some((Self::Event, "data")),
68 _ => None,
69 }
70 }
71}
72
73enum Key {
75 Known(Kind, &'static str),
76 Other(String),
77}
78
79struct KeySeed;
80
81impl<'de> DeserializeSeed<'de> for KeySeed {
82 type Value = Key;
83
84 fn deserialize<D: Deserializer<'de>>(self, deserializer: D) -> Result<Key, D::Error> {
85 struct KeyVisitor;
86
87 impl Visitor<'_> for KeyVisitor {
88 type Value = Key;
89
90 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91 f.write_str("a string key")
92 }
93
94 fn visit_str<E>(self, key: &str) -> Result<Key, E> {
95 Ok(match Kind::of(key) {
96 Some((kind, name)) => Key::Known(kind, name),
97 None => Key::Other(key.to_owned()),
98 })
99 }
100 }
101
102 deserializer.deserialize_identifier(KeyVisitor)
103 }
104}
105
106struct Keys<A> {
112 first: Option<&'static str>,
113 rest: A,
114 reply_key_seen: bool,
115}
116
117impl<'de, A: MapAccess<'de>> MapAccess<'de> for Keys<A> {
118 type Error = A::Error;
119
120 fn next_key_seed<K: DeserializeSeed<'de>>(
121 &mut self,
122 seed: K,
123 ) -> Result<Option<K::Value>, Self::Error> {
124 if let Some(key) = self.first.take() {
125 return seed.deserialize(key.into_deserializer()).map(Some);
126 }
127 match self.rest.next_key_seed(KeySeed)? {
128 None => Ok(None),
129 Some(Key::Known(kind, name)) => {
130 self.reply_key_seen |= matches!(kind, Kind::Reply);
131 seed.deserialize(name.into_deserializer()).map(Some)
132 }
133 Some(Key::Other(name)) => seed.deserialize(name.into_deserializer()).map(Some),
134 }
135 }
136
137 fn next_value_seed<V: DeserializeSeed<'de>>(
138 &mut self,
139 seed: V,
140 ) -> Result<V::Value, Self::Error> {
141 self.rest.next_value_seed(seed)
142 }
143}
144
145struct FrameVisitor;
146
147impl<'de> Visitor<'de> for FrameVisitor {
148 type Value = ServerFrame;
149
150 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151 f.write_str("a reply (`id`, `result`) or a bus event (`event`, `data`)")
152 }
153
154 fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<ServerFrame, A::Error> {
156 let (kind, key) = loop {
157 match map.next_key_seed(KeySeed)? {
158 Some(Key::Known(kind, name)) => break (kind, name),
159 Some(Key::Other(_)) => {
160 map.next_value::<IgnoredAny>()?;
161 }
162 None => return Err(A::Error::custom("missing `id`/`result` or `event`/`data`")),
163 }
164 };
165 let mut keys = Keys {
166 first: Some(key),
167 rest: map,
168 reply_key_seen: false,
169 };
170 match kind {
171 Kind::Reply => {
172 Reply::deserialize(MapAccessDeserializer::new(keys)).map(ServerFrame::Reply)
173 }
174 Kind::Event => {
175 let event = BusEvent::deserialize(MapAccessDeserializer::new(&mut keys))?;
176 if keys.reply_key_seen {
177 return Err(A::Error::custom("an event frame carries `id` or `result`"));
178 }
179 Ok(ServerFrame::Event(event))
180 }
181 }
182 }
183}
184
185impl<'de> Deserialize<'de> for ServerFrame {
186 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
187 deserializer.deserialize_map(FrameVisitor)
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194 use crate::protocol::{
195 BusEvent, ProcessEventKind, ProcessInfo, Reply, Response, RpcError, RpcErrorCode,
196 encode_frame,
197 };
198 use crate::status::ProcStatus;
199
200 fn sample_reply() -> Reply {
201 Reply {
202 id: 7,
203 result: Ok(Response::Pong),
204 }
205 }
206
207 fn sample_reply_with_id(id: u64) -> Reply {
208 Reply {
209 id,
210 ..sample_reply()
211 }
212 }
213
214 fn sample_event() -> BusEvent {
215 BusEvent::Process {
216 event: ProcessEventKind::Online,
217 info: ProcessInfo {
218 id: 3,
219 name: "web".to_string(),
220 status: ProcStatus::Online,
221 pid: Some(4242),
222 restarts: 0,
223 uptime_ms: 0,
224 fold: None,
225 depends_on: Vec::new(),
226 out_file: Some("/home/ada/.shep/logs/web-0-out.log".to_string()),
227 err_file: Some("/home/ada/.shep/logs/web-0-err.log".to_string()),
228 cpu_percent: None,
229 memory_bytes: None,
230 cpu_ms: None,
231 dog: None,
232 lambs: None,
233 last_exit: None,
234 smit: None,
235 instance: None,
236 handshook: None,
237 dog_stale: None,
238 pending: None,
239 overridden: None,
240 max_memory: None,
241 level_rules: Vec::new(),
242 reload_deadline_ms: None,
243 disabled: None,
244 log_timestamps: None,
245 },
246 manually: false,
247 at_ms: 1_700_000_000_000,
248 }
249 }
250
251 #[test]
252 fn server_frame_decodes_both_directions_of_the_stream() {
253 let reply = r#"{"id":1,"result":{"Ok":{"kind":"pong"}}}"#;
256 assert!(matches!(
257 serde_json::from_str::<ServerFrame>(reply).unwrap(),
258 ServerFrame::Reply(Reply { id: 1, .. })
259 ));
260 let event = r#"{"event":"log_out","data":{"id":3,"line":"ready"}}"#;
261 assert!(matches!(
262 serde_json::from_str::<ServerFrame>(event).unwrap(),
263 ServerFrame::Event(BusEvent::LogOut { id: 3, .. })
264 ));
265 }
266
267 #[test]
268 fn a_whole_payload_survives_the_trip_through_the_frame() {
269 let event = serde_json::to_string(&sample_event()).unwrap();
270 assert_eq!(
271 serde_json::from_str::<ServerFrame>(&event).unwrap(),
272 ServerFrame::Event(sample_event())
273 );
274 let reply = serde_json::to_string(&sample_reply()).unwrap();
275 assert_eq!(
276 serde_json::from_str::<ServerFrame>(&reply).unwrap(),
277 ServerFrame::Reply(sample_reply())
278 );
279 }
280
281 #[test]
282 fn a_malformed_reply_reports_its_own_failure() {
283 let err =
285 serde_json::from_str::<ServerFrame>(r#"{"id":1,"result":{"Ok":{"kind":"bogus"}}}"#)
286 .unwrap_err()
287 .to_string();
288 assert!(!err.contains("untagged"), "{err}");
289 assert!(err.contains("bogus"), "{err}");
290 }
291
292 #[test]
293 fn a_frame_with_no_known_key_is_refused() {
294 let err = serde_json::from_str::<ServerFrame>(r#"{"unrelated":1}"#)
295 .unwrap_err()
296 .to_string();
297 assert!(err.contains("event"), "{err}");
298 assert!(err.contains("result"), "{err}");
299 }
300
301 #[test]
302 fn key_order_does_not_change_which_frame_decodes() {
303 let reply = r#"{"result":{"Ok":{"kind":"pong"}},"id":1}"#;
304 assert_eq!(
305 serde_json::from_str::<ServerFrame>(reply).unwrap(),
306 ServerFrame::Reply(sample_reply_with_id(1))
307 );
308 let event = r#"{"data":{"id":3,"line":"ready"},"event":"log_out"}"#;
309 assert!(matches!(
310 serde_json::from_str::<ServerFrame>(event).unwrap(),
311 ServerFrame::Event(BusEvent::LogOut { id: 3, .. })
312 ));
313 }
314
315 #[test]
316 fn a_frame_carrying_both_kinds_of_key_is_never_taken_for_an_event() {
317 let event_first = r#"{"event":"log_out","data":{"id":3,"line":"ready"},"id":1,"result":{"Ok":{"kind":"pong"}}}"#;
320 let decoded = serde_json::from_str::<ServerFrame>(event_first);
321 assert!(decoded.is_err(), "{decoded:?}");
322
323 let reply_first = r#"{"id":1,"result":{"Ok":{"kind":"pong"}},"event":"log_out","data":{"id":3,"line":"ready"}}"#;
324 assert_eq!(
325 serde_json::from_str::<ServerFrame>(reply_first).unwrap(),
326 ServerFrame::Reply(sample_reply_with_id(1))
327 );
328 }
329
330 #[test]
331 fn an_unknown_leading_key_does_not_hide_a_reply() {
332 let reply = r#"{"extra":{"a":[1,2]},"id":1,"result":{"Ok":{"kind":"pong"}}}"#;
334 assert_eq!(
335 serde_json::from_str::<ServerFrame>(reply).unwrap(),
336 ServerFrame::Reply(sample_reply_with_id(1))
337 );
338 }
339
340 #[test]
341 fn server_frame_is_byte_identical_to_its_payload() {
342 let reply = sample_reply();
345 assert_eq!(
346 encode_frame(&ServerFrame::Reply(reply.clone())).unwrap(),
347 encode_frame(&reply).unwrap()
348 );
349 let event = sample_event();
350 assert_eq!(
351 encode_frame(&ServerFrame::Event(event.clone())).unwrap(),
352 encode_frame(&event).unwrap()
353 );
354 }
355
356 #[test]
357 fn an_error_reply_still_decodes_as_a_reply_frame() {
358 let err = Reply {
359 id: 2,
360 result: Err(RpcError {
361 code: RpcErrorCode::DeadlineExceeded,
362 message: "request deadline of 5000 ms expired".to_string(),
363 daemon_version: None,
364 }),
365 };
366 let json = serde_json::to_string(&err).unwrap();
367 assert_eq!(
368 serde_json::from_str::<ServerFrame>(&json).unwrap(),
369 ServerFrame::Reply(err)
370 );
371 }
372}