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: Option<String>,
93    },
94
95    #[error(
96        "manifest artifact path for {canister_id} snapshot {snapshot_id} does not match journal artifact path"
97    )]
98    ManifestJournalArtifactPathMismatch {
99        canister_id: String,
100        snapshot_id: String,
101        manifest: String,
102        journal: String,
103    },
104
105    #[error(
106        "manifest checksum for {canister_id} snapshot {snapshot_id} does not match journal checksum"
107    )]
108    ManifestJournalChecksumMismatch {
109        canister_id: String,
110        snapshot_id: String,
111        manifest: String,
112        journal: String,
113    },
114
115    #[error("artifact path does not exist: {0}")]
116    MissingArtifact(String),
117
118    #[error("manifest member {canister_id} snapshot {snapshot_id} has no journal artifact")]
119    MissingJournalArtifact {
120        canister_id: String,
121        snapshot_id: String,
122    },
123
124    #[error("journal artifact {canister_id} snapshot {snapshot_id} has no checksum")]
125    MissingJournalArtifactChecksum {
126        canister_id: String,
127        snapshot_id: String,
128    },
129
130    #[error(
131        "journal artifact {canister_id} snapshot {snapshot_id} must be checksum verified before durable publication; found {state:?}"
132    )]
133    ArtifactNotChecksumVerified {
134        canister_id: String,
135        snapshot_id: String,
136        state: ArtifactState,
137    },
138
139    #[error("journal artifact {canister_id} snapshot {snapshot_id} is not durable")]
140    NonDurableArtifact {
141        canister_id: String,
142        snapshot_id: String,
143    },
144
145    #[error("backup plan {field} does not match execution journal")]
146    PlanJournalMismatch {
147        field: &'static str,
148        plan: String,
149        journal: String,
150    },
151
152    #[error("backup plan operation {sequence} {field} does not match execution journal")]
153    PlanJournalOperationMismatch {
154        sequence: usize,
155        field: &'static str,
156        plan: String,
157        journal: String,
158    },
159
160    #[error("journal artifact {canister_id} snapshot {snapshot_id} is not declared in manifest")]
161    UnexpectedJournalArtifact {
162        canister_id: String,
163        snapshot_id: String,
164    },
165
166    #[error("unsupported artifact filesystem entry at {path}: {kind}")]
167    UnsupportedArtifactEntry { path: String, kind: String },
168}