morphix/
mutation.rs

1use std::fmt::Debug;
2
3use crate::{Adapter, MutationError, Path};
4
5/// A mutation representing a change to a value at a specific path.
6///
7/// `Mutation` captures both the location where a change occurred (via `path`) and the kind of
8/// change that was made (via `operation`). Mutations can be applied to values to reproduce the
9/// changes they represent.
10///
11/// ## Path Representation
12///
13/// The path is stored in reverse order for efficiency during collection.
14/// For example, a change at `foo.bar.baz` would have `path = ["baz", "bar", "foo"]`.
15///
16/// ## Example
17///
18/// ```
19/// use morphix::{JsonAdapter, Mutation, MutationKind};
20/// use serde_json::json;
21///
22/// // A mutation that replaces the value at path "user.name"
23/// let mutation = Mutation::<JsonAdapter> {
24///     path: vec!["user".into(), "name".into()].into(),
25///     kind: MutationKind::Replace(json!("Alice")),
26/// };
27///
28/// // Apply the mutation to a JSON value
29/// let mut data = json!({"user": {"name": "Bob", "age": 30}});
30/// mutation.apply(&mut data).unwrap();
31/// assert_eq!(data, json!({"user": {"name": "Alice", "age": 30}}));
32/// ```
33pub struct Mutation<A: Adapter> {
34    /// The path to the mutated value, stored in reverse order.
35    ///
36    /// An empty vec indicates a mutation at the root level.
37    pub path: Path<true>,
38
39    /// The kind of mutation that occurred.
40    pub kind: MutationKind<A>,
41}
42
43impl<A: Adapter> Mutation<A> {
44    /// Applies this mutation to a value.
45    ///
46    /// ## Errors
47    ///
48    /// - Returns [IndexError](MutationError::IndexError) if the path doesn't exist in the value.
49    /// - Returns [OperationError](MutationError::OperationError) if the mutation cannot be
50    ///   performed.
51    ///
52    /// # Example
53    ///
54    /// ```
55    /// use morphix::{Mutation, MutationKind, JsonAdapter};
56    /// use serde_json::json;
57    ///
58    /// let mut value = json!({"count": 0});
59    ///
60    /// Mutation::<JsonAdapter> {
61    ///     path: vec!["count".into()].into(),
62    ///     kind: MutationKind::Replace(json!(42)),
63    /// }
64    /// .apply(&mut value)
65    /// .unwrap();
66    ///
67    /// assert_eq!(value, json!({"count": 42}));
68    /// ```
69    pub fn apply(self, value: &mut A::Value) -> Result<(), MutationError> {
70        A::apply_mutation(value, self, &mut Default::default())
71    }
72
73    /// Coalesce many mutations as a single mutation.
74    ///
75    /// - Returns [`None`] if no mutations exist.
76    /// - Returns a single mutation if only one mutation exists.
77    /// - Returns a [`Batch`](MutationKind::Batch) mutation if multiple mutations exist.
78    pub fn coalesce(mut mutations: Vec<Mutation<A>>) -> Option<Mutation<A>> {
79        match mutations.len() {
80            0 => None,
81            1 => Some(mutations.swap_remove(0)),
82            _ => Some(Mutation {
83                path: vec![].into(),
84                kind: MutationKind::Batch(mutations),
85            }),
86        }
87    }
88}
89
90impl<A: Adapter> Debug for Mutation<A>
91where
92    A::Value: Debug,
93{
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        f.debug_struct("Mutation")
96            .field("path", &self.path.to_string())
97            .field("kind", &self.kind)
98            .finish()
99    }
100}
101
102impl<A: Adapter> Clone for Mutation<A>
103where
104    A::Value: Clone,
105{
106    fn clone(&self) -> Self {
107        Self {
108            path: self.path.clone(),
109            kind: self.kind.clone(),
110        }
111    }
112}
113
114impl<A: Adapter> PartialEq for Mutation<A>
115where
116    A::Value: PartialEq,
117{
118    fn eq(&self, other: &Self) -> bool {
119        self.path == other.path && self.kind == other.kind
120    }
121}
122
123impl<A: Adapter> Eq for Mutation<A> where A::Value: Eq {}
124
125/// The kind of mutation that occurred.
126///
127/// `MutationKind` represents the specific type of change made to a value.
128/// Different kinds enable optimizations and more precise change descriptions.
129///
130/// ## Variants
131///
132/// - [`Replace`](MutationKind::Replace): Complete replacement of a value
133/// - [`Append`](MutationKind::Append): Append operation for strings and vectors
134/// - [`Batch`](MutationKind::Batch): Multiple mutations combined
135///
136/// ## Example
137///
138/// ```
139/// use morphix::{JsonAdapter, Mutation, MutationKind, Observe, observe};
140/// use serde::Serialize;
141/// use serde_json::json;
142///
143/// #[derive(Serialize, Observe)]
144/// struct Document {
145///     title: String,
146///     content: String,
147///     tags: Vec<String>,
148/// }
149///
150/// let mut doc = Document {
151///     title: "Draft".to_string(),
152///     content: "Hello".to_string(),
153///     tags: vec!["todo".to_string()],
154/// };
155///
156/// let mutation = observe!(JsonAdapter, |mut doc| {
157///     doc.title = "Final".to_string();      // Replace
158///     doc.content.push_str(" World");       // Append
159///     doc.tags.push("done".to_string());    // Append
160/// }).unwrap().unwrap();
161///
162/// // The mutation contains a Batch with three kinds
163/// assert!(matches!(mutation.kind, MutationKind::Batch(_)));
164/// ```
165pub enum MutationKind<A: Adapter> {
166    /// `Replace` is the default mutation for [`DerefMut`](std::ops::DerefMut) operations.
167    ///
168    /// ## Examples
169    ///
170    /// ```
171    /// # #[derive(Default)]
172    /// # struct Foo {
173    /// #   a: FooA,
174    /// #   num: i32,
175    /// #   vec: Vec<i32>,
176    /// # }
177    /// # #[derive(Default)]
178    /// # struct FooA {
179    /// #   b: i32,
180    /// # }
181    /// # let mut foo = Foo::default();
182    /// foo.a.b = 1;        // Replace at .a.b
183    /// foo.num *= 2;       // Replace at .num
184    /// foo.vec.clear();    // Replace at .vec
185    /// ```
186    ///
187    /// ## Note
188    ///
189    /// If an operation can be represented as [`Append`](MutationKind::Append), it will be preferred
190    /// over `Replace` for efficiency.
191    Replace(A::Value),
192
193    /// `Append` represents adding data to the end of a string or vector. This is more efficient
194    /// than [`Replace`](MutationKind::Replace) because only the appended portion needs to be
195    /// serialized and transmitted.
196    ///
197    /// ## Examples
198    ///
199    /// ```
200    /// # #[derive(Default)]
201    /// # struct Foo {
202    /// #   a: FooA,
203    /// #   vec: Vec<i32>,
204    /// # }
205    /// # #[derive(Default)]
206    /// # struct FooA {
207    /// #   b: String,
208    /// # }
209    /// # let mut foo = Foo::default();
210    /// # let iter = vec![2, 3].into_iter();
211    /// foo.a.b += "text";          // Append to .a.b
212    /// foo.a.b.push_str("text");   // Append to .a.b
213    /// foo.vec.push(1);            // Append to .vec
214    /// foo.vec.extend(iter);       // Append to .vec
215    /// ```
216    Append(A::Value),
217
218    /// `Batch` combines multiple mutations that occurred during a single observation period. This
219    /// is automatically created when multiple independent changes are detected.
220    ///
221    /// ## Optimization
222    ///
223    /// The batch collector ([`BatchTree`](crate::BatchTree)) automatically optimizes mutations:
224    /// - Consecutive appends are merged
225    /// - Redundant changes are eliminated
226    /// - Nested paths are consolidated when possible
227    Batch(Vec<Mutation<A>>),
228}
229
230impl<A: Adapter> Debug for MutationKind<A>
231where
232    A::Value: Debug,
233{
234    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
235        match self {
236            MutationKind::Replace(value) => f.debug_tuple("Replace").field(value).finish(),
237            MutationKind::Append(value) => f.debug_tuple("Append").field(value).finish(),
238            MutationKind::Batch(mutations) => f.debug_tuple("Batch").field(mutations).finish(),
239        }
240    }
241}
242
243impl<A: Adapter> Clone for MutationKind<A>
244where
245    A::Value: Clone,
246{
247    fn clone(&self) -> Self {
248        match self {
249            MutationKind::Replace(value) => MutationKind::Replace(value.clone()),
250            MutationKind::Append(value) => MutationKind::Append(value.clone()),
251            MutationKind::Batch(mutations) => MutationKind::Batch(mutations.clone()),
252        }
253    }
254}
255
256impl<A: Adapter> PartialEq for MutationKind<A>
257where
258    A::Value: PartialEq,
259{
260    fn eq(&self, other: &Self) -> bool {
261        match (self, other) {
262            (MutationKind::Replace(a), MutationKind::Replace(b)) => a == b,
263            (MutationKind::Append(a), MutationKind::Append(b)) => a == b,
264            (MutationKind::Batch(a), MutationKind::Batch(b)) => a == b,
265            _ => false,
266        }
267    }
268}
269
270impl<A: Adapter> Eq for MutationKind<A> where A::Value: Eq {}
271
272#[cfg(test)]
273mod test {
274    use serde_json::json;
275
276    use super::*;
277    use crate::{JsonAdapter, MutationError};
278
279    #[test]
280    fn apply_set() {
281        let mut value = json!({"a": 1});
282        Mutation::<JsonAdapter> {
283            path: vec![].into(),
284            kind: MutationKind::Replace(json!({})),
285        }
286        .apply(&mut value)
287        .unwrap();
288        assert_eq!(value, json!({}));
289
290        let mut value = json!({});
291        Mutation::<JsonAdapter> {
292            path: vec!["a".into()].into(),
293            kind: MutationKind::Replace(json!(1)),
294        }
295        .apply(&mut value)
296        .unwrap();
297        assert_eq!(value, json!({"a": 1}));
298
299        let mut value = json!({"a": 1});
300        Mutation::<JsonAdapter> {
301            path: vec!["a".into()].into(),
302            kind: MutationKind::Replace(json!(2)),
303        }
304        .apply(&mut value)
305        .unwrap();
306        assert_eq!(value, json!({"a": 2}));
307
308        let error = Mutation::<JsonAdapter> {
309            path: vec!["a".into(), "b".into()].into(),
310            kind: MutationKind::Replace(json!(3)),
311        }
312        .apply(&mut json!({}))
313        .unwrap_err();
314        assert_eq!(
315            error,
316            MutationError::IndexError {
317                path: vec!["a".into()].into()
318            }
319        );
320
321        let error = Mutation::<JsonAdapter> {
322            path: vec!["a".into(), "b".into()].into(),
323            kind: MutationKind::Replace(json!(3)),
324        }
325        .apply(&mut json!({"a": 1}))
326        .unwrap_err();
327        assert_eq!(
328            error,
329            MutationError::IndexError {
330                path: vec!["a".into(), "b".into()].into(),
331            }
332        );
333
334        let error = Mutation::<JsonAdapter> {
335            path: vec!["a".into(), "b".into()].into(),
336            kind: MutationKind::Replace(json!(3)),
337        }
338        .apply(&mut json!({"a": []}))
339        .unwrap_err();
340        assert_eq!(
341            error,
342            MutationError::IndexError {
343                path: vec!["a".into(), "b".into()].into(),
344            }
345        );
346
347        let mut value = json!({"a": {}});
348        Mutation::<JsonAdapter> {
349            path: vec!["a".into(), "b".into()].into(),
350            kind: MutationKind::Replace(json!(3)),
351        }
352        .apply(&mut value)
353        .unwrap();
354        assert_eq!(value, json!({"a": {"b": 3}}));
355    }
356
357    #[test]
358    fn apply_append() {
359        let mut value = json!("2");
360        Mutation::<JsonAdapter> {
361            path: vec![].into(),
362            kind: MutationKind::Append(json!("34")),
363        }
364        .apply(&mut value)
365        .unwrap();
366        assert_eq!(value, json!("234"));
367
368        let mut value = json!([2]);
369        Mutation::<JsonAdapter> {
370            path: vec![].into(),
371            kind: MutationKind::Append(json!(["3", "4"])),
372        }
373        .apply(&mut value)
374        .unwrap();
375        assert_eq!(value, json!([2, "3", "4"]));
376
377        let error = Mutation::<JsonAdapter> {
378            path: vec![].into(),
379            kind: MutationKind::Append(json!(3)),
380        }
381        .apply(&mut json!(""))
382        .unwrap_err();
383        assert_eq!(
384            error,
385            MutationError::OperationError {
386                path: Default::default()
387            }
388        );
389
390        let error = Mutation::<JsonAdapter> {
391            path: vec![].into(),
392            kind: MutationKind::Append(json!("3")),
393        }
394        .apply(&mut json!({}))
395        .unwrap_err();
396        assert_eq!(error, MutationError::OperationError { path: vec![].into() });
397
398        let error = Mutation::<JsonAdapter> {
399            path: vec![].into(),
400            kind: MutationKind::Append(json!("3")),
401        }
402        .apply(&mut json!([]))
403        .unwrap_err();
404        assert_eq!(error, MutationError::OperationError { path: vec![].into() });
405
406        let error = Mutation::<JsonAdapter> {
407            path: vec![].into(),
408            kind: MutationKind::Append(json!([3])),
409        }
410        .apply(&mut json!(""))
411        .unwrap_err();
412        assert_eq!(error, MutationError::OperationError { path: vec![].into() });
413    }
414
415    #[test]
416    fn apply_batch() {
417        let mut value = json!({"a": {"b": {"c": {}}}});
418        Mutation::<JsonAdapter> {
419            path: vec![].into(),
420            kind: MutationKind::Batch(vec![]),
421        }
422        .apply(&mut value)
423        .unwrap();
424        assert_eq!(value, json!({"a": {"b": {"c": {}}}}));
425
426        let mut value = json!({"a": {"b": {"c": "1"}}});
427        let error = Mutation::<JsonAdapter> {
428            path: vec!["a".into(), "d".into()].into(),
429            kind: MutationKind::Batch(vec![]),
430        }
431        .apply(&mut value)
432        .unwrap_err();
433        assert_eq!(
434            error,
435            MutationError::IndexError {
436                path: vec!["a".into(), "d".into()].into(),
437            }
438        );
439
440        let mut value = json!({"a": {"b": {"c": "1"}}});
441        Mutation::<JsonAdapter> {
442            path: vec!["a".into()].into(),
443            kind: MutationKind::Batch(vec![
444                Mutation::<JsonAdapter> {
445                    path: vec!["b".into(), "c".into()].into(),
446                    kind: MutationKind::Append(json!("2")),
447                },
448                Mutation::<JsonAdapter> {
449                    path: vec!["d".into()].into(),
450                    kind: MutationKind::Replace(json!(3)),
451                },
452            ]),
453        }
454        .apply(&mut value)
455        .unwrap();
456        assert_eq!(value, json!({"a": {"b": {"c": "12"}, "d": 3}}));
457    }
458}