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]
59 pub fn is_empty(&self) -> bool {
60 self.cwd_changed.is_empty() && self.file_changed.is_none()
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67 use crate::subagent::HookType;
68
69 #[test]
70 fn hooks_config_default_is_empty() {
71 let cfg = HooksConfig::default();
72 assert!(cfg.is_empty());
73 }
74
75 #[test]
76 fn file_changed_config_default_debounce() {
77 let cfg = FileChangedConfig::default();
78 assert_eq!(cfg.debounce_ms, 500);
79 assert!(cfg.watch_paths.is_empty());
80 assert!(cfg.hooks.is_empty());
81 }
82
83 #[test]
84 fn hooks_config_parses_from_toml() {
85 let toml = r#"
86[[cwd_changed]]
87type = "command"
88command = "echo changed"
89timeout_secs = 10
90fail_closed = false
91
92[file_changed]
93watch_paths = ["src/", "Cargo.toml"]
94debounce_ms = 300
95[[file_changed.hooks]]
96type = "command"
97command = "cargo check"
98timeout_secs = 30
99fail_closed = false
100"#;
101 let cfg: HooksConfig = toml::from_str(toml).unwrap();
102 assert_eq!(cfg.cwd_changed.len(), 1);
103 assert_eq!(cfg.cwd_changed[0].command, "echo changed");
104 assert_eq!(cfg.cwd_changed[0].hook_type, HookType::Command);
105 let fc = cfg.file_changed.as_ref().unwrap();
106 assert_eq!(fc.watch_paths.len(), 2);
107 assert_eq!(fc.debounce_ms, 300);
108 assert_eq!(fc.hooks.len(), 1);
109 assert_eq!(fc.hooks[0].command, "cargo check");
110 }
111
112 #[test]
113 fn hooks_config_not_empty_with_cwd_hooks() {
114 let cfg = HooksConfig {
115 cwd_changed: vec![HookDef {
116 hook_type: HookType::Command,
117 command: "echo hi".into(),
118 timeout_secs: 10,
119 fail_closed: false,
120 }],
121 file_changed: None,
122 };
123 assert!(!cfg.is_empty());
124 }
125}