use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SystemEventPayload {
name: String,
kind: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pid: Option<u32>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
publishes: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
subscribes: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
error: Option<String>,
}
impl SystemEventPayload {
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub fn kind(&self) -> &str {
&self.kind
}
#[must_use]
pub fn pid(&self) -> Option<u32> {
self.pid
}
#[must_use]
pub fn publishes(&self) -> &[String] {
&self.publishes
}
#[must_use]
pub fn subscribes(&self) -> &[String] {
&self.subscribes
}
#[must_use]
pub fn error(&self) -> Option<&str> {
self.error.as_deref()
}
#[must_use]
pub fn is_source(&self) -> bool {
self.kind == "source"
}
#[must_use]
pub fn is_handler(&self) -> bool {
self.kind == "handler"
}
#[must_use]
pub fn is_sink(&self) -> bool {
self.kind == "sink"
}
#[must_use]
pub fn is_error(&self) -> bool {
self.error.is_some()
}
}
impl fmt::Display for SystemEventPayload {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} ({})", self.name, self.kind)?;
if let Some(pid) = self.pid {
write!(f, " [pid: {}]", pid)?;
}
if let Some(error) = &self.error {
write!(f, " error: {}", error)?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SystemShutdownPayload {
kind: String,
}
impl SystemShutdownPayload {
#[must_use]
pub fn kind(&self) -> &str {
&self.kind
}
#[must_use]
pub fn is_handler_shutdown(&self) -> bool {
self.kind == "handler"
}
#[must_use]
pub fn is_sink_shutdown(&self) -> bool {
self.kind == "sink"
}
}
impl fmt::Display for SystemShutdownPayload {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "shutdown ({})", self.kind)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn deserialize_started_source() -> Result<(), serde_json::Error> {
let json = r#"{
"name": "timer",
"kind": "source",
"pid": 1234,
"publishes": ["timer.tick"],
"subscribes": []
}"#;
let payload: SystemEventPayload = serde_json::from_str(json)?;
assert_eq!(payload.name(), "timer");
assert_eq!(payload.kind(), "source");
assert_eq!(payload.pid(), Some(1234));
assert_eq!(payload.publishes(), &["timer.tick"]);
assert!(payload.subscribes().is_empty());
assert!(payload.error().is_none());
assert!(payload.is_source());
assert!(!payload.is_handler());
assert!(!payload.is_sink());
assert!(!payload.is_error());
Ok(())
}
#[test]
fn deserialize_started_handler() -> Result<(), serde_json::Error> {
let json = r#"{
"name": "filter",
"kind": "handler",
"pid": 5678,
"publishes": ["timer.filtered"],
"subscribes": ["timer.tick"]
}"#;
let payload: SystemEventPayload = serde_json::from_str(json)?;
assert_eq!(payload.name(), "filter");
assert_eq!(payload.kind(), "handler");
assert_eq!(payload.pid(), Some(5678));
assert_eq!(payload.publishes(), &["timer.filtered"]);
assert_eq!(payload.subscribes(), &["timer.tick"]);
assert!(payload.is_handler());
Ok(())
}
#[test]
fn deserialize_started_sink() -> Result<(), serde_json::Error> {
let json = r#"{
"name": "console",
"kind": "sink",
"pid": 9012,
"subscribes": ["timer.filtered"]
}"#;
let payload: SystemEventPayload = serde_json::from_str(json)?;
assert_eq!(payload.name(), "console");
assert_eq!(payload.kind(), "sink");
assert_eq!(payload.pid(), Some(9012));
assert!(payload.publishes().is_empty());
assert_eq!(payload.subscribes(), &["timer.filtered"]);
assert!(payload.is_sink());
Ok(())
}
#[test]
fn deserialize_stopped_without_pid() -> Result<(), serde_json::Error> {
let json = r#"{
"name": "timer",
"kind": "source",
"publishes": ["timer.tick"]
}"#;
let payload: SystemEventPayload = serde_json::from_str(json)?;
assert_eq!(payload.name(), "timer");
assert!(payload.pid().is_none());
Ok(())
}
#[test]
fn deserialize_error_event() -> Result<(), serde_json::Error> {
let json = r#"{
"name": "failing-source",
"kind": "source",
"pid": 3456,
"publishes": ["data.event"],
"error": "Connection refused"
}"#;
let payload: SystemEventPayload = serde_json::from_str(json)?;
assert_eq!(payload.name(), "failing-source");
assert_eq!(payload.error(), Some("Connection refused"));
assert!(payload.is_error());
Ok(())
}
#[test]
fn deserialize_minimal_payload() -> Result<(), serde_json::Error> {
let json = r#"{"name": "test", "kind": "source"}"#;
let payload: SystemEventPayload = serde_json::from_str(json)?;
assert_eq!(payload.name(), "test");
assert_eq!(payload.kind(), "source");
assert!(payload.pid().is_none());
assert!(payload.publishes().is_empty());
assert!(payload.subscribes().is_empty());
assert!(payload.error().is_none());
Ok(())
}
#[test]
fn serialize_roundtrip() -> Result<(), serde_json::Error> {
let json = r#"{
"name": "my-handler",
"kind": "handler",
"pid": 42,
"publishes": ["output.event"],
"subscribes": ["input.event"]
}"#;
let payload: SystemEventPayload = serde_json::from_str(json)?;
let serialized = serde_json::to_string(&payload)?;
let restored: SystemEventPayload = serde_json::from_str(&serialized)?;
assert_eq!(payload, restored);
Ok(())
}
#[test]
fn display_format_basic() -> Result<(), serde_json::Error> {
let json = r#"{"name": "timer", "kind": "source", "pid": 1234}"#;
let payload: SystemEventPayload = serde_json::from_str(json)?;
let display = payload.to_string();
assert_eq!(display, "timer (source) [pid: 1234]");
Ok(())
}
#[test]
fn display_format_without_pid() -> Result<(), serde_json::Error> {
let json = r#"{"name": "timer", "kind": "source"}"#;
let payload: SystemEventPayload = serde_json::from_str(json)?;
let display = payload.to_string();
assert_eq!(display, "timer (source)");
Ok(())
}
#[test]
fn display_format_with_error() -> Result<(), serde_json::Error> {
let json = r#"{"name": "failing", "kind": "source", "error": "Connection refused"}"#;
let payload: SystemEventPayload = serde_json::from_str(json)?;
let display = payload.to_string();
assert_eq!(display, "failing (source) error: Connection refused");
Ok(())
}
#[test]
fn payload_is_clone() -> Result<(), serde_json::Error> {
let json = r#"{"name": "timer", "kind": "source"}"#;
let payload: SystemEventPayload = serde_json::from_str(json)?;
let cloned = payload.clone();
assert_eq!(payload, cloned);
Ok(())
}
#[test]
fn deserialize_handler_shutdown() -> Result<(), serde_json::Error> {
let json = r#"{"kind": "handler"}"#;
let payload: SystemShutdownPayload = serde_json::from_str(json)?;
assert_eq!(payload.kind(), "handler");
assert!(payload.is_handler_shutdown());
assert!(!payload.is_sink_shutdown());
Ok(())
}
#[test]
fn deserialize_sink_shutdown() -> Result<(), serde_json::Error> {
let json = r#"{"kind": "sink"}"#;
let payload: SystemShutdownPayload = serde_json::from_str(json)?;
assert_eq!(payload.kind(), "sink");
assert!(!payload.is_handler_shutdown());
assert!(payload.is_sink_shutdown());
Ok(())
}
#[test]
fn shutdown_serialize_roundtrip() -> Result<(), serde_json::Error> {
let json = r#"{"kind": "handler"}"#;
let payload: SystemShutdownPayload = serde_json::from_str(json)?;
let serialized = serde_json::to_string(&payload)?;
let restored: SystemShutdownPayload = serde_json::from_str(&serialized)?;
assert_eq!(payload, restored);
Ok(())
}
#[test]
fn shutdown_display_format() -> Result<(), serde_json::Error> {
let json = r#"{"kind": "handler"}"#;
let payload: SystemShutdownPayload = serde_json::from_str(json)?;
let display = payload.to_string();
assert_eq!(display, "shutdown (handler)");
Ok(())
}
#[test]
fn shutdown_is_clone() -> Result<(), serde_json::Error> {
let json = r#"{"kind": "sink"}"#;
let payload: SystemShutdownPayload = serde_json::from_str(json)?;
let cloned = payload.clone();
assert_eq!(payload, cloned);
Ok(())
}
}