1use std::borrow::Cow;
2use std::error;
3
4use derive_more::{Display, Error};
5use hyper::StatusCode;
6
7use crate::databases::database;
8use crate::models::torrent::MetadataError;
9use crate::tracker::service::TrackerAPIError;
10use crate::utils::parse_torrent::DecodeTorrentFileError;
11
12pub type ServiceResult<V> = Result<V, ServiceError>;
13
14#[derive(Debug, Display, PartialEq, Eq, Error)]
15#[allow(dead_code)]
16pub enum ServiceError {
17 #[display(fmt = "internal server error")]
18 InternalServerError,
19
20 #[display(fmt = "This server is is closed for registration. Contact admin if this is unexpected")]
21 ClosedForRegistration,
22
23 #[display(fmt = "Email is required")] EmailMissing,
25 #[display(fmt = "Please enter a valid email address")] EmailInvalid,
27
28 #[display(fmt = "The value you entered for URL is not a URL")] NotAUrl,
30
31 #[display(fmt = "Invalid username/email or password")]
32 WrongPasswordOrUsername,
33 #[display(fmt = "Invalid password")]
34 InvalidPassword,
35 #[display(fmt = "Username not found")]
36 UsernameNotFound,
37 #[display(fmt = "User not found")]
38 UserNotFound,
39
40 #[display(fmt = "Account not found")]
41 AccountNotFound,
42
43 #[display(fmt = "Can't allow profanity in usernames")]
45 ProfanityError,
46 #[display(fmt = "Username contains blacklisted words")]
49 BlacklistError,
50 #[display(fmt = "username_case_mapped violation")]
54 UsernameCaseMappedError,
55
56 #[display(fmt = "Password too short")]
57 PasswordTooShort,
58 #[display(fmt = "Password too long")]
59 PasswordTooLong,
60 #[display(fmt = "Passwords don't match")]
61 PasswordsDontMatch,
62
63 #[display(fmt = "Username not available")]
65 UsernameTaken,
66
67 #[display(fmt = "Invalid username. Usernames must consist of 1-20 alphanumeric characters, dashes, or underscore")]
68 UsernameInvalid,
69
70 #[display(fmt = "Email not available")]
72 EmailTaken,
73
74 #[display(fmt = "Please verify your email before logging in")]
75 EmailNotVerified,
76
77 #[display(fmt = "Token not found. Please sign in.")]
80 TokenNotFound,
81
82 #[display(fmt = "Token expired. Please sign in again.")]
84 TokenExpired,
85
86 #[display(fmt = "Token invalid.")]
87 TokenInvalid,
89
90 #[display(fmt = "Uploaded torrent is not valid.")]
91 InvalidTorrentFile,
92
93 #[display(fmt = "Uploaded torrent has an invalid pieces key.")]
94 InvalidTorrentPiecesLength,
95
96 #[display(fmt = "Only .torrent files can be uploaded.")]
97 InvalidFileType,
98
99 #[display(fmt = "Torrent title is too short.")]
100 InvalidTorrentTitleLength,
101
102 #[display(fmt = "Some mandatory metadata fields are missing.")]
103 MissingMandatoryMetadataFields,
104
105 #[display(fmt = "Selected category does not exist.")]
106 InvalidCategory,
107
108 #[display(fmt = "Selected tag does not exist.")]
109 InvalidTag,
110
111 #[display(fmt = "Unauthorized action.")]
112 UnauthorizedAction,
113
114 #[display(
115 fmt = "Unauthorized actions for guest users. Try logging in to check if you have permission to perform the action"
116 )]
117 UnauthorizedActionForGuests,
118
119 #[display(fmt = "This torrent already exists in our database.")]
120 InfoHashAlreadyExists,
121
122 #[display(fmt = "A torrent with the same canonical infohash already exists in our database.")]
123 CanonicalInfoHashAlreadyExists,
124
125 #[display(fmt = "A torrent with the same original infohash already exists in our database.")]
126 OriginalInfoHashAlreadyExists,
127
128 #[display(fmt = "This torrent title has already been used.")]
129 TorrentTitleAlreadyExists,
130
131 #[display(fmt = "Could not whitelist torrent.")]
132 WhitelistingError,
133
134 #[display(fmt = "Failed to send verification email.")]
135 FailedToSendVerificationEmail,
136
137 #[display(fmt = "Category already exists.")]
138 CategoryAlreadyExists,
139
140 #[display(fmt = "Category name cannot be empty.")]
141 CategoryNameEmpty,
142
143 #[display(fmt = "Tag already exists.")]
144 TagAlreadyExists,
145
146 #[display(fmt = "Tag name cannot be empty.")]
147 TagNameEmpty,
148
149 #[display(fmt = "Torrent not found.")]
150 TorrentNotFound,
151
152 #[display(fmt = "Category not found.")]
153 CategoryNotFound,
154
155 #[display(fmt = "Tag not found.")]
156 TagNotFound,
157
158 #[display(fmt = "Database error.")]
159 DatabaseError,
160
161 #[display(fmt = "Authentication error, please sign in")]
162 LoggedInUserNotFound,
163
164 #[display(fmt = "Sorry, we have an error with our tracker connection.")]
166 TrackerOffline,
167
168 #[display(fmt = "Tracker response error. The operation could not be performed.")]
169 TrackerResponseError,
170
171 #[display(fmt = "Tracker unknown response. Unexpected response from tracker. For example, if it can't be parsed.")]
172 TrackerUnknownResponse,
173
174 #[display(fmt = "Torrent not found in tracker.")]
175 TorrentNotFoundInTracker,
176
177 #[display(fmt = "Invalid tracker API token.")]
178 InvalidTrackerToken,
179 }
181
182impl From<sqlx::Error> for ServiceError {
183 fn from(e: sqlx::Error) -> Self {
184 eprintln!("{e:?}");
185
186 if let Some(err) = e.as_database_error() {
187 return if err.code() == Some(Cow::from("2067")) {
188 if err.message().contains("torrust_torrents.info_hash") {
189 println!("info_hash already exists {}", err.message());
190 ServiceError::InfoHashAlreadyExists
191 } else {
192 ServiceError::InternalServerError
193 }
194 } else {
195 ServiceError::TorrentNotFound
196 };
197 }
198
199 ServiceError::InternalServerError
200 }
201}
202
203impl From<database::Error> for ServiceError {
204 fn from(e: database::Error) -> Self {
205 map_database_error_to_service_error(&e)
206 }
207}
208
209impl From<argon2::password_hash::Error> for ServiceError {
210 fn from(e: argon2::password_hash::Error) -> Self {
211 eprintln!("{e}");
212 ServiceError::InternalServerError
213 }
214}
215
216impl From<std::io::Error> for ServiceError {
217 fn from(e: std::io::Error) -> Self {
218 eprintln!("{e}");
219 ServiceError::InternalServerError
220 }
221}
222
223impl From<Box<dyn error::Error>> for ServiceError {
224 fn from(e: Box<dyn error::Error>) -> Self {
225 eprintln!("{e}");
226 ServiceError::InternalServerError
227 }
228}
229
230impl From<serde_json::Error> for ServiceError {
231 fn from(e: serde_json::Error) -> Self {
232 eprintln!("{e}");
233 ServiceError::InternalServerError
234 }
235}
236
237impl From<MetadataError> for ServiceError {
238 fn from(e: MetadataError) -> Self {
239 eprintln!("{e}");
240 match e {
241 MetadataError::MissingTorrentTitle => ServiceError::MissingMandatoryMetadataFields,
242 MetadataError::InvalidTorrentTitleLength => ServiceError::InvalidTorrentTitleLength,
243 }
244 }
245}
246
247impl From<DecodeTorrentFileError> for ServiceError {
248 fn from(e: DecodeTorrentFileError) -> Self {
249 eprintln!("{e}");
250 match e {
251 DecodeTorrentFileError::InvalidTorrentPiecesLength => ServiceError::InvalidTorrentTitleLength,
252 DecodeTorrentFileError::CannotBencodeInfoDict
253 | DecodeTorrentFileError::InvalidInfoDictionary
254 | DecodeTorrentFileError::InvalidBencodeData => ServiceError::InvalidTorrentFile,
255 }
256 }
257}
258
259impl From<TrackerAPIError> for ServiceError {
260 fn from(e: TrackerAPIError) -> Self {
261 eprintln!("{e}");
262 match e {
263 TrackerAPIError::TrackerOffline { error: _ } => ServiceError::TrackerOffline,
264 TrackerAPIError::InternalServerError | TrackerAPIError::NotFound => ServiceError::TrackerResponseError,
265 TrackerAPIError::TorrentNotFound => ServiceError::TorrentNotFoundInTracker,
266 TrackerAPIError::UnexpectedResponseStatus
267 | TrackerAPIError::MissingResponseBody
268 | TrackerAPIError::FailedToParseTrackerResponse { body: _ } => ServiceError::TrackerUnknownResponse,
269 TrackerAPIError::CannotSaveUserKey => ServiceError::DatabaseError,
270 TrackerAPIError::InvalidToken => ServiceError::InvalidTrackerToken,
271 }
272 }
273}
274
275#[must_use]
276pub fn http_status_code_for_service_error(error: &ServiceError) -> StatusCode {
277 #[allow(clippy::match_same_arms)]
278 match error {
279 ServiceError::ClosedForRegistration => StatusCode::FORBIDDEN,
280 ServiceError::EmailInvalid => StatusCode::BAD_REQUEST,
281 ServiceError::NotAUrl => StatusCode::BAD_REQUEST,
282 ServiceError::WrongPasswordOrUsername => StatusCode::FORBIDDEN,
283 ServiceError::InvalidPassword => StatusCode::FORBIDDEN,
284 ServiceError::UsernameNotFound => StatusCode::NOT_FOUND,
285 ServiceError::UserNotFound => StatusCode::NOT_FOUND,
286 ServiceError::AccountNotFound => StatusCode::NOT_FOUND,
287 ServiceError::ProfanityError => StatusCode::BAD_REQUEST,
288 ServiceError::BlacklistError => StatusCode::BAD_REQUEST,
289 ServiceError::UsernameCaseMappedError => StatusCode::BAD_REQUEST,
290 ServiceError::PasswordTooShort => StatusCode::BAD_REQUEST,
291 ServiceError::PasswordTooLong => StatusCode::BAD_REQUEST,
292 ServiceError::PasswordsDontMatch => StatusCode::BAD_REQUEST,
293 ServiceError::UsernameTaken => StatusCode::BAD_REQUEST,
294 ServiceError::UsernameInvalid => StatusCode::BAD_REQUEST,
295 ServiceError::EmailTaken => StatusCode::BAD_REQUEST,
296 ServiceError::EmailNotVerified => StatusCode::FORBIDDEN,
297 ServiceError::TokenNotFound => StatusCode::UNAUTHORIZED,
298 ServiceError::TokenExpired => StatusCode::UNAUTHORIZED,
299 ServiceError::TokenInvalid => StatusCode::UNAUTHORIZED,
300 ServiceError::TorrentNotFound => StatusCode::NOT_FOUND,
301 ServiceError::InvalidTorrentFile => StatusCode::BAD_REQUEST,
302 ServiceError::InvalidTorrentPiecesLength => StatusCode::BAD_REQUEST,
303 ServiceError::InvalidFileType => StatusCode::BAD_REQUEST,
304 ServiceError::InvalidTorrentTitleLength => StatusCode::BAD_REQUEST,
305 ServiceError::MissingMandatoryMetadataFields => StatusCode::BAD_REQUEST,
306 ServiceError::InvalidCategory => StatusCode::BAD_REQUEST,
307 ServiceError::InvalidTag => StatusCode::BAD_REQUEST,
308 ServiceError::UnauthorizedAction => StatusCode::FORBIDDEN,
309 ServiceError::UnauthorizedActionForGuests => StatusCode::UNAUTHORIZED,
310 ServiceError::InfoHashAlreadyExists => StatusCode::BAD_REQUEST,
311 ServiceError::CanonicalInfoHashAlreadyExists => StatusCode::CONFLICT,
312 ServiceError::OriginalInfoHashAlreadyExists => StatusCode::CONFLICT,
313 ServiceError::TorrentTitleAlreadyExists => StatusCode::BAD_REQUEST,
314 ServiceError::TrackerOffline => StatusCode::SERVICE_UNAVAILABLE,
315 ServiceError::CategoryNameEmpty => StatusCode::BAD_REQUEST,
316 ServiceError::CategoryAlreadyExists => StatusCode::BAD_REQUEST,
317 ServiceError::TagNameEmpty => StatusCode::BAD_REQUEST,
318 ServiceError::TagAlreadyExists => StatusCode::BAD_REQUEST,
319 ServiceError::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR,
320 ServiceError::EmailMissing => StatusCode::NOT_FOUND,
321 ServiceError::FailedToSendVerificationEmail => StatusCode::INTERNAL_SERVER_ERROR,
322 ServiceError::WhitelistingError => StatusCode::INTERNAL_SERVER_ERROR,
323 ServiceError::DatabaseError => StatusCode::INTERNAL_SERVER_ERROR,
324 ServiceError::CategoryNotFound => StatusCode::NOT_FOUND,
325 ServiceError::TagNotFound => StatusCode::NOT_FOUND,
326 ServiceError::TrackerResponseError => StatusCode::INTERNAL_SERVER_ERROR,
327 ServiceError::TrackerUnknownResponse => StatusCode::INTERNAL_SERVER_ERROR,
328 ServiceError::TorrentNotFoundInTracker => StatusCode::NOT_FOUND,
329 ServiceError::InvalidTrackerToken => StatusCode::INTERNAL_SERVER_ERROR,
330 ServiceError::LoggedInUserNotFound => StatusCode::UNAUTHORIZED,
331 }
332}
333
334#[must_use]
335pub fn map_database_error_to_service_error(error: &database::Error) -> ServiceError {
336 #[allow(clippy::match_same_arms)]
337 match error {
338 database::Error::Error => ServiceError::InternalServerError,
339 database::Error::ErrorWithText(_) => ServiceError::InternalServerError,
340 database::Error::UsernameTaken => ServiceError::UsernameTaken,
341 database::Error::EmailTaken => ServiceError::EmailTaken,
342 database::Error::UserNotFound => ServiceError::UserNotFound,
343 database::Error::CategoryNotFound => ServiceError::InvalidCategory,
344 database::Error::TagAlreadyExists => ServiceError::TagAlreadyExists,
345 database::Error::TagNotFound => ServiceError::InvalidTag,
346 database::Error::TorrentNotFound => ServiceError::TorrentNotFound,
347 database::Error::TorrentAlreadyExists => ServiceError::InfoHashAlreadyExists,
348 database::Error::TorrentTitleAlreadyExists => ServiceError::TorrentTitleAlreadyExists,
349 database::Error::UnrecognizedDatabaseDriver => ServiceError::InternalServerError,
350 database::Error::TorrentInfoHashNotFound => ServiceError::TorrentNotFound,
351 }
352}