mi6_core/config/
hooks.rs

1//! Hook configuration for mi6.
2//!
3//! This module defines which hook events are enabled for capture.
4
5use serde::{Deserialize, Serialize};
6
7use crate::model::EventType;
8
9/// Macro to define HooksConfig with automatic snake_case to PascalCase mapping.
10///
11/// This macro generates the struct, Default impl, and enabled_hooks() method
12/// from a single definition, ensuring the field names and EventType variants
13/// are always in sync.
14macro_rules! define_hooks_config {
15    ($($field:ident => $variant:ident),* $(,)?) => {
16        #[derive(Debug, Clone, Serialize, Deserialize)]
17        #[serde(rename_all = "PascalCase")]
18        pub struct HooksConfig {
19            $(
20                #[serde(default = "default_true")]
21                pub $field: bool,
22            )*
23        }
24
25        impl Default for HooksConfig {
26            fn default() -> Self {
27                Self {
28                    $($field: true,)*
29                }
30            }
31        }
32
33        impl HooksConfig {
34            /// Returns enabled hooks as EventType values for type-safe handling.
35            pub fn enabled_hooks(&self) -> Vec<EventType> {
36                let mut hooks = Vec::new();
37                $(
38                    if self.$field {
39                        hooks.push(EventType::$variant);
40                    }
41                )*
42                hooks
43            }
44        }
45    };
46}
47
48define_hooks_config! {
49    session_start => SessionStart,
50    session_end => SessionEnd,
51    user_prompt_submit => UserPromptSubmit,
52    stop => Stop,
53    subagent_start => SubagentStart,
54    subagent_stop => SubagentStop,
55    notification => Notification,
56    permission_request => PermissionRequest,
57    pre_compact => PreCompact,
58    pre_tool_use => PreToolUse,
59    post_tool_use => PostToolUse,
60}
61
62fn default_true() -> bool {
63    true
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn test_enabled_hooks_returns_all_event_types() {
72        let config = HooksConfig::default();
73        let hooks = config.enabled_hooks();
74
75        // Verify all expected hooks are present as EventType variants
76        assert!(hooks.contains(&EventType::SessionStart));
77        assert!(hooks.contains(&EventType::SessionEnd));
78        assert!(hooks.contains(&EventType::UserPromptSubmit));
79        assert!(hooks.contains(&EventType::Stop));
80        assert!(hooks.contains(&EventType::SubagentStart));
81        assert!(hooks.contains(&EventType::SubagentStop));
82        assert!(hooks.contains(&EventType::Notification));
83        assert!(hooks.contains(&EventType::PermissionRequest));
84        assert!(hooks.contains(&EventType::PreCompact));
85        assert!(hooks.contains(&EventType::PreToolUse));
86        assert!(hooks.contains(&EventType::PostToolUse));
87
88        // Verify we have exactly 11 hooks
89        assert_eq!(hooks.len(), 11);
90    }
91
92    #[test]
93    fn test_enabled_hooks_respects_disabled_hooks() {
94        let config = HooksConfig {
95            session_start: false,
96            pre_tool_use: false,
97            ..Default::default()
98        };
99
100        let hooks = config.enabled_hooks();
101        // 11 total hooks - 2 disabled = 9
102        assert_eq!(hooks.len(), 9);
103        assert!(!hooks.contains(&EventType::SessionStart));
104        assert!(!hooks.contains(&EventType::PreToolUse));
105        assert!(hooks.contains(&EventType::SessionEnd));
106    }
107}