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")] EmailMissing,
22 #[display(fmt = "Please enter a valid email address")] EmailInvalid,
24
25 #[display(fmt = "The value you entered for URL is not a URL")] 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 #[display(fmt = "Can't allow profanity in usernames")]
40 ProfanityError,
41 #[display(fmt = "Username contains blacklisted words")]
44 BlacklistError,
45 #[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 #[display(fmt = "Username not available")]
60 UsernameTaken,
61
62 #[display(fmt = "Username contains illegal characters")]
63 UsernameInvalid,
64
65 #[display(fmt = "Email not available")]
67 EmailTaken,
68
69 #[display(fmt = "Please verify your email before logging in")]
70 EmailNotVerified,
71
72 #[display(fmt = "Token not found. Please sign in.")]
75 TokenNotFound,
76
77 #[display(fmt = "Token expired. Please sign in again.")]
79 TokenExpired,
80
81 #[display(fmt = "Token invalid.")]
82 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}