Skip to main content

canic_backup/persistence/
error.rs

1//! Module: persistence::error
2//!
3//! Responsibility: define typed persistence and integrity failures.
4//! Does not own: filesystem operations, JSON encoding, or checksum calculation.
5//! Boundary: aggregates lower-layer errors returned by persistence APIs.
6
7use crate::{
8    artifacts::ArtifactChecksumError,
9    execution::BackupExecutionJournalError,
10    journal::{ArtifactState, JournalValidationError},
11    manifest::ManifestValidationError,
12    plan::BackupPlanError,
13};
14
15use std::io;
16
17use thiserror::Error as ThisError;
18
19///
20/// PersistenceError
21///
22/// Typed persistence and integrity failure for backup layout operations.
23/// Owned by persistence and returned by backup file IO and verification APIs.
24///
25
26#[derive(Debug, ThisError)]
27pub enum PersistenceError {
28    #[error("restore reference conflicts with retained recovery authority: {path}")]
29    RestoreReferenceConflict { path: String },
30
31    #[error("invalid restore reference record: {path}")]
32    InvalidRestoreReferences { path: String },
33
34    #[error(
35        "artifact commit paths must be distinct siblings: temporary={temporary}, canonical={canonical}"
36    )]
37    ArtifactCommitPathMismatch {
38        temporary: String,
39        canonical: String,
40    },
41
42    #[error(
43        "artifact commit found both temporary and canonical directories: temporary={temporary}, canonical={canonical}"
44    )]
45    ArtifactCommitPathConflict {
46        temporary: String,
47        canonical: String,
48    },
49
50    #[error(
51        "artifact commit found neither temporary nor canonical directory: temporary={temporary}, canonical={canonical}"
52    )]
53    ArtifactCommitPathMissing {
54        temporary: String,
55        canonical: String,
56    },
57
58    #[error("durable artifact directory publication is unsupported on platform {platform}")]
59    ArtifactCommitUnsupportedPlatform { platform: &'static str },
60
61    #[error("artifact path escapes backup root: {artifact_path}")]
62    ArtifactPathEscapesBackup { artifact_path: String },
63
64    #[error("manifest backup id {manifest} does not match journal backup id {journal}")]
65    BackupIdMismatch { manifest: String, journal: String },
66
67    #[error(transparent)]
68    Checksum(#[from] ArtifactChecksumError),
69
70    #[error("backup execution operation {sequence} is {state} but has no matching receipt")]
71    ExecutionOperationMissingReceipt { sequence: usize, state: String },
72
73    #[error("backup execution operation {sequence} timestamp does not match latest receipt")]
74    ExecutionOperationReceiptTimestampMismatch { sequence: usize },
75
76    #[error(transparent)]
77    InvalidBackupPlan(#[from] BackupPlanError),
78
79    #[error(transparent)]
80    InvalidExecutionJournal(#[from] BackupExecutionJournalError),
81
82    #[error(transparent)]
83    InvalidJournal(#[from] JournalValidationError),
84
85    #[error(transparent)]
86    InvalidManifest(#[from] ManifestValidationError),
87
88    #[error(transparent)]
89    Io(#[from] io::Error),
90
91    #[error(transparent)]
92    Json(#[from] serde_json::Error),
93
94    #[error("manifest topology receipt {field} does not match journal topology receipt")]
95    ManifestJournalTopologyReceiptMismatch {
96        field: String,
97        manifest: String,
98        journal: String,
99    },
100
101    #[error("backup manifest conflicts with the existing canonical manifest: {path}")]
102    ManifestConflict { path: String },
103
104    #[error(
105        "manifest artifact path for {canister_id} snapshot {snapshot_id} does not match journal artifact path"
106    )]
107    ManifestJournalArtifactPathMismatch {
108        canister_id: String,
109        snapshot_id: String,
110        manifest: String,
111        journal: String,
112    },
113
114    #[error(
115        "manifest checksum for {canister_id} snapshot {snapshot_id} does not match journal checksum"
116    )]
117    ManifestJournalChecksumMismatch {
118        canister_id: String,
119        snapshot_id: String,
120        manifest: String,
121        journal: String,
122    },
123
124    #[error("artifact path does not exist: {0}")]
125    MissingArtifact(String),
126
127    #[error("manifest member {canister_id} snapshot {snapshot_id} has no journal artifact")]
128    MissingJournalArtifact {
129        canister_id: String,
130        snapshot_id: String,
131    },
132
133    #[error("journal artifact {canister_id} snapshot {snapshot_id} has no checksum")]
134    MissingJournalArtifactChecksum {
135        canister_id: String,
136        snapshot_id: String,
137    },
138
139    #[error(
140        "journal artifact {canister_id} snapshot {snapshot_id} must be checksum verified before durable publication; found {state:?}"
141    )]
142    ArtifactNotChecksumVerified {
143        canister_id: String,
144        snapshot_id: String,
145        state: ArtifactState,
146    },
147
148    #[error("journal artifact {canister_id} snapshot {snapshot_id} is not durable")]
149    NonDurableArtifact {
150        canister_id: String,
151        snapshot_id: String,
152    },
153
154    #[error("backup plan {field} does not match execution journal")]
155    PlanJournalMismatch {
156        field: &'static str,
157        plan: String,
158        journal: String,
159    },
160
161    #[error("backup plan operation {sequence} {field} does not match execution journal")]
162    PlanJournalOperationMismatch {
163        sequence: usize,
164        field: &'static str,
165        plan: String,
166        journal: String,
167    },
168
169    #[error("journal artifact {canister_id} snapshot {snapshot_id} is not declared in manifest")]
170    UnexpectedJournalArtifact {
171        canister_id: String,
172        snapshot_id: String,
173    },
174
175    #[error("unsupported artifact filesystem entry at {path}: {kind}")]
176    UnsupportedArtifactEntry { path: String, kind: String },
177}