Skip to main content

canic_backup/journal/
report.rs

1//! Module: journal::report
2//!
3//! Responsibility: project durable journal state into resumability reports.
4//! Does not own: journal validation, artifact state mutation, or persistence.
5//! Boundary: reads journal records and returns read-only reporting projections.
6
7use crate::journal::{ArtifactState, DownloadJournal, DownloadOperationMetrics, ResumeAction};
8
9use serde::{Deserialize, Serialize};
10
11impl DownloadJournal {
12    /// Build a resumability report from the current journal state.
13    #[must_use]
14    pub fn resume_report(&self) -> JournalResumeReport {
15        let mut counts = JournalStateCounts::default();
16        let mut artifacts = Vec::with_capacity(self.artifacts.len());
17
18        for artifact in &self.artifacts {
19            counts.record(artifact.state, artifact.resume_action());
20            artifacts.push(ArtifactResumeReport {
21                canister_id: artifact.canister_id.clone(),
22                snapshot_id: artifact.snapshot_id.clone(),
23                state: artifact.state,
24                resume_action: artifact.resume_action(),
25                artifact_path: artifact.artifact_path.clone(),
26                temp_path: artifact.temp_path.clone(),
27                updated_at: artifact.updated_at.clone(),
28            });
29        }
30
31        JournalResumeReport {
32            backup_id: self.backup_id.clone(),
33            discovery_topology_hash: self.discovery_topology_hash.clone(),
34            pre_snapshot_topology_hash: self.pre_snapshot_topology_hash.clone(),
35            total_artifacts: self.artifacts.len(),
36            is_complete: counts.skip == self.artifacts.len(),
37            pending_artifacts: self.artifacts.len() - counts.skip,
38            counts,
39            operation_metrics: self.operation_metrics.clone(),
40            artifacts,
41        }
42    }
43}
44
45///
46/// JournalResumeReport
47///
48/// Read-only resume projection of one download journal.
49/// Owned by backup journaling and consumed by status/reporting surfaces.
50///
51
52#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
53#[serde(deny_unknown_fields)]
54pub struct JournalResumeReport {
55    pub backup_id: String,
56    pub discovery_topology_hash: Option<String>,
57    pub pre_snapshot_topology_hash: Option<String>,
58    pub total_artifacts: usize,
59    pub is_complete: bool,
60    pub pending_artifacts: usize,
61    pub counts: JournalStateCounts,
62    pub operation_metrics: DownloadOperationMetrics,
63    pub artifacts: Vec<ArtifactResumeReport>,
64}
65
66///
67/// JournalStateCounts
68///
69/// Aggregated artifact state and resume-action counters.
70/// Owned by backup journaling and embedded in resume reports.
71///
72
73#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
74#[serde(deny_unknown_fields)]
75pub struct JournalStateCounts {
76    pub created: usize,
77    pub downloaded: usize,
78    pub checksum_verified: usize,
79    pub durable: usize,
80    pub download: usize,
81    pub verify_checksum: usize,
82    pub finalize: usize,
83    pub skip: usize,
84}
85
86impl JournalStateCounts {
87    const fn record(&mut self, state: ArtifactState, action: ResumeAction) {
88        match state {
89            ArtifactState::Created => self.created += 1,
90            ArtifactState::Downloaded => self.downloaded += 1,
91            ArtifactState::ChecksumVerified => self.checksum_verified += 1,
92            ArtifactState::Durable => self.durable += 1,
93        }
94
95        match action {
96            ResumeAction::Download => self.download += 1,
97            ResumeAction::VerifyChecksum => self.verify_checksum += 1,
98            ResumeAction::Finalize => self.finalize += 1,
99            ResumeAction::Skip => self.skip += 1,
100        }
101    }
102}
103
104///
105/// ArtifactResumeReport
106///
107/// Read-only resume projection for one artifact journal entry.
108/// Owned by backup journaling and embedded in resume reports.
109///
110
111#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
112#[serde(deny_unknown_fields)]
113pub struct ArtifactResumeReport {
114    pub canister_id: String,
115    pub snapshot_id: String,
116    pub state: ArtifactState,
117    pub resume_action: ResumeAction,
118    pub artifact_path: String,
119    pub temp_path: Option<String>,
120    pub updated_at: String,
121}