Skip to main content

canic_backup/persistence/
layout.rs

1//! Module: persistence::layout
2//!
3//! Responsibility: define canonical backup layout paths and validated file IO.
4//! Does not own: JSON mechanics, domain validation rules, or checksum verification.
5//! Boundary: exposes filesystem persistence operations for backup workflows.
6
7use crate::{
8    execution::BackupExecutionJournal,
9    journal::DownloadJournal,
10    manifest::DeploymentBackupManifest,
11    persistence::{
12        BackupExecutionIntegrityReport, BackupIntegrityReport, PersistenceError,
13        integrity::{verify_execution_integrity, verify_layout_integrity},
14        json::{read_json, write_json_atomic},
15    },
16    plan::BackupPlan,
17};
18
19use std::path::{Path, PathBuf};
20
21const MANIFEST_FILE_NAME: &str = "deployment-backup-manifest.json";
22const BACKUP_PLAN_FILE_NAME: &str = "backup-plan.json";
23const JOURNAL_FILE_NAME: &str = "download-journal.json";
24const EXECUTION_JOURNAL_FILE_NAME: &str = "backup-execution-journal.json";
25
26///
27/// BackupLayout
28///
29/// Canonical filesystem layout for one backup directory.
30/// Owned by persistence and used by backup runner and restore verification paths.
31///
32
33#[derive(Clone, Debug)]
34pub struct BackupLayout {
35    root: PathBuf,
36}
37
38impl BackupLayout {
39    /// Create a filesystem layout rooted at one backup directory.
40    #[must_use]
41    pub const fn new(root: PathBuf) -> Self {
42        Self { root }
43    }
44
45    /// Return the root backup directory path.
46    #[must_use]
47    pub fn root(&self) -> &Path {
48        &self.root
49    }
50
51    /// Return the canonical manifest path for this backup layout.
52    #[must_use]
53    pub fn manifest_path(&self) -> PathBuf {
54        self.root.join(MANIFEST_FILE_NAME)
55    }
56
57    /// Return the canonical backup plan path for this layout.
58    #[must_use]
59    pub fn backup_plan_path(&self) -> PathBuf {
60        self.root.join(BACKUP_PLAN_FILE_NAME)
61    }
62
63    /// Return the canonical mutable journal path for this backup layout.
64    #[must_use]
65    pub fn journal_path(&self) -> PathBuf {
66        self.root.join(JOURNAL_FILE_NAME)
67    }
68
69    /// Return the canonical backup execution journal path for this layout.
70    #[must_use]
71    pub fn execution_journal_path(&self) -> PathBuf {
72        self.root.join(EXECUTION_JOURNAL_FILE_NAME)
73    }
74
75    /// Write a validated manifest with atomic replace semantics.
76    pub fn write_manifest(
77        &self,
78        manifest: &DeploymentBackupManifest,
79    ) -> Result<(), PersistenceError> {
80        manifest.validate()?;
81        write_json_atomic(&self.manifest_path(), manifest)
82    }
83
84    /// Read and validate a manifest from this backup layout.
85    pub fn read_manifest(&self) -> Result<DeploymentBackupManifest, PersistenceError> {
86        let manifest = read_json(&self.manifest_path())?;
87        DeploymentBackupManifest::validate(&manifest)?;
88        Ok(manifest)
89    }
90
91    /// Write a validated backup plan with atomic replace semantics.
92    pub fn write_backup_plan(&self, plan: &BackupPlan) -> Result<(), PersistenceError> {
93        plan.validate()?;
94        write_json_atomic(&self.backup_plan_path(), plan)
95    }
96
97    /// Read and validate a backup plan from this layout.
98    pub fn read_backup_plan(&self) -> Result<BackupPlan, PersistenceError> {
99        let plan = read_json(&self.backup_plan_path())?;
100        BackupPlan::validate(&plan)?;
101        Ok(plan)
102    }
103
104    /// Write a validated download journal with atomic replace semantics.
105    pub fn write_journal(&self, journal: &DownloadJournal) -> Result<(), PersistenceError> {
106        journal.validate()?;
107        write_json_atomic(&self.journal_path(), journal)
108    }
109
110    /// Read and validate a download journal from this backup layout.
111    pub fn read_journal(&self) -> Result<DownloadJournal, PersistenceError> {
112        let journal = read_json(&self.journal_path())?;
113        DownloadJournal::validate(&journal)?;
114        Ok(journal)
115    }
116
117    /// Write a validated backup execution journal with atomic replace semantics.
118    pub fn write_execution_journal(
119        &self,
120        journal: &BackupExecutionJournal,
121    ) -> Result<(), PersistenceError> {
122        journal.validate()?;
123        write_json_atomic(&self.execution_journal_path(), journal)
124    }
125
126    /// Read and validate a backup execution journal from this layout.
127    pub fn read_execution_journal(&self) -> Result<BackupExecutionJournal, PersistenceError> {
128        let journal = read_json(&self.execution_journal_path())?;
129        BackupExecutionJournal::validate(&journal)?;
130        Ok(journal)
131    }
132
133    /// Validate the manifest, journal, and durable artifact checksums.
134    pub fn verify_integrity(&self) -> Result<BackupIntegrityReport, PersistenceError> {
135        let manifest = self.read_manifest()?;
136        let journal = self.read_journal()?;
137        verify_layout_integrity(self, &manifest, &journal)
138    }
139
140    /// Validate the persisted backup plan and execution journal agree.
141    pub fn verify_execution_integrity(
142        &self,
143    ) -> Result<BackupExecutionIntegrityReport, PersistenceError> {
144        let plan = self.read_backup_plan()?;
145        let journal = self.read_execution_journal()?;
146        verify_execution_integrity(&plan, &journal)
147    }
148}