kanade_shared/wire/
command.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Serialize, Deserialize, Debug, Clone)]
4pub struct Command {
5 pub id: String,
6 pub version: String,
7 pub request_id: String,
8 pub job_id: Option<String>,
9 pub shell: Shell,
10 pub script: String,
11 pub timeout_secs: u64,
12 pub jitter_secs: Option<u64>,
13 #[serde(default)]
17 pub run_as: RunAs,
18 #[serde(default, skip_serializing_if = "Option::is_none")]
22 pub cwd: Option<String>,
23}
24
25#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
26#[serde(rename_all = "lowercase")]
27pub enum Shell {
28 Powershell,
29 Cmd,
30}
31
32#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default)]
48#[serde(rename_all = "snake_case")]
49pub enum RunAs {
50 #[default]
53 System,
54 User,
59 SystemGui,
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 fn sample_command() -> Command {
70 Command {
71 id: "echo-test".into(),
72 version: "1.0.0".into(),
73 request_id: "req-1".into(),
74 job_id: Some("dep-1".into()),
75 shell: Shell::Powershell,
76 script: "echo hi".into(),
77 timeout_secs: 30,
78 jitter_secs: Some(5),
79 run_as: RunAs::System,
80 cwd: None,
81 }
82 }
83
84 #[test]
85 fn shell_serialises_lowercase() {
86 let json = serde_json::to_string(&Shell::Powershell).unwrap();
87 assert_eq!(json, "\"powershell\"");
88 let json = serde_json::to_string(&Shell::Cmd).unwrap();
89 assert_eq!(json, "\"cmd\"");
90 }
91
92 #[test]
93 fn run_as_serialises_snake_case() {
94 for (mode, expected) in [
95 (RunAs::System, "\"system\""),
96 (RunAs::User, "\"user\""),
97 (RunAs::SystemGui, "\"system_gui\""),
98 ] {
99 let json = serde_json::to_string(&mode).unwrap();
100 assert_eq!(json, expected, "serialise {mode:?}");
101 let back: RunAs = serde_json::from_str(expected).unwrap();
102 assert_eq!(back, mode, "round-trip {expected}");
103 }
104 }
105
106 #[test]
107 fn run_as_defaults_to_system() {
108 assert_eq!(RunAs::default(), RunAs::System);
109 }
110
111 #[test]
112 fn command_round_trips_through_json() {
113 let orig = sample_command();
114 let json = serde_json::to_string(&orig).expect("encode");
115 let decoded: Command = serde_json::from_str(&json).expect("decode");
116 assert_eq!(decoded.id, orig.id);
117 assert_eq!(decoded.version, orig.version);
118 assert_eq!(decoded.request_id, orig.request_id);
119 assert_eq!(decoded.job_id, orig.job_id);
120 assert_eq!(decoded.shell, orig.shell);
121 assert_eq!(decoded.script, orig.script);
122 assert_eq!(decoded.timeout_secs, orig.timeout_secs);
123 assert_eq!(decoded.jitter_secs, orig.jitter_secs);
124 assert_eq!(decoded.run_as, orig.run_as);
125 }
126
127 #[test]
128 fn command_round_trips_each_run_as_variant() {
129 for mode in [RunAs::System, RunAs::User, RunAs::SystemGui] {
130 let cmd = Command {
131 run_as: mode,
132 ..sample_command()
133 };
134 let json = serde_json::to_string(&cmd).unwrap();
135 let back: Command = serde_json::from_str(&json).unwrap();
136 assert_eq!(back.run_as, mode);
137 }
138 }
139
140 #[test]
141 fn command_accepts_missing_optional_fields() {
142 let json = r#"{
143 "id": "x",
144 "version": "1.0.0",
145 "request_id": "r",
146 "shell": "cmd",
147 "script": "echo",
148 "timeout_secs": 5
149 }"#;
150 let cmd: Command = serde_json::from_str(json).expect("decode");
151 assert!(cmd.job_id.is_none());
152 assert!(cmd.jitter_secs.is_none());
153 assert_eq!(cmd.shell, Shell::Cmd);
154 assert_eq!(cmd.run_as, RunAs::System);
156 assert!(cmd.cwd.is_none());
158 }
159}