morphix/observe/
snapshot.rs

1use std::marker::PhantomData;
2use std::ops::{Deref, DerefMut, Index, IndexMut};
3
4use serde::Serialize;
5
6use crate::{Adapter, Mutation, MutationKind, Observe, Observer};
7
8/// An observer that detects changes by comparing snapshots.
9///
10/// Unlike [`ShallowObserver`](super::ShallowObserver) which tracks any
11/// [`DerefMut`](std::ops::DerefMut) access as a mutation, `SnapshotObserver` creates an initial
12/// snapshot of the value and only reports mutation if the final value actually differs from the
13/// snapshot.
14///
15/// This observer is ideal for:
16/// - Small, cheaply cloneable types (e.g., `Uuid`, `DateTime`, small enums)
17/// - Types where [`DerefMut`] might be called without actual modification
18/// - Cases where you only care about actual value changes, not access patterns
19///
20/// ## Requirements
21///
22/// The observed type must implement:
23/// - [`Clone`] - for creating the snapshot (should be cheap)
24/// - [`PartialEq`] - for comparing the final value with the snapshot
25/// - [`Serialize`] - for generating the mutation
26///
27/// ## Example
28///
29/// ```
30/// use morphix::{JsonAdapter, Observe, Observer, observe};
31/// use serde::Serialize;
32/// use uuid::Uuid;
33///
34/// #[derive(Clone, PartialEq, Serialize, Observe)]
35/// struct Config {
36///     #[observe(snapshot)]
37///     id: Uuid,
38///     #[observe(snapshot)]
39///     status: Status,
40/// }
41///
42/// #[derive(Clone, PartialEq, Serialize)]
43/// enum Status {
44///     Active,
45///     Inactive,
46/// }
47///
48/// let mut config = Config {
49///     id: Uuid::new_v4(),
50///     status: Status::Active,
51/// };
52///
53/// let mutation = observe!(JsonAdapter, |mut config| {
54///     // `DerefMut` is called but value doesn't change
55///     config.status = Status::Active;
56/// }).unwrap();
57///
58/// assert_eq!(mutation, None); // No mutation because value didn't change
59/// ```
60///
61/// ## Performance Considerations
62///
63/// SnapshotObserver is most efficient when:
64/// - The type is cheap to clone (e.g., [`Copy`] types, small structs)
65/// - The type is cheap to compare (e.g., simple equality checks)
66/// - Changes are relatively rare compared to access
67///
68/// For large or expensive-to-clone types, consider using [ShallowObserver](super::ShallowObserver)
69/// or implementing a custom [Observe] trait.
70pub struct SnapshotObserver<'i, T> {
71    ptr: *mut T,
72    snapshot: T,
73    phantom: PhantomData<&'i mut T>,
74}
75
76impl<'i, T: Clone + PartialEq> Observer<'i> for SnapshotObserver<'i, T> {
77    #[inline]
78    fn observe(value: &'i mut T) -> Self {
79        Self {
80            ptr: value as *mut T,
81            snapshot: value.clone(),
82            phantom: PhantomData,
83        }
84    }
85
86    fn collect<A: Adapter>(this: Self) -> Result<Option<Mutation<A>>, A::Error>
87    where
88        T: Serialize,
89    {
90        Ok(if this.snapshot != *this {
91            Some(Mutation {
92                path_rev: vec![],
93                operation: MutationKind::Replace(A::serialize_value(&*this)?),
94            })
95        } else {
96            None
97        })
98    }
99}
100
101impl<'i, T> Deref for SnapshotObserver<'i, T> {
102    type Target = T;
103    fn deref(&self) -> &Self::Target {
104        unsafe { &*self.ptr }
105    }
106}
107
108impl<'i, T> DerefMut for SnapshotObserver<'i, T> {
109    fn deref_mut(&mut self) -> &mut Self::Target {
110        unsafe { &mut *self.ptr }
111    }
112}
113
114impl<'i, T: Index<U>, U> Index<U> for SnapshotObserver<'i, T> {
115    type Output = T::Output;
116    fn index(&self, index: U) -> &Self::Output {
117        (**self).index(index)
118    }
119}
120
121impl<'i, T: IndexMut<U>, U> IndexMut<U> for SnapshotObserver<'i, T> {
122    fn index_mut(&mut self, index: U) -> &mut Self::Output {
123        (**self).index_mut(index)
124    }
125}
126
127impl<'i, T: PartialEq<U>, U: ?Sized> PartialEq<U> for SnapshotObserver<'i, T> {
128    fn eq(&self, other: &U) -> bool {
129        (**self).eq(other)
130    }
131}
132
133impl<'i, T: PartialOrd<U>, U: ?Sized> PartialOrd<U> for SnapshotObserver<'i, T> {
134    fn partial_cmp(&self, other: &U) -> Option<std::cmp::Ordering> {
135        (**self).partial_cmp(other)
136    }
137}
138
139macro_rules! impl_assign_ops {
140    ($($trait:ident => $method:ident),* $(,)?) => {
141        $(
142            impl<'i, T: ::std::ops::$trait<U>, U> ::std::ops::$trait<U> for SnapshotObserver<'i, T> {
143                fn $method(&mut self, rhs: U) {
144                    (**self).$method(rhs);
145                }
146            }
147        )*
148    };
149}
150
151impl_assign_ops! {
152    AddAssign => add_assign,
153    SubAssign => sub_assign,
154    MulAssign => mul_assign,
155    DivAssign => div_assign,
156    RemAssign => rem_assign,
157    BitAndAssign => bitand_assign,
158    BitOrAssign => bitor_assign,
159    BitXorAssign => bitxor_assign,
160    ShlAssign => shl_assign,
161    ShrAssign => shr_assign,
162}
163
164macro_rules! impl_ops_copy {
165    ($($trait:ident => $method:ident),* $(,)?) => {
166        $(
167            impl<'i, T: Copy + ::std::ops::$trait<U>, U> ::std::ops::$trait<U> for SnapshotObserver<'i, T> {
168                type Output = T::Output;
169                fn $method(self, rhs: U) -> Self::Output {
170                    (*self).$method(rhs)
171                }
172            }
173        )*
174    };
175}
176
177impl_ops_copy! {
178    Add => add,
179    Sub => sub,
180    Mul => mul,
181    Div => div,
182    Rem => rem,
183    BitAnd => bitand,
184    BitOr => bitor,
185    BitXor => bitxor,
186    Shl => shl,
187    Shr => shr,
188}
189
190macro_rules! impl_observe {
191    ($($ty:ty $(=> $target:ty)?),* $(,)?) => {
192        $(
193            impl Observe for $ty {
194                type Observer<'i> = SnapshotObserver<'i, $ty>
195                where
196                    Self: 'i;
197            }
198        )*
199    };
200}
201
202impl_observe! {
203    usize, u8, u16, u32, u64, u128, isize, i8, i16, i32, i64, i128, f32, f64, bool,
204}