1use chrono::{DateTime, Duration, Utc};
2
3pub type Result<T> = core::result::Result<T, CoreError>;
4
5#[derive(thiserror::Error, Debug)]
9pub enum CoreError {
10 #[error("bad request: {0}")]
11 BadRequest(String),
12 #[error(transparent)]
13 Validation(#[from] ValidationError),
14 #[error(transparent)]
15 Unauthenticated(#[from] AuthError),
16 #[error(transparent)]
17 Forbidden(#[from] ForbiddenReason),
18 #[error(transparent)]
19 NotFound(#[from] ResourceKind),
20 #[error(transparent)]
21 Conflict(#[from] ConflictReason),
22 #[error("unsupported media type: {0}")]
23 UnsupportedMediaType(String),
24 #[error("rate limit exceeded: {limit} requests allowed per {window:?}")]
25 RateLimitExceeded { limit: u32, window: Duration },
26 #[error(transparent)]
27 Internal(#[from] InternalError),
28}
29
30#[derive(Debug, Clone)]
33pub enum CredentialField {
34 Username,
35 Email,
36 Password,
37 EmailOrPassword,
38 Token,
39 ApiKey,
40 ObjectId,
41}
42
43#[derive(Debug, Clone)]
44pub enum TokenErrorType {
45 Token,
46 AccessToken,
47 RefreshToken,
48 SessionToken,
49 PasswordResetToken,
50 EmailVerificationToken,
51}
52
53#[derive(thiserror::Error, Debug)]
56pub enum AuthError {
57 #[error("{token_type:?} expired at {expired_at}")]
58 TokenExpired {
59 token_type: TokenErrorType,
60 expired_at: DateTime<Utc>,
61 },
62 #[error("{token_type:?} token has an invalid signature")]
63 TokenInvalidSignature { token_type: TokenErrorType },
64 #[error("{token_type:?} token has an invalid audience")]
65 TokenInvalidAudience { token_type: TokenErrorType },
66 #[error("{token_type:?} token has an invalid issuer")]
67 TokenInvalidIssuer { token_type: TokenErrorType },
68 #[error("{token_type:?} token uses an invalid or missing algorithm")]
69 TokenInvalidAlgorithm { token_type: TokenErrorType },
70 #[error("{token_type:?} token is malformed")]
71 TokenMalformed { token_type: TokenErrorType },
72 #[error("{token_type:?} has already been used")]
73 TokenReplay { token_type: TokenErrorType },
74 #[error("{token_type:?} is invalid")]
75 TokenInvalid { token_type: TokenErrorType },
76 #[error("invalid credentials for {field:?}")]
77 InvalidCredentials { field: CredentialField },
78 #[error("account is not verified")]
79 AccountNotVerified,
80 #[error("jwt is not configured")]
81 JwtNotConfigured,
82}
83
84#[derive(thiserror::Error, Debug)]
85pub enum ForbiddenReason {
86 #[error("insufficient permissions to perform this action")]
87 InsufficientPermissions,
88 #[error("account has been suspended")]
89 AccountSuspended,
90}
91
92#[derive(thiserror::Error, Debug)]
93pub enum ResourceKind {
94 #[error("user not found (id: {id:?}, email: {email:?})")]
95 User {
96 id: Option<String>,
97 email: Option<String>,
98 },
99 #[error("role not found (id: {id:?})")]
100 Role { id: Option<String> },
101 #[error("{token_type:?} not found")]
102 Token { token_type: TokenErrorType },
103}
104
105#[derive(thiserror::Error, Debug)]
106pub enum ConflictReason {
107 #[error("{field:?} already exists")]
108 AlreadyExists { field: CredentialField },
109}
110
111#[derive(thiserror::Error, Debug)]
112pub enum ValidationError {
113 #[error("malformed {field:?}")]
114 Malformed { field: CredentialField },
115 #[error("missing required field: {field:?}")]
116 Missing { field: CredentialField },
117 #[error("invalid value for '{0}'")]
118 Invalid(String),
119}
120
121#[derive(thiserror::Error, Debug)]
122pub enum InternalError {
123 #[error("database error: {0}")]
124 Database(String),
125 #[error("hashing failure")]
126 Hashing,
127 #[error("failed to create {token_type:?}")]
128 TokenCreation { token_type: TokenErrorType },
129 #[error("failed to deliver email to {to}")]
130 EmailDelivery { to: String },
131 #[error("TLS configuration error at {path}")]
132 Tls { path: String },
133 #[error("I/O error: {0}")]
134 Io(String),
135 #[error("serialization error: {0}")]
136 Serialization(String),
137 #[error("cache error: {0}")]
138 Cache(String),
139 #[error("session error: {0}")]
140 Session(String),
141 #[error("JWT error: {0}")]
142 Jwt(String),
143 #[error("missing app data: {0}")]
144 MissingConfiguration(String),
145 #[error("missing configuration field: {field} - {reason}")]
146 MissingField { field: String, reason: String },
147 #[error("invalid config")]
148 InvalidConfig(Vec<CoreError>), }
150
151impl From<validator::ValidationErrors> for ValidationError {
153 fn from(e: validator::ValidationErrors) -> Self {
154 ValidationError::Invalid(e.to_string())
155 }
156}
157
158impl From<validator::ValidationError> for ValidationError {
159 fn from(e: validator::ValidationError) -> Self {
160 ValidationError::Invalid(e.to_string())
161 }
162}
163
164impl From<std::io::Error> for InternalError {
166 fn from(e: std::io::Error) -> Self {
167 InternalError::Io(e.to_string())
168 }
169}
170impl From<std::env::VarError> for InternalError {
171 fn from(e: std::env::VarError) -> Self {
172 InternalError::Io(e.to_string())
173 }
174}
175
176impl From<sqlx::Error> for InternalError {
177 fn from(e: sqlx::Error) -> Self {
178 InternalError::Database(e.to_string())
179 }
180}
181impl From<sqlx::migrate::MigrateError> for InternalError {
182 fn from(e: sqlx::migrate::MigrateError) -> Self {
183 InternalError::Database(e.to_string())
184 }
185}
186
187impl From<mongodb::error::Error> for InternalError {
188 fn from(e: mongodb::error::Error) -> Self {
189 InternalError::Database(e.to_string())
190 }
191}
192impl From<mongodb::bson::ser::Error> for InternalError {
193 fn from(e: mongodb::bson::ser::Error) -> Self {
194 InternalError::Database(e.to_string())
195 }
196}
197
198impl From<redis::RedisError> for InternalError {
199 fn from(e: redis::RedisError) -> Self {
200 InternalError::Cache(e.to_string())
201 }
202}
203impl From<memcache::MemcacheError> for InternalError {
204 fn from(e: memcache::MemcacheError) -> Self {
205 InternalError::Cache(e.to_string())
206 }
207}
208impl From<serde_json::Error> for InternalError {
209 fn from(e: serde_json::Error) -> Self {
210 InternalError::Serialization(e.to_string())
211 }
212}
213impl From<serde_yaml::Error> for InternalError {
214 fn from(e: serde_yaml::Error) -> Self {
215 InternalError::Serialization(e.to_string())
216 }
217}
218impl From<config::ConfigError> for InternalError {
219 fn from(e: config::ConfigError) -> Self {
220 InternalError::Serialization(e.to_string())
221 }
222}
223
224impl From<jsonwebtoken::errors::Error> for InternalError {
225 fn from(e: jsonwebtoken::errors::Error) -> Self {
226 InternalError::Jwt(e.to_string())
227 }
228}
229
230impl From<std::io::Error> for CoreError {
233 fn from(e: std::io::Error) -> Self {
234 CoreError::Internal(e.into())
235 }
236}
237impl From<std::env::VarError> for CoreError {
238 fn from(e: std::env::VarError) -> Self {
239 CoreError::Internal(e.into())
240 }
241}
242
243impl From<sqlx::Error> for CoreError {
244 fn from(e: sqlx::Error) -> Self {
245 CoreError::Internal(e.into())
246 }
247}
248impl From<sqlx::migrate::MigrateError> for CoreError {
249 fn from(e: sqlx::migrate::MigrateError) -> Self {
250 CoreError::Internal(e.into())
251 }
252}
253
254impl From<mongodb::error::Error> for CoreError {
255 fn from(e: mongodb::error::Error) -> Self {
256 CoreError::Internal(e.into())
257 }
258}
259impl From<mongodb::bson::ser::Error> for CoreError {
260 fn from(e: mongodb::bson::ser::Error) -> Self {
261 CoreError::Internal(e.into())
262 }
263}
264
265impl From<redis::RedisError> for CoreError {
266 fn from(e: redis::RedisError) -> Self {
267 CoreError::Internal(e.into())
268 }
269}
270impl From<memcache::MemcacheError> for CoreError {
271 fn from(e: memcache::MemcacheError) -> Self {
272 CoreError::Internal(e.into())
273 }
274}
275
276impl From<serde_json::Error> for CoreError {
277 fn from(e: serde_json::Error) -> Self {
278 CoreError::Internal(e.into())
279 }
280}
281impl From<serde_yaml::Error> for CoreError {
282 fn from(e: serde_yaml::Error) -> Self {
283 CoreError::Internal(e.into())
284 }
285}
286impl From<config::ConfigError> for CoreError {
287 fn from(e: config::ConfigError) -> Self {
288 CoreError::Internal(e.into())
289 }
290}
291
292impl From<jsonwebtoken::errors::Error> for CoreError {
294 fn from(_: jsonwebtoken::errors::Error) -> Self {
295 CoreError::Unauthenticated(AuthError::TokenInvalid {
296 token_type: TokenErrorType::AccessToken,
297 })
298 }
299}
300
301impl From<validator::ValidationErrors> for CoreError {
302 fn from(e: validator::ValidationErrors) -> Self {
303 CoreError::Validation(e.into())
304 }
305}
306impl From<validator::ValidationError> for CoreError {
307 fn from(e: validator::ValidationError) -> Self {
308 CoreError::Validation(e.into())
309 }
310}
311
312impl From<CoreError> for std::io::Error {
313 fn from(err: CoreError) -> Self {
314 std::io::Error::other(err.to_string())
315 }
316}