1use serde::{Deserialize, Serialize};
11use std::sync::atomic::{AtomicU32, Ordering};
12
13pub const SENTINEL: &str = "@@RZ:";
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct Envelope {
17 pub id: String,
18 pub from: String,
19 #[serde(skip_serializing_if = "Option::is_none")]
20 pub r#ref: Option<String>,
21 pub kind: MessageKind,
22 pub ts: u64,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
26#[serde(tag = "kind", content = "body", rename_all = "snake_case")]
27pub enum MessageKind {
28 Chat { text: String },
29 Hello { name: String, pane_id: String },
30 Ping,
31 Pong,
32 Error { message: String },
33}
34
35static COUNTER: AtomicU32 = AtomicU32::new(0);
36
37impl Envelope {
38 pub fn new(from: impl Into<String>, kind: MessageKind) -> Self {
39 let seq = COUNTER.fetch_add(1, Ordering::Relaxed);
40 let ts = std::time::SystemTime::now()
41 .duration_since(std::time::UNIX_EPOCH)
42 .unwrap_or_default()
43 .as_millis() as u64;
44 Self {
45 id: format!("{:04x}{:04x}", (ts & 0xFFFF) as u16, seq),
46 r#ref: None,
47 from: from.into(),
48 kind,
49 ts,
50 }
51 }
52
53 pub fn encode(&self) -> eyre::Result<String> {
55 let json = serde_json::to_string(self)?;
56 Ok(format!("{SENTINEL}{json}"))
57 }
58
59 pub fn decode(line: &str) -> eyre::Result<Self> {
61 let payload = line.strip_prefix(SENTINEL).unwrap_or(line);
62 Ok(serde_json::from_str(payload.trim())?)
63 }
64}