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(
29        "artifact commit paths must be distinct siblings: temporary={temporary}, canonical={canonical}"
30    )]
31    ArtifactCommitPathMismatch {
32        temporary: String,
33        canonical: String,
34    },
35
36    #[error(
37        "artifact commit found both temporary and canonical directories: temporary={temporary}, canonical={canonical}"
38    )]
39    ArtifactCommitPathConflict {
40        temporary: String,
41        canonical: String,
42    },
43
44    #[error(
45        "artifact commit found neither temporary nor canonical directory: temporary={temporary}, canonical={canonical}"
46    )]
47    ArtifactCommitPathMissing {
48        temporary: String,
49        canonical: String,
50    },
51
52    #[error("durable artifact directory publication is unsupported on platform {platform}")]
53    ArtifactCommitUnsupportedPlatform { platform: &'static str },
54
55    #[error("artifact path escapes backup root: {artifact_path}")]
56    ArtifactPathEscapesBackup { artifact_path: String },
57
58    #[error("manifest backup id {manifest} does not match journal backup id {journal}")]
59    BackupIdMismatch { manifest: String, journal: String },
60
61    #[error(transparent)]
62    Checksum(#[from] ArtifactChecksumError),
63
64    #[error("backup execution operation {sequence} is {state} but has no matching receipt")]
65    ExecutionOperationMissingReceipt { sequence: usize, state: String },
66
67    #[error("backup execution operation {sequence} timestamp does not match latest receipt")]
68    ExecutionOperationReceiptTimestampMismatch { sequence: usize },
69
70    #[error(transparent)]
71    InvalidBackupPlan(#[from] BackupPlanError),
72
73    #[error(transparent)]
74    InvalidExecutionJournal(#[from] BackupExecutionJournalError),
75
76    #[error(transparent)]
77    InvalidJournal(#[from] JournalValidationError),
78
79    #[error(transparent)]
80    InvalidManifest(#[from] ManifestValidationError),
81
82    #[error(transparent)]
83    Io(#[from] io::Error),
84
85    #[error(transparent)]
86    Json(#[from] serde_json::Error),
87
88    #[error("manifest topology receipt {field} does not match journal topology receipt")]
89    ManifestJournalTopologyReceiptMismatch {
90        field: String,
91        manifest: String,
92        journal: String,
93    },
94
95    #[error("backup manifest conflicts with the existing canonical manifest: {path}")]
96    ManifestConflict { path: String },
97
98    #[error(
99        "manifest artifact path for {canister_id} snapshot {snapshot_id} does not match journal artifact path"
100    )]
101    ManifestJournalArtifactPathMismatch {
102        canister_id: String,
103        snapshot_id: String,
104        manifest: String,
105        journal: String,
106    },
107
108    #[error(
109        "manifest checksum for {canister_id} snapshot {snapshot_id} does not match journal checksum"
110    )]
111    ManifestJournalChecksumMismatch {
112        canister_id: String,
113        snapshot_id: String,
114        manifest: String,
115        journal: String,
116    },
117
118    #[error("artifact path does not exist: {0}")]
119    MissingArtifact(String),
120
121    #[error("manifest member {canister_id} snapshot {snapshot_id} has no journal artifact")]
122    MissingJournalArtifact {
123        canister_id: String,
124        snapshot_id: String,
125    },
126
127    #[error("journal artifact {canister_id} snapshot {snapshot_id} has no checksum")]
128    MissingJournalArtifactChecksum {
129        canister_id: String,
130        snapshot_id: String,
131    },
132
133    #[error(
134        "journal artifact {canister_id} snapshot {snapshot_id} must be checksum verified before durable publication; found {state:?}"
135    )]
136    ArtifactNotChecksumVerified {
137        canister_id: String,
138        snapshot_id: String,
139        state: ArtifactState,
140    },
141
142    #[error("journal artifact {canister_id} snapshot {snapshot_id} is not durable")]
143    NonDurableArtifact {
144        canister_id: String,
145        snapshot_id: String,
146    },
147
148    #[error("backup plan {field} does not match execution journal")]
149    PlanJournalMismatch {
150        field: &'static str,
151        plan: String,
152        journal: String,
153    },
154
155    #[error("backup plan operation {sequence} {field} does not match execution journal")]
156    PlanJournalOperationMismatch {
157        sequence: usize,
158        field: &'static str,
159        plan: String,
160        journal: String,
161    },
162
163    #[error("journal artifact {canister_id} snapshot {snapshot_id} is not declared in manifest")]
164    UnexpectedJournalArtifact {
165        canister_id: String,
166        snapshot_id: String,
167    },
168
169    #[error("unsupported artifact filesystem entry at {path}: {kind}")]
170    UnsupportedArtifactEntry { path: String, kind: String },
171}