use serde::{Deserialize, Serialize};
use crate::envelope::Envelope;
pub const KIND_TEXT: &str = "text";
pub const RESTART_KIND_REQUEST: &str = "restart-request";
pub const RESTART_KIND_ACK: &str = "restart-ack";
pub const RESTART_KIND_CONFIRM: &str = "restart-confirm";
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RestartRequest {
pub handoff_path: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RestartAck {
pub handoff_path: String,
pub verified: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RestartConfirm {
pub handoff_path: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RestartMessage {
Request(RestartRequest),
Ack(RestartAck),
Confirm(RestartConfirm),
}
impl RestartMessage {
pub fn kind(&self) -> &'static str {
match self {
Self::Request(_) => RESTART_KIND_REQUEST,
Self::Ack(_) => RESTART_KIND_ACK,
Self::Confirm(_) => RESTART_KIND_CONFIRM,
}
}
pub fn handoff_path(&self) -> &str {
match self {
Self::Request(v) => &v.handoff_path,
Self::Ack(v) => &v.handoff_path,
Self::Confirm(v) => &v.handoff_path,
}
}
pub fn to_text(&self) -> Result<String, serde_json::Error> {
match self {
Self::Request(v) => serde_json::to_string(v),
Self::Ack(v) => serde_json::to_string(v),
Self::Confirm(v) => serde_json::to_string(v),
}
}
pub fn from_envelope(env: &Envelope) -> Result<Option<Self>, String> {
match env.kind_or_text() {
RESTART_KIND_REQUEST => serde_json::from_str::<RestartRequest>(&env.text)
.map(Self::Request)
.map(Some)
.map_err(|e| format!("invalid restart-request payload: {e}")),
RESTART_KIND_ACK => serde_json::from_str::<RestartAck>(&env.text)
.map(Self::Ack)
.map(Some)
.map_err(|e| format!("invalid restart-ack payload: {e}")),
RESTART_KIND_CONFIRM => serde_json::from_str::<RestartConfirm>(&env.text)
.map(Self::Confirm)
.map(Some)
.map_err(|e| format!("invalid restart-confirm payload: {e}")),
_ => Ok(None),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_restart_request_from_envelope() {
let mut env = Envelope::new(
"agent0",
r#"{"handoff_path":"/tmp/handoff.txt"}"#,
"2026-04-19T20:44:47Z",
);
env.kind = Some(RESTART_KIND_REQUEST.to_string());
let msg = RestartMessage::from_envelope(&env).unwrap();
assert_eq!(
msg,
Some(RestartMessage::Request(RestartRequest {
handoff_path: "/tmp/handoff.txt".to_string(),
}))
);
}
#[test]
fn ignores_non_restart_envelope_kinds() {
let env = Envelope::new("agent0", "hello", "2026-04-19T20:44:47Z");
assert_eq!(RestartMessage::from_envelope(&env).unwrap(), None);
}
#[test]
fn rejects_invalid_restart_payload() {
let mut env = Envelope::new("agent0", "not-json", "2026-04-19T20:44:47Z");
env.kind = Some(RESTART_KIND_ACK.to_string());
let err = RestartMessage::from_envelope(&env).unwrap_err();
assert!(err.contains("invalid restart-ack payload"));
}
}