torrust_index_backend/
errors.rs

1use std::borrow::Cow;
2use std::error;
3
4use derive_more::{Display, Error};
5use hyper::StatusCode;
6
7use crate::databases::database;
8
9pub type ServiceResult<V> = Result<V, ServiceError>;
10
11#[derive(Debug, Display, PartialEq, Eq, Error)]
12#[allow(dead_code)]
13pub enum ServiceError {
14    #[display(fmt = "internal server error")]
15    InternalServerError,
16
17    #[display(fmt = "This server is is closed for registration. Contact admin if this is unexpected")]
18    ClosedForRegistration,
19
20    #[display(fmt = "Email is required")] //405j
21    EmailMissing,
22    #[display(fmt = "Please enter a valid email address")] //405j
23    EmailInvalid,
24
25    #[display(fmt = "The value you entered for URL is not a URL")] //405j
26    NotAUrl,
27
28    #[display(fmt = "Invalid username/email or password")]
29    WrongPasswordOrUsername,
30    #[display(fmt = "Username not found")]
31    UsernameNotFound,
32    #[display(fmt = "User not found")]
33    UserNotFound,
34
35    #[display(fmt = "Account not found")]
36    AccountNotFound,
37
38    /// when the value passed contains profanity
39    #[display(fmt = "Can't allow profanity in usernames")]
40    ProfanityError,
41    /// when the value passed contains blacklisted words
42    /// see [blacklist](https://github.com/shuttlecraft/The-Big-Username-Blacklist)
43    #[display(fmt = "Username contains blacklisted words")]
44    BlacklistError,
45    /// when the value passed contains characters not present
46    /// in [UsernameCaseMapped](https://tools.ietf.org/html/rfc8265#page-7)
47    /// profile
48    #[display(fmt = "username_case_mapped violation")]
49    UsernameCaseMappedError,
50
51    #[display(fmt = "Password too short")]
52    PasswordTooShort,
53    #[display(fmt = "Username too long")]
54    PasswordTooLong,
55    #[display(fmt = "Passwords don't match")]
56    PasswordsDontMatch,
57
58    /// when the a username is already taken
59    #[display(fmt = "Username not available")]
60    UsernameTaken,
61
62    #[display(fmt = "Username contains illegal characters")]
63    UsernameInvalid,
64
65    /// email is already taken
66    #[display(fmt = "Email not available")]
67    EmailTaken,
68
69    #[display(fmt = "Please verify your email before logging in")]
70    EmailNotVerified,
71
72    /// when the a token name is already taken
73    /// token not found
74    #[display(fmt = "Token not found. Please sign in.")]
75    TokenNotFound,
76
77    /// token expired
78    #[display(fmt = "Token expired. Please sign in again.")]
79    TokenExpired,
80
81    #[display(fmt = "Token invalid.")]
82    /// token invalid
83    TokenInvalid,
84
85    #[display(fmt = "Torrent not found.")]
86    TorrentNotFound,
87
88    #[display(fmt = "Uploaded torrent is not valid.")]
89    InvalidTorrentFile,
90
91    #[display(fmt = "Uploaded torrent has an invalid pieces key.")]
92    InvalidTorrentPiecesLength,
93
94    #[display(fmt = "Only .torrent files can be uploaded.")]
95    InvalidFileType,
96
97    #[display(fmt = "Bad request.")]
98    BadRequest,
99
100    #[display(fmt = "Selected category does not exist.")]
101    InvalidCategory,
102
103    #[display(fmt = "Selected tag does not exist.")]
104    InvalidTag,
105
106    #[display(fmt = "Unauthorized action.")]
107    Unauthorized,
108
109    #[display(fmt = "This torrent already exists in our database.")]
110    InfoHashAlreadyExists,
111
112    #[display(fmt = "This torrent title has already been used.")]
113    TorrentTitleAlreadyExists,
114
115    #[display(fmt = "Sorry, we have an error with our tracker connection.")]
116    TrackerOffline,
117
118    #[display(fmt = "Could not whitelist torrent.")]
119    WhitelistingError,
120
121    #[display(fmt = "Failed to send verification email.")]
122    FailedToSendVerificationEmail,
123
124    #[display(fmt = "Category already exists.")]
125    CategoryAlreadyExists,
126
127    #[display(fmt = "Tag already exists.")]
128    TagAlreadyExists,
129
130    #[display(fmt = "Category not found.")]
131    CategoryNotFound,
132
133    #[display(fmt = "Tag not found.")]
134    TagNotFound,
135
136    #[display(fmt = "Database error.")]
137    DatabaseError,
138}
139
140impl From<sqlx::Error> for ServiceError {
141    fn from(e: sqlx::Error) -> Self {
142        eprintln!("{e:?}");
143
144        if let Some(err) = e.as_database_error() {
145            return if err.code() == Some(Cow::from("2067")) {
146                if err.message().contains("torrust_torrents.info_hash") {
147                    ServiceError::InfoHashAlreadyExists
148                } else {
149                    ServiceError::InternalServerError
150                }
151            } else {
152                ServiceError::TorrentNotFound
153            };
154        }
155
156        ServiceError::InternalServerError
157    }
158}
159
160impl From<database::Error> for ServiceError {
161    fn from(e: database::Error) -> Self {
162        map_database_error_to_service_error(&e)
163    }
164}
165
166impl From<argon2::password_hash::Error> for ServiceError {
167    fn from(e: argon2::password_hash::Error) -> Self {
168        eprintln!("{e}");
169        ServiceError::InternalServerError
170    }
171}
172
173impl From<std::io::Error> for ServiceError {
174    fn from(e: std::io::Error) -> Self {
175        eprintln!("{e}");
176        ServiceError::InternalServerError
177    }
178}
179
180impl From<Box<dyn error::Error>> for ServiceError {
181    fn from(e: Box<dyn error::Error>) -> Self {
182        eprintln!("{e}");
183        ServiceError::InternalServerError
184    }
185}
186
187impl From<serde_json::Error> for ServiceError {
188    fn from(e: serde_json::Error) -> Self {
189        eprintln!("{e}");
190        ServiceError::InternalServerError
191    }
192}
193
194#[must_use]
195pub fn http_status_code_for_service_error(error: &ServiceError) -> StatusCode {
196    #[allow(clippy::match_same_arms)]
197    match error {
198        ServiceError::ClosedForRegistration => StatusCode::FORBIDDEN,
199        ServiceError::EmailInvalid => StatusCode::BAD_REQUEST,
200        ServiceError::NotAUrl => StatusCode::BAD_REQUEST,
201        ServiceError::WrongPasswordOrUsername => StatusCode::FORBIDDEN,
202        ServiceError::UsernameNotFound => StatusCode::NOT_FOUND,
203        ServiceError::UserNotFound => StatusCode::NOT_FOUND,
204        ServiceError::AccountNotFound => StatusCode::NOT_FOUND,
205        ServiceError::ProfanityError => StatusCode::BAD_REQUEST,
206        ServiceError::BlacklistError => StatusCode::BAD_REQUEST,
207        ServiceError::UsernameCaseMappedError => StatusCode::BAD_REQUEST,
208        ServiceError::PasswordTooShort => StatusCode::BAD_REQUEST,
209        ServiceError::PasswordTooLong => StatusCode::BAD_REQUEST,
210        ServiceError::PasswordsDontMatch => StatusCode::BAD_REQUEST,
211        ServiceError::UsernameTaken => StatusCode::BAD_REQUEST,
212        ServiceError::UsernameInvalid => StatusCode::BAD_REQUEST,
213        ServiceError::EmailTaken => StatusCode::BAD_REQUEST,
214        ServiceError::EmailNotVerified => StatusCode::FORBIDDEN,
215        ServiceError::TokenNotFound => StatusCode::UNAUTHORIZED,
216        ServiceError::TokenExpired => StatusCode::UNAUTHORIZED,
217        ServiceError::TokenInvalid => StatusCode::UNAUTHORIZED,
218        ServiceError::TorrentNotFound => StatusCode::NOT_FOUND,
219        ServiceError::InvalidTorrentFile => StatusCode::BAD_REQUEST,
220        ServiceError::InvalidTorrentPiecesLength => StatusCode::BAD_REQUEST,
221        ServiceError::InvalidFileType => StatusCode::BAD_REQUEST,
222        ServiceError::BadRequest => StatusCode::BAD_REQUEST,
223        ServiceError::InvalidCategory => StatusCode::BAD_REQUEST,
224        ServiceError::InvalidTag => StatusCode::BAD_REQUEST,
225        ServiceError::Unauthorized => StatusCode::FORBIDDEN,
226        ServiceError::InfoHashAlreadyExists => StatusCode::BAD_REQUEST,
227        ServiceError::TorrentTitleAlreadyExists => StatusCode::BAD_REQUEST,
228        ServiceError::TrackerOffline => StatusCode::INTERNAL_SERVER_ERROR,
229        ServiceError::CategoryAlreadyExists => StatusCode::BAD_REQUEST,
230        ServiceError::TagAlreadyExists => StatusCode::BAD_REQUEST,
231        ServiceError::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR,
232        ServiceError::EmailMissing => StatusCode::NOT_FOUND,
233        ServiceError::FailedToSendVerificationEmail => StatusCode::INTERNAL_SERVER_ERROR,
234        ServiceError::WhitelistingError => StatusCode::INTERNAL_SERVER_ERROR,
235        ServiceError::DatabaseError => StatusCode::INTERNAL_SERVER_ERROR,
236        ServiceError::CategoryNotFound => StatusCode::NOT_FOUND,
237        ServiceError::TagNotFound => StatusCode::NOT_FOUND,
238    }
239}
240
241#[must_use]
242pub fn map_database_error_to_service_error(error: &database::Error) -> ServiceError {
243    #[allow(clippy::match_same_arms)]
244    match error {
245        database::Error::Error => ServiceError::InternalServerError,
246        database::Error::ErrorWithText(_) => ServiceError::InternalServerError,
247        database::Error::UsernameTaken => ServiceError::UsernameTaken,
248        database::Error::EmailTaken => ServiceError::EmailTaken,
249        database::Error::UserNotFound => ServiceError::UserNotFound,
250        database::Error::CategoryAlreadyExists => ServiceError::CategoryAlreadyExists,
251        database::Error::CategoryNotFound => ServiceError::InvalidCategory,
252        database::Error::TagAlreadyExists => ServiceError::TagAlreadyExists,
253        database::Error::TagNotFound => ServiceError::InvalidTag,
254        database::Error::TorrentNotFound => ServiceError::TorrentNotFound,
255        database::Error::TorrentAlreadyExists => ServiceError::InfoHashAlreadyExists,
256        database::Error::TorrentTitleAlreadyExists => ServiceError::TorrentTitleAlreadyExists,
257        database::Error::UnrecognizedDatabaseDriver => ServiceError::InternalServerError,
258    }
259}