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}