automorph 0.2.0

Derive macros for bidirectional Automerge-Rust struct synchronization
Documentation
//! Implementations for tuple types.

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

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

// Macro to implement Automorph for tuples of different sizes
macro_rules! impl_tuple {
    ($($idx:tt: $T:ident),+) => {
        impl<$($T: Automorph),+> Automorph for ($($T,)+) {
            type Changes = PrimitiveChanged;
            type Cursor = ScalarCursor;

            fn save<Doc: Transactable + ReadDoc>(
                &self,
                doc: &mut Doc,
                obj: impl AsRef<ObjId>,
                prop: impl Into<Prop>,
            ) -> Result<()> {
                let prop: Prop = prop.into();
                let obj = obj.as_ref();
                let cur = doc.get(obj, prop.clone())?;

                let list_id = match cur {
                    Some((Value::Object(ObjType::List), id)) => {
                        // Ensure correct length
                        let expected_len = impl_tuple!(@count $($T)+);
                        let list_len = doc.length(&id);
                        for idx in (expected_len..list_len).rev() {
                            doc.delete(&id, idx)?;
                        }
                        id
                    }
                    _ => doc.put_object(obj, prop, ObjType::List)?,
                };

                $(
                    self.$idx.save(doc, &list_id, $idx)?;
                )+

                Ok(())
            }

            fn load<Doc: ReadDoc>(
                doc: &Doc,
                obj: impl AsRef<ObjId>,
                prop: impl Into<Prop>,
            ) -> Result<Self> {
                let prop: Prop = prop.into();
                let obj = obj.as_ref();
                match doc.get(obj, prop)? {
                    Some((Value::Object(ObjType::List), id)) => {
                        Ok(($(
                            $T::load(doc, &id, $idx)
                                .map_err(|e| e.with_index($idx))?,
                        )+))
                    }
                    Some((v, _)) => Err(Error::type_mismatch("tuple (List)", Some(format!("{:?}", v)))),
                    None => Err(Error::missing_value()),
                }
            }

            fn load_at<Doc: ReadDoc>(
                doc: &Doc,
                obj: impl AsRef<ObjId>,
                prop: impl Into<Prop>,
                heads: &[ChangeHash],
            ) -> Result<Self> {
                let prop: Prop = prop.into();
                let obj = obj.as_ref();
                match doc.get_at(obj, prop, heads)? {
                    Some((Value::Object(ObjType::List), id)) => {
                        Ok(($(
                            $T::load_at(doc, &id, $idx, heads)
                                .map_err(|e| e.with_index($idx))?,
                        )+))
                    }
                    Some((v, _)) => Err(Error::type_mismatch("tuple (List)", Some(format!("{:?}", v)))),
                    None => Err(Error::missing_value()),
                }
            }

            fn diff<Doc: ReadDoc>(
                &self,
                doc: &Doc,
                obj: impl AsRef<ObjId>,
                prop: impl Into<Prop>,
            ) -> Result<Self::Changes> {
                let prop: Prop = prop.into();
                let obj = obj.as_ref();
                match doc.get(obj, prop)? {
                    Some((Value::Object(ObjType::List), id)) => {
                        $(
                            let changes = self.$idx.diff(doc, &id, $idx)
                                .map_err(|e| e.with_index($idx))?;
                            if changes.any() {
                                return Ok(PrimitiveChanged::new(true));
                            }
                        )+
                        Ok(PrimitiveChanged::new(false))
                    }
                    Some((v, _)) => Err(Error::type_mismatch("tuple (List)", Some(format!("{:?}", v)))),
                    None => Err(Error::missing_value()),
                }
            }

            fn diff_at<Doc: ReadDoc>(
                &self,
                doc: &Doc,
                obj: impl AsRef<ObjId>,
                prop: impl Into<Prop>,
                heads: &[ChangeHash],
            ) -> Result<Self::Changes> {
                let prop: Prop = prop.into();
                let obj = obj.as_ref();
                match doc.get_at(obj, prop, heads)? {
                    Some((Value::Object(ObjType::List), id)) => {
                        $(
                            let changes = self.$idx.diff_at(doc, &id, $idx, heads)
                                .map_err(|e| e.with_index($idx))?;
                            if changes.any() {
                                return Ok(PrimitiveChanged::new(true));
                            }
                        )+
                        Ok(PrimitiveChanged::new(false))
                    }
                    Some((v, _)) => Err(Error::type_mismatch("tuple (List)", Some(format!("{:?}", v)))),
                    None => Err(Error::missing_value()),
                }
            }

            fn update<Doc: ReadDoc>(
                &mut self,
                doc: &Doc,
                obj: impl AsRef<ObjId>,
                prop: impl Into<Prop>,
            ) -> Result<Self::Changes> {
                let prop: Prop = prop.into();
                let obj = obj.as_ref();
                match doc.get(obj, prop)? {
                    Some((Value::Object(ObjType::List), id)) => {
                        let mut any_changed = false;
                        $(
                            let changes = self.$idx.update(doc, &id, $idx)
                                .map_err(|e| e.with_index($idx))?;
                            if changes.any() {
                                any_changed = true;
                            }
                        )+
                        Ok(PrimitiveChanged::new(any_changed))
                    }
                    Some((v, _)) => Err(Error::type_mismatch("tuple (List)", Some(format!("{:?}", v)))),
                    None => Err(Error::missing_value()),
                }
            }

            fn update_at<Doc: ReadDoc>(
                &mut self,
                doc: &Doc,
                obj: impl AsRef<ObjId>,
                prop: impl Into<Prop>,
                heads: &[ChangeHash],
            ) -> Result<Self::Changes> {
                let prop: Prop = prop.into();
                let obj = obj.as_ref();
                match doc.get_at(obj, prop, heads)? {
                    Some((Value::Object(ObjType::List), id)) => {
                        let mut any_changed = false;
                        $(
                            let changes = self.$idx.update_at(doc, &id, $idx, heads)
                                .map_err(|e| e.with_index($idx))?;
                            if changes.any() {
                                any_changed = true;
                            }
                        )+
                        Ok(PrimitiveChanged::new(any_changed))
                    }
                    Some((v, _)) => Err(Error::type_mismatch("tuple (List)", Some(format!("{:?}", v)))),
                    None => Err(Error::missing_value()),
                }
            }
        }
    };

    // Helper to count tuple elements
    (@count $T:ident) => { 1usize };
    (@count $T:ident $($rest:ident)+) => { 1usize + impl_tuple!(@count $($rest)+) };
}

