automorph 0.2.0

Derive macros for bidirectional Automerge-Rust struct synchronization
Documentation
//! Implementations for wrapper types.
//!
//! This module provides implementations for:
//! - `Wrapping<T>` - Wrapping arithmetic integers
//! - `Saturating<T>` - Saturating arithmetic integers
//! - `NonZero*` - Non-zero integer types

use std::num::{
    NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, NonZeroIsize, NonZeroU8,
    NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize, Saturating, Wrapping,
};

use automerge::{ChangeHash, ObjId, Prop, ReadDoc, transaction::Transactable};

use crate::{Automorph, ChangeReport, Error, PrimitiveChanged, Result, ScalarCursor};

// Wrapping<T> implementation
impl<T: Automorph> Automorph for Wrapping<T> {
    type Changes = PrimitiveChanged;
    type Cursor = ScalarCursor;

    fn save<D: Transactable + ReadDoc>(
        &self,
        doc: &mut D,
        obj: impl AsRef<ObjId>,
        prop: impl Into<Prop>,
    ) -> Result<()> {
        self.0.save(doc, obj, prop)
    }

    fn load<D: ReadDoc>(doc: &D, obj: impl AsRef<ObjId>, prop: impl Into<Prop>) -> Result<Self> {
        Ok(Wrapping(T::load(doc, obj, prop)?))
    }

    fn load_at<D: ReadDoc>(
        doc: &D,
        obj: impl AsRef<ObjId>,
        prop: impl Into<Prop>,
        heads: &[ChangeHash],
    ) -> Result<Self> {
        Ok(Wrapping(T::load_at(doc, obj, prop, heads)?))
    }

    fn diff<D: ReadDoc>(
        &self,
        doc: &D,
        obj: impl AsRef<ObjId>,
        prop: impl Into<Prop>,
    ) -> Result<Self::Changes> {
        let inner_changes = self.0.diff(doc, obj, prop)?;
        Ok(PrimitiveChanged::new(inner_changes.any()))
    }

    fn diff_at<D: ReadDoc>(
        &self,
        doc: &D,
        obj: impl AsRef<ObjId>,
        prop: impl Into<Prop>,
        heads: &[ChangeHash],
    ) -> Result<Self::Changes> {
        let inner_changes = self.0.diff_at(doc, obj, prop, heads)?;
        Ok(PrimitiveChanged::new(inner_changes.any()))
    }

    fn update<D: ReadDoc>(
        &mut self,
        doc: &D,
        obj: impl AsRef<ObjId>,
        prop: impl Into<Prop>,
    ) -> Result<Self::Changes> {
        let inner_changes = self.0.update(doc, obj, prop)?;
        Ok(PrimitiveChanged::new(inner_changes.any()))
    }

    fn update_at<D: ReadDoc>(
        &mut self,
        doc: &D,
        obj: impl AsRef<ObjId>,
        prop: impl Into<Prop>,
        heads: &[ChangeHash],
    ) -> Result<Self::Changes> {
        let inner_changes = self.0.update_at(doc, obj, prop, heads)?;
        Ok(PrimitiveChanged::new(inner_changes.any()))
    }
}

// Saturating<T> implementation
impl<T: Automorph> Automorph for Saturating<T> {
    type Changes = PrimitiveChanged;
    type Cursor = ScalarCursor;

    fn save<D: Transactable + ReadDoc>(
        &self,
        doc: &mut D,
        obj: impl AsRef<ObjId>,
        prop: impl Into<Prop>,
    ) -> Result<()> {
        self.0.save(doc, obj, prop)
    }

    fn load<D: ReadDoc>(doc: &D, obj: impl AsRef<ObjId>, prop: impl Into<Prop>) -> Result<Self> {
        Ok(Saturating(T::load(doc, obj, prop)?))
    }

    fn load_at<D: ReadDoc>(
        doc: &D,
        obj: impl AsRef<ObjId>,
        prop: impl Into<Prop>,
        heads: &[ChangeHash],
    ) -> Result<Self> {
        Ok(Saturating(T::load_at(doc, obj, prop, heads)?))
    }

    fn diff<D: ReadDoc>(
        &self,
        doc: &D,
        obj: impl AsRef<ObjId>,
        prop: impl Into<Prop>,
    ) -> Result<Self::Changes> {
        let inner_changes = self.0.diff(doc, obj, prop)?;
        Ok(PrimitiveChanged::new(inner_changes.any()))
    }

    fn diff_at<D: ReadDoc>(
        &self,
        doc: &D,
        obj: impl AsRef<ObjId>,
        prop: impl Into<Prop>,
        heads: &[ChangeHash],
    ) -> Result<Self::Changes> {
        let inner_changes = self.0.diff_at(doc, obj, prop, heads)?;
        Ok(PrimitiveChanged::new(inner_changes.any()))
    }

    fn update<D: ReadDoc>(
        &mut self,
        doc: &D,
        obj: impl AsRef<ObjId>,
        prop: impl Into<Prop>,
    ) -> Result<Self::Changes> {
        let inner_changes = self.0.update(doc, obj, prop)?;
        Ok(PrimitiveChanged::new(inner_changes.any()))
    }

    fn update_at<D: ReadDoc>(
        &mut self,
        doc: &D,
        obj: impl AsRef<ObjId>,
        prop: impl Into<Prop>,
        heads: &[ChangeHash],
    ) -> Result<Self::Changes> {
        let inner_changes = self.0.update_at(doc, obj, prop, heads)?;
        Ok(PrimitiveChanged::new(inner_changes.any()))
    }
}

