1#![warn(missing_docs)]
2
3use serde::{Deserialize, Serialize};
8use serde_json::{Value, json};
9use std::fmt;
10
11pub type Result<T> = std::result::Result<T, DockerError>;
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct DockerError {
19 pub kind: Box<DockerErrorKind>,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
27pub enum ErrorCategory {
28 Validation,
30 Auth,
32 Permission,
34 NotFound,
36 Conflict,
38 RateLimited,
40 Network,
42 Storage,
44 Database,
46 Cache,
48 Config,
50 Timeout,
52 Internal,
54}
55
56impl ErrorCategory {
57 pub fn http_status(&self) -> u16 {
59 match self {
60 ErrorCategory::Validation => 400,
61 ErrorCategory::Auth => 401,
62 ErrorCategory::Permission => 403,
63 ErrorCategory::NotFound => 404,
64 ErrorCategory::Conflict => 409,
65 ErrorCategory::RateLimited => 429,
66 ErrorCategory::Network => 502,
67 ErrorCategory::Storage => 500,
68 ErrorCategory::Database => 500,
69 ErrorCategory::Cache => 500,
70 ErrorCategory::Config => 500,
71 ErrorCategory::Timeout => 408,
72 ErrorCategory::Internal => 500,
73 }
74 }
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
81pub enum DockerErrorKind {
82 InvalidFormat {
85 field: String,
87 expected: String,
89 },
90 OutOfRange {
92 field: String,
94 min: Option<String>,
96 max: Option<String>,
98 },
99 Required {
101 field: String,
103 },
104 AlreadyExists {
106 field: String,
108 value: String,
110 },
111 NotAllowed {
113 field: String,
115 allowed: Vec<String>,
117 },
118 InvalidParams {
120 param: String,
122 reason: String,
124 },
125
126 InvalidCredentials,
129 AccountLocked,
131 UserNotFound {
133 identifier: String,
135 },
136 UserAlreadyExists {
138 username: String,
140 },
141 InvalidToken {
143 reason: String,
145 },
146 TokenExpired,
148
149 PermissionDenied {
152 action: String,
154 },
155 Forbidden {
157 resource: String,
159 },
160
161 ResourceNotFound {
164 resource_type: String,
166 identifier: String,
168 },
169
170 ResourceConflict {
173 resource: String,
175 reason: String,
177 },
178
179 RateLimited {
182 limit: u64,
184 },
185
186 ConnectionFailed {
189 target: String,
191 },
192 DnsResolutionFailed {
194 host: String,
196 },
197 ServiceUnavailable {
199 service: String,
201 },
202
203 StorageReadFailed {
206 path: String,
208 },
209 StorageWriteFailed {
211 path: String,
213 },
214 StorageDeleteFailed {
216 path: String,
218 },
219 InsufficientCapacity {
221 required: u64,
223 available: u64,
225 },
226 StorageFileNotFound {
228 path: String,
230 },
231
232 ConfigMissing {
235 key: String,
237 },
238 ConfigInvalid {
240 key: String,
242 reason: String,
244 },
245
246 InternalError {
249 reason: String,
251 },
252 NotImplemented {
254 feature: String,
256 },
257 IoError {
259 operation: String,
261 reason: String,
263 },
264 JsonError {
266 reason: String,
268 },
269 ParseError {
271 type_name: String,
273 reason: String,
275 },
276 RequestError {
278 url: String,
280 reason: String,
282 },
283
284 ContainerError {
287 reason: String,
289 },
290 ImageError {
292 reason: String,
294 },
295 NetworkError {
297 reason: String,
299 },
300 RuntimeError {
302 reason: String,
304 },
305 RegistryError {
307 reason: String,
309 },
310 EtcdError {
312 reason: String,
314 },
315 MonitorError {
317 reason: String,
319 },
320 KubernetesError {
322 reason: String,
324 },
325}
326
327impl DockerErrorKind {
328 pub fn category(&self) -> ErrorCategory {
330 match self {
331 DockerErrorKind::InvalidFormat { .. }
332 | DockerErrorKind::OutOfRange { .. }
333 | DockerErrorKind::Required { .. }
334 | DockerErrorKind::AlreadyExists { .. }
335 | DockerErrorKind::NotAllowed { .. }
336 | DockerErrorKind::InvalidParams { .. } => ErrorCategory::Validation,
337
338 DockerErrorKind::InvalidCredentials
339 | DockerErrorKind::AccountLocked
340 | DockerErrorKind::UserNotFound { .. }
341 | DockerErrorKind::UserAlreadyExists { .. }
342 | DockerErrorKind::InvalidToken { .. }
343 | DockerErrorKind::TokenExpired => ErrorCategory::Auth,
344
345 DockerErrorKind::PermissionDenied { .. } | DockerErrorKind::Forbidden { .. } => {
346 ErrorCategory::Permission
347 }
348
349 DockerErrorKind::ResourceNotFound { .. } => ErrorCategory::NotFound,
350
351 DockerErrorKind::ResourceConflict { .. } => ErrorCategory::Conflict,
352
353 DockerErrorKind::RateLimited { .. } => ErrorCategory::RateLimited,
354
355 DockerErrorKind::ConnectionFailed { .. }
356 | DockerErrorKind::DnsResolutionFailed { .. }
357 | DockerErrorKind::ServiceUnavailable { .. } => ErrorCategory::Network,
358
359 DockerErrorKind::StorageReadFailed { .. }
360 | DockerErrorKind::StorageWriteFailed { .. }
361 | DockerErrorKind::StorageDeleteFailed { .. }
362 | DockerErrorKind::InsufficientCapacity { .. }
363 | DockerErrorKind::StorageFileNotFound { .. } => ErrorCategory::Storage,
364
365 DockerErrorKind::ConfigMissing { .. } | DockerErrorKind::ConfigInvalid { .. } => {
366 ErrorCategory::Config
367 }
368
369 DockerErrorKind::InternalError { .. }
370 | DockerErrorKind::NotImplemented { .. }
371 | DockerErrorKind::IoError { .. }
372 | DockerErrorKind::JsonError { .. }
373 | DockerErrorKind::ParseError { .. }
374 | DockerErrorKind::RequestError { .. }
375 | DockerErrorKind::ContainerError { .. }
376 | DockerErrorKind::ImageError { .. }
377 | DockerErrorKind::NetworkError { .. }
378 | DockerErrorKind::RuntimeError { .. }
379 | DockerErrorKind::RegistryError { .. }
380 | DockerErrorKind::EtcdError { .. }
381 | DockerErrorKind::MonitorError { .. }
382 | DockerErrorKind::KubernetesError { .. } => ErrorCategory::Internal,
383 }
384 }
385
386 pub fn i18n_key(&self) -> &'static str {
388 match self {
389 DockerErrorKind::InvalidFormat { .. } => "docker.error.validation.invalid_format",
390 DockerErrorKind::OutOfRange { .. } => "docker.error.validation.out_of_range",
391 DockerErrorKind::Required { .. } => "docker.error.validation.required",
392 DockerErrorKind::AlreadyExists { .. } => "docker.error.validation.already_exists",
393 DockerErrorKind::NotAllowed { .. } => "docker.error.validation.not_allowed",
394 DockerErrorKind::InvalidParams { .. } => "docker.error.validation.invalid_params",
395
396 DockerErrorKind::InvalidCredentials => "docker.error.auth.invalid_credentials",
397 DockerErrorKind::AccountLocked => "docker.error.auth.account_locked",
398 DockerErrorKind::UserNotFound { .. } => "docker.error.auth.user_not_found",
399 DockerErrorKind::UserAlreadyExists { .. } => "docker.error.auth.user_already_exists",
400 DockerErrorKind::InvalidToken { .. } => "docker.error.auth.invalid_token",
401 DockerErrorKind::TokenExpired => "docker.error.auth.token_expired",
402
403 DockerErrorKind::PermissionDenied { .. } => "docker.error.permission.denied",
404 DockerErrorKind::Forbidden { .. } => "docker.error.permission.forbidden",
405
406 DockerErrorKind::ResourceNotFound { .. } => "docker.error.not_found.resource",
407
408 DockerErrorKind::ResourceConflict { .. } => "docker.error.conflict.resource",
409
410 DockerErrorKind::RateLimited { .. } => "docker.error.rate_limited",
411
412 DockerErrorKind::ConnectionFailed { .. } => "docker.error.network.connection_failed",
413 DockerErrorKind::DnsResolutionFailed { .. } => {
414 "docker.error.network.dns_resolution_failed"
415 }
416 DockerErrorKind::ServiceUnavailable { .. } => {
417 "docker.error.network.service_unavailable"
418 }
419
420 DockerErrorKind::StorageReadFailed { .. } => "docker.error.storage.read_failed",
421 DockerErrorKind::StorageWriteFailed { .. } => "docker.error.storage.write_failed",
422 DockerErrorKind::StorageDeleteFailed { .. } => "docker.error.storage.delete_failed",
423 DockerErrorKind::InsufficientCapacity { .. } => {
424 "docker.error.storage.insufficient_capacity"
425 }
426 DockerErrorKind::StorageFileNotFound { .. } => "docker.error.storage.file_not_found",
427
428 DockerErrorKind::ConfigMissing { .. } => "docker.error.config.missing",
429 DockerErrorKind::ConfigInvalid { .. } => "docker.error.config.invalid",
430
431 DockerErrorKind::InternalError { .. } => "docker.error.internal.error",
432 DockerErrorKind::NotImplemented { .. } => "docker.error.internal.not_implemented",
433 DockerErrorKind::IoError { .. } => "docker.error.internal.io_error",
434 DockerErrorKind::JsonError { .. } => "docker.error.internal.json_error",
435 DockerErrorKind::ParseError { .. } => "docker.error.internal.parse_error",
436 DockerErrorKind::RequestError { .. } => "docker.error.internal.request_error",
437
438 DockerErrorKind::ContainerError { .. } => "docker.error.container.error",
439 DockerErrorKind::ImageError { .. } => "docker.error.image.error",
440 DockerErrorKind::NetworkError { .. } => "docker.error.network.error",
441 DockerErrorKind::RuntimeError { .. } => "docker.error.runtime.error",
442 DockerErrorKind::RegistryError { .. } => "docker.error.registry.error",
443 DockerErrorKind::EtcdError { .. } => "docker.error.etcd.error",
444 DockerErrorKind::MonitorError { .. } => "docker.error.monitor.error",
445 DockerErrorKind::KubernetesError { .. } => "docker.error.kubernetes.error",
446 }
447 }
448
449 pub fn i18n_data(&self) -> Value {
451 match self {
452 DockerErrorKind::InvalidFormat { field, expected } => {
453 json!({ "field": field, "expected": expected })
454 }
455 DockerErrorKind::OutOfRange { field, min, max } => {
456 json!({ "field": field, "min": min, "max": max })
457 }
458 DockerErrorKind::Required { field } => json!({ "field": field }),
459 DockerErrorKind::AlreadyExists { field, value } => {
460 json!({ "field": field, "value": value })
461 }
462 DockerErrorKind::NotAllowed { field, allowed } => {
463 json!({ "field": field, "allowed": allowed })
464 }
465 DockerErrorKind::InvalidParams { param, reason } => {
466 json!({ "param": param, "reason": reason })
467 }
468
469 DockerErrorKind::InvalidCredentials => json!({}),
470 DockerErrorKind::AccountLocked => json!({}),
471 DockerErrorKind::UserNotFound { identifier } => json!({ "identifier": identifier }),
472 DockerErrorKind::UserAlreadyExists { username } => json!({ "username": username }),
473 DockerErrorKind::InvalidToken { reason } => json!({ "reason": reason }),
474 DockerErrorKind::TokenExpired => json!({}),
475
476 DockerErrorKind::PermissionDenied { action } => json!({ "action": action }),
477 DockerErrorKind::Forbidden { resource } => json!({ "resource": resource }),
478
479 DockerErrorKind::ResourceNotFound {
480 resource_type,
481 identifier,
482 } => {
483 json!({ "resource_type": resource_type, "identifier": identifier })
484 }
485
486 DockerErrorKind::ResourceConflict { resource, reason } => {
487 json!({ "resource": resource, "reason": reason })
488 }
489
490 DockerErrorKind::RateLimited { limit } => json!({ "limit": limit }),
491
492 DockerErrorKind::ConnectionFailed { target } => json!({ "target": target }),
493 DockerErrorKind::DnsResolutionFailed { host } => json!({ "host": host }),
494 DockerErrorKind::ServiceUnavailable { service } => json!({ "service": service }),
495
496 DockerErrorKind::StorageReadFailed { path } => json!({ "path": path }),
497 DockerErrorKind::StorageWriteFailed { path } => json!({ "path": path }),
498 DockerErrorKind::StorageDeleteFailed { path } => json!({ "path": path }),
499 DockerErrorKind::InsufficientCapacity {
500 required,
501 available,
502 } => {
503 json!({ "required": required, "available": available })
504 }
505 DockerErrorKind::StorageFileNotFound { path } => json!({ "path": path }),
506
507 DockerErrorKind::ConfigMissing { key } => json!({ "key": key }),
508 DockerErrorKind::ConfigInvalid { key, reason } => {
509 json!({ "key": key, "reason": reason })
510 }
511
512 DockerErrorKind::InternalError { reason } => json!({ "reason": reason }),
513 DockerErrorKind::NotImplemented { feature } => json!({ "feature": feature }),
514 DockerErrorKind::IoError { operation, reason } => {
515 json!({ "operation": operation, "reason": reason })
516 }
517 DockerErrorKind::JsonError { reason } => json!({ "reason": reason }),
518 DockerErrorKind::ParseError { type_name, reason } => {
519 json!({ "type": type_name, "reason": reason })
520 }
521 DockerErrorKind::RequestError { url, reason } => {
522 json!({ "url": url, "reason": reason })
523 }
524
525 DockerErrorKind::ContainerError { reason } => json!({ "reason": reason }),
526 DockerErrorKind::ImageError { reason } => json!({ "reason": reason }),
527 DockerErrorKind::NetworkError { reason } => json!({ "reason": reason }),
528 DockerErrorKind::RuntimeError { reason } => json!({ "reason": reason }),
529 DockerErrorKind::RegistryError { reason } => json!({ "reason": reason }),
530 DockerErrorKind::EtcdError { reason } => json!({ "reason": reason }),
531 DockerErrorKind::MonitorError { reason } => json!({ "reason": reason }),
532 DockerErrorKind::KubernetesError { reason } => json!({ "reason": reason }),
533 }
534 }
535}
536
537impl fmt::Display for DockerErrorKind {
538 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
539 write!(f, "{}", self.i18n_key())
540 }
541}
542
543impl DockerError {
544 pub fn new(kind: DockerErrorKind) -> Self {
546 Self {
547 kind: Box::new(kind),
548 }
549 }
550
551 pub fn i18n_key(&self) -> &'static str {
553 self.kind.i18n_key()
554 }
555
556 pub fn i18n_data(&self) -> Value {
558 self.kind.i18n_data()
559 }
560
561 pub fn category(&self) -> ErrorCategory {
563 self.kind.category()
564 }
565
566 pub fn http_status(&self) -> u16 {
568 self.category().http_status()
569 }
570
571 pub fn invalid_format(field: impl Into<String>, expected: impl Into<String>) -> Self {
575 Self::new(DockerErrorKind::InvalidFormat {
576 field: field.into(),
577 expected: expected.into(),
578 })
579 }
580
581 pub fn out_of_range(
583 field: impl Into<String>,
584 min: Option<String>,
585 max: Option<String>,
586 ) -> Self {
587 Self::new(DockerErrorKind::OutOfRange {
588 field: field.into(),
589 min,
590 max,
591 })
592 }
593
594 pub fn required(field: impl Into<String>) -> Self {
596 Self::new(DockerErrorKind::Required {
597 field: field.into(),
598 })
599 }
600
601 pub fn already_exists(field: impl Into<String>, value: impl Into<String>) -> Self {
603 Self::new(DockerErrorKind::AlreadyExists {
604 field: field.into(),
605 value: value.into(),
606 })
607 }
608
609 pub fn not_allowed(field: impl Into<String>, allowed: Vec<String>) -> Self {
611 Self::new(DockerErrorKind::NotAllowed {
612 field: field.into(),
613 allowed,
614 })
615 }
616
617 pub fn invalid_params(param: impl Into<String>, reason: impl Into<String>) -> Self {
619 Self::new(DockerErrorKind::InvalidParams {
620 param: param.into(),
621 reason: reason.into(),
622 })
623 }
624
625 pub fn invalid_credentials() -> Self {
629 Self::new(DockerErrorKind::InvalidCredentials)
630 }
631
632 pub fn account_locked() -> Self {
634 Self::new(DockerErrorKind::AccountLocked)
635 }
636
637 pub fn user_not_found(identifier: impl Into<String>) -> Self {
639 Self::new(DockerErrorKind::UserNotFound {
640 identifier: identifier.into(),
641 })
642 }
643
644 pub fn user_already_exists(username: impl Into<String>) -> Self {
646 Self::new(DockerErrorKind::UserAlreadyExists {
647 username: username.into(),
648 })
649 }
650
651 pub fn invalid_token(reason: impl Into<String>) -> Self {
653 Self::new(DockerErrorKind::InvalidToken {
654 reason: reason.into(),
655 })
656 }
657
658 pub fn token_expired() -> Self {
660 Self::new(DockerErrorKind::TokenExpired)
661 }
662
663 pub fn permission_denied(action: impl Into<String>) -> Self {
667 Self::new(DockerErrorKind::PermissionDenied {
668 action: action.into(),
669 })
670 }
671
672 pub fn forbidden(resource: impl Into<String>) -> Self {
674 Self::new(DockerErrorKind::Forbidden {
675 resource: resource.into(),
676 })
677 }
678
679 pub fn not_found(resource_type: impl Into<String>, identifier: impl Into<String>) -> Self {
683 Self::new(DockerErrorKind::ResourceNotFound {
684 resource_type: resource_type.into(),
685 identifier: identifier.into(),
686 })
687 }
688
689 pub fn connection_failed(target: impl Into<String>) -> Self {
693 Self::new(DockerErrorKind::ConnectionFailed {
694 target: target.into(),
695 })
696 }
697
698 pub fn service_unavailable(service: impl Into<String>) -> Self {
700 Self::new(DockerErrorKind::ServiceUnavailable {
701 service: service.into(),
702 })
703 }
704
705 pub fn storage_read_failed(path: impl Into<String>) -> Self {
709 Self::new(DockerErrorKind::StorageReadFailed { path: path.into() })
710 }
711
712 pub fn storage_write_failed(path: impl Into<String>) -> Self {
714 Self::new(DockerErrorKind::StorageWriteFailed { path: path.into() })
715 }
716
717 pub fn storage_file_not_found(path: impl Into<String>) -> Self {
719 Self::new(DockerErrorKind::StorageFileNotFound { path: path.into() })
720 }
721
722 pub fn config_missing(key: impl Into<String>) -> Self {
726 Self::new(DockerErrorKind::ConfigMissing { key: key.into() })
727 }
728
729 pub fn config_invalid(key: impl Into<String>, reason: impl Into<String>) -> Self {
731 Self::new(DockerErrorKind::ConfigInvalid {
732 key: key.into(),
733 reason: reason.into(),
734 })
735 }
736
737 pub fn internal(reason: impl Into<String>) -> Self {
741 Self::new(DockerErrorKind::InternalError {
742 reason: reason.into(),
743 })
744 }
745
746 pub fn not_implemented(feature: impl Into<String>) -> Self {
748 Self::new(DockerErrorKind::NotImplemented {
749 feature: feature.into(),
750 })
751 }
752
753 pub fn io_error(operation: impl Into<String>, reason: impl Into<String>) -> Self {
755 Self::new(DockerErrorKind::IoError {
756 operation: operation.into(),
757 reason: reason.into(),
758 })
759 }
760
761 pub fn json_error(reason: impl Into<String>) -> Self {
763 Self::new(DockerErrorKind::JsonError {
764 reason: reason.into(),
765 })
766 }
767
768 pub fn parse_error(type_name: impl Into<String>, reason: impl Into<String>) -> Self {
770 Self::new(DockerErrorKind::ParseError {
771 type_name: type_name.into(),
772 reason: reason.into(),
773 })
774 }
775
776 pub fn request_error(url: impl Into<String>, reason: impl Into<String>) -> Self {
778 Self::new(DockerErrorKind::RequestError {
779 url: url.into(),
780 reason: reason.into(),
781 })
782 }
783
784 pub fn container_error(reason: impl Into<String>) -> Self {
788 Self::new(DockerErrorKind::ContainerError {
789 reason: reason.into(),
790 })
791 }
792
793 pub fn image_error(reason: impl Into<String>) -> Self {
795 Self::new(DockerErrorKind::ImageError {
796 reason: reason.into(),
797 })
798 }
799
800 pub fn network_error(reason: impl Into<String>) -> Self {
802 Self::new(DockerErrorKind::NetworkError {
803 reason: reason.into(),
804 })
805 }
806
807 pub fn runtime_error(reason: impl Into<String>) -> Self {
809 Self::new(DockerErrorKind::RuntimeError {
810 reason: reason.into(),
811 })
812 }
813
814 pub fn registry_error(reason: impl Into<String>) -> Self {
816 Self::new(DockerErrorKind::RegistryError {
817 reason: reason.into(),
818 })
819 }
820
821 pub fn etcd_error(reason: impl Into<String>) -> Self {
823 Self::new(DockerErrorKind::EtcdError {
824 reason: reason.into(),
825 })
826 }
827
828 pub fn monitor_error(reason: impl Into<String>) -> Self {
830 Self::new(DockerErrorKind::MonitorError {
831 reason: reason.into(),
832 })
833 }
834
835 pub fn kubernetes_error(reason: impl Into<String>) -> Self {
837 Self::new(DockerErrorKind::KubernetesError {
838 reason: reason.into(),
839 })
840 }
841}
842
843impl fmt::Display for DockerError {
844 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
845 write!(f, "{:?}: {}", self.kind.category(), self.kind.i18n_key())
846 }
847}
848
849impl std::error::Error for DockerError {}
850
851impl From<std::io::Error> for DockerError {
852 fn from(err: std::io::Error) -> Self {
853 Self::io_error("unknown", err.to_string())
854 }
855}
856
857impl From<serde_json::Error> for DockerError {
858 fn from(err: serde_json::Error) -> Self {
859 Self::json_error(err.to_string())
860 }
861}
862
863#[deprecated(note = "请使用 DockerError 替代")]
865pub type RustyDockerError = DockerError;