Skip to main content

teams_cli/
error.rs

1use serde::Serialize;
2use thiserror::Error;
3
4#[derive(Debug, Error)]
5pub enum AppError {
6    #[error("{0}")]
7    InvalidInput(String),
8    #[error("{0}")]
9    Auth(String),
10    #[error("{0}")]
11    Permission(String),
12    #[error("{0}")]
13    NotFound(String),
14    #[error("Microsoft Graph throttled the request")]
15    RateLimit(Option<u64>),
16    #[error("{0}")]
17    Api(String),
18    #[error("{0}")]
19    NonInteractive(String),
20    #[error("{0}")]
21    Io(#[from] std::io::Error),
22    #[error("{0}")]
23    Unexpected(String),
24}
25
26#[derive(Debug, Clone, Copy, Serialize)]
27pub struct ErrorContract {
28    pub kind: &'static str,
29    pub exit_code: i32,
30    pub retryable: bool,
31    pub description: &'static str,
32}
33
34pub const INVALID_INPUT: ErrorContract = ErrorContract {
35    kind: "usage",
36    exit_code: 2,
37    retryable: false,
38    description: "Arguments or local configuration are invalid",
39};
40pub const AUTH: ErrorContract = ErrorContract {
41    kind: "auth",
42    exit_code: 3,
43    retryable: false,
44    description: "Authentication is missing, expired, or rejected",
45};
46pub const PERMISSION: ErrorContract = ErrorContract {
47    kind: "permission_denied",
48    exit_code: 4,
49    retryable: false,
50    description: "The tenant or granted scopes do not permit the operation",
51};
52pub const NOT_FOUND: ErrorContract = ErrorContract {
53    kind: "not_found",
54    exit_code: 5,
55    retryable: false,
56    description: "The requested Teams resource does not exist",
57};
58pub const RATE_LIMIT: ErrorContract = ErrorContract {
59    kind: "rate_limit",
60    exit_code: 6,
61    retryable: true,
62    description: "Microsoft Graph throttled the request",
63};
64pub const API: ErrorContract = ErrorContract {
65    kind: "api_error",
66    exit_code: 7,
67    retryable: false,
68    description: "Microsoft Graph returned an API error",
69};
70pub const NON_INTERACTIVE: ErrorContract = ErrorContract {
71    kind: "tty_required",
72    exit_code: 8,
73    retryable: false,
74    description: "An interactive command was invoked without a terminal",
75};
76pub const UNEXPECTED: ErrorContract = ErrorContract {
77    kind: "internal",
78    exit_code: 1,
79    retryable: false,
80    description: "An unexpected local or transport error occurred",
81};
82
83pub const ALL: &[ErrorContract] = &[
84    INVALID_INPUT,
85    AUTH,
86    PERMISSION,
87    NOT_FOUND,
88    RATE_LIMIT,
89    API,
90    NON_INTERACTIVE,
91    UNEXPECTED,
92];
93
94impl AppError {
95    pub fn contract(&self) -> ErrorContract {
96        match self {
97            Self::InvalidInput(_) => INVALID_INPUT,
98            Self::Auth(_) => AUTH,
99            Self::Permission(_) => PERMISSION,
100            Self::NotFound(_) => NOT_FOUND,
101            Self::RateLimit(_) => RATE_LIMIT,
102            Self::Api(_) => API,
103            Self::NonInteractive(_) => NON_INTERACTIVE,
104            Self::Io(_) | Self::Unexpected(_) => UNEXPECTED,
105        }
106    }
107}