tycho-util 0.3.7

Shared utilities for node components.
Documentation
use crate::transactional::Transactional;

pub struct TransactionalOption<T> {
    value: Option<T>,
    #[allow(clippy::option_option)]
    snapshot: Option<Option<T>>,
}

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

    pub fn inner(&self) -> Option<&T> {
        self.value.as_ref()
    }

    pub fn inner_mut(&mut self) -> Option<&mut T> {
        self.value.as_mut()
    }

    pub fn set(&mut self, new: Option<T>) {
        if self.snapshot.is_none() && self.tx_active() {
            self.snapshot = Some(self.value.take());
        }
        self.value = new;
    }

    pub fn is_none(&self) -> bool {
        self.value.is_none()
    }

    pub fn is_some(&self) -> bool {
        self.value.is_some()
    }

    fn tx_active(&self) -> bool {
        self.value.as_ref().is_some_and(|v| v.in_tx()) || self.snapshot.is_some()
    }
}

impl<T: Transactional> Transactional for TransactionalOption<T> {
    fn begin(&mut self) {
        if let Some(inner) = &mut self.value {
            inner.begin();
        } else {
            self.snapshot = Some(None);
        }
    }

    fn commit(&mut self) {
        self.snapshot = None;
        if let Some(inner) = &mut self.value
            && inner.in_tx()
        {
            inner.commit();
        }
    }

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

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

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

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

#[cfg(test)]
mod tests {
    use super::*;
    use crate::transactional::value::TransactionalValue;

    #[test]
    fn rollback_some_to_none() {
        let mut opt = TransactionalOption::new(Some(TransactionalValue::new(10)));
        opt.begin();
        opt.set(None);
        assert!(opt.inner().is_none());
        opt.rollback();
        assert_eq!(**opt.inner().unwrap(), 10);
    }

    #[test]
    fn commit_some_to_none() {
        let mut opt = TransactionalOption::new(Some(TransactionalValue::new(10)));
        opt.begin();
        opt.set(None);
        opt.commit();
        assert!(opt.inner().is_none());
    }

    #[test]
    fn rollback_none_to_some() {
        let mut opt: TransactionalOption<TransactionalValue<i32>> = TransactionalOption::new(None);
        opt.begin();
        opt.set(Some(TransactionalValue::new(42)));
        assert_eq!(**opt.inner().unwrap(), 42);
        opt.rollback();
        assert!(opt.inner().is_none());
    }

    #[test]
    fn commit_none_to_some() {
        let mut opt: TransactionalOption<TransactionalValue<i32>> = TransactionalOption::new(None);
        opt.begin();
        opt.set(Some(TransactionalValue::new(42)));
        opt.commit();
        assert_eq!(**opt.inner().unwrap(), 42);
    }

    #[test]
    fn rollback_some_to_some() {
        let mut opt = TransactionalOption::new(Some(TransactionalValue::new(1)));
        opt.begin();
        opt.set(Some(TransactionalValue::new(2)));
        assert_eq!(**opt.inner().unwrap(), 2);
        opt.rollback();
        assert_eq!(**opt.inner().unwrap(), 1);
    }

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

    #[test]
    fn rollback_none_no_change() {
        let mut opt: TransactionalOption<TransactionalValue<i32>> = TransactionalOption::new(None);
        opt.begin();
        opt.rollback();
        assert!(opt.inner().is_none());
    }

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

    #[test]
    fn in_tx_none() {
        let mut opt: TransactionalOption<TransactionalValue<i32>> = TransactionalOption::new(None);
        assert!(!opt.in_tx());
        opt.begin();
        assert!(opt.in_tx());
        opt.rollback();
        assert!(!opt.in_tx());
    }

    #[test]
    fn set_new_value_during_tx_commit_no_panic() {
        let mut opt = TransactionalOption::new(Some(TransactionalValue::new(1)));
        opt.begin();
        opt.set(Some(TransactionalValue::new(99)));
        opt.commit();
        assert_eq!(**opt.inner().unwrap(), 99);
    }
}