Skip to main content

canic_cli/restore/
error.rs

1use canic_backup::{
2    persistence::PersistenceError,
3    restore::{
4        RestoreApplyDryRunError, RestoreApplyJournalError, RestorePlanError, RestoreRunnerError,
5    },
6};
7use thiserror::Error as ThisError;
8
9///
10/// RestoreCommandError
11///
12
13#[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    // Preserve the CLI-facing error variants while delegating runner ownership downward.
134    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}