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