Skip to main content

revolt_result/
lib.rs

1use std::fmt::Display;
2use std::panic::Location;
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    CannotDeleteMessage,
82    CannotEditMessage,
83    CannotJoinCall,
84    TooManyAttachments {
85        max: usize,
86    },
87    TooManyEmbeds {
88        max: usize,
89    },
90    TooManyReplies {
91        max: usize,
92    },
93    TooManyChannels {
94        max: usize,
95    },
96    EmptyMessage,
97    PayloadTooLarge,
98    CannotRemoveYourself,
99    GroupTooLarge {
100        max: usize,
101    },
102    AlreadyInGroup,
103    NotInGroup,
104    AlreadyPinned,
105    NotPinned,
106    InSlowmode {
107        retry_after: u64,
108    },
109
110    // ? Server related errors
111    CantCreateServers,
112    UnknownServer,
113    InvalidRole,
114    Banned,
115    TooManyServers {
116        max: usize,
117    },
118    TooManyEmoji {
119        max: usize,
120    },
121    TooManyRoles {
122        max: usize,
123    },
124    AlreadyInServer,
125    CannotTimeoutYourself,
126
127    // ? Bot related errors
128    ReachedMaximumBots,
129    IsBot,
130    IsNotBot,
131    BotIsPrivate,
132
133    // ? User safety related errors
134    CannotReportYourself,
135
136    // ? Permission errors
137    MissingPermission {
138        permission: String,
139    },
140    MissingUserPermission {
141        permission: String,
142    },
143    NotElevated,
144    NotPrivileged,
145    CannotGiveMissingPermissions,
146    NotOwner,
147    IsElevated,
148
149    // ? General errors
150    DatabaseError {
151        operation: String,
152        collection: String,
153    },
154    InternalError,
155    InvalidOperation,
156    InvalidCredentials,
157    InvalidProperty,
158    InvalidSession,
159    InvalidFlagValue,
160    NotAuthenticated,
161    DuplicateNonce,
162    NotFound,
163    NoEffect,
164    FailedValidation {
165        error: String,
166    },
167    HeaderTooLarge,
168    OperationFailed,
169    IncorrectData {
170        with: String,
171    },
172
173    // ? Voice errors
174    LiveKitUnavailable,
175    NotAVoiceChannel,
176    AlreadyConnected,
177    NotConnected,
178    UnknownNode,
179    // ? Micro-service errors
180    ProxyError,
181    FileTooSmall,
182    FileTooLarge {
183        max: usize,
184    },
185    FileTypeNotAllowed,
186    ImageProcessingFailed,
187    NoEmbedData,
188
189    // ? Legacy errors
190    VosoUnavailable,
191
192    // ? Feature flag disabled in the config
193    FeatureDisabled {
194        feature: String,
195    },
196
197    // ? Authentication
198    RenderFail,
199    MissingHeaders,
200    CaptchaFailed,
201    BlockedByShield,
202    UnverifiedAccount,
203    EmailFailed,
204    InvalidToken,
205    MissingInvite,
206    InvalidInvite,
207
208    CompromisedPassword,
209    ShortPassword,
210    Blacklisted,
211    LockedOut,
212
213    TotpAlreadyEnabled,
214    DisallowedMFAMethod,
215}
216
217#[macro_export]
218macro_rules! create_error {
219    ( $error: ident $( $tt:tt )? ) => {
220        $crate::Error {
221            error_type: $crate::ErrorType::$error $( $tt )?,
222            location: format!("{}:{}:{}", file!(), line!(), column!()),
223        }
224    };
225}
226
227#[macro_export]
228macro_rules! create_database_error {
229    ( $operation: expr, $collection: expr ) => {
230        $crate::create_error!(DatabaseError {
231            operation: $operation.to_string(),
232            collection: $collection.to_string()
233        })
234    };
235}
236
237#[macro_export]
238#[cfg(debug_assertions)]
239macro_rules! query {
240    ( $self: ident, $type: ident, $collection: expr, $($rest:expr),+ ) => {
241        Ok($self.$type($collection, $($rest),+).await.unwrap())
242    };
243}
244
245#[macro_export]
246#[cfg(not(debug_assertions))]
247macro_rules! query {
248    ( $self: ident, $type: ident, $collection: expr, $($rest:expr),+ ) => {
249        $self.$type($collection, $($rest),+).await
250            .map_err(|_| create_database_error!(stringify!($type), $collection))
251    };
252}
253
254pub trait ToRevoltError<T> {
255    #[track_caller]
256    fn to_internal_error(self) -> Result<T, Error>;
257}
258
259impl<T, E: std::fmt::Debug + std::error::Error> ToRevoltError<T> for Result<T, E> {
260    #[track_caller]
261    fn to_internal_error(self) -> Result<T, Error> {
262        let loc = Location::caller();
263
264        self.map_err(|e| {
265            log::error!("{e:?}");
266            #[cfg(feature = "sentry")]
267            sentry::capture_error(&e);
268
269            Error {
270                error_type: ErrorType::InternalError,
271                location: format!("{}:{}:{}", loc.file(), loc.line(), loc.column()),
272            }
273        })
274    }
275}
276
277impl<T> ToRevoltError<T> for Option<T> {
278    #[track_caller]
279    fn to_internal_error(self) -> Result<T, Error> {
280        let loc = Location::caller();
281
282        self.ok_or_else(|| Error {
283            error_type: ErrorType::InternalError,
284            location: format!("{}:{}:{}", loc.file(), loc.line(), loc.column()),
285        })
286    }
287}
288
289#[cfg(test)]
290mod tests {
291    use crate::ErrorType;
292
293    #[test]
294    fn use_macro_to_construct_error() {
295        let error = create_error!(LabelMe);
296        assert!(matches!(error.error_type, ErrorType::LabelMe));
297    }
298
299    #[test]
300    fn use_macro_to_construct_complex_error() {
301        let error = create_error!(LabelMe);
302        assert!(matches!(error.error_type, ErrorType::LabelMe));
303    }
304}