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