summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2022-03-20 07:53:06 +0200
committerLars Wirzenius <liw@liw.fi>2022-03-20 10:53:46 +0200
commit873738f0e1dc01f2a75e5e60ae68094b4558ed07 (patch)
tree93c353b763e0b90356e9671102d246dd63415ef0
parent7ae1f33ad0cd0df227e83997268ce2f3540db8d7 (diff)
downloadobnam2-873738f0e1dc01f2a75e5e60ae68094b4558ed07.tar.gz
refactor: put SchemaVersion, GenMeta in their own modules
For clarity, though these aren't yet used anywhere. That will happen soon. Sponsored-by: author
-rw-r--r--src/genmeta.rs57
-rw-r--r--src/lib.rs2
-rw-r--r--src/schema.rs173
3 files changed, 232 insertions, 0 deletions
diff --git a/src/genmeta.rs b/src/genmeta.rs
new file mode 100644
index 0000000..2ce4c4c
--- /dev/null
+++ b/src/genmeta.rs
@@ -0,0 +1,57 @@
+//! Backup generations metadata.
+
+use crate::schema::{SchemaVersion, VersionComponent};
+use serde::Serialize;
+use std::collections::HashMap;
+
+/// Metadata about the local generation.
+#[derive(Debug, Serialize)]
+pub struct GenerationMeta {
+ schema_version: SchemaVersion,
+ extras: HashMap<String, String>,
+}
+
+impl GenerationMeta {
+ /// Create from a hash map.
+ pub fn from(mut map: HashMap<String, String>) -> Result<Self, GenerationMetaError> {
+ let major: VersionComponent = metaint(&mut map, "schema_version_major")?;
+ let minor: VersionComponent = metaint(&mut map, "schema_version_minor")?;
+ Ok(Self {
+ schema_version: SchemaVersion::new(major, minor),
+ extras: map,
+ })
+ }
+
+ /// Return schema version of local generation.
+ pub fn schema_version(&self) -> SchemaVersion {
+ self.schema_version
+ }
+}
+
+fn metastr(map: &mut HashMap<String, String>, key: &str) -> Result<String, GenerationMetaError> {
+ if let Some(v) = map.remove(key) {
+ Ok(v)
+ } else {
+ Err(GenerationMetaError::NoMetaKey(key.to_string()))
+ }
+}
+
+fn metaint(map: &mut HashMap<String, String>, key: &str) -> Result<u32, GenerationMetaError> {
+ let v = metastr(map, key)?;
+ let v = v
+ .parse()
+ .map_err(|err| GenerationMetaError::BadMetaInteger(key.to_string(), err))?;
+ Ok(v)
+}
+
+/// Possible errors from getting generation metadata.
+#[derive(Debug, thiserror::Error)]
+pub enum GenerationMetaError {
+ /// Missing from from 'meta' table.
+ #[error("Generation 'meta' table does not have a row {0}")]
+ NoMetaKey(String),
+
+ /// Bad data in 'meta' table.
+ #[error("Generation 'meta' row {0} has badly formed integer: {1}")]
+ BadMetaInteger(String, std::num::ParseIntError),
+}
diff --git a/src/lib.rs b/src/lib.rs
index 6a30334..fdb318f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -25,10 +25,12 @@ pub mod fsentry;
pub mod fsiter;
pub mod generation;
pub mod genlist;
+pub mod genmeta;
pub mod index;
pub mod indexedstore;
pub mod passwords;
pub mod policy;
+pub mod schema;
pub mod server;
pub mod store;
pub mod workqueue;
diff --git a/src/schema.rs b/src/schema.rs
new file mode 100644
index 0000000..ae8c00b
--- /dev/null
+++ b/src/schema.rs
@@ -0,0 +1,173 @@
+//! Database schema versions.
+
+use serde::Serialize;
+
+/// The type of schema version components.
+pub type VersionComponent = u32;
+
+/// Schema version of the database storing the generation.
+///
+/// An Obnam client can restore a generation using schema version
+/// (x,y), if the client supports a schema version (x,z). If z < y,
+/// the client knows it may not be able to the generation faithfully,
+/// and should warn the user about this. If z >= y, the client knows
+/// it can restore the generation faithfully. If the client does not
+/// support any schema version x, it knows it can't restore the backup
+/// at all.
+#[derive(Debug, Clone, Copy, Serialize)]
+pub struct SchemaVersion {
+ /// Major version.
+ pub major: VersionComponent,
+ /// Minor version.
+ pub minor: VersionComponent,
+}
+
+impl SchemaVersion {
+ /// Create a new schema version object.
+ pub fn new(major: VersionComponent, minor: VersionComponent) -> Self {
+ Self { major, minor }
+ }
+
+ /// Return the major and minor version number component of a schema version.
+ pub fn version(&self) -> (VersionComponent, VersionComponent) {
+ (self.major, self.minor)
+ }
+
+ /// Is this schema version compatible with another schema version?
+ pub fn is_compatible_with(&self, other: &Self) -> bool {
+ self.major == other.major && self.minor >= other.minor
+ }
+}
+
+impl std::fmt::Display for SchemaVersion {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}.{}", self.major, self.minor)
+ }
+}
+
+impl std::str::FromStr for SchemaVersion {
+ type Err = SchemaVersionError;
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ if let Some(pos) = s.find('.') {
+ let major = parse_int(&s[..pos])?;
+ let minor = parse_int(&s[pos + 1..])?;
+ Ok(SchemaVersion::new(major, minor))
+ } else {
+ Err(SchemaVersionError::Invalid(s.to_string()))
+ }
+ }
+}
+
+fn parse_int(s: &str) -> Result<VersionComponent, SchemaVersionError> {
+ if let Ok(i) = s.parse() {
+ Ok(i)
+ } else {
+ Err(SchemaVersionError::InvalidComponent(s.to_string()))
+ }
+}
+
+/// Possible errors from parsing schema versions.
+#[derive(Debug, thiserror::Error, PartialEq, Eq)]
+pub enum SchemaVersionError {
+ /// Failed to parse a string as a schema version.
+ #[error("Invalid schema version {0:?}")]
+ Invalid(String),
+
+ /// Failed to parse a string as a schema version component.
+ #[error("Invalid schema version component {0:?}")]
+ InvalidComponent(String),
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use std::str::FromStr;
+
+ #[test]
+ fn from_string() {
+ let v = SchemaVersion::from_str("1.2").unwrap();
+ assert_eq!(v.version(), (1, 2));
+ }
+
+ #[test]
+ fn from_string_fails_if_empty() {
+ match SchemaVersion::from_str("") {
+ Err(SchemaVersionError::Invalid(s)) => assert_eq!(s, ""),
+ _ => unreachable!(),
+ }
+ }
+
+ #[test]
+ fn from_string_fails_if_empty_major() {
+ match SchemaVersion::from_str(".2") {
+ Err(SchemaVersionError::InvalidComponent(s)) => assert_eq!(s, ""),
+ _ => unreachable!(),
+ }
+ }
+
+ #[test]
+ fn from_string_fails_if_empty_minor() {
+ match SchemaVersion::from_str("1.") {
+ Err(SchemaVersionError::InvalidComponent(s)) => assert_eq!(s, ""),
+ _ => unreachable!(),
+ }
+ }
+
+ #[test]
+ fn from_string_fails_if_just_major() {
+ match SchemaVersion::from_str("1") {
+ Err(SchemaVersionError::Invalid(s)) => assert_eq!(s, "1"),
+ _ => unreachable!(),
+ }
+ }
+
+ #[test]
+ fn from_string_fails_if_nonnumeric_major() {
+ match SchemaVersion::from_str("a.2") {
+ Err(SchemaVersionError::InvalidComponent(s)) => assert_eq!(s, "a"),
+ _ => unreachable!(),
+ }
+ }
+
+ #[test]
+ fn from_string_fails_if_nonnumeric_minor() {
+ match SchemaVersion::from_str("1.a") {
+ Err(SchemaVersionError::InvalidComponent(s)) => assert_eq!(s, "a"),
+ _ => unreachable!(),
+ }
+ }
+
+ #[test]
+ fn compatible_with_self() {
+ let v = SchemaVersion::new(1, 2);
+ assert!(v.is_compatible_with(&v));
+ }
+
+ #[test]
+ fn compatible_with_older_minor_version() {
+ let old = SchemaVersion::new(1, 2);
+ let new = SchemaVersion::new(1, 3);
+ assert!(new.is_compatible_with(&old));
+ }
+
+ #[test]
+ fn not_compatible_with_newer_minor_version() {
+ let old = SchemaVersion::new(1, 2);
+ let new = SchemaVersion::new(1, 3);
+ assert!(!old.is_compatible_with(&new));
+ }
+
+ #[test]
+ fn not_compatible_with_older_major_version() {
+ let old = SchemaVersion::new(1, 2);
+ let new = SchemaVersion::new(2, 0);
+ assert!(!new.is_compatible_with(&old));
+ }
+
+ #[test]
+ fn not_compatible_with_newer_major_version() {
+ let old = SchemaVersion::new(1, 2);
+ let new = SchemaVersion::new(2, 0);
+ assert!(!old.is_compatible_with(&new));
+ }
+}