Skip to main content

sharepoint_cli/
error.rs

1//! Error types and exit code mapping.
2
3use std::fmt;
4
5#[derive(Debug)]
6pub enum CliError {
7    /// Bad user input or config (exit 2).
8    Input(String),
9    /// Authentication or token failure (exit 3).
10    Auth(String),
11    /// Read-only mode blocked a write (exit 2).
12    ReadOnly(String),
13    /// Site, library, or item not found (exit 4).
14    NotFound(String),
15    /// Graph API returned a non-2xx error (exit 5).
16    Api { status: u16, message: String },
17    /// Rate limited by Graph (exit 6).
18    RateLimit,
19    /// Underlying HTTP transport error (exit 1).
20    Http(String),
21    /// Anything else (exit 1).
22    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}