Skip to main content

scarab_plugin_api/events/
handler.rs

1//! Event handler types
2//!
3//! Defines the callback function types and result enums for event handlers.
4
5use super::EventArgs;
6
7/// Event handler function type
8///
9/// Handlers are called when events are dispatched. They receive event arguments
10/// and return a result indicating how to proceed with event processing.
11///
12/// Handlers must be `Send + Sync` to allow safe concurrent access across threads.
13pub type EventHandler = Box<dyn Fn(&EventArgs) -> EventResult + Send + Sync>;
14
15/// Result returned by event handlers
16///
17/// Determines how the event dispatch should proceed after a handler executes.
18#[derive(Clone, Debug, PartialEq, Eq)]
19pub enum EventResult {
20    /// Continue processing with the next handler
21    ///
22    /// This is the default behavior - the event will be passed to the next
23    /// handler in the priority chain.
24    Continue,
25
26    /// Stop processing and don't call remaining handlers
27    ///
28    /// Use this when an event has been fully handled and no further processing
29    /// is needed. Subsequent handlers will not be called.
30    Stop,
31
32    /// Modify the event data and continue
33    ///
34    /// Used primarily for legacy hook compatibility where handlers can transform
35    /// data (e.g., filtering output, modifying input). The modified data will be
36    /// passed to subsequent handlers.
37    Modified(Vec<u8>),
38}
39
40impl EventResult {
41    /// Check if this result indicates continuation
42    pub fn is_continue(&self) -> bool {
43        matches!(self, EventResult::Continue)
44    }
45
46    /// Check if this result indicates stopping
47    pub fn is_stop(&self) -> bool {
48        matches!(self, EventResult::Stop)
49    }
50
51    /// Check if this result contains modified data
52    pub fn is_modified(&self) -> bool {
53        matches!(self, EventResult::Modified(_))
54    }
55
56    /// Extract modified data if present
57    pub fn into_modified(self) -> Option<Vec<u8>> {
58        match self {
59            EventResult::Modified(data) => Some(data),
60            _ => None,
61        }
62    }
63
64    /// Get a reference to modified data if present
65    pub fn as_modified(&self) -> Option<&[u8]> {
66        match self {
67            EventResult::Modified(data) => Some(data),
68            _ => None,
69        }
70    }
71}
72
73/// Entry in the event handler registry
74///
75/// Stores handler metadata and the handler function itself.
76pub struct HandlerEntry {
77    /// Unique handler ID
78    pub id: u64,
79
80    /// Name of the plugin that registered this handler
81    pub plugin_name: String,
82
83    /// Handler priority (higher = called first)
84    ///
85    /// Handlers are sorted by priority in descending order. Typical priorities:
86    /// - 1000+: Critical system handlers
87    /// - 100-999: High priority plugin handlers
88    /// - 0-99: Normal priority handlers
89    /// - Negative: Low priority background handlers
90    pub priority: i32,
91
92    /// The handler function
93    pub handler: EventHandler,
94}
95
96impl HandlerEntry {
97    /// Create a new handler entry
98    pub fn new(
99        id: u64,
100        plugin_name: impl Into<String>,
101        priority: i32,
102        handler: EventHandler,
103    ) -> Self {
104        Self {
105            id,
106            plugin_name: plugin_name.into(),
107            priority,
108            handler,
109        }
110    }
111
112    /// Call this handler with event arguments
113    pub fn call(&self, args: &EventArgs) -> EventResult {
114        (self.handler)(args)
115    }
116}
117
118impl std::fmt::Debug for HandlerEntry {
119    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120        f.debug_struct("HandlerEntry")
121            .field("id", &self.id)
122            .field("plugin_name", &self.plugin_name)
123            .field("priority", &self.priority)
124            .field("handler", &"<function>")
125            .finish()
126    }
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132    use crate::events::EventType;
133
134    #[test]
135    fn test_event_result_checks() {
136        assert!(EventResult::Continue.is_continue());
137        assert!(!EventResult::Continue.is_stop());
138        assert!(!EventResult::Continue.is_modified());
139
140        assert!(EventResult::Stop.is_stop());
141        assert!(!EventResult::Stop.is_continue());
142
143        let modified = EventResult::Modified(vec![1, 2, 3]);
144        assert!(modified.is_modified());
145        assert!(!modified.is_stop());
146        assert_eq!(modified.as_modified(), Some(&[1, 2, 3][..]));
147    }
148
149    #[test]
150    fn test_event_result_into_modified() {
151        let result = EventResult::Modified(vec![1, 2, 3]);
152        assert_eq!(result.into_modified(), Some(vec![1, 2, 3]));
153
154        let result = EventResult::Continue;
155        assert_eq!(result.into_modified(), None);
156    }
157
158    #[test]
159    fn test_handler_entry_creation() {
160        let handler: EventHandler = Box::new(|_| EventResult::Continue);
161        let entry = HandlerEntry::new(1, "test-plugin", 100, handler);
162
163        assert_eq!(entry.id, 1);
164        assert_eq!(entry.plugin_name, "test-plugin");
165        assert_eq!(entry.priority, 100);
166    }
167
168    #[test]
169    fn test_handler_entry_call() {
170        let handler: EventHandler = Box::new(|args| {
171            if args.event_type == EventType::Bell {
172                EventResult::Stop
173            } else {
174                EventResult::Continue
175            }
176        });
177
178        let entry = HandlerEntry::new(1, "test", 0, handler);
179
180        let bell_args = EventArgs::new(EventType::Bell);
181        assert_eq!(entry.call(&bell_args), EventResult::Stop);
182
183        let other_args = EventArgs::new(EventType::TabCreated);
184        assert_eq!(entry.call(&other_args), EventResult::Continue);
185    }
186
187    #[test]
188    fn test_handler_entry_debug() {
189        let handler: EventHandler = Box::new(|_| EventResult::Continue);
190        let entry = HandlerEntry::new(42, "debug-test", 50, handler);
191        let debug_str = format!("{:?}", entry);
192
193        assert!(debug_str.contains("42"));
194        assert!(debug_str.contains("debug-test"));
195        assert!(debug_str.contains("50"));
196        assert!(debug_str.contains("<function>"));
197    }
198}