1use serde::{Deserialize, Serialize};
7
8pub const API_VERSION: u32 = 1;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct CommandSpec {
14 pub name: String,
16
17 pub about: String,
19
20 #[serde(default)]
22 pub version: Option<String>,
23
24 #[serde(default)]
26 pub author: Option<String>,
27
28 #[serde(default)]
30 pub args: Vec<ArgSpec>,
31
32 #[serde(default)]
34 pub subcommands: Vec<CommandSpec>,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct ArgSpec {
40 pub name: String,
42
43 #[serde(default)]
45 pub long: Option<String>,
46
47 #[serde(default)]
49 pub short: Option<char>,
50
51 #[serde(default)]
53 pub required: bool,
54
55 #[serde(default)]
57 pub help: String,
58
59 #[serde(default)]
61 pub value_name: Option<String>,
62
63 #[serde(default)]
65 pub default_value: Option<String>,
66
67 #[serde(default)]
69 pub possible_values: Option<Vec<String>>,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
74pub enum ExecuteResult {
75 Success(String),
77
78 Error(ExecuteError),
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct ExecuteError {
85 pub code: u8,
87
88 pub message: String,
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct PluginManifest {
95 pub api_version: u32,
97
98 pub command: CommandSpec,
100}
101
102impl PluginManifest {
103 pub fn new(command: CommandSpec) -> Self {
105 Self {
106 api_version: API_VERSION,
107 command,
108 }
109 }
110}
111
112impl ExecuteResult {
113 pub fn success(output: impl Into<String>) -> Self {
115 Self::Success(output.into())
116 }
117
118 pub fn user_error(message: impl Into<String>) -> Self {
120 Self::Error(ExecuteError {
121 code: 1,
122 message: message.into(),
123 })
124 }
125
126 pub fn system_error(message: impl Into<String>) -> Self {
128 Self::Error(ExecuteError {
129 code: 101,
130 message: message.into(),
131 })
132 }
133}
134
135impl CommandSpec {
136 pub fn new(name: impl Into<String>, about: impl Into<String>) -> Self {
138 Self {
139 name: name.into(),
140 about: about.into(),
141 version: None,
142 author: None,
143 args: Vec::new(),
144 subcommands: Vec::new(),
145 }
146 }
147
148 pub fn version(mut self, version: impl Into<String>) -> Self {
150 self.version = Some(version.into());
151 self
152 }
153
154 pub fn arg(mut self, arg: ArgSpec) -> Self {
156 self.args.push(arg);
157 self
158 }
159
160 pub fn subcommand(mut self, cmd: CommandSpec) -> Self {
162 self.subcommands.push(cmd);
163 self
164 }
165}
166
167impl ArgSpec {
168 pub fn positional(name: impl Into<String>) -> Self {
170 Self {
171 name: name.into(),
172 long: None,
173 short: None,
174 required: false,
175 help: String::new(),
176 value_name: None,
177 default_value: None,
178 possible_values: None,
179 }
180 }
181
182 pub fn option(name: impl Into<String>, long: impl Into<String>) -> Self {
184 Self {
185 name: name.into(),
186 long: Some(long.into()),
187 short: None,
188 required: false,
189 help: String::new(),
190 value_name: None,
191 default_value: None,
192 possible_values: None,
193 }
194 }
195
196 pub fn required(mut self) -> Self {
198 self.required = true;
199 self
200 }
201
202 pub fn help(mut self, help: impl Into<String>) -> Self {
204 self.help = help.into();
205 self
206 }
207
208 pub fn short(mut self, short: char) -> Self {
210 self.short = Some(short);
211 self
212 }
213
214 pub fn default(mut self, value: impl Into<String>) -> Self {
216 self.default_value = Some(value.into());
217 self
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 #[test]
226 fn test_command_spec_serialization() {
227 let spec = CommandSpec::new("hello", "Says hello")
228 .version("1.0.0")
229 .arg(
230 ArgSpec::positional("name")
231 .help("Name to greet")
232 .default("World"),
233 );
234
235 let bytes = rmp_serde::to_vec(&spec).unwrap();
236 let decoded: CommandSpec = rmp_serde::from_slice(&bytes).unwrap();
237
238 assert_eq!(decoded.name, "hello");
239 assert_eq!(decoded.about, "Says hello");
240 assert_eq!(decoded.args.len(), 1);
241 }
242
243 #[test]
244 fn test_execute_result_serialization() {
245 let result = ExecuteResult::success("Hello, World!");
246 let bytes = rmp_serde::to_vec(&result).unwrap();
247 let decoded: ExecuteResult = rmp_serde::from_slice(&bytes).unwrap();
248
249 match decoded {
250 ExecuteResult::Success(s) => assert_eq!(s, "Hello, World!"),
251 _ => panic!("Expected success"),
252 }
253 }
254}