1use std::{collections::HashMap, path::PathBuf};
2
3use serde::{de::Error, Deserialize, Deserializer};
4
5use crate::process::{validate_stdout_config, LoggingType, MessagingType};
6
7use super::{BufferCapacity, CmdOptions};
8
9impl<'de> Deserialize<'de> for BufferCapacity {
10 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
11 where
12 D: Deserializer<'de>,
13 {
14 #[derive(Deserialize)]
15 struct BufferCapacityHelper(usize);
16
17 let helper = BufferCapacityHelper::deserialize(deserializer)?;
18 BufferCapacity::try_from(helper.0).map_err(Error::custom)
19 }
20}
21
22impl serde::Serialize for BufferCapacity {
23 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
24 where
25 S: serde::Serializer,
26 {
27 serializer.serialize_i64(self.inner as i64)
28 }
29}
30
31impl<'de> Deserialize<'de> for CmdOptions {
32 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
33 where
34 D: Deserializer<'de>,
35 {
36 #[derive(Deserialize, Default)]
37 #[serde(default)]
38 struct CmdOptionsHelper {
39 current_dir: Option<PathBuf>,
40 clear_envs: bool,
41 envs: HashMap<String, String>,
42 envs_to_remove: Vec<String>,
43 output_buffer_capacity: BufferCapacity,
44 message_input: Option<MessagingType>,
45 message_output: Option<MessagingType>,
46 logging_type: Option<LoggingType>,
47 }
48
49 let mut helper = CmdOptionsHelper::deserialize(deserializer)?;
50
51 validate_stdout_config(helper.message_output.as_ref(), helper.logging_type.as_ref())
52 .map_err(Error::custom)?;
53
54 if !helper.envs.is_empty() && !helper.envs_to_remove.is_empty() {
55 for env in &helper.envs_to_remove {
56 helper.envs.remove(env);
57 }
58 }
59
60 Ok(CmdOptions {
61 current_dir: helper.current_dir,
62 clear_envs: helper.clear_envs,
63 envs: helper.envs,
64 envs_to_remove: helper.envs_to_remove,
65 output_buffer_capacity: helper.output_buffer_capacity,
66 message_input: helper.message_input,
67 message_output: helper.message_output,
68 logging_type: helper.logging_type,
69 })
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use std::{collections::HashMap, path::PathBuf};
76
77 use crate::process::{
78 BufferCapacity, Cmd, CmdOptions, LoggingType, MessagingType, Script, ScriptRunConfig,
79 ScriptingLanguage,
80 };
81
82 #[test]
85 fn should_serialize_buffer_capacity() {
86 let capacity = BufferCapacity::try_from(123).unwrap();
87
88 let serialized = serde_json::to_string(&capacity).unwrap();
89 assert_eq!("123", serialized);
90 }
91
92 #[test]
93 fn should_deserialize_buffer_capacity() {
94 let expected = BufferCapacity::try_from(9876).unwrap();
95
96 let deserialized = serde_json::from_str("9876").unwrap();
97 assert_eq!(expected, deserialized);
98 }
99
100 #[test]
101 fn should_return_err_when_deserializing_invalid_buffer_capacity() {
102 let value = usize::MAX / 2 + 1;
103 let result = serde_json::from_str::<'_, BufferCapacity>(&value.to_string());
104 assert!(result.is_err());
105 let expected_msg =
106 "Buffer capacity must be greater than 0 and less or equal usize::MAX / 2";
107 assert!(result.err().unwrap().to_string().contains(expected_msg));
108
109 let value = 0;
110 let result = serde_json::from_str::<'_, BufferCapacity>(&value.to_string());
111 assert!(result.is_err());
112 assert!(result.err().unwrap().to_string().contains(expected_msg));
113
114 let value = -1;
115 let result = serde_json::from_str::<'_, BufferCapacity>(&value.to_string());
116 assert!(result.is_err());
117
118 let value = 3.4;
119 let result = serde_json::from_str::<'_, BufferCapacity>(&value.to_string());
120 assert!(result.is_err());
121 }
122
123 #[test]
126 fn should_serialize_and_deserialize_options() {
127 let options = create_options();
128
129 let serialized = serde_json::to_string(&options).unwrap();
130 assert_eq!(options_json(), serialized);
131
132 let deserialized = serde_json::from_str(&serialized).unwrap();
133 assert_eq!(options, deserialized);
134 }
135
136 #[test]
137 fn should_deserialize_options_with_defaults() {
138 let deserialized: CmdOptions =
140 serde_json::from_str(options_without_option_type_fields()).unwrap();
141 assert!(!deserialized.clear_envs);
142 assert_eq!(
143 BufferCapacity::default(),
144 deserialized.output_buffer_capacity
145 );
146
147 let serialized = r#"{"current_dir": "/bin", "clear_envs": true}"#;
149 let deserialized: CmdOptions = serde_json::from_str(serialized).unwrap();
150 let expected = CmdOptions {
151 current_dir: PathBuf::from("/bin").into(),
152 clear_envs: true,
153 ..Default::default()
154 };
155 assert_eq!(expected, deserialized);
156
157 let deserialized: CmdOptions = serde_json::from_str("{}").unwrap();
159 assert_eq!(CmdOptions::default(), deserialized);
160 }
161
162 #[test]
163 fn should_return_err_when_deserializing_invalid_options() {
164 let serialized = options_json_with_invalid_buffer_capacity();
166 let result = serde_json::from_str::<'_, CmdOptions>(serialized);
167 let expected_msg =
168 "Buffer capacity must be greater than 0 and less or equal usize::MAX / 2";
169 result_contains_err_msg(&result, expected_msg);
170
171 let serialized = options_json_with_invalid_message_output();
173 let result = serde_json::from_str::<'_, CmdOptions>(serialized);
174 let expected_msg = format!(
175 "Cannot use {:?} together with {:?} for stdout configuration",
176 MessagingType::StandardIo,
177 LoggingType::StdoutOnly
178 );
179 result_contains_err_msg(&result, &expected_msg);
180 }
181
182 #[test]
183 fn should_properly_set_envs_during_deserialization() {
184 let serialized = options_json_with_wrong_defined_envs();
185
186 let options = serde_json::from_str::<'_, CmdOptions>(serialized).unwrap();
187
188 let envs = options.envs;
189 assert_eq!(1, envs.len()); let env = envs.get("ENV1");
191 assert!(env.is_some());
192 assert_eq!("value1", env.unwrap());
193 }
194
195 fn create_options() -> CmdOptions {
196 let mut envs = HashMap::new();
197 envs.insert("ENV1", "value1");
198 envs.insert("PATH", "/bin");
199
200 let mut options = CmdOptions::default();
201 options.set_current_dir("/some/path".into());
202 options.clear_inherited_envs(true);
203 options.set_envs(envs);
204 options.remove_env("PATH");
205 options.set_message_output_buffer_capacity(BufferCapacity::try_from(24).unwrap());
206 options.set_message_input(MessagingType::StandardIo);
207 options
208 .set_message_output(MessagingType::NamedPipe)
209 .unwrap();
210 options.set_logging_type(LoggingType::StderrOnly).unwrap();
211 options
212 }
213
214 fn options_json() -> &'static str {
215 r#"{"current_dir":"/some/path","clear_envs":true,"envs":{"ENV1":"value1"},"envs_to_remove":["PATH"],"output_buffer_capacity":24,"message_input":"StandardIo","message_output":"NamedPipe","logging_type":"StderrOnly"}"#
216 }
217
218 fn options_without_option_type_fields() -> &'static str {
220 r#"{"current_dir":"/some/path","envs":{"ENV1":"value1"},"envs_to_remove":["PATH"],"message_input":"StandardIo","message_output":"NamedPipe","logging_type":"StderrOnly"}"#
221 }
222
223 fn options_json_with_invalid_buffer_capacity() -> &'static str {
224 r#"{"current_dir":"/some/path","clear_envs":true,"envs":{"ENV1":"value1"},"envs_to_remove":["PATH"],"output_buffer_capacity":0,"message_input":"StandardIo","message_output":"NamedPipe","logging_type":"StderrOnly"}"#
225 }
226
227 fn options_json_with_invalid_message_output() -> &'static str {
228 r#"{"current_dir":"/some/path","clear_envs":true,"envs":{"ENV1":"value1"},"envs_to_remove":["PATH"],"output_buffer_capacity":7,"message_input":"StandardIo","message_output":"StandardIo","logging_type":"StdoutOnly"}"#
229 }
230
231 fn options_json_with_wrong_defined_envs() -> &'static str {
232 r#"{"current_dir":"/some/path","clear_envs":true,"envs":{"ENV1":"value1","PATH":"/bin"},"envs_to_remove":["PATH"],"output_buffer_capacity":24,"message_input":"StandardIo","message_output":"NamedPipe","logging_type":"StderrOnly"}"#
233 }
234
235 fn result_contains_err_msg<T, E: ToString>(result: &Result<T, E>, expected_msg: &str) {
236 assert!(result.is_err());
237 assert!(result
238 .as_ref()
239 .err()
240 .unwrap()
241 .to_string()
242 .contains(expected_msg));
243 }
244
245 #[test]
248 fn should_serialize_and_deserialize_cmd() {
249 let options = create_options();
250 let cmd = Cmd::with_args_and_options("ls", ["-l", "/bin"], options);
251
252 let serialized = serde_json::to_string(&cmd).unwrap();
253 let deserialized = serde_json::from_str(&serialized).unwrap();
254
255 assert_eq!(cmd, deserialized);
256 }
257
258 #[test]
259 fn should_serialize_and_deserialize_cmd_with_defaults() {
260 let expected = Cmd::with_args("ls", ["-l", "/bin"]);
262 let serialized = r#"{"cmd":"ls","args":["-l","/bin"]}"#;
263 let deserialized = serde_json::from_str(serialized).unwrap();
264 assert_eq!(expected, deserialized);
265
266 let expected = Cmd::new("ls");
268 let serialized = r#"{"cmd":"ls"}"#;
269 let deserialized = serde_json::from_str(serialized).unwrap();
270 assert_eq!(expected, deserialized);
271 }
272
273 #[test]
276 fn should_serialize_and_deserialize_script() {
277 let options = create_options();
278 let run_config = ScriptRunConfig::new("bash", ["-C"], "sh");
279 let lang = ScriptingLanguage::Other(run_config);
280 let content = r#"
281 echo Hello
282 echo World
283 "#;
284 let script = Script::with_args_and_options(lang, content, ["-l", "/bin"], options);
285
286 let serialized = serde_json::to_string(&script).unwrap();
287 let deserialized = serde_json::from_str(&serialized).unwrap();
288
289 assert_eq!(script, deserialized);
290 }
291
292 #[test]
293 fn should_deserialize_script_with_defaults() {
294 let lang = ScriptingLanguage::Bash;
295 let content = "echo";
296
297 let script = Script::with_args(lang.clone(), content, ["Hello", "World"]);
299 let serialized = r#"{"content":"echo","args":["Hello","World"]}"#;
300 let deserialized = serde_json::from_str(serialized).unwrap();
301 assert_eq!(script, deserialized);
302
303 let script = Script::new(lang, content);
305 let serialized = r#"{"content":"echo"}"#;
306 let deserialized = serde_json::from_str(serialized).unwrap();
307 assert_eq!(script, deserialized);
308 }
309}