1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct CommandDefinition {
7 pub id: String,
9
10 pub name: String,
12
13 pub description: String,
15
16 pub command: String,
18
19 pub arguments: Vec<CommandArgument>,
21
22 pub enabled: bool,
24
25 pub inject_output: bool,
27
28 pub timeout_seconds: u64,
30
31 pub tags: Vec<String>,
33
34 pub metadata: HashMap<String, String>,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct CommandArgument {
41 pub name: String,
43
44 pub description: String,
46
47 pub required: bool,
49
50 pub default: Option<String>,
52
53 pub validation_pattern: Option<String>,
55
56 pub arg_type: ArgumentType,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
62#[serde(rename_all = "lowercase")]
63pub enum ArgumentType {
64 String,
65 Number,
66 Boolean,
67 Path,
68 Choice(Vec<String>),
69}
70
71#[derive(Debug, Clone)]
73pub struct CommandContext {
74 pub cwd: String,
76
77 pub env: HashMap<String, String>,
79
80 pub arguments: HashMap<String, String>,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct CommandExecutionResult {
87 pub command_id: String,
89
90 pub exit_code: i32,
92
93 pub stdout: String,
95
96 pub stderr: String,
98
99 pub success: bool,
101
102 pub duration_ms: u64,
104}
105
106impl CommandDefinition {
107 pub fn new(id: impl Into<String>, name: impl Into<String>, command: impl Into<String>) -> Self {
109 Self {
110 id: id.into(),
111 name: name.into(),
112 description: String::new(),
113 command: command.into(),
114 arguments: Vec::new(),
115 enabled: true,
116 inject_output: false,
117 timeout_seconds: 0,
118 tags: Vec::new(),
119 metadata: HashMap::new(),
120 }
121 }
122
123 pub fn with_description(mut self, description: impl Into<String>) -> Self {
125 self.description = description.into();
126 self
127 }
128
129 pub fn with_argument(mut self, argument: CommandArgument) -> Self {
131 self.arguments.push(argument);
132 self
133 }
134
135 pub fn with_inject_output(mut self, inject: bool) -> Self {
137 self.inject_output = inject;
138 self
139 }
140
141 pub fn with_timeout(mut self, seconds: u64) -> Self {
143 self.timeout_seconds = seconds;
144 self
145 }
146
147 pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
149 self.tags.push(tag.into());
150 self
151 }
152
153 pub fn with_enabled(mut self, enabled: bool) -> Self {
155 self.enabled = enabled;
156 self
157 }
158}
159
160impl CommandArgument {
161 pub fn new(name: impl Into<String>, arg_type: ArgumentType) -> Self {
163 Self {
164 name: name.into(),
165 description: String::new(),
166 required: false,
167 default: None,
168 validation_pattern: None,
169 arg_type,
170 }
171 }
172
173 pub fn with_description(mut self, description: impl Into<String>) -> Self {
175 self.description = description.into();
176 self
177 }
178
179 pub fn with_required(mut self, required: bool) -> Self {
181 self.required = required;
182 self
183 }
184
185 pub fn with_default(mut self, default: impl Into<String>) -> Self {
187 self.default = Some(default.into());
188 self
189 }
190
191 pub fn with_validation_pattern(mut self, pattern: impl Into<String>) -> Self {
193 self.validation_pattern = Some(pattern.into());
194 self
195 }
196}
197
198impl CommandExecutionResult {
199 pub fn new(command_id: impl Into<String>, exit_code: i32) -> Self {
201 Self {
202 command_id: command_id.into(),
203 exit_code,
204 stdout: String::new(),
205 stderr: String::new(),
206 success: exit_code == 0,
207 duration_ms: 0,
208 }
209 }
210
211 pub fn with_stdout(mut self, stdout: impl Into<String>) -> Self {
213 self.stdout = stdout.into();
214 self
215 }
216
217 pub fn with_stderr(mut self, stderr: impl Into<String>) -> Self {
219 self.stderr = stderr.into();
220 self
221 }
222
223 pub fn with_duration(mut self, duration_ms: u64) -> Self {
225 self.duration_ms = duration_ms;
226 self
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 #[test]
235 fn test_command_definition_creation() {
236 let cmd = CommandDefinition::new("test-cmd", "Test Command", "echo hello");
237 assert_eq!(cmd.id, "test-cmd");
238 assert_eq!(cmd.name, "Test Command");
239 assert_eq!(cmd.command, "echo hello");
240 assert!(cmd.enabled);
241 assert!(!cmd.inject_output);
242 }
243
244 #[test]
245 fn test_command_definition_builder() {
246 let cmd = CommandDefinition::new("test-cmd", "Test Command", "echo hello")
247 .with_description("A test command")
248 .with_inject_output(true)
249 .with_timeout(30)
250 .with_tag("test")
251 .with_enabled(false);
252
253 assert_eq!(cmd.description, "A test command");
254 assert!(cmd.inject_output);
255 assert_eq!(cmd.timeout_seconds, 30);
256 assert_eq!(cmd.tags, vec!["test"]);
257 assert!(!cmd.enabled);
258 }
259
260 #[test]
261 fn test_command_argument_creation() {
262 let arg = CommandArgument::new("name", ArgumentType::String)
263 .with_description("User name")
264 .with_required(true);
265
266 assert_eq!(arg.name, "name");
267 assert_eq!(arg.description, "User name");
268 assert!(arg.required);
269 assert_eq!(arg.arg_type, ArgumentType::String);
270 }
271
272 #[test]
273 fn test_execution_result_creation() {
274 let result = CommandExecutionResult::new("test-cmd", 0)
275 .with_stdout("output")
276 .with_duration(100);
277
278 assert_eq!(result.command_id, "test-cmd");
279 assert_eq!(result.exit_code, 0);
280 assert!(result.success);
281 assert_eq!(result.stdout, "output");
282 assert_eq!(result.duration_ms, 100);
283 }
284}