Skip to main content

grabcli_core/
output.rs

1//! Output context: human-readable lines vs. a single `--json` document, and a
2//! uniform way to emit structured errors.
3
4use std::io::{self, Write};
5
6use crate::error::Error;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum Format {
10    Human,
11    Json,
12}
13
14#[derive(Debug, Clone, Copy)]
15pub struct Output {
16    pub format: Format,
17}
18
19impl Output {
20    pub fn new(json: bool) -> Self {
21        Self {
22            format: if json { Format::Json } else { Format::Human },
23        }
24    }
25
26    pub fn is_json(&self) -> bool {
27        self.format == Format::Json
28    }
29
30    /// Print a value as pretty JSON (only in JSON mode).
31    pub fn json(&self, value: &serde_json::Value) {
32        if self.is_json()
33            && let Ok(s) = serde_json::to_string_pretty(value)
34        {
35            println!("{s}");
36        }
37    }
38
39    /// Print a human line (only in human mode).
40    pub fn line(&self, text: impl AsRef<str>) {
41        if !self.is_json() {
42            println!("{}", text.as_ref());
43        }
44    }
45
46    /// A warning on stderr (both modes, so it never corrupts `--json` stdout).
47    pub fn warn(&self, text: impl AsRef<str>) {
48        let _ = writeln!(io::stderr(), "{}", text.as_ref());
49    }
50
51    /// Emit an error to stderr in the active format and return its exit code.
52    pub fn emit_error(&self, err: &Error) -> i32 {
53        match self.format {
54            Format::Json => {
55                if let Ok(s) = serde_json::to_string_pretty(&err.to_json()) {
56                    let _ = writeln!(io::stderr(), "{s}");
57                }
58            }
59            Format::Human => {
60                let _ = writeln!(
61                    io::stderr(),
62                    "error ({}): {}",
63                    err.kind.as_str(),
64                    err.message
65                );
66            }
67        }
68        err.exit_code()
69    }
70}