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
168    // ? Voice errors
169    LiveKitUnavailable,
170    NotAVoiceChannel,
171    AlreadyConnected,
172    NotConnected,
173    UnknownNode,
174    // ? Micro-service errors
175    ProxyError,
176    FileTooSmall,
177    FileTooLarge {
178        max: usize,
179    },
180    FileTypeNotAllowed,
181    ImageProcessingFailed,
182    NoEmbedData,
183
184    // ? Legacy errors
185    VosoUnavailable,
186
187    // ? Feature flag disabled in the config
188    FeatureDisabled {
189        feature: String,
190    },
191}
192
193#[macro_export]
194macro_rules! create_error {
195    ( $error: ident $( $tt:tt )? ) => {
196        $crate::Error {
197            error_type: $crate::ErrorType::$error $( $tt )?,
198            location: format!("{}:{}:{}", file!(), line!(), column!()),
199        }
200    };
201}
202
203#[macro_export]
204macro_rules! create_database_error {
205    ( $operation: expr, $collection: expr ) => {
206        $crate::create_error!(DatabaseError {
207            operation: $operation.to_string(),
208            collection: $collection.to_string()
209        })
210    };
211}
212
213#[macro_export]
214#[cfg(debug_assertions)]
215macro_rules! query {
216    ( $self: ident, $type: ident, $collection: expr, $($rest:expr),+ ) => {
217        Ok($self.$type($collection, $($rest),+).await.unwrap())
218    };
219}
220
221#[macro_export]
222#[cfg(not(debug_assertions))]
223macro_rules! query {
224    ( $self: ident, $type: ident, $collection: expr, $($rest:expr),+ ) => {
225        $self.$type($collection, $($rest),+).await
226            .map_err(|_| create_database_error!(stringify!($type), $collection))
227    };
228}
229
230pub trait ToRevoltError<T> {
231    #[track_caller]
232    fn to_internal_error(self) -> Result<T, Error>;
233}
234
235impl<T, E: std::fmt::Debug + std::error::Error> ToRevoltError<T> for Result<T, E> {
236    #[track_caller]
237    fn to_internal_error(self) -> Result<T, Error> {
238        let loc = Location::caller();
239
240        self.map_err(|e| {
241            log::error!("{e:?}");
242            #[cfg(feature = "sentry")]
243            sentry::capture_error(&e);
244
245            Error {
246                error_type: ErrorType::InternalError,
247                location: format!("{}:{}:{}", loc.file(), loc.line(), loc.column()),
248            }
249        })
250    }
251}
252
253impl<T> ToRevoltError<T> for Option<T> {
254    #[track_caller]
255    fn to_internal_error(self) -> Result<T, Error> {
256        let loc = Location::caller();
257
258        self.ok_or_else(|| Error {
259            error_type: ErrorType::InternalError,
260            location: format!("{}:{}:{}", loc.file(), loc.line(), loc.column()),
261        })
262    }
263}
264
265#[cfg(test)]
266mod tests {
267    use crate::ErrorType;
268
269    #[test]
270    fn use_macro_to_construct_error() {
271        let error = create_error!(LabelMe);
272        assert!(matches!(error.error_type, ErrorType::LabelMe));
273    }
274
275    #[test]
276    fn use_macro_to_construct_complex_error() {
277        let error = create_error!(LabelMe);
278        assert!(matches!(error.error_type, ErrorType::LabelMe));
279    }
280}