1//! Database version utils.
23use std::{
4fs, io,
5 path::{Path, PathBuf},
6};
78/// The name of the file that contains the version of the database.
9pub const DB_VERSION_FILE_NAME: &str = "database.version";
10/// The version of the database stored in the [`DB_VERSION_FILE_NAME`] file in the same directory as
11/// database.
12pub const DB_VERSION: u64 = 2;
1314/// Error when checking a database version using [`check_db_version_file`]
15#[derive(thiserror::Error, Debug)]
16pub enum DatabaseVersionError {
17/// Unable to determine the version of the database; the file is missing.
18#[error("unable to determine the version of the database, file is missing")]
19MissingFile,
20/// Unable to determine the version of the database; the file is malformed.
21#[error("unable to determine the version of the database, file is malformed")]
22MalformedFile,
23/// Breaking database change detected.
24 ///
25 /// Your database version is incompatible with the latest database version.
26#[error(
27"breaking database change detected: your database version (v{version}) \
28 is incompatible with the latest database version (v{DB_VERSION})"
29)]
30VersionMismatch {
31/// The detected version in the database.
32version: u64,
33 },
34/// IO error occurred while reading the database version file.
35#[error("IO error occurred while reading {path}: {err}")]
36IORead {
37/// The encountered IO error.
38err: io::Error,
39/// The path to the database version file.
40path: PathBuf,
41 },
42}
4344/// Checks the database version file with [`DB_VERSION_FILE_NAME`] name.
45///
46/// Returns [Ok] if file is found and has one line which equals to [`DB_VERSION`].
47/// Otherwise, returns different [`DatabaseVersionError`] error variants.
48pub fn check_db_version_file<P: AsRef<Path>>(db_path: P) -> Result<(), DatabaseVersionError> {
49let version = get_db_version(db_path)?;
50if version != DB_VERSION {
51return Err(DatabaseVersionError::VersionMismatch { version })
52 }
5354Ok(())
55}
5657/// Returns the database version from file with [`DB_VERSION_FILE_NAME`] name.
58///
59/// Returns [Ok] if file is found and contains a valid version.
60/// Otherwise, returns different [`DatabaseVersionError`] error variants.
61pub fn get_db_version<P: AsRef<Path>>(db_path: P) -> Result<u64, DatabaseVersionError> {
62let version_file_path = db_version_file_path(db_path);
63match fs::read_to_string(&version_file_path) {
64Ok(raw_version) => {
65Ok(raw_version.parse::<u64>().map_err(|_| DatabaseVersionError::MalformedFile)?)
66 }
67Err(err) if err.kind() == io::ErrorKind::NotFound => Err(DatabaseVersionError::MissingFile),
68Err(err) => Err(DatabaseVersionError::IORead { err, path: version_file_path }),
69 }
70}
7172/// Creates a database version file with [`DB_VERSION_FILE_NAME`] name containing [`DB_VERSION`]
73/// string.
74///
75/// This function will create a file if it does not exist,
76/// and will entirely replace its contents if it does.
77pub fn create_db_version_file<P: AsRef<Path>>(db_path: P) -> io::Result<()> {
78fs::write(db_version_file_path(db_path), DB_VERSION.to_string())
79}
8081/// Returns a database version file path.
82pub fn db_version_file_path<P: AsRef<Path>>(db_path: P) -> PathBuf {
83db_path.as_ref().join(DB_VERSION_FILE_NAME)
84}
8586#[cfg(test)]
87mod tests {
88use super::{check_db_version_file, db_version_file_path, DatabaseVersionError};
89use assert_matches::assert_matches;
90use std::fs;
91use tempfile::tempdir;
9293#[test]
94fn missing_file() {
95let dir = tempdir().unwrap();
9697let result = check_db_version_file(&dir);
98assert_matches!(result, Err(DatabaseVersionError::MissingFile));
99 }
100101#[test]
102fn malformed_file() {
103let dir = tempdir().unwrap();
104 fs::write(db_version_file_path(&dir), "invalid-version").unwrap();
105106let result = check_db_version_file(&dir);
107assert_matches!(result, Err(DatabaseVersionError::MalformedFile));
108 }
109110#[test]
111fn version_mismatch() {
112let dir = tempdir().unwrap();
113 fs::write(db_version_file_path(&dir), "0").unwrap();
114115let result = check_db_version_file(&dir);
116assert_matches!(result, Err(DatabaseVersionError::VersionMismatch { version: 0 }));
117 }
118}