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
25pub type Result<T, E = Error> = std::result::Result<T, E>;
27
28#[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 #[cfg_attr(feature = "serde", serde(flatten))]
36 pub error_type: ErrorType,
37
38 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#[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 LabelMe,
59
60 AlreadyOnboarded,
62
63 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 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 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 ReachedMaximumBots,
129 IsBot,
130 IsNotBot,
131 BotIsPrivate,
132
133 CannotReportYourself,
135
136 MissingPermission {
138 permission: String,
139 },
140 MissingUserPermission {
141 permission: String,
142 },
143 NotElevated,
144 NotPrivileged,
145 CannotGiveMissingPermissions,
146 NotOwner,
147 IsElevated,
148
149 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 LiveKitUnavailable,
175 NotAVoiceChannel,
176 AlreadyConnected,
177 NotConnected,
178 UnknownNode,
179 ProxyError,
181 FileTooSmall,
182 FileTooLarge {
183 max: usize,
184 },
185 FileTypeNotAllowed,
186 ImageProcessingFailed,
187 NoEmbedData,
188
189 VosoUnavailable,
191
192 FeatureDisabled {
194 feature: String,
195 },
196
197 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}