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}