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}