Skip to main content

unifi_cli/
output.rs

1use std::io::IsTerminal;
2
3/// Output configuration for agent-friendly CLI design.
4///
5/// Supports TTY detection (auto-JSON when piped), quiet mode,
6/// and structured JSON output for all commands including mutations.
7#[derive(Clone, Copy)]
8pub struct OutputConfig {
9    pub json: bool,
10    pub quiet: bool,
11}
12
13impl OutputConfig {
14    pub fn new(json_flag: bool, quiet: bool) -> Self {
15        let json = json_flag || !std::io::stdout().is_terminal();
16        Self { json, quiet }
17    }
18
19    /// Print data to stdout (tables or JSON). Always shown.
20    pub fn print_data(&self, data: &str) {
21        println!("{data}");
22    }
23
24    /// Print a human-readable message to stderr. Suppressed by --quiet.
25    pub fn print_message(&self, msg: &str) {
26        if !self.quiet {
27            eprintln!("{msg}");
28        }
29    }
30
31    /// Print a structured JSON result for mutation commands.
32    /// In JSON mode, prints to stdout. In human mode, prints message to stderr.
33    pub fn print_result(&self, json_value: &serde_json::Value, human_message: &str) {
34        if self.json {
35            println!(
36                "{}",
37                serde_json::to_string_pretty(json_value).expect("failed to serialize JSON")
38            );
39        } else {
40            self.print_message(human_message);
41        }
42    }
43}
44
45/// Exit codes for agent-friendly error handling.
46/// Agents can branch on specific failure modes without parsing error text.
47pub mod exit_codes {
48    pub const SUCCESS: i32 = 0;
49    pub const CONFIG_ERROR: i32 = 2;
50    pub const AUTH_ERROR: i32 = 3;
51    pub const NOT_FOUND: i32 = 4;
52    pub const API_ERROR: i32 = 5;
53    pub const GENERAL_ERROR: i32 = 1;
54}
55
56/// Map an error to a specific exit code based on its type.
57pub fn exit_code_for_error(err: &dyn std::error::Error) -> i32 {
58    let msg = err.to_string();
59    if msg.starts_with("Authentication error:") {
60        exit_codes::AUTH_ERROR
61    } else if msg.starts_with("Not found:") {
62        exit_codes::NOT_FOUND
63    } else if msg.starts_with("API error") {
64        exit_codes::API_ERROR
65    } else {
66        exit_codes::GENERAL_ERROR
67    }
68}