1use thiserror::Error;
2
3const ERROR_INVALID_API_KEY: &str = "invalid key";
4const ERROR_INVALID_USER_OR_PASSWORD: &[&str] = &[
5 "Incorrect username or password",
6 "username must be provided",
7 "password must be provided",
8];
9const ERROR_INVALID_CAPTCHA_CODE: &[&str] = &[
10 "Please correctly complete reCAPTCHA",
11 "Please complete reCAPTCHA to continue",
12];
13const ERROR_INVALID_TOTP_CODE: &str = "invalid code";
14const ERROR_TOTP_TOKEN_TIMED_OUT: &str = "two-factor login attempt timed out";
15const ERROR_INVALID_TOTP_TOKEN: &str = "invalid token";
16const ERROR_INVALID_USER: &[&str] = &["invalid user", "user_id: expected database ID integer"];
17const ERROR_INVALID_COLLECTION: &[&str] = &[
18 "invalid collection",
19 "collection_id: expected database id",
20 "collection_id: expected integer",
21];
22const ERROR_INVALID_GAME: &[&str] = &[
23 "invalid game",
24 "game_id: expected database id",
25 "game_id: expected integer",
26];
27const ERROR_INVALID_UPLOAD: &[&str] = &[
28 "invalid upload",
29 "upload_id: expected database id",
30 "upload_id: expected integer",
31];
32const ERROR_INVALID_BUILD: &[&str] = &[
33 "invalid build",
34 "build_id: expected database id",
35 "build_id: expected integer",
36];
37const ERROR_INVALID_TARGET_BUILD: &str =
38 "target_build_id: expected empty, or integer then database id";
39const ERROR_NO_UPGRADE_PATH: &str = "no upgrade path";
40
41#[derive(Error, Debug)]
43#[error("An API call to \"{url}\" failed:\n{kind}")]
44pub struct ItchRequestJSONError<T>
45where
46 T: std::error::Error + std::fmt::Debug,
47{
48 pub url: String,
49 #[source]
50 pub kind: ItchRequestJSONErrorKind<T>,
51}
52
53#[derive(Error, Debug)]
54pub enum ItchRequestJSONErrorKind<T>
55where
56 T: std::error::Error + std::fmt::Debug,
57{
58 #[error(
59 "Error while sending request, redirect loop was detected or redirect limit was exhausted:\n{0}"
60 )]
61 CouldntSend(#[source] reqwest::Error),
62
63 #[error("Couldn't get the network request response body:\n{0}")]
64 CouldntGetText(#[source] reqwest::Error),
65
66 #[error("Couldn't parse the request response body into JSON:\n{body}\n\n{error}")]
67 InvalidJSON {
68 body: String,
69 #[source]
70 error: serde_json::Error,
71 },
72
73 #[error("The itch.io API server returned an error:\n{0}")]
74 ServerRepliedWithError(T),
75}
76
77#[derive(Error, Debug)]
78#[error("The provided API key is invalid!")]
79pub struct InvalidApiKey;
80
81#[derive(Error, Debug)]
82#[error("The username or the password is incorrect.")]
83pub struct IncorrectUsernameOrPassword;
84
85#[derive(Error, Debug)]
86#[error("The reCAPTCHA response code is incorrect!")]
87pub struct IncorrectCaptchaCode;
88
89#[derive(Error, Debug)]
90#[error("The TOTP code is incorrect!")]
91pub struct IncorrectTOTPCode;
92
93#[derive(Error, Debug)]
94#[error(
95 "The TOTP token timed out!
96Login again with a username and password to get another TOTP token."
97)]
98pub struct TOTPTokenTimedOut;
99
100#[derive(Error, Debug)]
101#[error("The TOTP token is invalid!")]
102pub struct InvalidTOTPToken;
103
104#[derive(Error, Debug)]
105#[error("The provided user ID is invalid.")]
106pub struct InvalidUserID;
107
108#[derive(Error, Debug)]
109#[error("The provided collection ID is invalid.")]
110pub struct InvalidCollectionID;
111
112#[derive(Error, Debug)]
113#[error("The provided game ID is invalid.")]
114pub struct InvalidGameID;
115
116#[derive(Error, Debug)]
117#[error("The provided upload ID is invalid.")]
118pub struct InvalidUploadID;
119
120#[derive(Error, Debug)]
121#[error("The provided build ID is invalid.")]
122pub struct InvalidBuildID;
123
124#[derive(Error, Debug)]
125#[error("The provided target build ID is invalid.")]
126pub struct InvalidTargetBuildID;
127
128#[derive(Error, Debug)]
129#[error("No upgrade path was found.")]
130pub struct NoUpgradePath;
131
132#[derive(Error, Debug)]
134pub enum ApiResponseErrorKind {
135 #[error(transparent)]
136 InvalidApiKey(#[from] InvalidApiKey),
137
138 #[error(transparent)]
139 IncorrectUsernameOrPassword(#[from] IncorrectUsernameOrPassword),
140
141 #[error(transparent)]
142 IncorrectCaptchaCode(#[from] IncorrectCaptchaCode),
143
144 #[error(transparent)]
145 IncorrectTOTPCode(#[from] IncorrectTOTPCode),
146
147 #[error(transparent)]
148 TOTPTokenTimedOut(#[from] TOTPTokenTimedOut),
149
150 #[error(transparent)]
151 InvalidTOTPToken(#[from] InvalidTOTPToken),
152
153 #[error(transparent)]
154 InvalidUserID(#[from] InvalidUserID),
155
156 #[error(transparent)]
157 InvalidCollectionID(#[from] InvalidCollectionID),
158
159 #[error(transparent)]
160 InvalidGameID(#[from] InvalidGameID),
161
162 #[error(transparent)]
163 InvalidUploadID(#[from] InvalidUploadID),
164
165 #[error(transparent)]
166 InvalidBuildID(#[from] InvalidBuildID),
167
168 #[error(transparent)]
169 InvalidTargetBuildID(#[from] InvalidTargetBuildID),
170
171 #[error(transparent)]
172 NoUpgradePath(#[from] NoUpgradePath),
173
174 #[error("An unknown error occurred!")]
175 Other,
176}
177
178impl From<&[String]> for ApiResponseErrorKind {
179 fn from(value: &[String]) -> Self {
180 match value {
181 [v] if v == ERROR_INVALID_API_KEY => Self::InvalidApiKey(InvalidApiKey),
182 [v, ..] if ERROR_INVALID_USER_OR_PASSWORD.contains(&&**v) => {
183 Self::IncorrectUsernameOrPassword(IncorrectUsernameOrPassword)
184 }
185 [v] if ERROR_INVALID_CAPTCHA_CODE.contains(&&**v) => {
186 Self::IncorrectCaptchaCode(IncorrectCaptchaCode)
187 }
188 [v] if v == ERROR_INVALID_TOTP_CODE => Self::IncorrectTOTPCode(IncorrectTOTPCode),
189 [v] if v == ERROR_TOTP_TOKEN_TIMED_OUT => Self::TOTPTokenTimedOut(TOTPTokenTimedOut),
190 [v] if v == ERROR_INVALID_TOTP_TOKEN => Self::InvalidTOTPToken(InvalidTOTPToken),
191 [v] if ERROR_INVALID_USER.contains(&&**v) => Self::InvalidUserID(InvalidUserID),
192 [v] if ERROR_INVALID_COLLECTION.contains(&&**v) => {
193 Self::InvalidCollectionID(InvalidCollectionID)
194 }
195 [v] if ERROR_INVALID_GAME.contains(&&**v) => Self::InvalidGameID(InvalidGameID),
196 [v] if ERROR_INVALID_UPLOAD.contains(&&**v) => Self::InvalidUploadID(InvalidUploadID),
197 [v] if ERROR_INVALID_BUILD.contains(&&**v) => Self::InvalidBuildID(InvalidBuildID),
198 [v] if ERROR_INVALID_TARGET_BUILD == v => Self::InvalidTargetBuildID(InvalidTargetBuildID),
199 [v] if v == ERROR_NO_UPGRADE_PATH => Self::NoUpgradePath(NoUpgradePath),
200 _ => Self::Other,
201 }
202 }
203}
204
205#[derive(Error, Debug)]
206#[error("{kind}\n{errors:#?}")]
207pub struct ApiResponseError {
208 pub errors: Vec<String>,
209 #[source]
210 pub kind: ApiResponseErrorKind,
211}
212
213impl From<Vec<String>> for ApiResponseError {
214 fn from(value: Vec<String>) -> Self {
215 Self {
216 kind: value.as_slice().into(),
217 errors: value,
218 }
219 }
220}
221
222#[derive(Error, Debug)]
224pub enum ApiResponseCommonErrors {
225 #[error(transparent)]
226 InvalidApiKey(#[from] InvalidApiKey),
227
228 #[error("An unknown error occurred:\n{0:#?}")]
229 Other(Vec<String>),
230}
231
232impl From<ApiResponseError> for ApiResponseCommonErrors {
233 fn from(value: ApiResponseError) -> Self {
234 match value.kind {
235 ApiResponseErrorKind::InvalidApiKey(v) => v.into(),
236 _ => Self::Other(value.errors),
237 }
238 }
239}
240
241#[derive(Error, Debug)]
243pub enum LoginResponseError {
244 #[error(transparent)]
245 IncorrectUsernameOrPassword(#[from] IncorrectUsernameOrPassword),
246
247 #[error(transparent)]
248 IncorrectCaptchaCode(#[from] IncorrectCaptchaCode),
249
250 #[error(transparent)]
251 Other(#[from] ApiResponseCommonErrors),
252}
253
254impl From<ApiResponseError> for LoginResponseError {
255 fn from(value: ApiResponseError) -> Self {
256 match value.kind {
257 ApiResponseErrorKind::IncorrectUsernameOrPassword(v) => v.into(),
258 ApiResponseErrorKind::IncorrectCaptchaCode(v) => v.into(),
259 _ => Self::Other(value.into()),
260 }
261 }
262}
263
264#[derive(Error, Debug)]
266pub enum TOTPResponseError {
267 #[error(transparent)]
268 IncorrectTOTPCode(#[from] IncorrectTOTPCode),
269
270 #[error(transparent)]
271 TOTPTokenTimedOut(#[from] TOTPTokenTimedOut),
272
273 #[error(transparent)]
274 InvalidTOTPToken(#[from] InvalidTOTPToken),
275
276 #[error(transparent)]
277 Other(#[from] ApiResponseCommonErrors),
278}
279
280impl From<ApiResponseError> for TOTPResponseError {
281 fn from(value: ApiResponseError) -> Self {
282 match value.kind {
283 ApiResponseErrorKind::IncorrectTOTPCode(v) => v.into(),
284 ApiResponseErrorKind::TOTPTokenTimedOut(v) => v.into(),
285 ApiResponseErrorKind::InvalidTOTPToken(v) => v.into(),
286 _ => Self::Other(value.into()),
287 }
288 }
289}
290
291#[derive(Error, Debug)]
293pub enum UserResponseError {
294 #[error(transparent)]
295 InvalidUserID(#[from] InvalidUserID),
296
297 #[error(transparent)]
298 Other(#[from] ApiResponseCommonErrors),
299}
300
301impl From<ApiResponseError> for UserResponseError {
302 fn from(value: ApiResponseError) -> Self {
303 match value.kind {
304 ApiResponseErrorKind::InvalidUserID(v) => v.into(),
305 _ => Self::Other(value.into()),
306 }
307 }
308}
309
310#[derive(Error, Debug)]
312pub enum CollectionResponseError {
313 #[error(transparent)]
314 InvalidCollectionID(#[from] InvalidCollectionID),
315
316 #[error(transparent)]
317 Other(#[from] ApiResponseCommonErrors),
318}
319
320impl From<ApiResponseError> for CollectionResponseError {
321 fn from(value: ApiResponseError) -> Self {
322 match value.kind {
323 ApiResponseErrorKind::InvalidCollectionID(v) => v.into(),
324 _ => Self::Other(value.into()),
325 }
326 }
327}
328
329#[derive(Error, Debug)]
331pub enum GameResponseError {
332 #[error(transparent)]
333 InvalidGameID(#[from] InvalidGameID),
334
335 #[error(transparent)]
336 Other(#[from] ApiResponseCommonErrors),
337}
338
339impl From<ApiResponseError> for GameResponseError {
340 fn from(value: ApiResponseError) -> Self {
341 match value.kind {
342 ApiResponseErrorKind::InvalidGameID(v) => v.into(),
343 _ => Self::Other(value.into()),
344 }
345 }
346}
347
348#[derive(Error, Debug)]
350pub enum UploadResponseError {
351 #[error(transparent)]
352 InvalidUploadID(#[from] InvalidUploadID),
353
354 #[error(transparent)]
355 Other(#[from] ApiResponseCommonErrors),
356}
357
358impl From<ApiResponseError> for UploadResponseError {
359 fn from(value: ApiResponseError) -> Self {
360 match value.kind {
361 ApiResponseErrorKind::InvalidUploadID(v) => v.into(),
362 ApiResponseErrorKind::InvalidGameID(_) => Self::InvalidUploadID(InvalidUploadID),
363 _ => Self::Other(value.into()),
364 }
365 }
366}
367
368#[derive(Error, Debug)]
370pub enum BuildResponseError {
371 #[error(transparent)]
372 InvalidBuildID(#[from] InvalidBuildID),
373
374 #[error(transparent)]
375 Other(#[from] ApiResponseCommonErrors),
376}
377
378impl From<ApiResponseError> for BuildResponseError {
379 fn from(value: ApiResponseError) -> Self {
380 match value.kind {
381 ApiResponseErrorKind::InvalidBuildID(v) => v.into(),
382 ApiResponseErrorKind::InvalidUploadID(_) | ApiResponseErrorKind::InvalidGameID(_) => {
383 Self::InvalidBuildID(InvalidBuildID)
384 }
385 _ => Self::Other(value.into()),
386 }
387 }
388}
389
390#[derive(Error, Debug)]
392pub enum UpgradePathResponseError {
393 #[error(transparent)]
394 NoUpgradePath(#[from] NoUpgradePath),
395
396 #[error(transparent)]
397 InvalidBuildID(#[from] InvalidBuildID),
398
399 #[error(transparent)]
400 InvalidTargetBuildID(#[from] InvalidTargetBuildID),
401
402 #[error(transparent)]
403 Other(#[from] ApiResponseCommonErrors),
404}
405
406impl From<ApiResponseError> for UpgradePathResponseError {
407 fn from(value: ApiResponseError) -> Self {
408 match value.kind {
409 ApiResponseErrorKind::NoUpgradePath(v) => v.into(),
410 ApiResponseErrorKind::InvalidBuildID(v) => v.into(),
411 ApiResponseErrorKind::InvalidUploadID(_) | ApiResponseErrorKind::InvalidGameID(_) => {
412 Self::InvalidBuildID(InvalidBuildID)
413 }
414 ApiResponseErrorKind::InvalidTargetBuildID(v) => v.into(),
415 _ => Self::Other(value.into()),
416 }
417 }
418}