Skip to main content

teams_cli/
output.rs

1use std::io::IsTerminal;
2
3use clap::ValueEnum;
4use serde::Serialize;
5
6use crate::error::AppError;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
9pub enum OutputFormat {
10    Auto,
11    Text,
12    Json,
13}
14
15#[derive(Debug, Clone, Copy)]
16pub struct Output {
17    pub format: OutputFormat,
18    pub quiet: bool,
19}
20
21impl Output {
22    pub fn json(self) -> bool {
23        matches!(self.format, OutputFormat::Json)
24            || (matches!(self.format, OutputFormat::Auto) && !std::io::stdout().is_terminal())
25    }
26
27    pub fn value<T: Serialize>(
28        &self,
29        value: &T,
30        text: impl FnOnce() -> String,
31    ) -> Result<(), AppError> {
32        if self.json() {
33            println!(
34                "{}",
35                serde_json::to_string_pretty(value)
36                    .map_err(|e| AppError::Unexpected(e.to_string()))?
37            );
38        } else {
39            println!("{}", text());
40        }
41        Ok(())
42    }
43
44    pub fn note(&self, message: impl AsRef<str>) {
45        if !self.quiet {
46            eprintln!("{}", message.as_ref());
47        }
48    }
49}
50
51pub fn print_error(error: &AppError, structured: bool) {
52    if !structured {
53        eprintln!("Error: {error}");
54        if !matches!(error, AppError::Unhealthy(_)) {
55            eprintln!("Hint: run `teams doctor` for a guided diagnosis.");
56        }
57    }
58    let contract = error.contract();
59    if structured {
60        let mut envelope = serde_json::json!({
61            "error": {
62                "kind": contract.kind,
63                "message": error.to_string(),
64                "retryable": contract.retryable,
65            }
66        });
67        if let AppError::RateLimit(Some(seconds)) = error {
68            envelope["error"]["retry_after_seconds"] = (*seconds).into();
69        }
70        eprintln!("{}", serde_json::to_string(&envelope).unwrap_or_else(|_| r#"{"error":{"kind":"unexpected_error","message":"error serialization failed","retryable":false}}"#.into()));
71    }
72}
73
74pub fn structured_from_args() -> bool {
75    let args: Vec<String> = std::env::args().collect();
76    let explicit_text = args.windows(2).any(|w| w == ["--output", "text"])
77        || args.iter().any(|a| a == "--output=text" || a == "-otext");
78    let explicit_json = args.windows(2).any(|w| w == ["--output", "json"])
79        || args
80            .iter()
81            .any(|a| a == "--output=json" || a == "-ojson" || a == "--json");
82    !explicit_text && (explicit_json || !std::io::stdout().is_terminal())
83}