1use serde::{Deserialize, Serialize};
4use std::fmt;
5
6#[must_use]
24pub fn is_interactive_tool(tool_name: &str) -> bool {
25 let trimmed = tool_name.trim();
26 !trimmed.is_empty()
27 && matches!(
28 trimmed,
29 "AskUserQuestion" | "EnterPlanMode" | "ExitPlanMode"
30 )
31}
32
33const HOOK_EVENT_VARIANTS: &[(HookEventType, &str)] = &[
36 (HookEventType::PreToolUse, "PreToolUse"),
37 (HookEventType::PostToolUse, "PostToolUse"),
38 (HookEventType::PostToolUseFailure, "PostToolUseFailure"),
39 (HookEventType::UserPromptSubmit, "UserPromptSubmit"),
40 (HookEventType::Stop, "Stop"),
41 (HookEventType::SubagentStart, "SubagentStart"),
42 (HookEventType::SubagentStop, "SubagentStop"),
43 (HookEventType::SessionStart, "SessionStart"),
44 (HookEventType::SessionEnd, "SessionEnd"),
45 (HookEventType::PreCompact, "PreCompact"),
46 (HookEventType::Setup, "Setup"),
47 (HookEventType::Notification, "Notification"),
48];
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
54#[serde(rename_all = "PascalCase")]
55pub enum HookEventType {
56 PreToolUse,
59 PostToolUse,
61 PostToolUseFailure,
63
64 UserPromptSubmit,
67 Stop,
69
70 SubagentStart,
73 SubagentStop,
75
76 SessionStart,
79 SessionEnd,
81
82 PreCompact,
85 Setup,
87
88 Notification,
91}
92
93impl HookEventType {
94 #[must_use]
99 pub fn as_str(&self) -> &'static str {
100 for (variant, name) in HOOK_EVENT_VARIANTS {
102 if variant == self {
103 return name;
104 }
105 }
106 "Unknown"
108 }
109
110 #[must_use]
112 pub fn is_pre_event(&self) -> bool {
113 matches!(
114 self,
115 Self::PreToolUse
116 | Self::SessionStart
117 | Self::PreCompact
118 | Self::SubagentStart
119 | Self::Setup
120 )
121 }
122
123 #[must_use]
125 pub fn is_post_event(&self) -> bool {
126 matches!(
127 self,
128 Self::PostToolUse
129 | Self::PostToolUseFailure
130 | Self::SessionEnd
131 | Self::Stop
132 | Self::SubagentStop
133 )
134 }
135
136 #[must_use]
140 pub fn from_event_name(name: &str) -> Option<Self> {
141 HOOK_EVENT_VARIANTS
142 .iter()
143 .find(|(_, s)| *s == name)
144 .map(|(v, _)| *v)
145 }
146}
147
148impl fmt::Display for HookEventType {
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150 f.write_str(self.as_str())
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn test_hook_event_parsing() {
160 assert_eq!(
161 HookEventType::from_event_name("PreToolUse"),
162 Some(HookEventType::PreToolUse)
163 );
164 assert_eq!(HookEventType::from_event_name("Unknown"), None);
165 }
166
167 #[test]
168 fn test_hook_event_classification() {
169 assert!(HookEventType::PreToolUse.is_pre_event());
170 assert!(HookEventType::PostToolUse.is_post_event());
171 assert!(!HookEventType::PreToolUse.is_post_event());
172 }
173
174 #[test]
175 fn test_is_interactive_tool() {
176 assert!(is_interactive_tool("AskUserQuestion"));
178 assert!(is_interactive_tool("EnterPlanMode"));
179 assert!(is_interactive_tool("ExitPlanMode"));
180
181 assert!(!is_interactive_tool("Bash"));
183 assert!(!is_interactive_tool("Read"));
184 assert!(!is_interactive_tool("Write"));
185 assert!(!is_interactive_tool("Edit"));
186 assert!(!is_interactive_tool("WebSearch"));
187 assert!(!is_interactive_tool("Grep"));
188 assert!(!is_interactive_tool("Glob"));
189 assert!(!is_interactive_tool("Task"));
190 }
191
192 #[test]
193 fn test_is_interactive_tool_edge_cases() {
194 assert!(!is_interactive_tool(""));
196
197 assert!(!is_interactive_tool(" "));
199 assert!(!is_interactive_tool("\t"));
200 assert!(!is_interactive_tool("\n"));
201
202 assert!(!is_interactive_tool("askuserquestion"));
204 assert!(!is_interactive_tool("ASKUSERQUESTION"));
205 assert!(!is_interactive_tool("AskUserquestion"));
206
207 assert!(!is_interactive_tool("AskUser"));
209 assert!(!is_interactive_tool("Question"));
210 assert!(!is_interactive_tool("EnterPlan"));
211
212 assert!(is_interactive_tool(" AskUserQuestion "));
214 assert!(is_interactive_tool("\tEnterPlanMode\n"));
215 }
216
217 #[test]
218 fn test_hook_event_all_variants_parse() {
219 assert_eq!(
221 HookEventType::from_event_name("PreToolUse"),
222 Some(HookEventType::PreToolUse)
223 );
224 assert_eq!(
225 HookEventType::from_event_name("PostToolUse"),
226 Some(HookEventType::PostToolUse)
227 );
228 assert_eq!(
229 HookEventType::from_event_name("PostToolUseFailure"),
230 Some(HookEventType::PostToolUseFailure)
231 );
232
233 assert_eq!(
235 HookEventType::from_event_name("UserPromptSubmit"),
236 Some(HookEventType::UserPromptSubmit)
237 );
238 assert_eq!(
239 HookEventType::from_event_name("Stop"),
240 Some(HookEventType::Stop)
241 );
242
243 assert_eq!(
245 HookEventType::from_event_name("SubagentStart"),
246 Some(HookEventType::SubagentStart)
247 );
248 assert_eq!(
249 HookEventType::from_event_name("SubagentStop"),
250 Some(HookEventType::SubagentStop)
251 );
252
253 assert_eq!(
255 HookEventType::from_event_name("SessionStart"),
256 Some(HookEventType::SessionStart)
257 );
258 assert_eq!(
259 HookEventType::from_event_name("SessionEnd"),
260 Some(HookEventType::SessionEnd)
261 );
262
263 assert_eq!(
265 HookEventType::from_event_name("PreCompact"),
266 Some(HookEventType::PreCompact)
267 );
268 assert_eq!(
269 HookEventType::from_event_name("Setup"),
270 Some(HookEventType::Setup)
271 );
272
273 assert_eq!(
275 HookEventType::from_event_name("Notification"),
276 Some(HookEventType::Notification)
277 );
278 }
279
280 #[test]
281 fn test_hook_event_classification_extended() {
282 assert!(HookEventType::PreToolUse.is_pre_event());
284 assert!(HookEventType::SessionStart.is_pre_event());
285 assert!(HookEventType::PreCompact.is_pre_event());
286 assert!(HookEventType::SubagentStart.is_pre_event());
287 assert!(HookEventType::Setup.is_pre_event());
288
289 assert!(HookEventType::PostToolUse.is_post_event());
291 assert!(HookEventType::PostToolUseFailure.is_post_event());
292 assert!(HookEventType::SessionEnd.is_post_event());
293 assert!(HookEventType::Stop.is_post_event());
294 assert!(HookEventType::SubagentStop.is_post_event());
295 }
296}