1use schemars::JsonSchema;
21use serde::{Deserialize, Serialize};
22
23pub(crate) const DEFAULT_CHECK_PROCESS_TAIL_LINES: usize = 50;
24
25fn default_read_file_mode() -> ReadFileMode {
26 ReadFileMode::Preview
27}
28
29#[derive(Debug, Deserialize, Serialize, JsonSchema)]
31pub struct ExecParams {
32 pub command: String,
34
35 #[serde(default)]
42 pub background: bool,
43
44 pub timeout_ms: Option<u64>,
46
47 pub log_path: Option<String>,
51}
52
53#[derive(Debug, Deserialize, Serialize, JsonSchema)]
55pub struct SudoExecParams {
56 pub command: String,
58
59 #[serde(default)]
66 pub background: bool,
67
68 pub timeout_ms: Option<u64>,
70
71 pub log_path: Option<String>,
75}
76
77#[derive(Debug, Deserialize, Serialize, JsonSchema)]
83pub struct CheckProcessParams {
84 pub job_id: String,
86
87 #[serde(default = "default_tail_lines")]
89 pub tail_lines: usize,
90}
91
92#[derive(Debug, Clone, Copy, Deserialize, Serialize, JsonSchema, PartialEq, Eq)]
94#[serde(rename_all = "lowercase")]
95pub enum ReadFileMode {
96 Preview,
98 Head,
100 Tail,
102 Full,
104}
105
106impl ReadFileMode {
107 pub const fn as_str(self) -> &'static str {
108 match self {
109 Self::Preview => "preview",
110 Self::Head => "head",
111 Self::Tail => "tail",
112 Self::Full => "full",
113 }
114 }
115}
116
117#[derive(Debug, Deserialize, Serialize, JsonSchema)]
118pub struct ReadFileParams {
119 pub remote_path: String,
121
122 #[serde(default = "default_read_file_mode")]
124 pub mode: ReadFileMode,
125
126 pub lines: Option<usize>,
128
129 pub timeout_ms: Option<u64>,
131}
132
133#[derive(Debug, Deserialize, Serialize, JsonSchema)]
135#[serde(deny_unknown_fields)]
136pub struct ApplyPatchParams {
137 pub patch: String,
139}
140
141fn default_tail_lines() -> usize {
142 DEFAULT_CHECK_PROCESS_TAIL_LINES
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn test_exec_params_deserialize() {
151 let json = r#"{"command": "echo hello"}"#;
152 let params: ExecParams = serde_json::from_str(json).unwrap();
153 assert_eq!(params.command, "echo hello");
154 assert!(!params.background);
155 assert!(params.timeout_ms.is_none());
156 assert!(params.log_path.is_none());
157 }
158
159 #[test]
160 fn test_exec_params_deserialize_background() {
161 let json = r#"{"command": "sleep 10", "background": true, "timeout_ms": 1000, "log_path": "/tmp/x.log"}"#;
162 let params: ExecParams = serde_json::from_str(json).unwrap();
163 assert_eq!(params.command, "sleep 10");
164 assert!(params.background);
165 assert_eq!(params.timeout_ms, Some(1000));
166 assert_eq!(params.log_path.as_deref(), Some("/tmp/x.log"));
167 }
168
169 #[test]
170 fn test_sudo_exec_params_deserialize() {
171 let json = r#"{"command": "apt update"}"#;
172 let params: SudoExecParams = serde_json::from_str(json).unwrap();
173 assert_eq!(params.command, "apt update");
174 assert!(!params.background);
175 assert!(params.timeout_ms.is_none());
176 assert!(params.log_path.is_none());
177 }
178
179 #[test]
180 fn test_check_process_params_deserialize() {
181 let json = r#"{"job_id": "job-123"}"#;
182 let params: CheckProcessParams = serde_json::from_str(json).unwrap();
183 assert_eq!(params.job_id, "job-123");
184 assert_eq!(params.tail_lines, 50);
185 }
186
187 #[test]
188 fn test_check_process_params_with_tail_lines() {
189 let json = r#"{"job_id": "job-123", "tail_lines": 100}"#;
190 let params: CheckProcessParams = serde_json::from_str(json).unwrap();
191 assert_eq!(params.job_id, "job-123");
192 assert_eq!(params.tail_lines, 100);
193 }
194
195 #[test]
196 fn test_read_file_params_deserialize() {
197 let json = r#"{"remote_path": "/etc/hosts"}"#;
198 let params: ReadFileParams = serde_json::from_str(json).unwrap();
199 assert_eq!(params.remote_path, "/etc/hosts");
200 assert_eq!(params.mode, ReadFileMode::Preview);
201 assert_eq!(params.lines, None);
202 assert!(params.timeout_ms.is_none());
203 }
204
205 #[test]
206 fn test_read_file_params_deserialize_with_timeout() {
207 let json = r#"{"remote_path": "/etc/hosts", "timeout_ms": 2500}"#;
208 let params: ReadFileParams = serde_json::from_str(json).unwrap();
209 assert_eq!(params.remote_path, "/etc/hosts");
210 assert_eq!(params.mode, ReadFileMode::Preview);
211 assert_eq!(params.lines, None);
212 assert_eq!(params.timeout_ms, Some(2500));
213 }
214
215 #[test]
216 fn test_read_file_params_deserialize_with_mode_and_lines() {
217 let json = r#"{"remote_path":"/etc/hosts","mode":"tail","lines":120}"#;
218 let params: ReadFileParams = serde_json::from_str(json).unwrap();
219 assert_eq!(params.remote_path, "/etc/hosts");
220 assert_eq!(params.mode, ReadFileMode::Tail);
221 assert_eq!(params.lines, Some(120));
222 assert!(params.timeout_ms.is_none());
223 }
224
225 #[test]
226 fn test_read_file_mode_serialization_is_lowercase() {
227 let value = serde_json::to_value(ReadFileMode::Full).unwrap();
228 assert_eq!(value, serde_json::json!("full"));
229 }
230
231 #[test]
232 fn test_apply_patch_params_deserialize_and_reject_unknown_fields() {
233 let json = r#"{"patch":"*** Begin Patch\n*** Delete File: /tmp/old\n*** End Patch"}"#;
234 let params: ApplyPatchParams = serde_json::from_str(json).unwrap();
235 assert!(params.patch.contains("*** Delete File"));
236
237 let err =
238 serde_json::from_str::<ApplyPatchParams>(r#"{"patch":"x","remote_path":"/tmp/x"}"#)
239 .unwrap_err();
240 assert!(err.to_string().contains("unknown field `remote_path`"));
241 }
242}