revolt_result/
lib.rs

1use std::panic::Location;
2use std::fmt::Display;
3
4#[cfg(feature = "serde")]
5#[macro_use]
6extern crate serde;
7
8#[cfg(feature = "schemas")]
9#[macro_use]
10extern crate schemars;
11
12#[cfg(feature = "utoipa")]
13#[macro_use]
14extern crate utoipa;
15
16#[cfg(feature = "rocket")]
17pub mod rocket;
18
19#[cfg(feature = "axum")]
20pub mod axum;
21
22#[cfg(feature = "okapi")]
23pub mod okapi;
24
25/// Result type with custom Error
26pub type Result<T, E = Error> = std::result::Result<T, E>;
27
28/// Error information
29#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
30#[cfg_attr(feature = "schemas", derive(JsonSchema))]
31#[cfg_attr(feature = "utoipa", derive(ToSchema))]
32#[derive(Debug, Clone)]
33pub struct Error {
34    /// Type of error and additional information
35    #[cfg_attr(feature = "serde", serde(flatten))]
36    pub error_type: ErrorType,
37
38    /// Where this error occurred
39    pub location: String,
40}
41
42impl Display for Error {
43    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44        write!(f, "{:?} occurred in {}", self.error_type, self.location)
45    }
46}
47
48impl std::error::Error for Error {}
49
50/// Possible error types
51#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
52#[cfg_attr(feature = "serde", serde(tag = "type"))]
53#[cfg_attr(feature = "schemas", derive(JsonSchema))]
54#[cfg_attr(feature = "utoipa", derive(ToSchema))]
55#[derive(Debug, Clone)]
56pub enum ErrorType {
57    /// This error was not labeled :(
58    LabelMe,
59
60    // ? Onboarding related errors
61    AlreadyOnboarded,
62
63    // ? User related errors
64    UsernameTaken,
65    InvalidUsername,
66    DiscriminatorChangeRatelimited,
67    UnknownUser,
68    AlreadyFriends,
69    AlreadySentRequest,
70    Blocked,
71    BlockedByOther,
72    NotFriends,
73    TooManyPendingFriendRequests {
74        max: usize,
75    },
76
77    // ? Channel related errors
78    UnknownChannel,
79    UnknownAttachment,
80    UnknownMessage,
81    CannotEditMessage,
82    CannotJoinCall,
83    TooManyAttachments {
84        max: usize,
85    },
86    TooManyEmbeds {
87        max: usize,
88    },
89    TooManyReplies {
90        max: usize,
91    },
92    TooManyChannels {
93        max: usize,
94    },
95    EmptyMessage,
96    PayloadTooLarge,
97    CannotRemoveYourself,
98    GroupTooLarge {
99        max: usize,
100    },
101    AlreadyInGroup,
102    NotInGroup,
103    AlreadyPinned,
104    NotPinned,
105
106    // ? Server related errors
107    UnknownServer,
108    InvalidRole,
109    Banned,
110    TooManyServers {
111        max: usize,
112    },
113    TooManyEmoji {
114        max: usize,
115    },
116    TooManyRoles {
117        max: usize,
118    },
119    AlreadyInServer,
120    CannotTimeoutYourself,
121
122    // ? Bot related errors
123    ReachedMaximumBots,
124    IsBot,
125    IsNotBot,
126    BotIsPrivate,
127
128    // ? User safety related errors
129    CannotReportYourself,
130
131    // ? Permission errors
132    MissingPermission {
133        permission: String,
134    },
135    MissingUserPermission {
136        permission: String,
137    },
138    NotElevated,
139    NotPrivileged,
140    CannotGiveMissingPermissions,
141    NotOwner,
142    IsElevated,
143
144    // ? General errors
145    DatabaseError {
146        operation: String,
147        collection: String,
148    },
149    InternalError,
150    InvalidOperation,
151    InvalidCredentials,
152    InvalidProperty,
153    InvalidSession,
154    InvalidFlagValue,
155    NotAuthenticated,
156    DuplicateNonce,
157    NotFound,
158    NoEffect,
159    FailedValidation {
160        error: String,
161    },
162
163    // ? Voice errors
164    LiveKitUnavailable,
165    NotAVoiceChannel,
166    AlreadyConnected,
167    NotConnected,
168    UnknownNode,
169    // ? Micro-service errors
170    ProxyError,
171    FileTooSmall,
172    FileTooLarge {
173        max: usize,
174    },
175    FileTypeNotAllowed,
176    ImageProcessingFailed,
177    NoEmbedData,
178
179    // ? Legacy errors
180    VosoUnavailable,
181
182    // ? Feature flag disabled in the config
183    FeatureDisabled {
184        feature: String,
185    },
186}
187
188#[macro_export]
189macro_rules! create_error {
190    ( $error: ident $( $tt:tt )? ) => {
191        $crate::Error {
192            error_type: $crate::ErrorType::$error $( $tt )?,
193            location: format!("{}:{}:{}", file!(), line!(), column!()),
194        }
195    };
196}
197
198#[macro_export]
199macro_rules! create_database_error {
200    ( $operation: expr, $collection: expr ) => {
201        $crate::create_error!(DatabaseError {
202            operation: $operation.to_string(),
203            collection: $collection.to_string()
204        })
205    };
206}
207
208#[macro_export]
209#[cfg(debug_assertions)]
210macro_rules! query {
211    ( $self: ident, $type: ident, $collection: expr, $($rest:expr),+ ) => {
212        Ok($self.$type($collection, $($rest),+).await.unwrap())
213    };
214}
215
216#[macro_export]
217#[cfg(not(debug_assertions))]
218macro_rules! query {
219    ( $self: ident, $type: ident, $collection: expr, $($rest:expr),+ ) => {
220        $self.$type($collection, $($rest),+).await
221            .map_err(|_| create_database_error!(stringify!($type), $collection))
222    };
223}
224
225pub trait ToRevoltError<T> {
226    #[track_caller]
227    fn to_internal_error(self) -> Result<T, Error>;
228}
229
230impl<T, E: std::fmt::Debug + std::error::Error> ToRevoltError<T> for Result<T, E> {
231    #[track_caller]
232    fn to_internal_error(self) -> Result<T, Error> {
233        let loc = Location::caller();
234
235        self
236            .map_err(|e| {
237                log::error!("{e:?}");
238                #[cfg(feature = "sentry")]
239                sentry::capture_error(&e);
240
241                Error {
242                    error_type: ErrorType::InternalError,
243                    location: format!("{}:{}:{}", loc.file(), loc.line(), loc.column())
244                }
245            })
246    }
247}
248
249impl<T> ToRevoltError<T> for Option<T> {
250    #[track_caller]
251    fn to_internal_error(self) -> Result<T, Error> {
252        let loc = Location::caller();
253
254        self.ok_or_else(|| {
255            Error {
256                error_type: ErrorType::InternalError,
257                location: format!("{}:{}:{}", loc.file(), loc.line(), loc.column())
258            }
259        })
260    }
261}
262
263#[cfg(test)]
264mod tests {
265    use crate::ErrorType;
266
267    #[test]
268    fn use_macro_to_construct_error() {
269        let error = create_error!(LabelMe);
270        assert!(matches!(error.error_type, ErrorType::LabelMe));
271    }
272
273    #[test]
274    fn use_macro_to_construct_complex_error() {
275        let error = create_error!(LabelMe);
276        assert!(matches!(error.error_type, ErrorType::LabelMe));
277    }
278}