logicaffeine_system/relay_proto.rs
1//! The relay wire protocol — shared verbatim by the native server/client
2//! (`super::relay`) and the browser client (`super::relay_browser`). Both
3//! targets serialize the SAME [`RelayFrame`] with `bincode`, so a browser tab and
4//! a native node speak an identical language across the WebSocket.
5//!
6//! This module is target-agnostic (no `tokio`, no `web-sys`) so it compiles into
7//! both the native binary and the wasm bundle.
8
9use serde::{Deserialize, Serialize};
10
11/// A frame on the relay WebSocket. Client→relay: `Subscribe`/`Unsubscribe`/
12/// `Publish`. Relay→client: `SubAck`/`Event`.
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14pub enum RelayFrame {
15 /// Client → relay: start receiving `Event`s for `topic`.
16 Subscribe { topic: String },
17 /// Client → relay: stop receiving `topic`.
18 Unsubscribe { topic: String },
19 /// Client → relay: deliver `data` to every subscriber of `topic`.
20 Publish { topic: String, data: Vec<u8> },
21 /// Relay → client: acknowledges a `Subscribe` is registered (so a publish
22 /// that races a subscribe cannot be missed — the client awaits this).
23 SubAck { topic: String },
24 /// Relay → client: a published message on a topic the client subscribes to.
25 Event { topic: String, data: Vec<u8> },
26}
27
28impl RelayFrame {
29 /// Serialize to the bytes that cross the WebSocket (a binary message).
30 pub fn to_bytes(&self) -> Vec<u8> {
31 bincode::serialize(self).expect("relay frame is serializable")
32 }
33
34 /// Deserialize a frame from WebSocket bytes; `None` on a malformed frame.
35 pub fn from_bytes(bytes: &[u8]) -> Option<RelayFrame> {
36 bincode::deserialize(bytes).ok()
37 }
38}
39
40#[cfg(test)]
41mod tests {
42 use super::*;
43
44 #[test]
45 fn frames_round_trip_through_bytes() {
46 for frame in [
47 RelayFrame::Subscribe { topic: "chat".into() },
48 RelayFrame::Unsubscribe { topic: "chat".into() },
49 RelayFrame::Publish { topic: "chat".into(), data: vec![1, 2, 3] },
50 RelayFrame::SubAck { topic: "chat".into() },
51 RelayFrame::Event { topic: "chat".into(), data: vec![9] },
52 ] {
53 let bytes = frame.to_bytes();
54 assert_eq!(RelayFrame::from_bytes(&bytes), Some(frame));
55 }
56 }
57}