Skip to main content

morphix/impls/collections/
hash_map.rs

1//! Observer implementation for [`HashMap<K, V>`].
2
3use std::borrow::Borrow;
4use std::cell::UnsafeCell;
5use std::collections::hash_map::Entry;
6use std::collections::{HashMap, TryReserveError};
7use std::fmt::Debug;
8use std::hash::Hash;
9use std::iter::FusedIterator;
10use std::marker::PhantomData;
11use std::ops::{Deref, DerefMut, Index, IndexMut};
12
13use serde::Serialize;
14
15use crate::general::Snapshot;
16use crate::helper::macros::{default_impl_ref_observe, delegate_methods};
17use crate::helper::{AsDeref, AsDerefMut, ObserverState, Pointer, QuasiObserver, Succ, Unsigned, Zero};
18use crate::observe::{DefaultSpec, Observer, SerializeObserver};
19use crate::{MutationKind, Mutations, Observe, PathSegment};
20
21enum ValueState {
22    /// Key existed in the original map and was overwritten via [`insert`](HashMapObserver::insert).
23    Replaced,
24    /// Key is new (did not exist in the original map), added via
25    /// [`insert`](HashMapObserver::insert).
26    Inserted,
27    /// Key existed in the original map and was removed.
28    Deleted,
29}
30
31struct HashMapObserverState<K, O> {
32    mutated: bool,
33    diff: HashMap<K, ValueState>,
34    /// Boxed to ensure pointer stability: [`HashMap`] rehashing moves all entries to a new
35    /// allocation, which would invalidate references to inline values. [`Box`] adds a layer
36    /// of indirection so that only the pointer is moved, not the observer itself.
37    inner: UnsafeCell<HashMap<K, Box<O>>>,
38}
39
40impl<K, O> Default for HashMapObserverState<K, O> {
41    fn default() -> Self {
42        Self {
43            mutated: false,
44            diff: Default::default(),
45            inner: Default::default(),
46        }
47    }
48}
49
50impl<K, O> ObserverState for HashMapObserverState<K, O>
51where
52    K: Clone + Eq + Hash,
53    O: QuasiObserver<InnerDepth = Zero, Head: Sized>,
54{
55    type Target = HashMap<K, O::Head>;
56
57    fn invalidate(this: &mut Self, map: &Self::Target) {
58        if !this.mutated {
59            this.mutated = true;
60            for key in map.keys() {
61                this.mark_deleted(key.clone());
62            }
63        }
64        this.inner.get_mut().clear();
65    }
66}
67
68impl<K, O> HashMapObserverState<K, O>
69where
70    K: Eq + Hash,
71{
72    fn mark_deleted(&mut self, key: K) {
73        self.inner.get_mut().remove(&key);
74        match self.diff.entry(key) {
75            Entry::Occupied(mut e) => {
76                if matches!(e.get(), ValueState::Inserted) {
77                    e.remove();
78                } else {
79                    e.insert(ValueState::Deleted);
80                }
81            }
82            Entry::Vacant(e) => {
83                e.insert(ValueState::Deleted);
84            }
85        }
86    }
87}
88
89/// Iterator produced by [`HashMapObserver::extract_if`].
90pub struct ExtractIf<'a, K, V, O, F>
91where
92    F: FnMut(&K, &mut V) -> bool,
93{
94    inner: std::collections::hash_map::ExtractIf<'a, K, V, F>,
95    state: Option<&'a mut HashMapObserverState<K, O>>,
96}
97
98impl<K, V, O, F> Iterator for ExtractIf<'_, K, V, O, F>
99where
100    K: Clone + Eq + Hash,
101    F: FnMut(&K, &mut V) -> bool,
102{
103    type Item = (K, V);
104
105    fn next(&mut self) -> Option<Self::Item> {
106        let (key, value) = self.inner.next()?;
107        if let Some(state) = &mut self.state {
108            state.mark_deleted(key.clone());
109        }
110        Some((key, value))
111    }
112
113    fn size_hint(&self) -> (usize, Option<usize>) {
114        self.inner.size_hint()
115    }
116}
117
118impl<K, V, O, F> FusedIterator for ExtractIf<'_, K, V, O, F>
119where
120    K: Clone + Eq + Hash,
121    F: FnMut(&K, &mut V) -> bool,
122{
123}
124
125impl<K, V, O, F> Debug for ExtractIf<'_, K, V, O, F>
126where
127    K: Debug,
128    V: Debug,
129    F: FnMut(&K, &mut V) -> bool,
130{
131    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132        self.inner.fmt(f)
133    }
134}
135
136/// Observer implementation for [`HashMap<K, V>`].
137///
138/// ## Limitations
139///
140/// Most methods (e.g. [`insert`](Self::insert), [`remove`](Self::remove),
141/// [`get_mut`](Self::get_mut)) require `K: Clone` because the observer maintains its own
142/// [`HashMap`] of cloned keys to track per-key observers independently of the observed map's
143/// internal storage.
144pub struct HashMapObserver<K, O, S: ?Sized, D = Zero> {
145    ptr: Pointer<S>,
146    state: HashMapObserverState<K, O>,
147    phantom: PhantomData<D>,
148}
149
150impl<K, O, S: ?Sized, D> Deref for HashMapObserver<K, O, S, D> {
151    type Target = Pointer<S>;
152
153    fn deref(&self) -> &Self::Target {
154        &self.ptr
155    }
156}
157
158impl<K, O, S: ?Sized, D> DerefMut for HashMapObserver<K, O, S, D> {
159    fn deref_mut(&mut self) -> &mut Self::Target {
160        std::ptr::from_mut(self).expose_provenance();
161        Pointer::invalidate(&mut self.ptr);
162        &mut self.ptr
163    }
164}
165
166impl<K, O, S: ?Sized, D> QuasiObserver for HashMapObserver<K, O, S, D>
167where
168    K: Clone + Eq + Hash,
169    D: Unsigned,
170    S: AsDeref<D, Target = HashMap<K, O::Head>>,
171    O: Observer<InnerDepth = Zero, Head: Sized>,
172{
173    type Head = S;
174    type OuterDepth = Succ<Zero>;
175    type InnerDepth = D;
176
177    fn invalidate(this: &mut Self) {
178        ObserverState::invalidate(&mut this.state, (*this.ptr).as_deref());
179    }
180}
181
182impl<K, O, S: ?Sized, D> Observer for HashMapObserver<K, O, S, D>
183where
184    D: Unsigned,
185    S: AsDeref<D, Target = HashMap<K, O::Head>>,
186    O: Observer<InnerDepth = Zero>,
187    O::Head: Sized,
188    K: Clone + Eq + Hash,
189{
190    fn observe(head: &mut Self::Head) -> Self {
191        let this = Self {
192            ptr: Pointer::new(head),
193            state: Default::default(),
194            phantom: PhantomData,
195        };
196        Pointer::register_state::<_, D>(&this.ptr, &this.state);
197        this
198    }
199
200    unsafe fn relocate(this: &mut Self, head: &mut Self::Head) {
201        Pointer::set(this, head);
202    }
203}
204
205impl<K, O, S: ?Sized, D> HashMapObserver<K, O, S, D>
206where
207    D: Unsigned,
208    S: AsDerefMut<D, Target = HashMap<K, O::Head>>,
209    O: Observer<InnerDepth = Zero> + SerializeObserver,
210    O::Head: Serialize + Sized + 'static,
211    K: Serialize + Clone + Eq + Hash + Into<PathSegment> + 'static,
212{
213    unsafe fn partial_flush(&mut self) -> Mutations {
214        let diff = std::mem::take(&mut self.state.diff);
215        let mut inner = std::mem::take(self.state.inner.get_mut());
216        let mut mutations = Mutations::new();
217        for (key, value_state) in diff {
218            match value_state {
219                ValueState::Deleted => {
220                    #[cfg(feature = "delete")]
221                    mutations.insert(key, MutationKind::Delete);
222                    #[cfg(not(feature = "delete"))]
223                    return Mutations::replace((*self).untracked_ref());
224                }
225                ValueState::Replaced | ValueState::Inserted => {
226                    inner.remove(&key);
227                    let value = (*self)
228                        .untracked_ref()
229                        .get(&key)
230                        .expect("replaced key not found in observed map");
231                    mutations.insert(key, Mutations::replace(value));
232                }
233            }
234        }
235        for (key, mut ob) in inner {
236            let value = self
237                .untracked_mut()
238                .get_mut(&key)
239                .expect("observer key not found in observed map");
240            unsafe { O::relocate(&mut ob, value) }
241            mutations.insert(key, unsafe { O::flush(&mut ob) });
242        }
243        mutations
244    }
245}
246
247impl<K, O, S: ?Sized, D> SerializeObserver for HashMapObserver<K, O, S, D>
248where
249    D: Unsigned,
250    S: AsDerefMut<D, Target = HashMap<K, O::Head>>,
251    O: Observer<InnerDepth = Zero> + SerializeObserver,
252    O::Head: Serialize + Sized + 'static,
253    K: Serialize + Clone + Eq + Hash + Into<PathSegment> + 'static,
254{
255    unsafe fn flush(this: &mut Self) -> Mutations {
256        if !this.state.mutated {
257            return unsafe { this.partial_flush() };
258        }
259        this.state.mutated = false;
260        this.state.diff.clear();
261        this.state.inner.get_mut().clear();
262        Mutations::replace((*this).untracked_ref())
263    }
264
265    unsafe fn flat_flush(this: &mut Self) -> (Mutations, bool) {
266        if !this.state.mutated {
267            return (unsafe { this.partial_flush() }, false);
268        }
269        this.state.mutated = false;
270        this.state.inner.get_mut().clear();
271        // After DerefMut, diff contains only Deleted entries representing original keys.
272        // Emit Replace for each current key, Delete for original keys no longer present.
273        let mut diff = std::mem::take(&mut this.state.diff);
274        let map = (*this.ptr).as_deref();
275        let mut mutations = Mutations::new();
276        for (key, value) in map {
277            diff.remove(key);
278            mutations.insert(key.clone(), Mutations::replace(value));
279        }
280        for (key, _) in diff {
281            #[cfg(feature = "delete")]
282            mutations.insert(key, MutationKind::Delete);
283            #[cfg(not(feature = "delete"))]
284            unreachable!("delete feature is not enabled");
285        }
286        (mutations, true)
287    }
288}
289
290impl<K, O, S: ?Sized, D, V> HashMapObserver<K, O, S, D>
291where
292    D: Unsigned,
293    S: AsDerefMut<D, Target = HashMap<K, V>>,
294    O: Observer<InnerDepth = Zero, Head = V>,
295    K: Clone + Eq + Hash,
296{
297    delegate_methods! { untracked_mut() as HashMap =>
298        pub fn reserve(&mut self, additional: usize);
299        pub fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError>;
300        pub fn shrink_to_fit(&mut self);
301        pub fn shrink_to(&mut self, min_capacity: usize);
302    }
303}
304
305impl<K, O, S: ?Sized, D> HashMapObserver<K, O, S, D>
306where
307    D: Unsigned,
308    S: AsDerefMut<D, Target = HashMap<K, O::Head>>,
309    O: Observer<InnerDepth = Zero>,
310    O::Head: Sized,
311    K: Clone + Eq + Hash,
312{
313    /// See [`HashMap::get`].
314    pub fn get<Q>(&self, key: &Q) -> Option<&O>
315    where
316        K: Borrow<Q>,
317        Q: Eq + Hash + ?Sized,
318    {
319        let key_cloned = (*self.ptr).as_deref().get_key_value(key)?.0.clone();
320        let value = unsafe { Pointer::as_mut(&self.ptr) }.as_deref_mut().get_mut(key)?;
321        match unsafe { (*self.state.inner.get()).entry(key_cloned) } {
322            Entry::Occupied(occupied) => {
323                let ob = occupied.into_mut().as_mut();
324                unsafe { O::relocate(ob, value) }
325                Some(ob)
326            }
327            Entry::Vacant(vacant) => Some(vacant.insert(Box::new(O::observe(value)))),
328        }
329    }
330}
331
332impl<K, O, S: ?Sized, D> HashMapObserver<K, O, S, D>
333where
334    D: Unsigned,
335    S: AsDerefMut<D, Target = HashMap<K, O::Head>>,
336    O: Observer<InnerDepth = Zero>,
337    O::Head: Sized,
338    K: Clone + Eq + Hash,
339{
340    fn __force_all(&mut self) -> &mut HashMap<K, Box<O>> {
341        let map = (*self.ptr).as_deref_mut();
342        let inner = self.state.inner.get_mut();
343        for (key, value) in map.iter_mut() {
344            match inner.entry(key.clone()) {
345                Entry::Occupied(occupied) => {
346                    let observer = occupied.into_mut().as_mut();
347                    unsafe { O::relocate(observer, value) }
348                }
349                Entry::Vacant(vacant) => {
350                    vacant.insert(Box::new(O::observe(value)));
351                }
352            }
353        }
354        inner
355    }
356
357    /// See [`HashMap::get_mut`].
358    pub fn get_mut<Q>(&mut self, key: &Q) -> Option<&mut O>
359    where
360        K: Borrow<Q> + Eq + Hash,
361        Q: Eq + Hash + ?Sized,
362    {
363        let key_cloned = (*self.ptr).as_deref().get_key_value(key)?.0.clone();
364        let value = (*self.ptr).as_deref_mut().get_mut(key)?;
365        match self.state.inner.get_mut().entry(key_cloned) {
366            Entry::Occupied(occupied) => {
367                let ob = occupied.into_mut().as_mut();
368                unsafe { O::relocate(ob, value) }
369                Some(ob)
370            }
371            Entry::Vacant(vacant) => Some(vacant.insert(Box::new(O::observe(value)))),
372        }
373    }
374
375    /// See [`HashMap::clear`].
376    pub fn clear(&mut self) {
377        self.state.inner.get_mut().clear();
378        if (*self).untracked_ref().is_empty() {
379            self.untracked_mut().clear()
380        } else {
381            self.tracked_mut().clear()
382        }
383    }
384
385    /// See [`HashMap::insert`].
386    pub fn insert(&mut self, key: K, value: O::Head) -> Option<O::Head>
387    where
388        K: Eq + Hash,
389    {
390        if self.state.mutated {
391            return self.tracked_mut().insert(key, value);
392        }
393        let key_cloned = key.clone();
394        let old_value = (*self.ptr).as_deref_mut().insert(key_cloned, value);
395        self.state.inner.get_mut().remove(&key);
396        match self.state.diff.entry(key) {
397            Entry::Occupied(mut e) => {
398                if matches!(e.get(), ValueState::Deleted) {
399                    e.insert(ValueState::Replaced);
400                }
401            }
402            Entry::Vacant(e) => {
403                if old_value.is_some() {
404                    e.insert(ValueState::Replaced);
405                } else {
406                    e.insert(ValueState::Inserted);
407                }
408            }
409        }
410        old_value
411    }
412
413    /// See [`HashMap::remove`].
414    pub fn remove<Q>(&mut self, key: &Q) -> Option<O::Head>
415    where
416        K: Borrow<Q> + Eq + Hash,
417        Q: Eq + Hash + ?Sized,
418    {
419        if self.state.mutated {
420            return self.tracked_mut().remove(key);
421        }
422        let (key, old_value) = (*self.ptr).as_deref_mut().remove_entry(key)?;
423        self.state.mark_deleted(key);
424        Some(old_value)
425    }
426
427    /// See [`HashMap::remove_entry`].
428    pub fn remove_entry<Q>(&mut self, key: &Q) -> Option<(K, O::Head)>
429    where
430        K: Borrow<Q> + Eq + Hash,
431        Q: Eq + Hash + ?Sized,
432    {
433        if self.state.mutated {
434            return self.tracked_mut().remove_entry(key);
435        }
436        let (key, old_value) = (*self.ptr).as_deref_mut().remove_entry(key)?;
437        self.state.mark_deleted(key.clone());
438        Some((key, old_value))
439    }
440
441    /// See [`HashMap::retain`].
442    pub fn retain<F>(&mut self, mut f: F)
443    where
444        K: Eq + Hash,
445        F: FnMut(&K, &mut O::Head) -> bool,
446    {
447        self.extract_if(|k, v| !f(k, v)).for_each(drop);
448    }
449
450    /// See [`HashMap::extract_if`].
451    pub fn extract_if<F>(&mut self, pred: F) -> ExtractIf<'_, K, O::Head, O, F>
452    where
453        K: Eq + Hash,
454        F: FnMut(&K, &mut O::Head) -> bool,
455    {
456        let inner = (*self.ptr).as_deref_mut().extract_if(pred);
457        let state = if self.state.mutated {
458            None
459        } else {
460            Some(&mut self.state)
461        };
462        ExtractIf { inner, state }
463    }
464
465    /// See [`HashMap::iter_mut`].
466    pub fn iter_mut(&mut self) -> impl Iterator<Item = (&K, &mut O)> + '_
467    where
468        K: Eq + Hash,
469    {
470        self.__force_all().iter_mut().map(|(k, v)| (k, v.as_mut()))
471    }
472
473    /// See [`HashMap::values_mut`].
474    pub fn values_mut(&mut self) -> impl Iterator<Item = &mut O> + '_
475    where
476        K: Eq + Hash,
477    {
478        self.__force_all().values_mut().map(|v| v.as_mut())
479    }
480}
481
482impl<K, V, O, S: ?Sized, D> Debug for HashMapObserver<K, O, S, D>
483where
484    K: Clone + Eq + Hash,
485    D: Unsigned,
486    S: AsDeref<D, Target = HashMap<K, V>>,
487    O: Observer<InnerDepth = Zero, Head = V>,
488    HashMap<K, V>: Debug,
489{
490    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
491        f.debug_tuple("HashMapObserver").field(&self.untracked_ref()).finish()
492    }
493}
494
495impl<K, V, O, S: ?Sized, D> PartialEq<HashMap<K, V>> for HashMapObserver<K, O, S, D>
496where
497    K: Clone + Eq + Hash,
498    D: Unsigned,
499    S: AsDeref<D, Target = HashMap<K, V>>,
500    O: Observer<InnerDepth = Zero, Head = V>,
501    HashMap<K, V>: PartialEq,
502{
503    fn eq(&self, other: &HashMap<K, V>) -> bool {
504        self.untracked_ref().eq(other)
505    }
506}
507
508impl<K1, K2, V1, V2, O1, O2, S1: ?Sized, S2: ?Sized, D1, D2> PartialEq<HashMapObserver<K2, O2, S2, D2>>
509    for HashMapObserver<K1, O1, S1, D1>
510where
511    K1: Clone + Eq + Hash,
512    K2: Clone + Eq + Hash,
513    D1: Unsigned,
514    D2: Unsigned,
515    S1: AsDeref<D1, Target = HashMap<K1, V1>>,
516    S2: AsDeref<D2, Target = HashMap<K2, V2>>,
517    O1: Observer<InnerDepth = Zero, Head = V1>,
518    O2: Observer<InnerDepth = Zero, Head = V2>,
519    HashMap<K1, V1>: PartialEq<HashMap<K2, V2>>,
520{
521    fn eq(&self, other: &HashMapObserver<K2, O2, S2, D2>) -> bool {
522        self.untracked_ref().eq(other.untracked_ref())
523    }
524}
525
526impl<K, V, O, S: ?Sized, D> Eq for HashMapObserver<K, O, S, D>
527where
528    K: Clone + Eq + Hash,
529    D: Unsigned,
530    S: AsDeref<D, Target = HashMap<K, V>>,
531    O: Observer<InnerDepth = Zero, Head = V>,
532    HashMap<K, V>: Eq,
533{
534}
535
536impl<'q, K, O, S: ?Sized, D, V, Q: ?Sized> Index<&'q Q> for HashMapObserver<K, O, S, D>
537where
538    D: Unsigned,
539    S: AsDerefMut<D, Target = HashMap<K, V>>,
540    O: Observer<InnerDepth = Zero, Head = V>,
541    K: Borrow<Q> + Clone + Eq + Hash,
542    Q: Eq + Hash,
543{
544    type Output = O;
545
546    fn index(&self, index: &'q Q) -> &Self::Output {
547        self.get(index).expect("no entry found for key")
548    }
549}
550
551impl<'q, K, O, S: ?Sized, D, V, Q: ?Sized> IndexMut<&'q Q> for HashMapObserver<K, O, S, D>
552where
553    D: Unsigned,
554    S: AsDerefMut<D, Target = HashMap<K, V>>,
555    O: Observer<InnerDepth = Zero, Head = V>,
556    K: Borrow<Q> + Clone + Eq + Hash,
557    Q: Eq + Hash,
558{
559    fn index_mut(&mut self, index: &'q Q) -> &mut Self::Output {
560        self.get_mut(index).expect("no entry found for key")
561    }
562}
563
564// TODO: this inserts elements one by one, which is much slower than `HashMap::extend`.
565// Consider a bulk-insert approach that updates `diff` in one pass.
566impl<K, O, S: ?Sized, D> Extend<(K, O::Head)> for HashMapObserver<K, O, S, D>
567where
568    D: Unsigned,
569    S: AsDerefMut<D, Target = HashMap<K, O::Head>>,
570    O: Observer<InnerDepth = Zero>,
571    O::Head: Sized,
572    K: Clone + Eq + Hash,
573{
574    fn extend<I: IntoIterator<Item = (K, O::Head)>>(&mut self, iter: I) {
575        for (key, value) in iter {
576            self.insert(key, value);
577        }
578    }
579}
580
581impl<K: Clone + Eq + Hash, V: Observe> Observe for HashMap<K, V> {
582    type Observer<'ob, S, D>
583        = HashMapObserver<K, V::Observer<'ob, V, Zero>, S, D>
584    where
585        Self: 'ob,
586        D: Unsigned,
587        S: AsDerefMut<D, Target = Self> + ?Sized + 'ob;
588
589    type Spec = DefaultSpec;
590}
591
592default_impl_ref_observe! {
593    impl [K, V] RefObserve for HashMap<K, V>;
594}
595
596impl<K, V> Snapshot for HashMap<K, V>
597where
598    K: Snapshot,
599    K::Snapshot: Eq + Hash,
600    V: Snapshot,
601{
602    type Snapshot = HashMap<K::Snapshot, V::Snapshot>;
603
604    fn to_snapshot(&self) -> Self::Snapshot {
605        self.iter()
606            .map(|(key, value)| (key.to_snapshot(), value.to_snapshot()))
607            .collect()
608    }
609
610    fn eq_snapshot(&self, snapshot: &Self::Snapshot) -> bool {
611        self.len() == snapshot.len()
612            && self
613                .iter()
614                .zip(snapshot.iter())
615                .all(|((key_a, value_a), (key_b, value_b))| key_a.eq_snapshot(key_b) && value_a.eq_snapshot(value_b))
616    }
617}
618
619#[cfg(test)]
620mod tests {
621    use std::collections::HashMap;
622
623    use morphix_test_utils::*;
624    use serde_json::json;
625
626    use super::*;
627    use crate::adapter::Json;
628    use crate::observe::{ObserveExt, SerializeObserverExt};
629    use crate::{Mutation, MutationKind};
630
631    #[test]
632    fn remove_nonexistent_key() {
633        let mut map = HashMap::from([("a", "x".to_string())]);
634        let mut ob = map.__observe();
635        assert_eq!(ob.remove("nonexistent"), None);
636        let Json(mutation) = ob.flush().unwrap();
637        assert_eq!(mutation, None);
638    }
639
640    #[test]
641    fn insert_then_remove() {
642        let mut map = HashMap::from([("a", "x".to_string())]);
643        let mut ob = map.__observe();
644        assert_eq!(ob.insert("b", "y".to_string()), None);
645        assert_eq!(ob.remove("b"), Some("y".to_string()));
646        assert_eq!(ob.untracked_ref().len(), 1);
647        assert_eq!(ob.untracked_ref().get("a"), Some(&"x".to_string()));
648        let Json(mutation) = ob.flush().unwrap();
649        assert_eq!(mutation, None);
650    }
651
652    #[test]
653    fn remove_then_insert() {
654        let mut map = HashMap::from([("a", "x".to_string())]);
655        let mut ob = map.__observe();
656        assert_eq!(ob.remove("a"), Some("x".to_string()));
657        assert_eq!(ob.insert("a", "y".to_string()), None);
658        assert_eq!(ob.untracked_ref().get("a"), Some(&"y".to_string()));
659        let Json(mutation) = ob.flush().unwrap();
660        assert_eq!(mutation, Some(replace!(a, json!("y"))));
661    }
662
663    #[test]
664    fn remove_entry() {
665        let mut map = HashMap::from([("a", "x".to_string()), ("b", "y".to_string())]);
666        let mut ob = map.__observe();
667        assert_eq!(ob.remove_entry("a"), Some(("a", "x".to_string())));
668        assert_eq!(ob.untracked_ref().len(), 1);
669        let Json(mutation) = ob.flush().unwrap();
670        assert_eq!(mutation, Some(delete!(a)));
671    }
672
673    #[test]
674    fn retain() {
675        let mut map = HashMap::from([("a", 1i32), ("b", 2), ("c", 3)]);
676        let mut ob = map.__observe();
677        ob.retain(|_, v| *v % 2 != 0);
678        assert_eq!(ob.untracked_ref(), &HashMap::from([("a", 1), ("c", 3)]));
679        let Json(mutation) = ob.flush().unwrap();
680        assert_eq!(mutation, Some(delete!(b)));
681    }
682
683    #[test]
684    fn extend() {
685        let mut map = HashMap::from([("a", "x".to_string())]);
686        let mut ob = map.__observe();
687        ob.extend([("b", "y".to_string()), ("c", "z".to_string())]);
688        assert_eq!(ob.untracked_ref().len(), 3);
689        let Json(mutation) = ob.flush().unwrap();
690        assert!(mutation.is_some());
691    }
692
693    #[test]
694    fn extract_if() {
695        let mut map = HashMap::from([("a", 1i32), ("b", 2), ("c", 3), ("d", 4)]);
696        let mut ob = map.__observe();
697        let extracted: HashMap<_, _> = ob.extract_if(|_, v| *v % 2 == 0).collect();
698        assert_eq!(extracted, HashMap::from([("b", 2), ("d", 4)]));
699        assert_eq!(ob.untracked_ref(), &HashMap::from([("a", 1), ("c", 3)]));
700        let Json(mutation) = ob.flush().unwrap();
701        assert!(mutation.is_some());
702        let mutation = mutation.unwrap();
703        assert!(matches!(mutation.kind, MutationKind::Batch(_)));
704    }
705
706    #[test]
707    fn extract_if_insert_then_extract() {
708        let mut map = HashMap::from([("a", 1i32)]);
709        let mut ob = map.__observe();
710        ob.insert("b", 2);
711        // extract "b" which was just inserted: net no-op
712        let extracted: HashMap<_, _> = ob.extract_if(|k, _| *k == "b").collect();
713        assert_eq!(extracted, HashMap::from([("b", 2)]));
714        let Json(mutation) = ob.flush().unwrap();
715        assert_eq!(mutation, None);
716    }
717
718    #[test]
719    fn get_mut_then_insert() {
720        let mut map = HashMap::from([("a", "x".to_string())]);
721        let mut ob = map.__observe();
722        ob.get_mut("a").unwrap().push_str(" world");
723        ob.insert("a", "bye".to_string());
724        assert_eq!(ob.untracked_ref().get("a"), Some(&"bye".to_string()));
725        let Json(mutation) = ob.flush().unwrap();
726        assert_eq!(mutation, Some(replace!(a, json!("bye"))));
727    }
728
729    #[test]
730    fn insert_then_get_mut() {
731        let mut map = HashMap::from([("a", "x".to_string())]);
732        let mut ob = map.__observe();
733        ob.insert("b", "hello".to_string());
734        ob.get_mut("b").unwrap().push_str(" world");
735        assert_eq!(ob.untracked_ref().get("b"), Some(&"hello world".to_string()));
736        let Json(mutation) = ob.flush().unwrap();
737        assert_eq!(mutation, Some(replace!(b, json!("hello world"))));
738    }
739
740    #[test]
741    fn iter_mut() {
742        let mut map = HashMap::from([("a", "x".to_string()), ("b", "y".to_string())]);
743        let mut ob = map.__observe();
744        for (_, v) in ob.iter_mut() {
745            v.push_str("!");
746        }
747        assert_eq!(ob.untracked_ref().get("a"), Some(&"x!".to_string()));
748        assert_eq!(ob.untracked_ref().get("b"), Some(&"y!".to_string()));
749        let Json(mutation) = ob.flush().unwrap();
750        assert!(mutation.is_some());
751        let mutation = mutation.unwrap();
752        assert!(matches!(mutation.kind, MutationKind::Batch(_)));
753        if let MutationKind::Batch(batch) = mutation.kind {
754            assert_eq!(batch.len(), 2);
755            for m in &batch {
756                assert_eq!(m.kind, MutationKind::Append(json!("!")));
757            }
758        }
759    }
760
761    #[test]
762    fn values_mut() {
763        let mut map = HashMap::from([("a", "hello".to_string()), ("b", "world".to_string())]);
764        let mut ob = map.__observe();
765        for v in ob.values_mut() {
766            v.push('~');
767        }
768        let Json(mutation) = ob.flush().unwrap();
769        assert!(mutation.is_some());
770        let mutation = mutation.unwrap();
771        assert!(matches!(mutation.kind, MutationKind::Batch(_)));
772        if let MutationKind::Batch(batch) = mutation.kind {
773            assert_eq!(batch.len(), 2);
774            for m in &batch {
775                assert_eq!(m.kind, MutationKind::Append(json!("~")));
776            }
777        }
778    }
779
780    fn sorted_mutations(mutation: Option<Mutation<serde_json::Value>>) -> Vec<Mutation<serde_json::Value>> {
781        let Some(mutation) = mutation else {
782            return vec![];
783        };
784        let mut batch = match mutation.kind {
785            MutationKind::Batch(batch) => batch,
786            _ => vec![mutation],
787        };
788        batch.sort_by(|a, b| a.path.cmp(&b.path));
789        batch
790    }
791
792    #[test]
793    fn flush_flatten_no_change() {
794        let mut map = HashMap::from([("a", 1i32), ("b", 2)]);
795        let mut ob = map.__observe();
796        let Json(mutation) = ob.flat_flush().unwrap();
797        assert_eq!(mutation, None);
798    }
799
800    #[test]
801    fn flush_flatten_deref_mut_only() {
802        let mut map = HashMap::from([("a", 1i32), ("b", 2)]);
803        let mut ob = map.__observe();
804        **ob = HashMap::from([("a", 10), ("b", 20)]);
805        let Json(mutation) = ob.flat_flush().unwrap();
806        let batch = sorted_mutations(mutation);
807        assert_eq!(batch.len(), 2);
808        assert_eq!(batch[0], replace!(a, json!(10)));
809        assert_eq!(batch[1], replace!(b, json!(20)));
810    }
811
812    // Inserted key, then deref_mut to a value without that key → no Delete for the inserted key
813    #[test]
814    fn flush_flatten_inserted_then_absent() {
815        let mut map = HashMap::from([("a", 1i32)]);
816        let mut ob = map.__observe();
817        ob.insert("b", 2);
818        **ob = HashMap::from([("a", 10)]);
819        let Json(mutation) = ob.flat_flush().unwrap();
820        let batch = sorted_mutations(mutation);
821        assert_eq!(batch.len(), 1);
822        assert_eq!(batch[0], replace!(a, json!(10)));
823    }
824
825    // Inserted key, then deref_mut to a value with that key → Replace for the key
826    #[test]
827    fn flush_flatten_inserted_then_present() {
828        let mut map = HashMap::from([("a", 1i32)]);
829        let mut ob = map.__observe();
830        ob.insert("b", 2);
831        **ob = HashMap::from([("a", 10), ("b", 20)]);
832        let Json(mutation) = ob.flat_flush().unwrap();
833        let batch = sorted_mutations(mutation);
834        assert_eq!(batch.len(), 2);
835        assert_eq!(batch[0], replace!(a, json!(10)));
836        assert_eq!(batch[1], replace!(b, json!(20)));
837    }
838
839    // Deleted key, then deref_mut to a value without that key → Delete for the key
840    #[test]
841    fn flush_flatten_deleted_then_absent() {
842        let mut map = HashMap::from([("a", 1i32), ("b", 2)]);
843        let mut ob = map.__observe();
844        ob.remove("b");
845        **ob = HashMap::from([("a", 10)]);
846        let Json(mutation) = ob.flat_flush().unwrap();
847        let batch = sorted_mutations(mutation);
848        assert_eq!(batch.len(), 2);
849        assert_eq!(batch[0], replace!(a, json!(10)));
850        assert_eq!(batch[1], delete!(b));
851    }
852
853    // Deleted key, then deref_mut to a value with that key → Replace (not Delete)
854    #[test]
855    fn flush_flatten_deleted_then_present() {
856        let mut map = HashMap::from([("a", 1i32), ("b", 2)]);
857        let mut ob = map.__observe();
858        ob.remove("b");
859        **ob = HashMap::from([("a", 10), ("b", 20)]);
860        let Json(mutation) = ob.flat_flush().unwrap();
861        let batch = sorted_mutations(mutation);
862        assert_eq!(batch.len(), 2);
863        assert_eq!(batch[0], replace!(a, json!(10)));
864        assert_eq!(batch[1], replace!(b, json!(20)));
865    }
866
867    // Replaced key, then deref_mut to a value without that key → Delete for the key
868    #[test]
869    fn flush_flatten_replaced_then_absent() {
870        let mut map = HashMap::from([("a", 1i32), ("b", 2)]);
871        let mut ob = map.__observe();
872        ob.insert("b", 99);
873        **ob = HashMap::from([("a", 10)]);
874        let Json(mutation) = ob.flat_flush().unwrap();
875        let batch = sorted_mutations(mutation);
876        assert_eq!(batch.len(), 2);
877        assert_eq!(batch[0], replace!(a, json!(10)));
878        assert_eq!(batch[1], delete!(b));
879    }
880
881    // Replaced key, then deref_mut to a value with that key → Replace
882    #[test]
883    fn flush_flatten_replaced_then_present() {
884        let mut map = HashMap::from([("a", 1i32), ("b", 2)]);
885        let mut ob = map.__observe();
886        ob.insert("b", 99);
887        **ob = HashMap::from([("a", 10), ("b", 20)]);
888        let Json(mutation) = ob.flat_flush().unwrap();
889        let batch = sorted_mutations(mutation);
890        assert_eq!(batch.len(), 2);
891        assert_eq!(batch[0], replace!(a, json!(10)));
892        assert_eq!(batch[1], replace!(b, json!(20)));
893    }
894
895    // Without deref_mut, flat_flush returns granular mutations with is_replace=false
896    #[test]
897    fn flush_flatten_granular() {
898        let mut map = HashMap::from([("a", 1i32), ("b", 2)]);
899        let mut ob = map.__observe();
900        ob.insert("a", 10);
901        let Json(mutation) = ob.flat_flush().unwrap();
902        assert_eq!(mutation, Some(replace!(a, json!(10))));
903    }
904
905    // deref_mut replaces with entirely new keys
906    #[test]
907    fn flush_flatten_deref_mut_new_keys() {
908        let mut map = HashMap::from([("a", 1i32), ("b", 2)]);
909        let mut ob = map.__observe();
910        **ob = HashMap::from([("c", 30)]);
911        let Json(mutation) = ob.flat_flush().unwrap();
912        let batch = sorted_mutations(mutation);
913        assert_eq!(batch.len(), 3);
914        assert_eq!(batch[0], delete!(a));
915        assert_eq!(batch[1], delete!(b));
916        assert_eq!(batch[2], replace!(c, json!(30)));
917    }
918}