use serde::Deserialize;
use thiserror::Error;
pub trait FormatVersion {
const VERSION: u32;
fn format_version(&self) -> u32;
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(untagged)]
pub enum Stored<T> {
Read(T),
Unread {
version: u32,
},
}
impl<T: FormatVersion> Stored<T> {
pub fn into_current(self) -> Result<T, StoredFormatError> {
let found = match self {
Self::Read(value) if value.format_version() == T::VERSION => return Ok(value),
Self::Read(value) => value.format_version(),
Self::Unread { version } if version == T::VERSION => {
return Err(StoredFormatError::Malformed { version });
}
Self::Unread { version } => version,
};
Err(StoredFormatError::UnsupportedVersion {
found,
supported: T::VERSION,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
#[non_exhaustive]
pub enum StoredFormatError {
#[error("saved in format version {found}; this release reads version {supported}")]
UnsupportedVersion {
found: u32,
supported: u32,
},
#[error(
"saved in format version {version}, which this release reads, but it does not read in \
that format"
)]
Malformed {
version: u32,
},
}
#[cfg(test)]
#[allow(clippy::unwrap_used)] mod tests {
use super::*;
use serde::Serialize;
#[derive(Debug, PartialEq, Deserialize, Serialize)]
struct Saved {
version: u32,
quantity: u32,
#[serde(default)]
note: String,
}
impl FormatVersion for Saved {
const VERSION: u32 = 3;
fn format_version(&self) -> u32 {
self.version
}
}
fn read(json: serde_json::Value) -> Result<Saved, StoredFormatError> {
serde_json::from_value::<Stored<Saved>>(json)
.unwrap()
.into_current()
}
#[test]
fn a_value_in_the_current_version_reads_and_one_without_a_later_field_takes_its_default() {
let saved = Saved {
version: 3,
quantity: 7,
note: "kept".into(),
};
assert_eq!(read(serde_json::to_value(&saved).unwrap()), Ok(saved));
assert_eq!(
read(serde_json::json!({ "version": 3, "quantity": 7 })),
Ok(Saved {
version: 3,
quantity: 7,
note: String::new(),
})
);
}
#[test]
fn a_value_in_another_version_is_refused_with_it_whatever_its_shape() {
let unsupported = Err(StoredFormatError::UnsupportedVersion {
found: 4,
supported: 3,
});
assert_eq!(
read(serde_json::json!({ "version": 4, "quantity": 7 })),
unsupported
);
assert_eq!(
read(serde_json::json!({ "version": 4, "quantity": "seven", "units": [] })),
unsupported
);
}
#[test]
fn a_value_claiming_the_current_version_in_another_shape_is_malformed() {
assert_eq!(
read(serde_json::json!({ "version": 3, "quantity": "seven" })),
Err(StoredFormatError::Malformed { version: 3 })
);
}
#[test]
fn a_value_without_a_version_is_not_read() {
assert!(
serde_json::from_value::<Stored<Saved>>(serde_json::json!({ "quantity": 7 })).is_err()
);
}
}