1use std::fmt;
4
5#[derive(Debug)]
6pub enum CliError {
7 Input(String),
9 Auth(String),
11 ReadOnly(String),
13 NotFound(String),
15 Api { status: u16, message: String },
17 RateLimit,
19 Http(String),
21 Other(String),
23}
24
25impl fmt::Display for CliError {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 match self {
28 CliError::Input(m) => write!(f, "{m}"),
29 CliError::Auth(m) => write!(f, "{m}"),
30 CliError::ReadOnly(m) => write!(f, "{m}"),
31 CliError::NotFound(m) => write!(f, "{m}"),
32 CliError::Api { status, message } => write!(f, "Graph API {status}: {message}"),
33 CliError::RateLimit => write!(f, "rate limited by Microsoft Graph"),
34 CliError::Http(m) => write!(f, "{m}"),
35 CliError::Other(m) => write!(f, "{m}"),
36 }
37 }
38}
39
40impl std::error::Error for CliError {}
41
42impl From<reqwest::Error> for CliError {
43 fn from(err: reqwest::Error) -> Self {
44 CliError::Http(err.to_string())
45 }
46}
47
48impl From<std::io::Error> for CliError {
49 fn from(err: std::io::Error) -> Self {
50 CliError::Other(err.to_string())
51 }
52}
53
54impl From<serde_json::Error> for CliError {
55 fn from(err: serde_json::Error) -> Self {
56 CliError::Other(format!("JSON error: {err}"))
57 }
58}
59
60pub mod exit_codes {
61 pub const SUCCESS: i32 = 0;
62 pub const GENERAL: i32 = 1;
63 pub const INPUT: i32 = 2;
64 pub const AUTH: i32 = 3;
65 pub const NOT_FOUND: i32 = 4;
66 pub const API: i32 = 5;
67 pub const RATE_LIMIT: i32 = 6;
68}
69
70pub fn exit_code_for(err: &CliError) -> i32 {
71 match err {
72 CliError::Input(_) | CliError::ReadOnly(_) => exit_codes::INPUT,
73 CliError::Auth(_) => exit_codes::AUTH,
74 CliError::NotFound(_) => exit_codes::NOT_FOUND,
75 CliError::Api { .. } => exit_codes::API,
76 CliError::RateLimit => exit_codes::RATE_LIMIT,
77 CliError::Http(_) | CliError::Other(_) => exit_codes::GENERAL,
78 }
79}
80
81pub type Result<T> = std::result::Result<T, CliError>;
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn input_error_maps_to_exit_2() {
89 assert_eq!(exit_code_for(&CliError::Input("x".into())), 2);
90 }
91
92 #[test]
93 fn read_only_error_maps_to_exit_2() {
94 assert_eq!(exit_code_for(&CliError::ReadOnly("x".into())), 2);
95 }
96
97 #[test]
98 fn auth_error_maps_to_exit_3() {
99 assert_eq!(exit_code_for(&CliError::Auth("x".into())), 3);
100 }
101
102 #[test]
103 fn not_found_maps_to_exit_4() {
104 assert_eq!(exit_code_for(&CliError::NotFound("x".into())), 4);
105 }
106
107 #[test]
108 fn api_error_maps_to_exit_5() {
109 assert_eq!(
110 exit_code_for(&CliError::Api {
111 status: 500,
112 message: "x".into()
113 }),
114 5
115 );
116 }
117
118 #[test]
119 fn rate_limit_maps_to_exit_6() {
120 assert_eq!(exit_code_for(&CliError::RateLimit), 6);
121 }
122
123 #[test]
124 fn http_error_maps_to_general() {
125 assert_eq!(exit_code_for(&CliError::Http("x".into())), 1);
126 }
127
128 #[test]
129 fn other_error_maps_to_general() {
130 assert_eq!(exit_code_for(&CliError::Other("x".into())), 1);
131 }
132
133 #[test]
134 fn display_includes_status_for_api() {
135 let e = CliError::Api {
136 status: 404,
137 message: "not found".into(),
138 };
139 assert_eq!(format!("{e}"), "Graph API 404: not found");
140 }
141}