use std::sync::Arc;
use serde::{Deserialize, Serialize};
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
)]
pub struct ConfigVersion(pub u64);
impl ConfigVersion {
pub const SENTINEL: Self = ConfigVersion(0);
pub fn next(self) -> Self {
ConfigVersion(self.0.saturating_add(1))
}
pub fn is_sentinel(self) -> bool {
self.0 == 0
}
}
impl std::fmt::Display for ConfigVersion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "v{}", self.0)
}
}
#[derive(Debug)]
pub struct VersionedConfig<T> {
inner: Arc<T>,
version: ConfigVersion,
}
impl<T> VersionedConfig<T> {
pub fn new(value: T, version: ConfigVersion) -> Self {
Self {
inner: Arc::new(value),
version,
}
}
pub fn sentinel(value: T) -> Self {
Self::new(value, ConfigVersion::SENTINEL)
}
pub fn version(&self) -> ConfigVersion {
self.version
}
pub fn value(&self) -> &T {
&self.inner
}
pub fn arc(&self) -> Arc<T> {
Arc::clone(&self.inner)
}
}
impl<T> Clone for VersionedConfig<T> {
fn clone(&self) -> Self {
Self {
inner: Arc::clone(&self.inner),
version: self.version,
}
}
}
impl<T: PartialEq> PartialEq for VersionedConfig<T> {
fn eq(&self, other: &Self) -> bool {
self.version == other.version && *self.inner == *other.inner
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ConfigDelta<T> {
pub from_version: ConfigVersion,
pub to_version: ConfigVersion,
pub new_value: T,
}
impl<T> ConfigDelta<T> {
pub fn new(from_version: ConfigVersion, new_value: T) -> Self {
Self {
to_version: from_version.next(),
from_version,
new_value,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sentinel_is_v0() {
assert_eq!(ConfigVersion::SENTINEL, ConfigVersion(0));
assert!(ConfigVersion::SENTINEL.is_sentinel());
assert!(!ConfigVersion(1).is_sentinel());
}
#[test]
fn next_is_monotonic() {
let v = ConfigVersion(5);
assert_eq!(v.next(), ConfigVersion(6));
}
#[test]
fn next_saturates_at_max() {
let max = ConfigVersion(u64::MAX);
assert_eq!(max.next(), max);
}
#[test]
fn version_display_format() {
assert_eq!(format!("{}", ConfigVersion(7)), "v7");
assert_eq!(format!("{}", ConfigVersion::SENTINEL), "v0");
}
#[test]
fn versioned_config_clone_shares_arc() {
let c = VersionedConfig::new(42u32, ConfigVersion(1));
let c2 = c.clone();
assert!(Arc::ptr_eq(&c.arc(), &c2.arc()));
assert_eq!(c.version(), c2.version());
}
#[test]
fn versioned_config_sentinel_constructor() {
let c = VersionedConfig::sentinel("hello".to_string());
assert!(c.version().is_sentinel());
assert_eq!(c.value(), "hello");
}
#[test]
fn versioned_config_eq_compares_version_and_value() {
let a = VersionedConfig::new(42u32, ConfigVersion(1));
let b = VersionedConfig::new(42u32, ConfigVersion(1));
let c = VersionedConfig::new(42u32, ConfigVersion(2));
let d = VersionedConfig::new(43u32, ConfigVersion(1));
assert_eq!(a, b);
assert_ne!(a, c);
assert_ne!(a, d);
}
#[test]
fn delta_to_version_is_one_past_from() {
let d = ConfigDelta::new(ConfigVersion(7), "x");
assert_eq!(d.from_version, ConfigVersion(7));
assert_eq!(d.to_version, ConfigVersion(8));
}
#[test]
fn version_ordering_is_numeric() {
assert!(ConfigVersion(1) < ConfigVersion(2));
assert!(ConfigVersion(2) > ConfigVersion(1));
assert!(ConfigVersion(1) == ConfigVersion(1));
}
}