1use serde::{Deserialize, Serialize};
2use std::fmt;
3use thiserror::Error;
4
5#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
11pub enum YandexErrorCode {
12 EmptyDates,
14 EmptyPaths,
15 EntityValidationError,
16 FieldValidationError,
17 InvalidUrl,
18 NoChanges,
19 SomeDatesAreUnavailable,
20 UrlsAreCorrupted,
21 WrongRegion,
22
23 AccessForbidden,
25 InvalidOauthToken,
26 InvalidUserId,
27 HostsLimitExceeded,
28 FeedsLimitExceeded,
29 BatchLimitExceeded,
30 FeedsCategoryBan,
31 LimitsExceeded,
32
33 ResourceNotFound,
35 HostNotIndexed,
36 HostNotLoaded,
37 HostNotVerified,
38 HostNotFound,
39 SitemapNotFound,
40 SitemapNotAdded,
41 TaskNotFound,
42 QueryIdNotFound,
43 BadHttpCode,
44 BadMimeType,
45 RequestNotFound,
46 TimedOut,
47 FeedAlreadyAdded,
48 OnlyHttps,
49 ManyUrlsForRemove,
50 IncorrectUrl,
51 NotExist,
52
53 MethodNotAllowed,
55
56 ContentTypeUnsupported,
58
59 UrlAlreadyAdded,
61 HostAlreadyAdded,
62 VerificationAlreadyInProgress,
63 TextAlreadyAdded,
64 SitemapAlreadyAdded,
65
66 UploadAddressExpired,
68
69 RequestEntityTooLarge,
71 PayloadTooLarge,
72
73 ContentEncodingUnsupported,
75
76 TextLengthConstraintsViolation,
78 NoVerificationRecord,
79
80 QuotaExceeded,
82 TooManyRequestsError,
83
84 #[serde(untagged)]
86 Unknown(String),
87}
88
89impl fmt::Display for YandexErrorCode {
90 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91 match self {
92 YandexErrorCode::Unknown(s) => write!(f, "{}", s),
93 _ => {
94 let json = serde_json::to_string(self).unwrap_or_else(|_| "UNKNOWN".to_string());
95 write!(f, "{}", json.trim_matches('"'))
96 }
97 }
98 }
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct YandexApiErrorResponse {
107 pub error_code: YandexErrorCode,
109
110 pub error_message: String,
112
113 #[serde(skip_serializing_if = "Option::is_none")]
115 pub acceptable_types: Option<Vec<String>>,
116
117 #[serde(skip_serializing_if = "Option::is_none")]
119 pub valid_until: Option<String>,
120}
121
122#[derive(Debug, Error)]
124pub enum YandexWebmasterError {
125 #[error("HTTP request failed: {0}")]
127 HttpError(#[from] reqwest::Error),
128
129 #[error("Middleware request failed: {0}")]
131 MiddlewareHttpError(#[from] reqwest_middleware::Error),
132
133 #[error("Failed to parse response: {0}")]
135 ParseError(#[from] serde_json::Error),
136
137 #[error("Failed serialize url: {0}")]
139 SerdeQsError(#[from] serde_qs::Error),
140
141 #[error("Middleware error: {0}")]
143 MiddlewareError(String),
144
145 #[error("Authentication failed: missing or invalid OAuth token")]
147 AuthenticationError,
148
149 #[error("API error ({error_code}): {error_message}", error_code = .response.error_code, error_message = .response.error_message)]
151 ApiError {
152 status: u16,
154 response: YandexApiErrorResponse,
156 },
157
158 #[error("API error: {0}")]
160 GenericApiError(String),
161}
162
163pub type Result<T> = std::result::Result<T, YandexWebmasterError>;
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 #[test]
171 fn test_parse_entity_validation_error() {
172 let json = r#"{
173 "error_code": "ENTITY_VALIDATION_ERROR",
174 "error_message": "some string"
175 }"#;
176
177 let result: YandexApiErrorResponse = serde_json::from_str(json).unwrap();
178 assert_eq!(result.error_code, YandexErrorCode::EntityValidationError);
179 assert_eq!(result.error_message, "some string");
180 assert!(result.acceptable_types.is_none());
181 assert!(result.valid_until.is_none());
182 }
183
184 #[test]
185 fn test_parse_invalid_url() {
186 let json = r#"{
187 "error_code": "INVALID_URL",
188 "error_message": "some string"
189 }"#;
190
191 let result: YandexApiErrorResponse = serde_json::from_str(json).unwrap();
192 assert_eq!(result.error_code, YandexErrorCode::InvalidUrl);
193 assert_eq!(result.error_message, "some string");
194 }
195
196 #[test]
197 fn test_parse_no_changes() {
198 let json = r#"{
199 "error_code": "NO_CHANGES",
200 "error_message": "some string"
201 }"#;
202
203 let result: YandexApiErrorResponse = serde_json::from_str(json).unwrap();
204 assert_eq!(result.error_code, YandexErrorCode::NoChanges);
205 assert_eq!(result.error_message, "some string");
206 }
207
208 #[test]
209 fn test_parse_wrong_region() {
210 let json = r#"{
211 "error_code": "WRONG_REGION",
212 "error_message": "some string"
213 }"#;
214
215 let result: YandexApiErrorResponse = serde_json::from_str(json).unwrap();
216 assert_eq!(result.error_code, YandexErrorCode::WrongRegion);
217 assert_eq!(result.error_message, "some string");
218 }
219
220 #[test]
221 fn test_parse_access_forbidden() {
222 let json = r#"{
223 "error_code": "ACCESS_FORBIDDEN",
224 "error_message": "explicit error message"
225 }"#;
226
227 let result: YandexApiErrorResponse = serde_json::from_str(json).unwrap();
228 assert_eq!(result.error_code, YandexErrorCode::AccessForbidden);
229 assert_eq!(result.error_message, "explicit error message");
230 }
231
232 #[test]
233 fn test_parse_invalid_oauth_token() {
234 let json = r#"{
235 "error_code": "INVALID_OAUTH_TOKEN",
236 "error_message": "explicit error message"
237 }"#;
238
239 let result: YandexApiErrorResponse = serde_json::from_str(json).unwrap();
240 assert_eq!(result.error_code, YandexErrorCode::InvalidOauthToken);
241 assert_eq!(result.error_message, "explicit error message");
242 }
243
244 #[test]
245 fn test_parse_hosts_limit_exceeded() {
246 let json = r#"{
247 "error_code": "HOSTS_LIMIT_EXCEEDED",
248 "limit": 1,
249 "error_message": "explicit error message"
250 }"#;
251
252 let result: YandexApiErrorResponse = serde_json::from_str(json).unwrap();
253 assert_eq!(result.error_code, YandexErrorCode::HostsLimitExceeded);
254 assert_eq!(result.error_message, "explicit error message");
255 }
256
257 #[test]
258 fn test_parse_feeds_limit_exceeded() {
259 let json = r#"{
260 "error_code": "FEEDS_LIMIT_EXCEEDED",
261 "limit": 1,
262 "error_message": "explicit error message"
263 }"#;
264
265 let result: YandexApiErrorResponse = serde_json::from_str(json).unwrap();
266 assert_eq!(result.error_code, YandexErrorCode::FeedsLimitExceeded);
267 assert_eq!(result.error_message, "explicit error message");
268 }
269
270 #[test]
271 fn test_parse_resource_not_found() {
272 let json = r#"{
273 "error_code": "RESOURCE_NOT_FOUND",
274 "error_message": "some string"
275 }"#;
276
277 let result: YandexApiErrorResponse = serde_json::from_str(json).unwrap();
278 assert_eq!(result.error_code, YandexErrorCode::ResourceNotFound);
279 assert_eq!(result.error_message, "some string");
280 }
281
282 #[test]
283 fn test_parse_host_not_indexed() {
284 let json = r#"{
285 "error_code": "HOST_NOT_INDEXED",
286 "host_id": "http:ya.ru:80",
287 "error_message": "some string"
288 }"#;
289
290 let result: YandexApiErrorResponse = serde_json::from_str(json).unwrap();
291 assert_eq!(result.error_code, YandexErrorCode::HostNotIndexed);
292 assert_eq!(result.error_message, "some string");
293 }
294
295 #[test]
296 fn test_parse_host_not_found() {
297 let json = r#"{
298 "error_code": "HOST_NOT_FOUND",
299 "host_id": "http:ya.ru:80",
300 "error_message": "explicit error message"
301 }"#;
302
303 let result: YandexApiErrorResponse = serde_json::from_str(json).unwrap();
304 assert_eq!(result.error_code, YandexErrorCode::HostNotFound);
305 assert_eq!(result.error_message, "explicit error message");
306 }
307
308 #[test]
309 fn test_parse_sitemap_not_found() {
310 let json = r#"{
311 "error_code": "SITEMAP_NOT_FOUND",
312 "host_id": "http:ya.ru:80",
313 "sitemap_id": "c7-fe:80-c0",
314 "error_message": "some string"
315 }"#;
316
317 let result: YandexApiErrorResponse = serde_json::from_str(json).unwrap();
318 assert_eq!(result.error_code, YandexErrorCode::SitemapNotFound);
319 assert_eq!(result.error_message, "some string");
320 }
321
322 #[test]
323 fn test_parse_task_not_found() {
324 let json = r#"{
325 "error_code": "TASK_NOT_FOUND",
326 "error_message": "some string"
327 }"#;
328
329 let result: YandexApiErrorResponse = serde_json::from_str(json).unwrap();
330 assert_eq!(result.error_code, YandexErrorCode::TaskNotFound);
331 assert_eq!(result.error_message, "some string");
332 }
333
334 #[test]
335 fn test_parse_query_id_not_found() {
336 let json = r#"{
337 "error_code": "QUERY_ID_NOT_FOUND",
338 "error_message": "some string"
339 }"#;
340
341 let result: YandexApiErrorResponse = serde_json::from_str(json).unwrap();
342 assert_eq!(result.error_code, YandexErrorCode::QueryIdNotFound);
343 assert_eq!(result.error_message, "some string");
344 }
345
346 #[test]
347 fn test_parse_method_not_allowed() {
348 let json = r#"{
349 "error_code": "METHOD_NOT_ALLOWED",
350 "error_message": "explicit error message"
351 }"#;
352
353 let result: YandexApiErrorResponse = serde_json::from_str(json).unwrap();
354 assert_eq!(result.error_code, YandexErrorCode::MethodNotAllowed);
355 assert_eq!(result.error_message, "explicit error message");
356 }
357
358 #[test]
359 fn test_parse_content_type_unsupported_with_acceptable_types() {
360 let json = r#"{
361 "error_code": "CONTENT_TYPE_UNSUPPORTED",
362 "acceptable_types": [
363 "application/json",
364 "application/xml"
365 ],
366 "error_message": "explicit error message"
367 }"#;
368
369 let result: YandexApiErrorResponse = serde_json::from_str(json).unwrap();
370 assert_eq!(result.error_code, YandexErrorCode::ContentTypeUnsupported);
371 assert_eq!(result.error_message, "explicit error message");
372 assert_eq!(
373 result.acceptable_types,
374 Some(vec![
375 "application/json".to_string(),
376 "application/xml".to_string()
377 ])
378 );
379 }
380
381 #[test]
382 fn test_parse_url_already_added() {
383 let json = r#"{
384 "error_code": "URL_ALREADY_ADDED",
385 "error_message": "some string"
386 }"#;
387
388 let result: YandexApiErrorResponse = serde_json::from_str(json).unwrap();
389 assert_eq!(result.error_code, YandexErrorCode::UrlAlreadyAdded);
390 assert_eq!(result.error_message, "some string");
391 }
392
393 #[test]
394 fn test_parse_host_already_added() {
395 let json = r#"{
396 "error_code": "HOST_ALREADY_ADDED",
397 "host_id": "http:ya.ru:80",
398 "verified": false,
399 "error_message": "some string"
400 }"#;
401
402 let result: YandexApiErrorResponse = serde_json::from_str(json).unwrap();
403 assert_eq!(result.error_code, YandexErrorCode::HostAlreadyAdded);
404 assert_eq!(result.error_message, "some string");
405 }
406
407 #[test]
408 fn test_parse_verification_already_in_progress() {
409 let json = r#"{
410 "error_code": "VERIFICATION_ALREADY_IN_PROGRESS",
411 "verification_type": "META_TAG",
412 "error_message": "some string"
413 }"#;
414
415 let result: YandexApiErrorResponse = serde_json::from_str(json).unwrap();
416 assert_eq!(
417 result.error_code,
418 YandexErrorCode::VerificationAlreadyInProgress
419 );
420 assert_eq!(result.error_message, "some string");
421 }
422
423 #[test]
424 fn test_parse_sitemap_already_added() {
425 let json = r#"{
426 "error_code": "SITEMAP_ALREADY_ADDED",
427 "sitemap_id": "c7-fe:80-c0",
428 "error_message": "some string"
429 }"#;
430
431 let result: YandexApiErrorResponse = serde_json::from_str(json).unwrap();
432 assert_eq!(result.error_code, YandexErrorCode::SitemapAlreadyAdded);
433 assert_eq!(result.error_message, "some string");
434 }
435
436 #[test]
437 fn test_parse_upload_address_expired_with_valid_until() {
438 let json = r#"{
439 "error_code": "UPLOAD_ADDRESS_EXPIRED",
440 "valid_until": "2016-01-01T00:00:00,000+0300",
441 "error_message": "some string"
442 }"#;
443
444 let result: YandexApiErrorResponse = serde_json::from_str(json).unwrap();
445 assert_eq!(result.error_code, YandexErrorCode::UploadAddressExpired);
446 assert_eq!(result.error_message, "some string");
447 assert_eq!(
448 result.valid_until,
449 Some("2016-01-01T00:00:00,000+0300".to_string())
450 );
451 }
452
453 #[test]
454 fn test_parse_request_entity_too_large() {
455 let json = r#"{
456 "error_code": "REQUEST_ENTITY_TOO_LARGE",
457 "error_message": "some string"
458 }"#;
459
460 let result: YandexApiErrorResponse = serde_json::from_str(json).unwrap();
461 assert_eq!(result.error_code, YandexErrorCode::RequestEntityTooLarge);
462 assert_eq!(result.error_message, "some string");
463 }
464
465 #[test]
466 fn test_parse_content_encoding_unsupported() {
467 let json = r#"{
468 "error_code": "CONTENT_ENCODING_UNSUPPORTED",
469 "error_message": "some string"
470 }"#;
471
472 let result: YandexApiErrorResponse = serde_json::from_str(json).unwrap();
473 assert_eq!(
474 result.error_code,
475 YandexErrorCode::ContentEncodingUnsupported
476 );
477 assert_eq!(result.error_message, "some string");
478 }
479
480 #[test]
481 fn test_parse_quota_exceeded() {
482 let json = r#"{
483 "error_code": "QUOTA_EXCEEDED",
484 "error_message": "some string"
485 }"#;
486
487 let result: YandexApiErrorResponse = serde_json::from_str(json).unwrap();
488 assert_eq!(result.error_code, YandexErrorCode::QuotaExceeded);
489 assert_eq!(result.error_message, "some string");
490 }
491
492 #[test]
493 fn test_parse_too_many_requests() {
494 let json = r#"{
495 "error_code": "TOO_MANY_REQUESTS_ERROR",
496 "error_message": "some string"
497 }"#;
498
499 let result: YandexApiErrorResponse = serde_json::from_str(json).unwrap();
500 assert_eq!(result.error_code, YandexErrorCode::TooManyRequestsError);
501 assert_eq!(result.error_message, "some string");
502 }
503
504 #[test]
505 fn test_parse_unknown_error_code() {
506 let json = r#"{
507 "error_code": "SOME_UNKNOWN_ERROR",
508 "error_message": "unknown error occurred"
509 }"#;
510
511 let result: YandexApiErrorResponse = serde_json::from_str(json).unwrap();
512 assert_eq!(
513 result.error_code,
514 YandexErrorCode::Unknown("SOME_UNKNOWN_ERROR".to_string())
515 );
516 assert_eq!(result.error_message, "unknown error occurred");
517 }
518
519 #[test]
520 fn test_error_code_display() {
521 assert_eq!(YandexErrorCode::InvalidUrl.to_string(), "INVALID_URL");
522 assert_eq!(YandexErrorCode::HostNotFound.to_string(), "HOST_NOT_FOUND");
523 assert_eq!(
524 YandexErrorCode::Unknown("CUSTOM_ERROR".to_string()).to_string(),
525 "CUSTOM_ERROR"
526 );
527 }
528
529 #[test]
530 fn test_error_display() {
531 let error = YandexWebmasterError::ApiError {
532 status: 404,
533 response: YandexApiErrorResponse {
534 error_code: YandexErrorCode::HostNotFound,
535 error_message: "Host not found in user's list".to_string(),
536 acceptable_types: None,
537 valid_until: None,
538 },
539 };
540
541 let error_string = error.to_string();
542 assert!(error_string.contains("HOST_NOT_FOUND"));
543 assert!(error_string.contains("Host not found in user's list"));
544 }
545
546 #[test]
547 fn test_parse_with_extra_fields_ignored() {
548 let json = r#"{
550 "error_code": "HOST_NOT_FOUND",
551 "host_id": "http:example.com:80",
552 "extra_field": "should be ignored",
553 "error_message": "Host not found"
554 }"#;
555
556 let result: YandexApiErrorResponse = serde_json::from_str(json).unwrap();
557 assert_eq!(result.error_code, YandexErrorCode::HostNotFound);
558 assert_eq!(result.error_message, "Host not found");
559 }
560}