Skip to main content

rskit_hook/
types.rs

1//! Generic observe-only hook event types.
2//!
3//! Domain-specific event types implement [`Event`]. Handlers receive read-only,
4//! concrete event references and can only return success or a typed [`HookError`].
5
6use std::any::Any;
7use std::fmt;
8
9/// A string-based event type identifier.
10#[derive(Debug, Clone, PartialEq, Eq, Hash)]
11pub struct EventType(String);
12
13impl EventType {
14    /// Create a new event type from any string-like value.
15    #[must_use]
16    pub fn new(name: impl Into<String>) -> Self {
17        Self(name.into())
18    }
19
20    /// Return the inner string slice.
21    #[must_use]
22    pub fn as_str(&self) -> &str {
23        &self.0
24    }
25}
26
27impl fmt::Display for EventType {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        f.write_str(&self.0)
30    }
31}
32
33/// Trait that all hook and in-process bus events must implement.
34///
35/// The public hook API dispatches on the concrete Rust event type. The
36/// [`EventType`] remains available as stable human-readable metadata for logs,
37/// metrics, and diagnostics.
38pub trait Event: Any + Send + Sync + 'static {
39    /// Return the event type discriminator for this event.
40    fn event_type(&self) -> EventType;
41}
42
43/// Hook failure. Fatal errors are rare and may stop the owning loop.
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct HookError {
46    message: String,
47    fatal: bool,
48}
49
50impl HookError {
51    /// Create a non-fatal hook error.
52    #[must_use]
53    pub fn new(message: impl Into<String>) -> Self {
54        Self {
55            message: message.into(),
56            fatal: false,
57        }
58    }
59
60    /// Create a fatal hook error.
61    #[must_use]
62    pub fn fatal(message: impl Into<String>) -> Self {
63        Self {
64            message: message.into(),
65            fatal: true,
66        }
67    }
68
69    /// Whether the owning loop should stop.
70    #[must_use]
71    pub const fn is_fatal(&self) -> bool {
72        self.fatal
73    }
74
75    /// Error message.
76    #[must_use]
77    pub fn message(&self) -> &str {
78        &self.message
79    }
80}
81
82impl fmt::Display for HookError {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        f.write_str(&self.message)
85    }
86}
87
88impl std::error::Error for HookError {}
89
90/// The outcome returned by a hook handler.
91pub type HookResult = Result<(), HookError>;
92
93#[cfg(test)]
94mod tests {
95    use super::{Event, EventType, HookError, HookResult};
96
97    struct Ping {
98        count: u32,
99    }
100
101    impl Event for Ping {
102        fn event_type(&self) -> EventType {
103            EventType::new("ping")
104        }
105    }
106
107    #[test]
108    fn event_type_equality() {
109        assert_eq!(EventType::new("ping"), EventType::new("ping"));
110        assert_ne!(EventType::new("ping"), EventType::new("pong"));
111    }
112
113    #[test]
114    fn event_type_is_static_metadata() {
115        let ping = Ping { count: 42 };
116        assert_eq!(ping.event_type(), EventType::new("ping"));
117        assert_eq!(ping.count, 42);
118    }
119
120    #[test]
121    fn hook_result_success() {
122        let result: HookResult = Ok(());
123        assert!(result.is_ok());
124    }
125
126    #[test]
127    fn hook_error_fatal_flag() {
128        let err = HookError::fatal("budget exceeded");
129        assert!(err.is_fatal());
130        assert_eq!(err.message(), "budget exceeded");
131    }
132}