use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ControlMessage {
Resize {
cols: u16,
rows: u16,
#[serde(default)]
pixel_width: u16,
#[serde(default)]
pixel_height: u16,
},
Signal { name: String },
Eof,
Exit { code: i32 },
}
impl ControlMessage {
pub fn to_json(&self) -> serde_json::Result<bytes::Bytes> {
serde_json::to_vec(self).map(bytes::Bytes::from)
}
pub fn from_slice(b: &[u8]) -> serde_json::Result<Self> {
serde_json::from_slice(b)
}
}
#[cfg(unix)]
pub fn signal_from_name(name: &str) -> Option<i32> {
use libc::*;
match name {
"HUP" => Some(SIGHUP),
"INT" => Some(SIGINT),
"QUIT" => Some(SIGQUIT),
"TERM" => Some(SIGTERM),
"KILL" => Some(SIGKILL),
"USR1" => Some(SIGUSR1),
"USR2" => Some(SIGUSR2),
"TSTP" => Some(SIGTSTP),
"CONT" => Some(SIGCONT),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip_resize() {
let msg = ControlMessage::Resize {
cols: 80,
rows: 24,
pixel_width: 0,
pixel_height: 0,
};
let bytes = msg.to_json().unwrap();
let back = ControlMessage::from_slice(&bytes).unwrap();
assert_eq!(msg, back);
}
#[test]
fn round_trip_resize_with_pixels() {
let msg = ControlMessage::Resize {
cols: 120,
rows: 40,
pixel_width: 800,
pixel_height: 600,
};
let bytes = msg.to_json().unwrap();
let back = ControlMessage::from_slice(&bytes).unwrap();
assert_eq!(msg, back);
}
#[test]
fn round_trip_signal() {
let msg = ControlMessage::Signal {
name: "INT".to_string(),
};
let bytes = msg.to_json().unwrap();
let back = ControlMessage::from_slice(&bytes).unwrap();
assert_eq!(msg, back);
}
#[test]
fn round_trip_eof() {
let msg = ControlMessage::Eof;
let bytes = msg.to_json().unwrap();
let back = ControlMessage::from_slice(&bytes).unwrap();
assert_eq!(msg, back);
}
#[test]
fn round_trip_exit() {
let msg = ControlMessage::Exit { code: 42 };
let bytes = msg.to_json().unwrap();
let back = ControlMessage::from_slice(&bytes).unwrap();
assert_eq!(msg, back);
}
#[test]
fn to_json_emits_snake_case_type_tag() {
let resize = ControlMessage::Resize {
cols: 80,
rows: 24,
pixel_width: 0,
pixel_height: 0,
};
let json: serde_json::Value = serde_json::from_slice(&resize.to_json().unwrap()).unwrap();
assert_eq!(json["type"], "resize");
assert_eq!(json["cols"], 80);
assert_eq!(json["rows"], 24);
assert_eq!(json["pixel_width"], 0);
assert_eq!(json["pixel_height"], 0);
let signal = ControlMessage::Signal {
name: "INT".to_string(),
};
let json: serde_json::Value = serde_json::from_slice(&signal.to_json().unwrap()).unwrap();
assert_eq!(json["type"], "signal");
assert_eq!(json["name"], "INT");
let eof = ControlMessage::Eof;
let json: serde_json::Value = serde_json::from_slice(&eof.to_json().unwrap()).unwrap();
assert_eq!(json["type"], "eof");
let exit = ControlMessage::Exit { code: 0 };
let json: serde_json::Value = serde_json::from_slice(&exit.to_json().unwrap()).unwrap();
assert_eq!(json["type"], "exit");
assert_eq!(json["code"], 0);
}
#[test]
fn resize_omits_pixel_defaults_on_deserialize() {
let json = br#"{"type":"resize","cols":80,"rows":24}"#;
let msg = ControlMessage::from_slice(json).unwrap();
match msg {
ControlMessage::Resize {
cols,
rows,
pixel_width,
pixel_height,
} => {
assert_eq!(cols, 80);
assert_eq!(rows, 24);
assert_eq!(pixel_width, 0);
assert_eq!(pixel_height, 0);
}
_ => panic!("expected Resize"),
}
}
#[test]
fn from_slice_unknown_type_returns_error() {
let json = br#"{"type":"unknown"}"#;
assert!(ControlMessage::from_slice(json).is_err());
}
#[cfg(unix)]
#[test]
fn signal_from_name_known() {
use libc::*;
assert_eq!(signal_from_name("HUP"), Some(SIGHUP));
assert_eq!(signal_from_name("INT"), Some(SIGINT));
assert_eq!(signal_from_name("QUIT"), Some(SIGQUIT));
assert_eq!(signal_from_name("TERM"), Some(SIGTERM));
assert_eq!(signal_from_name("KILL"), Some(SIGKILL));
assert_eq!(signal_from_name("USR1"), Some(SIGUSR1));
assert_eq!(signal_from_name("USR2"), Some(SIGUSR2));
assert_eq!(signal_from_name("TSTP"), Some(SIGTSTP));
assert_eq!(signal_from_name("CONT"), Some(SIGCONT));
}
#[cfg(unix)]
#[test]
fn signal_from_name_unknown() {
assert_eq!(signal_from_name("NOPE"), None);
assert_eq!(signal_from_name(""), None);
assert_eq!(signal_from_name("int"), None);
}
}