1use serde_json::Value;
7use std::fmt;
8use std::str::FromStr;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
12pub enum OutputFormat {
13 #[default]
15 Json,
16 Human,
18 Text,
20}
21
22impl FromStr for OutputFormat {
23 type Err = String;
24
25 fn from_str(s: &str) -> Result<Self, Self::Err> {
26 match s.to_lowercase().as_str() {
27 "json" => Ok(OutputFormat::Json),
28 "human" => Ok(OutputFormat::Human),
29 "text" => Ok(OutputFormat::Text),
30 _ => Err(format!(
31 "Unknown output format '{}'. Valid options: json, human, text",
32 s
33 )),
34 }
35 }
36}
37
38impl fmt::Display for OutputFormat {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 match self {
41 OutputFormat::Json => write!(f, "json"),
42 OutputFormat::Human => write!(f, "human"),
43 OutputFormat::Text => write!(f, "text"),
44 }
45 }
46}
47
48pub struct ResponseFormatter;
50
51impl ResponseFormatter {
52 pub fn format(response: &Value, format: OutputFormat) -> String {
54 match format {
55 OutputFormat::Json => Self::format_json(response),
56 OutputFormat::Human => Self::format_human(response),
57 OutputFormat::Text => Self::format_text(response),
58 }
59 }
60
61 fn format_json(response: &Value) -> String {
63 serde_json::to_string_pretty(response).unwrap_or_else(|_| response.to_string())
64 }
65
66 fn format_human(response: &Value) -> String {
68 let mut output = String::new();
69
70 if let Some(obj) = response.as_object() {
72 if let Some(vault) = obj.get("vault").and_then(|v| v.as_str()) {
74 output.push_str(&format!("š¦ Vault: {}\n", vault));
75 }
76
77 if let Some(op) = obj.get("operation").and_then(|v| v.as_str()) {
79 output.push_str(&format!("āļø Operation: {}\n", op));
80 }
81
82 if let Some(success) = obj.get("success").and_then(|v| v.as_bool()) {
84 let status = if success { "ā
Success" } else { "ā Failed" };
85 output.push_str(&format!("Status: {}\n", status));
86 }
87
88 output.push('\n');
89
90 if let Some(data) = obj.get("data") {
92 output.push_str("š Data:\n");
93 output.push_str(&Self::format_value_indented(data, 2));
94 }
95
96 if let Some(warnings) = obj.get("warnings").and_then(|v| v.as_array())
98 && !warnings.is_empty()
99 {
100 output.push_str("\nā ļø Warnings:\n");
101 for warning in warnings {
102 if let Some(msg) = warning.as_str() {
103 output.push_str(&format!(" ⢠{}\n", msg));
104 }
105 }
106 }
107
108 if let Some(steps) = obj.get("next_steps").and_then(|v| v.as_array())
110 && !steps.is_empty()
111 {
112 output.push_str("\nš Next Steps:\n");
113 for (i, step) in steps.iter().enumerate() {
114 if let Some(s) = step.as_str() {
115 output.push_str(&format!(" {}. {}\n", i + 1, s));
116 }
117 }
118 }
119
120 if let Some(took) = obj.get("took_ms").and_then(|v| v.as_u64()) {
122 output.push_str(&format!("\nā±ļø Took: {}ms\n", took));
123 }
124
125 if let Some(count) = obj.get("count").and_then(|v| v.as_u64()) {
127 output.push_str(&format!("Count: {}\n", count));
128 }
129 }
130
131 if output.is_empty() {
132 Self::format_json(response)
133 } else {
134 output
135 }
136 }
137
138 fn format_text(response: &Value) -> String {
140 let mut output = String::new();
141
142 if let Some(obj) = response.as_object() {
143 if let Some(success) = obj.get("success").and_then(|v| v.as_bool()) {
145 output.push_str(if success {
146 "ā Success\n"
147 } else {
148 "ā Failed\n"
149 });
150 }
151
152 if let Some(op) = obj.get("operation").and_then(|v| v.as_str()) {
153 output.push_str(&format!("{}\n", op));
154 }
155
156 if let Some(data) = obj.get("data") {
158 match data {
159 Value::Object(map) => {
160 for (key, value) in map.iter().take(5) {
161 output.push_str(&format!("{}: ", key));
162 match value {
163 Value::String(s) => output.push_str(&format!("{}\n", s)),
164 Value::Number(n) => output.push_str(&format!("{}\n", n)),
165 Value::Bool(b) => output.push_str(&format!("{}\n", b)),
166 Value::Array(arr) => {
167 output.push_str(&format!("[{} items]\n", arr.len()))
168 }
169 _ => output.push_str("...\n"),
170 }
171 }
172 }
173 Value::Array(arr) => {
174 output.push_str(&format!("[{} items]\n", arr.len()));
175 }
176 Value::String(s) => output.push_str(&format!("{}\n", s)),
177 _ => {}
178 }
179 }
180
181 if let Some(took) = obj.get("took_ms").and_then(|v| v.as_u64()) {
182 output.push_str(&format!("({} ms)\n", took));
183 }
184 }
185
186 if output.is_empty() {
187 Self::format_json(response)
188 } else {
189 output
190 }
191 }
192
193 fn format_value_indented(value: &Value, indent: usize) -> String {
195 let indent_str = " ".repeat(indent);
196
197 match value {
198 Value::Object(map) => {
199 let mut result = String::new();
200 for (key, val) in map.iter() {
201 result.push_str(&format!("{}{}: ", indent_str, key));
202 match val {
203 Value::String(s) => result.push_str(&format!("{}\n", s)),
204 Value::Number(n) => result.push_str(&format!("{}\n", n)),
205 Value::Bool(b) => result.push_str(&format!("{}\n", b)),
206 Value::Array(arr) => {
207 result.push_str(&format!("[{} items]\n", arr.len()));
208 for (i, item) in arr.iter().take(3).enumerate() {
209 result.push_str(&format!("{} [{}] {}\n", indent_str, i, item));
210 }
211 if arr.len() > 3 {
212 result.push_str(&format!(
213 "{} ... and {} more\n",
214 indent_str,
215 arr.len() - 3
216 ));
217 }
218 }
219 Value::Object(_) => {
220 result.push_str(&format!(
221 "{}\n",
222 Self::format_value_indented(val, indent + 2)
223 ));
224 }
225 Value::Null => result.push_str("null\n"),
226 }
227 }
228 result
229 }
230 Value::Array(arr) => {
231 let mut result = String::from("[\n");
232 for (i, item) in arr.iter().take(5).enumerate() {
233 result.push_str(&format!("{} [{}] {}\n", indent_str, i, item));
234 }
235 if arr.len() > 5 {
236 result.push_str(&format!("{} ... and {} more\n", indent_str, arr.len() - 5));
237 }
238 result.push_str(&format!("{}]\n", indent_str));
239 result
240 }
241 Value::String(s) => format!("{}\n", s),
242 other => format!("{}\n", other),
243 }
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250 use serde_json::json;
251
252 #[test]
253 fn test_output_format_parse() {
254 assert_eq!(OutputFormat::from_str("json").unwrap(), OutputFormat::Json);
255 assert_eq!(OutputFormat::from_str("JSON").unwrap(), OutputFormat::Json);
256 assert_eq!(
257 OutputFormat::from_str("human").unwrap(),
258 OutputFormat::Human
259 );
260 assert_eq!(OutputFormat::from_str("text").unwrap(), OutputFormat::Text);
261 assert!(OutputFormat::from_str("invalid").is_err());
262 }
263
264 #[test]
265 fn test_format_json() {
266 let response = json!({
267 "success": true,
268 "data": {"test": "value"}
269 });
270 let formatted = ResponseFormatter::format(&response, OutputFormat::Json);
271 assert!(formatted.contains("\"success\": true"));
272 }
273
274 #[test]
275 fn test_format_human() {
276 let response = json!({
277 "vault": "personal",
278 "operation": "read_note",
279 "success": true,
280 "data": {"content": "test"},
281 "took_ms": 42
282 });
283 let formatted = ResponseFormatter::format(&response, OutputFormat::Human);
284 assert!(formatted.contains("Vault: personal"));
285 assert!(formatted.contains("ā
Success"));
286 assert!(formatted.contains("42ms"));
287 }
288
289 #[test]
290 fn test_format_text() {
291 let response = json!({
292 "success": true,
293 "operation": "search",
294 "data": [1, 2, 3]
295 });
296 let formatted = ResponseFormatter::format(&response, OutputFormat::Text);
297 assert!(formatted.contains("ā Success"));
298 assert!(formatted.contains("search"));
299 }
300}