1use canic_backup::{
2 persistence::PersistenceError,
3 restore::{
4 RestoreApplyDryRunError, RestoreApplyJournalError, RestorePlanError, RestoreRunnerError,
5 },
6};
7use thiserror::Error as ThisError;
8
9#[derive(Debug, ThisError)]
14pub enum RestoreCommandError {
15 #[error("{0}")]
16 Usage(&'static str),
17
18 #[error("missing required option {0}")]
19 MissingOption(&'static str),
20
21 #[error("use either --manifest or --backup-dir, not both")]
22 ConflictingManifestSources,
23
24 #[error("--require-verified requires --backup-dir")]
25 RequireVerifiedNeedsBackupDir,
26
27 #[error("restore apply currently requires --dry-run")]
28 ApplyRequiresDryRun,
29
30 #[error("restore run requires --dry-run, --execute, or --unclaim-pending")]
31 RestoreRunRequiresMode,
32
33 #[error("use only one restore run mode: --dry-run, --execute, or --unclaim-pending")]
34 RestoreRunConflictingModes,
35
36 #[error("restore run command failed for operation {sequence}: status={status}")]
37 RestoreRunCommandFailed { sequence: usize, status: String },
38
39 #[error("restore apply journal is locked: {lock_path}")]
40 RestoreApplyJournalLocked { lock_path: String },
41
42 #[error("restore plan for backup {backup_id} is not restore-ready: reasons={reasons:?}")]
43 RestoreNotReady {
44 backup_id: String,
45 reasons: Vec<String>,
46 },
47
48 #[error(
49 "restore apply journal for backup {backup_id} has pending operations: pending={pending_operations}, next={next_transition_sequence:?}"
50 )]
51 RestoreApplyPending {
52 backup_id: String,
53 pending_operations: usize,
54 next_transition_sequence: Option<usize>,
55 },
56
57 #[error(
58 "restore apply journal for backup {backup_id} is incomplete: completed={completed_operations}, total={operation_count}"
59 )]
60 RestoreApplyIncomplete {
61 backup_id: String,
62 completed_operations: usize,
63 operation_count: usize,
64 },
65
66 #[error(
67 "restore apply journal for backup {backup_id} has failed operations: failed={failed_operations}"
68 )]
69 RestoreApplyFailed {
70 backup_id: String,
71 failed_operations: usize,
72 },
73
74 #[error("restore apply journal for backup {backup_id} is not ready: reasons={reasons:?}")]
75 RestoreApplyNotReady {
76 backup_id: String,
77 reasons: Vec<String>,
78 },
79
80 #[error("restore apply report for backup {backup_id} requires attention: outcome={outcome:?}")]
81 RestoreApplyReportNeedsAttention {
82 backup_id: String,
83 outcome: canic_backup::restore::RestoreApplyReportOutcome,
84 },
85
86 #[error(
87 "restore apply journal for backup {backup_id} has no executable command: operation_available={operation_available}, complete={complete}, blocked_reasons={blocked_reasons:?}"
88 )]
89 RestoreApplyCommandUnavailable {
90 backup_id: String,
91 operation_available: bool,
92 complete: bool,
93 blocked_reasons: Vec<String>,
94 },
95
96 #[error(
97 "restore apply journal next operation changed before claim: expected={expected}, actual={actual:?}"
98 )]
99 RestoreRunClaimSequenceMismatch {
100 expected: usize,
101 actual: Option<usize>,
102 },
103
104 #[error("unknown option {0}")]
105 UnknownOption(String),
106
107 #[error("option --sequence requires a non-negative integer value")]
108 InvalidSequence,
109
110 #[error("option {option} requires a positive integer value")]
111 InvalidPositiveInteger { option: &'static str },
112
113 #[error(transparent)]
114 Io(#[from] std::io::Error),
115
116 #[error(transparent)]
117 Json(#[from] serde_json::Error),
118
119 #[error(transparent)]
120 Persistence(#[from] PersistenceError),
121
122 #[error(transparent)]
123 RestorePlan(#[from] RestorePlanError),
124
125 #[error(transparent)]
126 RestoreApplyDryRun(#[from] RestoreApplyDryRunError),
127
128 #[error(transparent)]
129 RestoreApplyJournal(#[from] RestoreApplyJournalError),
130}
131
132impl From<RestoreRunnerError> for RestoreCommandError {
133 fn from(error: RestoreRunnerError) -> Self {
135 match error {
136 RestoreRunnerError::CommandFailed { sequence, status } => {
137 Self::RestoreRunCommandFailed { sequence, status }
138 }
139 RestoreRunnerError::JournalLocked { lock_path } => {
140 Self::RestoreApplyJournalLocked { lock_path }
141 }
142 RestoreRunnerError::Pending {
143 backup_id,
144 pending_operations,
145 next_transition_sequence,
146 } => Self::RestoreApplyPending {
147 backup_id,
148 pending_operations,
149 next_transition_sequence,
150 },
151 RestoreRunnerError::Failed {
152 backup_id,
153 failed_operations,
154 } => Self::RestoreApplyFailed {
155 backup_id,
156 failed_operations,
157 },
158 RestoreRunnerError::NotReady { backup_id, reasons } => {
159 Self::RestoreApplyNotReady { backup_id, reasons }
160 }
161 RestoreRunnerError::CommandUnavailable {
162 backup_id,
163 operation_available,
164 complete,
165 blocked_reasons,
166 } => Self::RestoreApplyCommandUnavailable {
167 backup_id,
168 operation_available,
169 complete,
170 blocked_reasons,
171 },
172 RestoreRunnerError::ClaimSequenceMismatch { expected, actual } => {
173 Self::RestoreRunClaimSequenceMismatch { expected, actual }
174 }
175 RestoreRunnerError::Io(error) => Self::Io(error),
176 RestoreRunnerError::Json(error) => Self::Json(error),
177 RestoreRunnerError::Journal(error) => Self::RestoreApplyJournal(error),
178 }
179 }
180}