// Implement for tuples of sizes 1-16
impl_tuple!(0: T0);
impl_tuple!(0: T0, 1: T1);
impl_tuple!(0: T0, 1: T1, 2: T2);
impl_tuple!(0: T0, 1: T1, 2: T2, 3: T3);
impl_tuple!(0: T0, 1: T1, 2: T2, 3: T3, 4: T4);
impl_tuple!(0: T0, 1: T1, 2: T2, 3: T3, 4: T4, 5: T5);
impl_tuple!(0: T0, 1: T1, 2: T2, 3: T3, 4: T4, 5: T5, 6: T6);
impl_tuple!(0: T0, 1: T1, 2: T2, 3: T3, 4: T4, 5: T5, 6: T6, 7: T7);
impl_tuple!(0: T0, 1: T1, 2: T2, 3: T3, 4: T4, 5: T5, 6: T6, 7: T7, 8: T8);
impl_tuple!(0: T0, 1: T1, 2: T2, 3: T3, 4: T4, 5: T5, 6: T6, 7: T7, 8: T8, 9: T9);
impl_tuple!(0: T0, 1: T1, 2: T2, 3: T3, 4: T4, 5: T5, 6: T6, 7: T7, 8: T8, 9: T9, 10: T10);
impl_tuple!(0: T0, 1: T1, 2: T2, 3: T3, 4: T4, 5: T5, 6: T6, 7: T7, 8: T8, 9: T9, 10: T10, 11: T11);
impl_tuple!(0: T0, 1: T1, 2: T2, 3: T3, 4: T4, 5: T5, 6: T6, 7: T7, 8: T8, 9: T9, 10: T10, 11: T11, 12: T12);
impl_tuple!(0: T0, 1: T1, 2: T2, 3: T3, 4: T4, 5: T5, 6: T6, 7: T7, 8: T8, 9: T9, 10: T10, 11: T11, 12: T12, 13: T13);
impl_tuple!(0: T0, 1: T1, 2: T2, 3: T3, 4: T4, 5: T5, 6: T6, 7: T7, 8: T8, 9: T9, 10: T10, 11: T11, 12: T12, 13: T13, 14: T14);
impl_tuple!(0: T0, 1: T1, 2: T2, 3: T3, 4: T4, 5: T5, 6: T6, 7: T7, 8: T8, 9: T9, 10: T10, 11: T11, 12: T12, 13: T13, 14: T14, 15: T15);