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::UnconfirmedSend {
68 chat_id,
69 client_message_id,
70 ..
71 } = error
72 {
73 envelope["error"]["chatId"] = chat_id.as_str().into();
74 envelope["error"]["clientMessageId"] = client_message_id.as_str().into();
75 envelope["error"]["delivery_state"] = "unknown".into();
76 }
77 if let AppError::RateLimit(Some(seconds)) = error {
78 envelope["error"]["retry_after_seconds"] = (*seconds).into();
79 }
80 eprintln!("{}", serde_json::to_string(&envelope).unwrap_or_else(|_| r#"{"error":{"kind":"unexpected_error","message":"error serialization failed","retryable":false}}"#.into()));
81 }
82}
83
84pub fn structured_from_args() -> bool {
85 let args: Vec<String> = std::env::args().collect();
86 let explicit_text = args.windows(2).any(|w| w == ["--output", "text"])
87 || args.iter().any(|a| a == "--output=text" || a == "-otext");
88 let explicit_json = args.windows(2).any(|w| w == ["--output", "json"])
89 || args
90 .iter()
91 .any(|a| a == "--output=json" || a == "-ojson" || a == "--json");
92 !explicit_text && (explicit_json || !std::io::stdout().is_terminal())
93}