rustfs_cli/output/
formatter.rs

1//! Output formatter for human-readable and JSON output
2//!
3//! Ensures consistent output formatting across all commands.
4//! JSON output follows the schema defined in schemas/output_v1.json.
5
6use serde::Serialize;
7
8use super::OutputConfig;
9
10/// Formatter for CLI output
11///
12/// Handles both human-readable and JSON output formats based on configuration.
13/// When JSON mode is enabled, all output is strict JSON without colors or progress.
14#[derive(Debug, Clone)]
15#[allow(dead_code)]
16pub struct Formatter {
17    config: OutputConfig,
18}
19
20#[allow(dead_code)]
21impl Formatter {
22    /// Create a new formatter with the given configuration
23    pub fn new(config: OutputConfig) -> Self {
24        Self { config }
25    }
26
27    /// Check if JSON output mode is enabled
28    pub fn is_json(&self) -> bool {
29        self.config.json
30    }
31
32    /// Check if quiet mode is enabled
33    pub fn is_quiet(&self) -> bool {
34        self.config.quiet
35    }
36
37    /// Check if colors are enabled
38    pub fn colors_enabled(&self) -> bool {
39        !self.config.no_color && !self.config.json
40    }
41
42    /// Output a value
43    ///
44    /// In JSON mode, serializes the value to JSON.
45    /// In human mode, uses the Display implementation.
46    pub fn output<T: Serialize + std::fmt::Display>(&self, value: &T) {
47        if self.config.quiet {
48            return;
49        }
50
51        if self.config.json {
52            // JSON output: strict, no colors, no extra formatting
53            match serde_json::to_string_pretty(value) {
54                Ok(json) => println!("{json}"),
55                Err(e) => eprintln!("Error serializing output: {e}"),
56            }
57        } else {
58            println!("{value}");
59        }
60    }
61
62    /// Output a success message
63    pub fn success(&self, message: &str) {
64        if self.config.quiet {
65            return;
66        }
67
68        if self.config.json {
69            // In JSON mode, success is indicated by exit code, not message
70            return;
71        }
72
73        if self.colors_enabled() {
74            println!("\x1b[32m✓\x1b[0m {message}");
75        } else {
76            println!("✓ {message}");
77        }
78    }
79
80    /// Output an error message
81    ///
82    /// Errors are always printed, even in quiet mode.
83    pub fn error(&self, message: &str) {
84        if self.config.json {
85            let error = serde_json::json!({
86                "error": message
87            });
88            eprintln!(
89                "{}",
90                serde_json::to_string_pretty(&error).unwrap_or_else(|_| message.to_string())
91            );
92        } else if self.colors_enabled() {
93            eprintln!("\x1b[31m✗\x1b[0m {message}");
94        } else {
95            eprintln!("✗ {message}");
96        }
97    }
98
99    /// Output a warning message
100    pub fn warning(&self, message: &str) {
101        if self.config.quiet || self.config.json {
102            return;
103        }
104
105        if self.colors_enabled() {
106            eprintln!("\x1b[33m⚠\x1b[0m {message}");
107        } else {
108            eprintln!("⚠ {message}");
109        }
110    }
111
112    /// Output JSON directly
113    ///
114    /// Used when you want to output a pre-built JSON structure.
115    pub fn json<T: Serialize>(&self, value: &T) {
116        match serde_json::to_string_pretty(value) {
117            Ok(json) => println!("{json}"),
118            Err(e) => eprintln!("Error serializing output: {e}"),
119        }
120    }
121
122    /// Print a line of text (respects quiet mode)
123    pub fn println(&self, message: &str) {
124        if self.config.quiet {
125            return;
126        }
127        println!("{message}");
128    }
129}
130
131impl Default for Formatter {
132    fn default() -> Self {
133        Self::new(OutputConfig::default())
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    #[test]
142    fn test_formatter_default() {
143        let formatter = Formatter::default();
144        assert!(!formatter.is_json());
145        assert!(!formatter.is_quiet());
146        assert!(formatter.colors_enabled());
147    }
148
149    #[test]
150    fn test_formatter_json_mode() {
151        let config = OutputConfig {
152            json: true,
153            ..Default::default()
154        };
155        let formatter = Formatter::new(config);
156        assert!(formatter.is_json());
157        assert!(!formatter.colors_enabled()); // Colors disabled in JSON mode
158    }
159
160    #[test]
161    fn test_formatter_no_color() {
162        let config = OutputConfig {
163            no_color: true,
164            ..Default::default()
165        };
166        let formatter = Formatter::new(config);
167        assert!(!formatter.colors_enabled());
168    }
169}