use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum WakePriority {
Interval,
ChannelActivity,
QueuedWork,
Retry,
UserAction,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WakeReason {
pub priority: WakePriority,
#[serde(skip_serializing_if = "Option::is_none")]
pub channel: Option<String>,
pub description: String,
pub seq: u64,
pub timestamp: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum AgentMessage {
Handshake {
agent_id: String,
watch_channels: Vec<String>,
watch_targets: Vec<String>,
},
Ack { up_to_seq: u64 },
Pong,
Shutdown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum CoordinatorMessage {
HandshakeAck {
accepted: bool,
fallback_interval_s: u64,
},
Wake { reason: WakeReason },
Ping,
Shutdown,
}
pub fn encode<T: Serialize>(msg: &T) -> Result<String, serde_json::Error> {
let mut s = serde_json::to_string(msg)?;
s.push('\n');
Ok(s)
}
pub fn decode<T: for<'de> Deserialize<'de>>(line: &str) -> Result<T, serde_json::Error> {
serde_json::from_str(line.trim())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encode_decode_agent_handshake() {
let msg = AgentMessage::Handshake {
agent_id: "atlas".to_string(),
watch_channels: vec!["dev".to_string()],
watch_targets: vec!["@atlas".to_string()],
};
let encoded = encode(&msg).unwrap();
assert!(encoded.ends_with('\n'));
let decoded: AgentMessage = decode(&encoded).unwrap();
match decoded {
AgentMessage::Handshake { agent_id, .. } => assert_eq!(agent_id, "atlas"),
_ => panic!("wrong variant"),
}
}
#[test]
fn test_encode_decode_wake() {
let msg = CoordinatorMessage::Wake {
reason: WakeReason {
priority: WakePriority::UserAction,
channel: Some("dev".to_string()),
description: "@atlas mentioned in #dev".to_string(),
seq: 42,
timestamp: Utc::now(),
},
};
let encoded = encode(&msg).unwrap();
let decoded: CoordinatorMessage = decode(&encoded).unwrap();
match decoded {
CoordinatorMessage::Wake { reason } => {
assert_eq!(reason.priority, WakePriority::UserAction);
assert_eq!(reason.seq, 42);
}
_ => panic!("wrong variant"),
}
}
#[test]
fn test_wake_priority_ordering() {
assert!(WakePriority::UserAction > WakePriority::ChannelActivity);
assert!(WakePriority::ChannelActivity > WakePriority::Interval);
assert!(WakePriority::Retry > WakePriority::Interval);
}
#[test]
fn test_encode_decode_ack() {
let msg = AgentMessage::Ack { up_to_seq: 99 };
let encoded = encode(&msg).unwrap();
let decoded: AgentMessage = decode(&encoded).unwrap();
match decoded {
AgentMessage::Ack { up_to_seq } => assert_eq!(up_to_seq, 99),
_ => panic!("wrong variant"),
}
}
}