toasty_cli/migration/
snapshot_file.rs

1use anyhow::{Result, bail};
2use serde::{Deserialize, Serialize};
3use std::fmt;
4use std::path::Path;
5use std::str::FromStr;
6use toasty_core::schema::db::Schema;
7use toml_edit::{DocumentMut, Item};
8
9const SNAPSHOT_FILE_VERSION: u32 = 1;
10
11/// Snapshot file containing the current database schema state
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct SnapshotFile {
14    /// Snapshot file format version
15    version: u32,
16
17    /// The database schema
18    pub schema: Schema,
19}
20
21impl SnapshotFile {
22    /// Create a new snapshot file with the given schema
23    pub fn new(schema: Schema) -> Self {
24        Self {
25            version: SNAPSHOT_FILE_VERSION,
26            schema,
27        }
28    }
29
30    /// Load a snapshot file from a TOML file
31    pub fn load(path: impl AsRef<Path>) -> Result<Self> {
32        let contents = std::fs::read_to_string(path.as_ref())?;
33        contents.parse()
34    }
35
36    /// Save the snapshot file to a TOML file
37    pub fn save(&self, path: impl AsRef<Path>) -> Result<()> {
38        std::fs::write(path.as_ref(), self.to_string())?;
39        Ok(())
40    }
41}
42
43impl FromStr for SnapshotFile {
44    type Err = anyhow::Error;
45
46    fn from_str(s: &str) -> Result<Self> {
47        let file: SnapshotFile = toml::from_str(s)?;
48
49        // Validate version
50        if file.version != SNAPSHOT_FILE_VERSION {
51            bail!(
52                "Unsupported snapshot file version: {}. Expected version {}",
53                file.version,
54                SNAPSHOT_FILE_VERSION
55            );
56        }
57
58        Ok(file)
59    }
60}
61
62impl fmt::Display for SnapshotFile {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        let doc = self.to_toml_document().map_err(|_| fmt::Error)?;
65        write!(f, "{}", doc)
66    }
67}
68
69impl SnapshotFile {
70    fn to_toml_document(&self) -> Result<DocumentMut> {
71        let mut doc = toml_edit::ser::to_document(self)?;
72        for (_key, item) in doc.as_table_mut().iter_mut() {
73            if item.is_inline_table() {
74                let mut placeholder = Item::None;
75                std::mem::swap(item, &mut placeholder);
76                let mut table = placeholder.into_table().unwrap();
77
78                for (_key, item) in table.iter_mut() {
79                    if item.is_array() {
80                        let mut placeholder = Item::None;
81                        std::mem::swap(item, &mut placeholder);
82                        let mut array = placeholder.into_array_of_tables().unwrap();
83
84                        for table in array.iter_mut() {
85                            for (_key, item) in table.iter_mut() {
86                                if item.is_array() {
87                                    let mut placeholder = Item::None;
88                                    std::mem::swap(item, &mut placeholder);
89                                    let array = placeholder.into_array_of_tables().unwrap();
90                                    *item = array.into();
91                                }
92                            }
93                        }
94
95                        *item = array.into();
96                    }
97                }
98
99                *item = table.into();
100            }
101        }
102
103        Ok(doc)
104    }
105}