tycho-util 0.3.9

Shared utilities for node components.
Documentation
use std::ops::{Deref, DerefMut};

use crate::transactional::Transactional;

pub struct TransactionalValue<T> {
    value: T,
    snapshot: Option<T>,
}

impl<T> TransactionalValue<T> {
    pub fn new(value: T) -> Self {
        Self {
            value,
            snapshot: None,
        }
    }
}

impl<T> Deref for TransactionalValue<T> {
    type Target = T;
    fn deref(&self) -> &T {
        &self.value
    }
}

impl<T> DerefMut for TransactionalValue<T> {
    fn deref_mut(&mut self) -> &mut T {
        &mut self.value
    }
}

impl<T: Default> Default for TransactionalValue<T> {
    fn default() -> Self {
        Self::new(T::default())
    }
}

impl<T> From<T> for TransactionalValue<T> {
    fn from(value: T) -> Self {
        Self::new(value)
    }
}

impl<T> AsRef<T> for TransactionalValue<T> {
    fn as_ref(&self) -> &T {
        &self.value
    }
}

impl<T> AsMut<T> for TransactionalValue<T> {
    fn as_mut(&mut self) -> &mut T {
        &mut self.value
    }
}

impl<T: Clone> Transactional for TransactionalValue<T> {
    fn begin(&mut self) {
        debug_assert!(self.snapshot.is_none());
        self.snapshot = Some(self.value.clone());
    }

    fn commit(&mut self) {
        debug_assert!(self.snapshot.is_some());
        self.snapshot = None;
    }

    fn rollback(&mut self) {
        if let Some(snap) = self.snapshot.take() {
            self.value = snap;
        }
    }

    fn in_tx(&self) -> bool {
        self.snapshot.is_some()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn new_and_deref() {
        let v = TransactionalValue::new(42);
        assert_eq!(*v, 42);
    }

    #[test]
    fn deref_mut() {
        let mut v = TransactionalValue::new(1);
        *v = 10;
        assert_eq!(*v, 10);
    }

    #[test]
    fn default_trait() {
        let v: TransactionalValue<i32> = TransactionalValue::default();
        assert_eq!(*v, 0);
    }

    #[test]
    fn in_tx_tracking() {
        let mut v = TransactionalValue::new(0);
        assert!(!v.in_tx());
        v.begin();
        assert!(v.in_tx());
        v.commit();
        assert!(!v.in_tx());
    }

    #[test]
    fn commit_keeps_new_value() {
        let mut v = TransactionalValue::new(1);
        v.begin();
        *v = 2;
        v.commit();
        assert_eq!(*v, 2);
    }

    #[test]
    fn rollback_restores_original() {
        let mut v = TransactionalValue::new(1);
        v.begin();
        *v = 999;
        v.rollback();
        assert_eq!(*v, 1);
    }

    #[test]
    fn rollback_after_multiple_mutations() {
        let mut v = TransactionalValue::new(1);
        v.begin();
        *v = 2;
        *v = 3;
        *v = 4;
        v.rollback();
        assert_eq!(*v, 1);
    }

    #[test]
    fn rollback_no_change() {
        let mut v = TransactionalValue::new(5);
        v.begin();
        v.rollback();
        assert_eq!(*v, 5);
    }

    #[test]
    fn rollback_outside_tx_is_noop() {
        let mut v = TransactionalValue::new(5);
        v.rollback();
        assert_eq!(*v, 5);
    }

    #[test]
    fn sequential_commit_then_rollback() {
        let mut v = TransactionalValue::new(1);
        v.begin();
        *v = 2;
        v.commit();

        v.begin();
        *v = 3;
        v.rollback();
        assert_eq!(*v, 2);
    }

    #[test]
    fn sequential_rollback_then_commit() {
        let mut v = TransactionalValue::new(1);
        v.begin();
        *v = 2;
        v.rollback();

        v.begin();
        *v = 3;
        v.commit();
        assert_eq!(*v, 3);
    }

    #[test]
    fn works_with_string() {
        let mut v = TransactionalValue::new(String::from("hello"));
        v.begin();
        v.push_str(" world");
        assert_eq!(&*v, "hello world");
        v.rollback();
        assert_eq!(&*v, "hello");
    }

    #[test]
    fn works_with_vec() {
        let mut v = TransactionalValue::new(vec![1, 2, 3]);
        v.begin();
        v.push(4);
        v.push(5);
        assert_eq!(&*v, &[1, 2, 3, 4, 5]);
        v.rollback();
        assert_eq!(&*v, &[1, 2, 3]);
    }

    #[test]
    #[cfg(debug_assertions)]
    #[should_panic]
    fn double_begin_panics_in_debug() {
        let mut v = TransactionalValue::new(1);
        v.begin();
        v.begin();
    }
}