1use clap::ValueEnum;
14
15#[derive(Copy, Clone, PartialEq, Eq, Debug, Default, ValueEnum)]
18pub enum OutputFormat {
19 #[default]
21 Table,
22 Json,
25}
26
27pub fn format_error(err: &anyhow::Error, format: OutputFormat) -> String {
34 match format {
35 OutputFormat::Table => format!("error: {err:#}"),
36 OutputFormat::Json => format_json_error(err, serde_json::to_string_pretty),
37 }
38}
39
40fn format_json_error<E>(
41 err: &anyhow::Error,
42 serialize: impl FnOnce(&serde_json::Value) -> Result<String, E>,
43) -> String
44where
45 E: std::fmt::Display,
46{
47 let payload = serde_json::json!({ "error": err.to_string() });
48 serialize(&payload).unwrap_or_else(|_| format!("{{\"error\": \"{err}\"}}"))
49}
50
51#[cfg(test)]
52mod tests {
53 use super::*;
54
55 #[test]
56 fn output_format_defaults_to_table() {
57 assert_eq!(OutputFormat::default(), OutputFormat::Table);
58 }
59
60 #[test]
61 fn format_error_table_matches_existing_plain_text_format() {
62 let err = anyhow::anyhow!("boom");
63 assert_eq!(
64 format_error(&err, OutputFormat::Table),
65 format!("error: {err:#}")
66 );
67 }
68
69 #[test]
70 fn format_error_json_is_valid_json_with_message() {
71 let err = anyhow::anyhow!("boom");
72 let out = format_error(&err, OutputFormat::Json);
73 let value: serde_json::Value = serde_json::from_str(&out).unwrap();
74 assert_eq!(value["error"], "boom");
75 }
76
77 #[test]
78 fn format_error_json_is_pretty_printed() {
79 let err = anyhow::anyhow!("boom");
80 let out = format_error(&err, OutputFormat::Json);
81 assert!(out.contains('\n'), "expected multi-line pretty JSON");
82 }
83
84 #[test]
85 fn format_error_preserves_anyhows_context_chain() {
86 let err = anyhow::anyhow!("root cause").context("higher-level context");
87 let table = format_error(&err, OutputFormat::Table);
88 assert!(table.contains("root cause"));
89 assert!(table.contains("higher-level context"));
90 }
91
92 #[test]
93 fn format_error_keeps_a_displayable_fallback_when_json_encoding_fails() {
94 let err = anyhow::anyhow!("boom");
95 let output = format_json_error(&err, |_| Err::<String, _>("injected failure"));
96 assert_eq!(output, r#"{"error": "boom"}"#);
97 }
98}