agent_config/spec/hook/
builder.rs1use crate::error::AgentConfigError;
4
5use super::{
6 validate_hook_string, Event, HookCommand, HookRuntimeOptions, HookSpec, HookStringKind,
7 Matcher, RulesBlock, ScriptTemplate,
8};
9
10#[derive(Debug, Clone)]
12pub struct HookSpecBuilder {
13 pub(super) tag: String,
14 pub(super) command: Option<HookCommand>,
15 pub(super) matcher: Matcher,
16 pub(super) event: Event,
17 pub(super) rules: Option<RulesBlock>,
18 pub(super) script: Option<ScriptTemplate>,
19 pub(super) friendly_name: Option<String>,
20 pub(super) options: HookRuntimeOptions,
21}
22
23impl HookSpecBuilder {
24 pub fn command_program<I, S>(mut self, program: impl Into<String>, args: I) -> Self
31 where
32 I: IntoIterator<Item = S>,
33 S: Into<String>,
34 {
35 self.command = Some(HookCommand::Program {
36 program: program.into(),
37 args: args.into_iter().map(Into::into).collect(),
38 });
39 self
40 }
41
42 pub fn command_shell_unchecked(mut self, command: impl Into<String>) -> Self {
48 self.command = Some(HookCommand::ShellUnchecked {
49 command: command.into(),
50 });
51 self
52 }
53
54 pub fn matcher(mut self, m: Matcher) -> Self {
56 self.matcher = m;
57 self
58 }
59
60 pub fn event(mut self, e: Event) -> Self {
62 self.event = e;
63 self
64 }
65
66 pub fn rules(mut self, content: impl Into<String>) -> Self {
69 self.rules = Some(RulesBlock {
70 content: content.into(),
71 });
72 self
73 }
74
75 pub fn script(mut self, script: ScriptTemplate) -> Self {
77 self.script = Some(script);
78 self
79 }
80
81 pub fn friendly_name(mut self, name: impl Into<String>) -> Self {
83 self.friendly_name = Some(name.into());
84 self
85 }
86
87 pub fn timeout_seconds(mut self, timeout: u64) -> Self {
89 self.options.timeout_seconds = Some(timeout);
90 self
91 }
92
93 pub fn status_message(mut self, msg: impl Into<String>) -> Self {
95 self.options.status_message = Some(msg.into());
96 self
97 }
98
99 pub fn async_run(mut self, val: bool) -> Self {
101 self.options.async_run = Some(val);
102 self
103 }
104
105 pub fn shell(mut self, sh: impl Into<String>) -> Self {
107 self.options.shell = Some(sh.into());
108 self
109 }
110
111 pub fn windows_command(mut self, cmd: HookCommand) -> Self {
113 self.options.windows_command = Some(cmd);
114 self
115 }
116
117 pub fn codex_inline_toml(mut self, val: bool) -> Self {
119 self.options.codex_inline_toml = Some(val);
120 self
121 }
122
123 pub fn build(self) -> HookSpec {
133 self.try_build().expect("HookSpec missing `command`")
134 }
135
136 pub fn try_build(self) -> Result<HookSpec, AgentConfigError> {
151 HookSpec::validate_tag(&self.tag)?;
152 let command = self.command.ok_or(AgentConfigError::MissingSpecField {
153 id: "<builder>",
154 field: "command",
155 })?;
156 command.validate()?;
157 match &self.matcher {
158 Matcher::All | Matcher::Bash => {}
159 Matcher::Exact(s) | Matcher::Regex(s) => {
160 validate_hook_string(s, HookStringKind::Matcher)?;
161 }
162 Matcher::AnyOf(list) => {
163 if list.is_empty() {
164 return Err(AgentConfigError::InvalidTag {
165 tag: String::new(),
166 reason: "matcher AnyOf must contain at least one entry",
167 });
168 }
169 for s in list {
170 validate_hook_string(s, HookStringKind::Matcher)?;
171 }
172 }
173 }
174 if let Event::Custom(name) = &self.event {
175 validate_hook_string(name, HookStringKind::CustomEvent)?;
176 }
177 Ok(HookSpec {
178 tag: self.tag,
179 command,
180 matcher: self.matcher,
181 event: self.event,
182 rules: self.rules,
183 script: self.script,
184 friendly_name: self.friendly_name,
185 options: self.options,
186 })
187 }
188}