1use std::io::IsTerminal;
2
3pub fn use_color() -> bool {
5 std::io::stdout().is_terminal()
6}
7
8#[derive(Clone, Copy)]
13pub struct OutputConfig {
14 pub json: bool,
15 pub quiet: bool,
16}
17
18impl OutputConfig {
19 pub fn new(json_flag: bool, quiet: bool) -> Self {
20 let json = json_flag || !std::io::stdout().is_terminal();
21 Self { json, quiet }
22 }
23
24 pub fn print_data(&self, data: &str) {
26 println!("{data}");
27 }
28
29 pub fn print_message(&self, msg: &str) {
31 if !self.quiet {
32 eprintln!("{msg}");
33 }
34 }
35
36 pub fn print_result(&self, json_value: &serde_json::Value, human_message: &str) {
42 if self.json {
43 println!(
44 "{}",
45 serde_json::to_string_pretty(json_value).expect("failed to serialize JSON")
46 );
47 } else {
48 println!("{human_message}");
49 }
50 }
51}
52
53pub mod exit_codes {
56 pub const SUCCESS: i32 = 0;
58 pub const GENERAL_ERROR: i32 = 1;
60 pub const INPUT_ERROR: i32 = 2;
62 pub const AUTH_ERROR: i32 = 3;
64 pub const NOT_FOUND: i32 = 4;
66 pub const API_ERROR: i32 = 5;
68 pub const RATE_LIMIT: i32 = 6;
70}
71
72pub fn exit_code_for_error(err: &(dyn std::error::Error + 'static)) -> i32 {
74 if let Some(api_err) = err.downcast_ref::<crate::api::ApiError>() {
75 match api_err {
76 crate::api::ApiError::Auth(_) => exit_codes::AUTH_ERROR,
77 crate::api::ApiError::NotFound(_) => exit_codes::NOT_FOUND,
78 crate::api::ApiError::InvalidInput(_) => exit_codes::INPUT_ERROR,
79 crate::api::ApiError::RateLimit => exit_codes::RATE_LIMIT,
80 crate::api::ApiError::Api { .. } => exit_codes::API_ERROR,
81 crate::api::ApiError::Http(_) | crate::api::ApiError::Other(_) => {
82 exit_codes::GENERAL_ERROR
83 }
84 }
85 } else {
86 exit_codes::GENERAL_ERROR
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93 use crate::api::ApiError;
94
95 #[test]
96 fn exit_code_for_auth_error() {
97 let err = ApiError::Auth("bad token".into());
98 assert_eq!(exit_code_for_error(&err), exit_codes::AUTH_ERROR);
99 }
100
101 #[test]
102 fn exit_code_for_not_found() {
103 let err = ApiError::NotFound("PROJ-123".into());
104 assert_eq!(exit_code_for_error(&err), exit_codes::NOT_FOUND);
105 }
106
107 #[test]
108 fn exit_code_for_invalid_input() {
109 let err = ApiError::InvalidInput("bad key format".into());
110 assert_eq!(exit_code_for_error(&err), exit_codes::INPUT_ERROR);
111 }
112
113 #[test]
114 fn exit_code_for_rate_limit() {
115 let err = ApiError::RateLimit;
116 assert_eq!(exit_code_for_error(&err), exit_codes::RATE_LIMIT);
117 }
118
119 #[test]
120 fn exit_code_for_api_error() {
121 let err = ApiError::Api {
122 status: 500,
123 message: "Internal Server Error".into(),
124 };
125 assert_eq!(exit_code_for_error(&err), exit_codes::API_ERROR);
126 }
127
128 #[test]
129 fn exit_code_for_other_error() {
130 let err = ApiError::Other("something".into());
131 assert_eq!(exit_code_for_error(&err), exit_codes::GENERAL_ERROR);
132 }
133}