1use std::path::PathBuf;
5
6use serde::{Deserialize, Serialize};
7
8use crate::subagent::HookDef;
9
10fn default_debounce_ms() -> u64 {
11 500
12}
13
14#[derive(Debug, Clone, Deserialize, Serialize)]
16#[serde(default)]
17pub struct FileChangedConfig {
18 pub watch_paths: Vec<PathBuf>,
20 #[serde(default = "default_debounce_ms")]
22 pub debounce_ms: u64,
23 #[serde(default)]
25 pub hooks: Vec<HookDef>,
26}
27
28impl Default for FileChangedConfig {
29 fn default() -> Self {
30 Self {
31 watch_paths: Vec::new(),
32 debounce_ms: default_debounce_ms(),
33 hooks: Vec::new(),
34 }
35 }
36}
37
38#[derive(Debug, Clone, Default, Deserialize, Serialize)]
40#[serde(default)]
41pub struct HooksConfig {
42 pub cwd_changed: Vec<HookDef>,
44 pub file_changed: Option<FileChangedConfig>,
46}
47
48impl HooksConfig {
49 #[must_use]
50 pub fn is_empty(&self) -> bool {
51 self.cwd_changed.is_empty() && self.file_changed.is_none()
52 }
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58 use crate::subagent::HookType;
59
60 #[test]
61 fn hooks_config_default_is_empty() {
62 let cfg = HooksConfig::default();
63 assert!(cfg.is_empty());
64 }
65
66 #[test]
67 fn file_changed_config_default_debounce() {
68 let cfg = FileChangedConfig::default();
69 assert_eq!(cfg.debounce_ms, 500);
70 assert!(cfg.watch_paths.is_empty());
71 assert!(cfg.hooks.is_empty());
72 }
73
74 #[test]
75 fn hooks_config_parses_from_toml() {
76 let toml = r#"
77[[cwd_changed]]
78type = "command"
79command = "echo changed"
80timeout_secs = 10
81fail_closed = false
82
83[file_changed]
84watch_paths = ["src/", "Cargo.toml"]
85debounce_ms = 300
86[[file_changed.hooks]]
87type = "command"
88command = "cargo check"
89timeout_secs = 30
90fail_closed = false
91"#;
92 let cfg: HooksConfig = toml::from_str(toml).unwrap();
93 assert_eq!(cfg.cwd_changed.len(), 1);
94 assert_eq!(cfg.cwd_changed[0].command, "echo changed");
95 assert_eq!(cfg.cwd_changed[0].hook_type, HookType::Command);
96 let fc = cfg.file_changed.as_ref().unwrap();
97 assert_eq!(fc.watch_paths.len(), 2);
98 assert_eq!(fc.debounce_ms, 300);
99 assert_eq!(fc.hooks.len(), 1);
100 assert_eq!(fc.hooks[0].command, "cargo check");
101 }
102
103 #[test]
104 fn hooks_config_not_empty_with_cwd_hooks() {
105 let cfg = HooksConfig {
106 cwd_changed: vec![HookDef {
107 hook_type: HookType::Command,
108 command: "echo hi".into(),
109 timeout_secs: 10,
110 fail_closed: false,
111 }],
112 file_changed: None,
113 };
114 assert!(!cfg.is_empty());
115 }
116}