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}