use crate::version::ProtocolVersion;
use atm_core::{SessionId, SessionView};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum MessageType {
Connect {
#[serde(skip_serializing_if = "Option::is_none")]
client_id: Option<String>,
},
StatusUpdate {
data: serde_json::Value,
},
HookEvent {
data: serde_json::Value,
},
PiEvent {
data: serde_json::Value,
},
ListSessions,
Subscribe {
#[serde(skip_serializing_if = "Option::is_none")]
session_id: Option<SessionId>,
},
Unsubscribe,
Ping {
seq: u64,
},
Disconnect,
Discover,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClientMessage {
pub protocol_version: ProtocolVersion,
#[serde(flatten)]
pub message: MessageType,
}
impl ClientMessage {
pub fn new(message: MessageType) -> Self {
Self {
protocol_version: ProtocolVersion::CURRENT,
message,
}
}
pub fn connect(client_id: Option<String>) -> Self {
Self::new(MessageType::Connect { client_id })
}
pub fn status_update(data: serde_json::Value) -> Self {
Self::new(MessageType::StatusUpdate { data })
}
pub fn hook_event(data: serde_json::Value) -> Self {
Self::new(MessageType::HookEvent { data })
}
pub fn pi_event(data: serde_json::Value) -> Self {
Self::new(MessageType::PiEvent { data })
}
pub fn list_sessions() -> Self {
Self::new(MessageType::ListSessions)
}
pub fn subscribe(session_id: Option<SessionId>) -> Self {
Self::new(MessageType::Subscribe { session_id })
}
pub fn ping(seq: u64) -> Self {
Self::new(MessageType::Ping { seq })
}
pub fn disconnect() -> Self {
Self::new(MessageType::Disconnect)
}
pub fn discover() -> Self {
Self::new(MessageType::Discover)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum DaemonMessage {
Connected {
protocol_version: ProtocolVersion,
client_id: String,
},
Rejected {
reason: String,
protocol_version: ProtocolVersion,
},
SessionList {
sessions: Vec<SessionView>,
},
SessionUpdated {
session: Box<SessionView>,
},
SessionRemoved {
session_id: SessionId,
},
Pong {
seq: u64,
},
Error {
message: String,
#[serde(skip_serializing_if = "Option::is_none")]
code: Option<String>,
},
DiscoveryComplete {
discovered: u32,
failed: u32,
},
}
impl DaemonMessage {
pub fn connected(client_id: String) -> Self {
Self::Connected {
protocol_version: ProtocolVersion::CURRENT,
client_id,
}
}
pub fn rejected(reason: &str) -> Self {
Self::Rejected {
reason: reason.to_string(),
protocol_version: ProtocolVersion::CURRENT,
}
}
pub fn session_list(sessions: Vec<SessionView>) -> Self {
Self::SessionList { sessions }
}
pub fn session_updated(session: SessionView) -> Self {
Self::SessionUpdated {
session: Box::new(session),
}
}
pub fn session_removed(session_id: SessionId) -> Self {
Self::SessionRemoved { session_id }
}
pub fn pong(seq: u64) -> Self {
Self::Pong { seq }
}
pub fn error(message: &str) -> Self {
Self::Error {
message: message.to_string(),
code: None,
}
}
pub fn error_with_code(message: &str, code: &str) -> Self {
Self::Error {
message: message.to_string(),
code: Some(code.to_string()),
}
}
pub fn discovery_complete(discovered: u32, failed: u32) -> Self {
Self::DiscoveryComplete { discovered, failed }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_client_message_serialization() {
let msg = ClientMessage::ping(42);
let json = serde_json::to_string(&msg).unwrap();
assert!(json.contains("\"type\":\"ping\""));
assert!(json.contains("\"seq\":42"));
}
#[test]
fn test_daemon_message_serialization() {
let msg = DaemonMessage::connected("client-123".to_string());
let json = serde_json::to_string(&msg).unwrap();
assert!(json.contains("\"type\":\"connected\""));
assert!(json.contains("\"client_id\":\"client-123\""));
}
#[test]
fn test_message_roundtrip() {
let original = ClientMessage::subscribe(Some(SessionId::new("test-session")));
let json = serde_json::to_string(&original).unwrap();
let parsed: ClientMessage = serde_json::from_str(&json).unwrap();
match parsed.message {
MessageType::Subscribe { session_id } => {
assert_eq!(
session_id.map(|s| s.as_str().to_string()),
Some("test-session".to_string())
);
}
_ => panic!("Expected Subscribe message"),
}
}
}