1use serde::{Deserialize, Serialize};
9use serde_json::{Value, json};
10
11#[derive(Debug, Clone)]
13pub struct OutputFormatter {
14 json_mode: bool,
15 quiet_mode: bool,
16}
17
18impl OutputFormatter {
19 pub const fn new(json_mode: bool, quiet_mode: bool) -> Self {
25 Self {
26 json_mode,
27 quiet_mode,
28 }
29 }
30
31 pub fn format(&self, result: &CommandResult) -> String {
33 match (self.json_mode, self.quiet_mode) {
34 (true, _) => serde_json::to_string(result).unwrap_or_else(|_| {
36 json!({
37 "status": "error",
38 "command": "unknown",
39 "message": "Failed to serialize response"
40 })
41 .to_string()
42 }),
43 (false, true) => String::new(),
45 (false, false) => Self::format_text(result),
47 }
48 }
49
50 pub fn progress(&self, msg: &str) {
55 if !self.quiet_mode && !self.json_mode {
56 eprintln!("{msg}");
57 }
58 }
59
60 pub fn section(&self, title: &str) {
62 self.progress(&format!("==> {title}"));
63 }
64
65 fn format_text(result: &CommandResult) -> String {
66 match result.status.as_str() {
67 "success" => {
68 let mut output = format!("✓ {} succeeded", result.command);
69
70 if !result.warnings.is_empty() {
71 output.push_str("\n\nWarnings:");
72 for warning in &result.warnings {
73 output.push_str(&format!("\n • {warning}"));
74 }
75 }
76
77 output
78 },
79 "validation-failed" => {
80 let mut output = format!("✗ {} validation failed", result.command);
81
82 if !result.errors.is_empty() {
83 output.push_str("\n\nErrors:");
84 for error in &result.errors {
85 output.push_str(&format!("\n • {error}"));
86 }
87 }
88
89 output
90 },
91 "error" => {
92 let mut output = format!("✗ {} error", result.command);
93
94 if let Some(msg) = &result.message {
95 output.push_str(&format!("\n {msg}"));
96 }
97
98 if let Some(code) = &result.code {
99 output.push_str(&format!("\n Code: {code}"));
100 }
101
102 output
103 },
104 _ => format!("? {} - unknown status: {}", result.command, result.status),
105 }
106 }
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct CommandResult {
112 pub status: String,
114
115 pub command: String,
117
118 #[serde(skip_serializing_if = "Option::is_none")]
120 pub data: Option<Value>,
121
122 #[serde(skip_serializing_if = "Option::is_none")]
124 pub message: Option<String>,
125
126 #[serde(skip_serializing_if = "Option::is_none")]
128 pub code: Option<String>,
129
130 #[serde(skip_serializing_if = "Vec::is_empty")]
132 pub errors: Vec<String>,
133
134 #[serde(skip_serializing_if = "Vec::is_empty")]
136 pub warnings: Vec<String>,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct CliHelp {
146 pub name: String,
148
149 pub version: String,
151
152 pub about: String,
154
155 pub global_options: Vec<ArgumentHelp>,
157
158 pub subcommands: Vec<CommandHelp>,
160
161 pub exit_codes: Vec<ExitCodeHelp>,
163}
164
165#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct CommandHelp {
168 pub name: String,
170
171 pub about: String,
173
174 pub arguments: Vec<ArgumentHelp>,
176
177 pub options: Vec<ArgumentHelp>,
179
180 #[serde(skip_serializing_if = "Vec::is_empty")]
182 pub subcommands: Vec<CommandHelp>,
183
184 #[serde(skip_serializing_if = "Vec::is_empty")]
186 pub examples: Vec<String>,
187}
188
189#[derive(Debug, Clone, Serialize, Deserialize)]
191pub struct ArgumentHelp {
192 pub name: String,
194
195 #[serde(skip_serializing_if = "Option::is_none")]
197 pub short: Option<String>,
198
199 #[serde(skip_serializing_if = "Option::is_none")]
201 pub long: Option<String>,
202
203 pub help: String,
205
206 pub required: bool,
208
209 #[serde(skip_serializing_if = "Option::is_none")]
211 pub default_value: Option<String>,
212
213 pub takes_value: bool,
215
216 #[serde(skip_serializing_if = "Vec::is_empty")]
218 pub possible_values: Vec<String>,
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize)]
223pub struct ExitCodeHelp {
224 pub code: i32,
226
227 pub name: String,
229
230 pub description: String,
232}
233
234#[derive(Debug, Clone, Serialize, Deserialize)]
236pub struct OutputSchema {
237 pub command: String,
239
240 pub schema_version: String,
242
243 pub format: String,
245
246 pub success: serde_json::Value,
248
249 pub error: serde_json::Value,
251}
252
253#[derive(Debug, Clone, Serialize, Deserialize)]
255pub struct CommandSummary {
256 pub name: String,
258
259 pub description: String,
261
262 pub has_subcommands: bool,
264}
265
266pub fn get_exit_codes() -> Vec<ExitCodeHelp> {
268 vec![
269 ExitCodeHelp {
270 code: 0,
271 name: "success".to_string(),
272 description: "Command completed successfully".to_string(),
273 },
274 ExitCodeHelp {
275 code: 1,
276 name: "error".to_string(),
277 description: "Command failed with an error".to_string(),
278 },
279 ExitCodeHelp {
280 code: 2,
281 name: "validation_failed".to_string(),
282 description: "Validation failed (schema or input invalid)".to_string(),
283 },
284 ]
285}
286
287impl CommandResult {
288 pub fn success(command: &str, data: Value) -> Self {
290 Self {
291 status: "success".to_string(),
292 command: command.to_string(),
293 data: Some(data),
294 message: None,
295 code: None,
296 errors: Vec::new(),
297 warnings: Vec::new(),
298 }
299 }
300
301 pub fn success_with_warnings(command: &str, data: Value, warnings: Vec<String>) -> Self {
303 Self {
304 status: "success".to_string(),
305 command: command.to_string(),
306 data: Some(data),
307 message: None,
308 code: None,
309 errors: Vec::new(),
310 warnings,
311 }
312 }
313
314 pub fn error(command: &str, message: &str, code: &str) -> Self {
316 Self {
317 status: "error".to_string(),
318 command: command.to_string(),
319 data: None,
320 message: Some(message.to_string()),
321 code: Some(code.to_string()),
322 errors: Vec::new(),
323 warnings: Vec::new(),
324 }
325 }
326}
327
328#[cfg(test)]
329mod tests;