use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum WebEvent {
SyncInit {
buffers: Vec<BufferMeta>,
connections: Vec<ConnectionMeta>,
mention_count: u32,
active_buffer_id: Option<String>,
timestamp_format: String,
},
NewMessage {
buffer_id: String,
message: WireMessage,
},
TopicChanged {
buffer_id: String,
topic: Option<String>,
set_by: Option<String>,
},
NickEvent {
buffer_id: String,
kind: NickEventKind,
nick: String,
new_nick: Option<String>,
prefix: Option<String>,
modes: Option<String>,
away: Option<bool>,
message: Option<String>,
},
BufferCreated { buffer: BufferMeta },
BufferClosed { buffer_id: String },
ActivityChanged {
buffer_id: String,
activity: u8,
unread_count: u32,
},
ConnectionStatus {
conn_id: String,
label: String,
connected: bool,
nick: String,
},
MentionAlert {
buffer_id: String,
message: WireMessage,
},
Messages {
buffer_id: String,
messages: Vec<WireMessage>,
has_more: bool,
#[serde(skip_serializing_if = "Option::is_none")]
session_id: Option<String>,
},
NickList {
buffer_id: String,
nicks: Vec<WireNick>,
#[serde(skip_serializing_if = "Option::is_none")]
session_id: Option<String>,
},
MentionsList {
mentions: Vec<WireMention>,
#[serde(skip_serializing_if = "Option::is_none")]
session_id: Option<String>,
},
ActiveBufferChanged { buffer_id: String },
SettingsChanged {
timestamp_format: String,
line_height: f32,
theme: String,
nick_column_width: u32,
nick_max_length: u32,
},
Error { message: String },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum WebCommand {
SendMessage { buffer_id: String, text: String },
SwitchBuffer { buffer_id: String },
MarkRead { buffer_id: String, up_to: i64 },
FetchMessages {
buffer_id: String,
limit: u32,
before: Option<i64>,
},
FetchNickList { buffer_id: String },
FetchMentions,
RunCommand { buffer_id: String, text: String },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BufferMeta {
pub id: String,
pub connection_id: String,
pub name: String,
pub buffer_type: String,
pub topic: Option<String>,
pub unread_count: u32,
pub activity: u8,
pub nick_count: u32,
#[serde(default)]
pub modes: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConnectionMeta {
pub id: String,
pub label: String,
pub nick: String,
pub connected: bool,
#[serde(default)]
pub user_modes: String,
#[serde(default)]
pub lag: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WireMessage {
pub id: u64,
pub timestamp: i64,
pub msg_type: String,
pub nick: Option<String>,
pub nick_mode: Option<String>,
pub text: String,
pub highlight: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WireNick {
pub nick: String,
pub prefix: String,
pub modes: String,
pub away: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WireMention {
pub id: i64,
pub timestamp: i64,
pub buffer_id: String,
pub channel: String,
pub nick: String,
pub text: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum NickEventKind {
Join,
Part,
Quit,
NickChange,
ModeChange,
AwayChange,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn web_event_serializes_with_type_tag() {
let event = WebEvent::BufferClosed {
buffer_id: "libera/#rust".into(),
};
let json = serde_json::to_string(&event).unwrap();
assert!(json.contains(r#""type":"BufferClosed"#));
assert!(json.contains(r#""buffer_id":"libera/#rust"#));
}
#[test]
fn web_command_deserializes_from_json() {
let json = r#"{"type":"SendMessage","buffer_id":"libera/#rust","text":"hello"}"#;
let cmd: WebCommand = serde_json::from_str(json).unwrap();
assert!(matches!(cmd, WebCommand::SendMessage { .. }));
}
#[test]
fn wire_message_roundtrip() {
let msg = WireMessage {
id: 42,
timestamp: 1_710_000_000,
msg_type: "message".into(),
nick: Some("ferris".into()),
nick_mode: Some("@".into()),
text: "hello 🚀".into(),
highlight: false,
};
let json = serde_json::to_string(&msg).unwrap();
let decoded: WireMessage = serde_json::from_str(&json).unwrap();
assert_eq!(decoded.id, 42);
assert_eq!(decoded.nick.as_deref(), Some("ferris"));
assert_eq!(decoded.text, "hello 🚀");
}
#[test]
fn fetch_messages_with_null_before() {
let json = r#"{"type":"FetchMessages","buffer_id":"x","limit":50,"before":null}"#;
let cmd: WebCommand = serde_json::from_str(json).unwrap();
assert!(matches!(
cmd,
WebCommand::FetchMessages { before: None, .. }
));
}
}