Skip to main content

lit/
formatter.rs

1use crate::errors::LitError;
2use crate::response::{CommandResponse, OutputFormat};
3use serde::Serialize;
4
5/// Output format including MsgPack
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum Format {
8    Json,
9    JsonPretty,
10    Human,
11    MsgPack,
12}
13
14impl Format {
15    /// Resolve from CLI flags, env var, and config.
16    ///
17    /// `pretty` selects indented JSON (token-heavy, for humans). The default
18    /// JSON output is compact for agent token efficiency.
19    pub fn resolve(json: bool, human: bool, output: Option<&str>, pretty: bool) -> Self {
20        if human {
21            return Format::Human;
22        }
23        if json {
24            return if pretty {
25                Format::JsonPretty
26            } else {
27                Format::Json
28            };
29        }
30        if let Some(fmt) = output {
31            return match fmt {
32                "human" | "text" => Format::Human,
33                "msgpack" => Format::MsgPack,
34                "json-pretty" | "pretty" => Format::JsonPretty,
35                _ if pretty => Format::JsonPretty,
36                _ => Format::Json,
37            };
38        }
39        match std::env::var("LIT_OUTPUT").as_deref() {
40            Ok("human") | Ok("text") => Format::Human,
41            Ok("msgpack") => Format::MsgPack,
42            Ok("json-pretty") | Ok("pretty") => Format::JsonPretty,
43            _ if pretty => Format::JsonPretty,
44            _ => Format::Json,
45        }
46    }
47
48    /// Convert to OutputFormat for backward compatibility
49    pub fn to_output_format(self) -> OutputFormat {
50        match self {
51            Format::Json | Format::JsonPretty | Format::MsgPack => OutputFormat::Json,
52            Format::Human => OutputFormat::Human,
53        }
54    }
55}
56
57/// Format a response in the specified format (including MsgPack)
58pub fn format_response<R: CommandResponse + Serialize>(response: &R, format: Format) -> Vec<u8> {
59    match format {
60        Format::Json => response.to_json_output().into_bytes(),
61        Format::JsonPretty => response.to_json_output_pretty().into_bytes(),
62        Format::Human => response.human_readable().into_bytes(),
63        Format::MsgPack => {
64            let data = serde_json::to_value(response).unwrap_or(serde_json::Value::Null);
65            let envelope = MsgPackEnvelope {
66                status: "ok",
67                command: response.command_name(),
68                data,
69            };
70            rmp_serde::to_vec(&envelope).unwrap_or_default()
71        }
72    }
73}
74
75/// Format an error in the specified format (including MsgPack)
76pub fn format_error(error: &LitError, command: &str, format: Format) -> Vec<u8> {
77    let err_obj = serde_json::json!({
78        "status": "error",
79        "command": command,
80        "error": {
81            "code": error.error_code(),
82            "message": error.user_message(),
83            "suggestions": error.suggestions(),
84        }
85    });
86
87    match format {
88        Format::Json => serde_json::to_string(&err_obj)
89            .unwrap_or_default()
90            .into_bytes(),
91        Format::JsonPretty => serde_json::to_string_pretty(&err_obj)
92            .unwrap_or_default()
93            .into_bytes(),
94        Format::Human => {
95            let mut out = format!("error: {}", error.user_message());
96            let suggestions = error.suggestions();
97            if !suggestions.is_empty() {
98                out.push_str("\n\nhint:");
99                for s in suggestions {
100                    out.push_str(&format!("\n  {}", s));
101                }
102            }
103            out.into_bytes()
104        }
105        Format::MsgPack => rmp_serde::to_vec(&err_obj).unwrap_or_default(),
106    }
107}
108
109/// Wrapper for MsgPack serialization
110#[derive(Serialize)]
111struct MsgPackEnvelope<'a> {
112    status: &'a str,
113    command: &'a str,
114    data: serde_json::Value,
115}