1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
use crate::models::Permissions;
#[cfg(feature = "client")]
use serde::Deserialize;
use serde::Serialize;
#[cfg(feature = "openapi")]
use utoipa::ToSchema;
/// A type alias for a [`Result`] with the error type [`Error`].
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Copy, Clone, Debug, Serialize)]
#[cfg_attr(feature = "client", derive(Deserialize))]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "snake_case")]
pub enum MalformedBodyErrorType {
/// Invalid content type.
InvalidContentType,
/// Body was invalid UTF-8.
InvalidUtf8,
/// Received invalid JSON body.
InvalidJson,
}
/// An error that occurs within Adapt.
#[derive(Clone, Debug, Serialize)]
#[cfg_attr(feature = "client", derive(Deserialize))]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Error {
/// Received a malformed JSON or MsgPack body.
MalformedBody {
/// Extra information about the error.
error_type: MalformedBodyErrorType,
/// A generalized message about the error.
message: String,
},
/// You are missing the request body in an endpoint that requires it. This is commonly JSON
/// or MsgPack.
MissingBody {
/// The error message.
message: String,
},
/// Invalid field in the request body.
InvalidField {
/// The field that failed validation.
field: String,
/// The error message.
message: String,
},
/// You are missing a required field in the request body.
MissingField {
/// The name of the missing field.
field: String,
/// The error message.
message: String,
},
/// Could not resolve a plausible IP address from the request.
MalformedIp {
/// The error message.
message: String,
},
/// The entity was not found.
NotFound {
/// The type of item that couldn't be found.
entity: String,
/// The error message.
message: String,
},
/// Tried authorizing a bot account with anything but an authentication token.
UnsupportedAuthMethod {
/// The error message.
message: String,
},
/// The request required a valid authentication token, but one of the following happened:
///
/// * The token was not provided.
/// * The token was malformed, i.e. a non-UTF-8 string.
/// * The token does not exist or is invalid.
InvalidToken {
/// The error message.
message: String,
},
/// Invalid login credentials were provided, i.e. an invalid password.
InvalidCredentials {
/// Which credential was invalid.
what: String,
/// The error message.
message: String,
},
/// You must be a member of the guild to perform the requested action.
NotMember {
/// The ID of the guild you are not a member of.
guild_id: u64,
/// The error message.
message: String,
},
/// You must be the owner of the guild to perform the requested action.
NotOwner {
/// The ID of the guild you are not the owner of.
guild_id: u64,
/// The error message.
message: String,
},
/// You are too low in the role hierarchy to perform the requested action.
RoleTooLow {
/// The ID of the guild you are not the owner of.
guild_id: u64,
/// The ID of your top role. This is the role you possess with the highest position.
/// This is `None` if you have no roles (the default role).
top_role_id: Option<u64>,
/// The position of your top role.
top_role_position: u16,
/// The desired position your top role should be in the role hierarchy.
desired_position: u16,
/// The error message.
message: String,
},
/// You are missing the required permissions to perform the requested action.
MissingPermissions {
/// The ID of the guild you are missing permissions in.
guild_id: u64,
/// The permissions required to perform the requested action.
permissions: Permissions,
/// The error message.
message: String,
},
/// You are trying to delete a managed role.
RoleIsManaged {
/// The ID of the guild the role is in.
guild_id: u64,
/// The ID of the role that is managed.
role_id: u64,
/// The error message.
message: String,
},
/// Something was already taken, e.g. a username or email.
AlreadyTaken {
/// What was already taken.
what: String,
/// The error message.
message: String,
},
/// You are sending requests too quickly are you are being rate limited.
Ratelimited {
/// How long you should wait before sending another request, in whole seconds.
retry_after: f32,
/// The IP address that is being rate limited.
ip: String,
/// The ratelimited message.
message: String,
},
/// Internal server error occured, this is likely a bug.
InternalError {
/// What caused the error. `None` if unknown.
what: Option<String>,
/// The error message.
message: String,
/// A debug version of the error, or `None` if there is no debug version.
debug: Option<String>,
},
}
impl Error {
/// The HTTP status code associated with this error. If this error is not sent over HTTP,
/// this will be `None`.
#[must_use]
pub const fn http_status_code(&self) -> Option<u16> {
Some(match self {
Self::MalformedBody { .. }
| Self::MissingBody { .. }
| Self::InvalidField { .. }
| Self::MissingField { .. }
| Self::MalformedIp { .. }
| Self::UnsupportedAuthMethod { .. } => 400,
Self::InvalidToken { .. } | Self::InvalidCredentials { .. } => 401,
Self::NotMember { .. }
| Self::NotOwner { .. }
| Self::MissingPermissions { .. }
| Self::RoleTooLow { .. }
| Self::RoleIsManaged { .. } => 403,
Self::NotFound { .. } => 404,
Self::AlreadyTaken { .. } => 409,
Self::Ratelimited { .. } => 429,
Self::InternalError { .. } => 500,
})
}
}
#[cfg(feature = "db")]
impl From<sqlx::Error> for Error {
fn from(e: sqlx::Error) -> Self {
Self::InternalError {
what: Some("database".into()),
message: e.to_string(),
debug: Some(format!("{e:?}")),
}
}
}
#[cfg(feature = "auth")]
impl From<argon2_async::Error> for Error {
fn from(e: argon2_async::Error) -> Self {
Self::InternalError {
what: Some("hasher".into()),
message: e.to_string(),
debug: Some(format!("{e:?}")),
}
}
}
/// An extension trait for [`Option`] that adds [`NotFoundExt::ok_or_not_found`].
pub trait NotFoundExt<T> {
/// Converts an [`Option`] to a [`Result`] with [`Error::NotFound`] if it is [`None`].
///
/// # Example
/// ```no_run
/// use essence::error::NotFoundExt;
///
/// assert_eq!(Some(5).ok_or_not_found("user", "user not found"), Ok(5));
/// ```
fn ok_or_not_found(self, entity: impl ToString, message: impl ToString) -> Result<T>;
}
impl<T> NotFoundExt<T> for Option<T> {
#[inline]
fn ok_or_not_found(self, entity: impl ToString, message: impl ToString) -> Result<T> {
self.ok_or_else(|| Error::NotFound {
entity: entity.to_string(),
message: message.to_string(),
})
}
}