// Macro for NonZero types
macro_rules! impl_nonzero {
    ($nonzero:ty, $inner:ty, $name:expr) => {
        impl Automorph for $nonzero {
            type Changes = PrimitiveChanged;
            type Cursor = ScalarCursor;

            fn save<D: Transactable + ReadDoc>(
                &self,
                doc: &mut D,
                obj: impl AsRef<ObjId>,
                prop: impl Into<Prop>,
            ) -> Result<()> {
                self.get().save(doc, obj, prop)
            }

            fn load<D: ReadDoc>(
                doc: &D,
                obj: impl AsRef<ObjId>,
                prop: impl Into<Prop>,
            ) -> Result<Self> {
                let val = <$inner>::load(doc, obj, prop)?;
                <$nonzero>::new(val)
                    .ok_or_else(|| Error::invalid_value(concat!($name, " cannot be zero")))
            }

            fn load_at<D: ReadDoc>(
                doc: &D,
                obj: impl AsRef<ObjId>,
                prop: impl Into<Prop>,
                heads: &[ChangeHash],
            ) -> Result<Self> {
                let val = <$inner>::load_at(doc, obj, prop, heads)?;
                <$nonzero>::new(val)
                    .ok_or_else(|| Error::invalid_value(concat!($name, " cannot be zero")))
            }

            fn diff<D: ReadDoc>(
                &self,
                doc: &D,
                obj: impl AsRef<ObjId>,
                prop: impl Into<Prop>,
            ) -> Result<Self::Changes> {
                let inner_changes = self.get().diff(doc, obj, prop)?;
                Ok(PrimitiveChanged::new(inner_changes.any()))
            }

            fn diff_at<D: ReadDoc>(
                &self,
                doc: &D,
                obj: impl AsRef<ObjId>,
                prop: impl Into<Prop>,
                heads: &[ChangeHash],
            ) -> Result<Self::Changes> {
                let inner_changes = self.get().diff_at(doc, obj, prop, heads)?;
                Ok(PrimitiveChanged::new(inner_changes.any()))
            }

            fn update<D: ReadDoc>(
                &mut self,
                doc: &D,
                obj: impl AsRef<ObjId>,
                prop: impl Into<Prop>,
            ) -> Result<Self::Changes> {
                let val = <$inner>::load(doc, obj, prop)?;
                let new_val = <$nonzero>::new(val)
                    .ok_or_else(|| Error::invalid_value(concat!($name, " cannot be zero")))?;
                let changed = *self != new_val;
                if changed {
                    *self = new_val;
                }
                Ok(PrimitiveChanged::new(changed))
            }

            fn update_at<D: ReadDoc>(
                &mut self,
                doc: &D,
                obj: impl AsRef<ObjId>,
                prop: impl Into<Prop>,
                heads: &[ChangeHash],
            ) -> Result<Self::Changes> {
                let val = <$inner>::load_at(doc, obj, prop, heads)?;
                let new_val = <$nonzero>::new(val)
                    .ok_or_else(|| Error::invalid_value(concat!($name, " cannot be zero")))?;
                let changed = *self != new_val;
                if changed {
                    *self = new_val;
                }
                Ok(PrimitiveChanged::new(changed))
            }
        }
    };
}

impl_nonzero!(NonZeroI8, i8, "NonZeroI8");
impl_nonzero!(NonZeroI16, i16, "NonZeroI16");
impl_nonzero!(NonZeroI32, i32, "NonZeroI32");
impl_nonzero!(NonZeroI64, i64, "NonZeroI64");
impl_nonzero!(NonZeroI128, i128, "NonZeroI128");
impl_nonzero!(NonZeroIsize, isize, "NonZeroIsize");
impl_nonzero!(NonZeroU8, u8, "NonZeroU8");
impl_nonzero!(NonZeroU16, u16, "NonZeroU16");
impl_nonzero!(NonZeroU32, u32, "NonZeroU32");
impl_nonzero!(NonZeroU64, u64, "NonZeroU64");
impl_nonzero!(NonZeroU128, u128, "NonZeroU128");
impl_nonzero!(NonZeroUsize, usize, "NonZeroUsize");

#[cfg(test)]
mod tests {
    use super::*;
    use automerge::{AutoCommit, ROOT};

    #[test]
    fn test_wrapping() {
        let mut doc = AutoCommit::new();

        let val = Wrapping(42i64);
        val.save(&mut doc, &ROOT, "val").unwrap();

        let restored = Wrapping::<i64>::load(&doc, &ROOT, "val").unwrap();
        assert_eq!(restored, Wrapping(42));
    }

    #[test]
    fn test_saturating() {
        let mut doc = AutoCommit::new();

        let val = Saturating(42i64);
        val.save(&mut doc, &ROOT, "val").unwrap();

        let restored = Saturating::<i64>::load(&doc, &ROOT, "val").unwrap();
        assert_eq!(restored, Saturating(42));
    }

    #[test]
    fn test_nonzero() {
        let mut doc = AutoCommit::new();

        let val = NonZeroU64::new(42).unwrap();
        val.save(&mut doc, &ROOT, "val").unwrap();

        let restored = NonZeroU64::load(&doc, &ROOT, "val").unwrap();
        assert_eq!(restored.get(), 42);
    }

    #[test]
    fn test_nonzero_zero_error() {
        let mut doc = AutoCommit::new();

        // Store a zero
        0i64.save(&mut doc, &ROOT, "val").unwrap();

        // Try to read as NonZero
        let result = NonZeroI64::load(&doc, &ROOT, "val");
        assert!(result.is_err());
    }
}