mcp_execution_cli/
formatters.rs1use anyhow::Result;
6use colored::Colorize;
7use mcp_execution_core::cli::OutputFormat;
8use serde::Serialize;
9
10pub fn format_output<T: Serialize>(data: &T, format: OutputFormat) -> Result<String> {
44 match format {
45 OutputFormat::Json => json::format(data),
46 OutputFormat::Text => text::format(data),
47 OutputFormat::Pretty => pretty::format(data),
48 }
49}
50
51pub mod json {
53 use super::{Result, Serialize};
54
55 pub fn format<T: Serialize>(data: &T) -> Result<String> {
59 let json = serde_json::to_string_pretty(data)?;
60 Ok(json)
61 }
62
63 pub fn format_compact<T: Serialize>(data: &T) -> Result<String> {
65 let json = serde_json::to_string(data)?;
66 Ok(json)
67 }
68}
69
70pub mod text {
72 use super::{Result, Serialize, json};
73
74 pub fn format<T: Serialize>(data: &T) -> Result<String> {
79 json::format_compact(data)
81 }
82}
83
84pub mod pretty {
86 use super::{Colorize, Result, Serialize};
87
88 pub fn format<T: Serialize>(data: &T) -> Result<String> {
92 let value = serde_json::to_value(data)?;
94
95 format_value(&value, 0)
97 }
98
99 fn format_value(value: &serde_json::Value, indent: usize) -> Result<String> {
101 use serde_json::Value;
102
103 let indent_str = " ".repeat(indent);
104 let next_indent_str = " ".repeat(indent + 1);
105
106 match value {
107 Value::Null => Ok("null".dimmed().to_string()),
108 Value::Bool(b) => Ok(b.to_string().yellow().to_string()),
109 Value::Number(n) => Ok(n.to_string().cyan().to_string()),
110 Value::String(s) => Ok(format!("\"{}\"", s.green())),
111 Value::Array(arr) => {
112 if arr.is_empty() {
113 return Ok("[]".to_string());
114 }
115
116 let mut result = "[\n".to_string();
117 for (i, item) in arr.iter().enumerate() {
118 result.push_str(&next_indent_str);
119 result.push_str(&format_value(item, indent + 1)?);
120 if i < arr.len() - 1 {
121 result.push(',');
122 }
123 result.push('\n');
124 }
125 result.push_str(&indent_str);
126 result.push(']');
127 Ok(result)
128 }
129 Value::Object(obj) => {
130 if obj.is_empty() {
131 return Ok("{}".to_string());
132 }
133
134 let mut result = "{\n".to_string();
135 let entries: Vec<_> = obj.iter().collect();
136 for (i, (key, val)) in entries.iter().enumerate() {
137 result.push_str(&next_indent_str);
138 result.push_str(&format!("\"{}\": ", key.blue().bold()));
139 result.push_str(&format_value(val, indent + 1)?);
140 if i < entries.len() - 1 {
141 result.push(',');
142 }
143 result.push('\n');
144 }
145 result.push_str(&indent_str);
146 result.push('}');
147 Ok(result)
148 }
149 }
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156 use serde::Serialize;
157
158 #[derive(Serialize)]
159 struct TestData {
160 name: String,
161 count: i32,
162 enabled: bool,
163 }
164
165 #[test]
166 fn test_json_format() {
167 let data = TestData {
168 name: "test".to_string(),
169 count: 42,
170 enabled: true,
171 };
172
173 let output = json::format(&data).unwrap();
174 assert!(output.contains("\"name\""));
175 assert!(output.contains("\"test\""));
176 assert!(output.contains("\"count\""));
177 assert!(output.contains("42"));
178 assert!(output.contains("\"enabled\""));
179 assert!(output.contains("true"));
180 }
181
182 #[test]
183 fn test_json_format_compact() {
184 let data = TestData {
185 name: "test".to_string(),
186 count: 42,
187 enabled: true,
188 };
189
190 let output = json::format_compact(&data).unwrap();
191 assert!(!output.contains('\n'));
193 assert!(output.contains("\"name\":\"test\""));
194 }
195
196 #[test]
197 fn test_text_format() {
198 let data = TestData {
199 name: "test".to_string(),
200 count: 42,
201 enabled: true,
202 };
203
204 let output = text::format(&data).unwrap();
205 assert!(!output.contains('\n'));
207 assert!(output.contains("\"name\":\"test\""));
208 }
209
210 #[test]
211 fn test_pretty_format() {
212 let data = TestData {
213 name: "test".to_string(),
214 count: 42,
215 enabled: true,
216 };
217
218 let output = pretty::format(&data).unwrap();
219 assert!(output.contains("name"));
221 assert!(output.contains("test"));
222 assert!(output.contains("count"));
223 assert!(output.contains("42"));
224 }
225
226 #[test]
227 fn test_format_output_json() {
228 let data = TestData {
229 name: "test".to_string(),
230 count: 42,
231 enabled: true,
232 };
233
234 let output = format_output(&data, OutputFormat::Json).unwrap();
235 assert!(output.contains("\"name\""));
236 }
237
238 #[test]
239 fn test_format_output_text() {
240 let data = TestData {
241 name: "test".to_string(),
242 count: 42,
243 enabled: true,
244 };
245
246 let output = format_output(&data, OutputFormat::Text).unwrap();
247 assert!(output.contains("\"name\""));
248 }
249
250 #[test]
251 fn test_format_output_pretty() {
252 let data = TestData {
253 name: "test".to_string(),
254 count: 42,
255 enabled: true,
256 };
257
258 let output = format_output(&data, OutputFormat::Pretty).unwrap();
259 assert!(output.contains("name"));
260 }
261}