Skip to main content

amethystate_core/primitives/
map_ops.rs

1use crate::AmeBackendSync;
2use crate::primitives::error::{ReactiveMapError, ReactiveMapResult};
3use crate::primitives::map_core::{ReactiveMapKey, ReactiveMapValue};
4use crate::{MapChange, ReactiveMapCore};
5use std::borrow::Borrow;
6
7use serde::de::DeserializeOwned;
8use std::fmt::Display;
9use std::str::FromStr;
10use std::sync::Arc;
11use uuid::Uuid;
12
13pub fn map_get<B, K, V>(backend: &B, path: &str, key: &K) -> ReactiveMapResult<Option<V>, B::Error>
14where
15    B: AmeBackendSync,
16    K: Display,
17    V: DeserializeOwned,
18{
19    Ok(backend.get(&format!("{}.{}", path, key))?)
20}
21
22pub fn map_contains_key<B, K, V>(
23    backend: &B,
24    path: &str,
25    key: &K,
26) -> ReactiveMapResult<bool, B::Error>
27where
28    B: AmeBackendSync,
29    K: Display,
30    V: DeserializeOwned,
31{
32    map_get::<B, K, V>(backend, path, key).map(|v| v.is_some())
33}
34
35pub fn map_entries<B, K, V>(backend: &B, path: &str) -> ReactiveMapResult<Vec<(K, V)>, B::Error>
36where
37    B: AmeBackendSync,
38    K: FromStr,
39    V: DeserializeOwned + Default,
40{
41    let prefix = format!("{}.", path);
42    let kvs = backend.scan_prefix(&prefix)?;
43
44    let mut results = Vec::new();
45
46    for (full_path, raw) in kvs {
47        if let Some(key_str) = full_path.strip_prefix(&prefix)
48            && let Ok(k) = K::from_str(key_str)
49            && let Ok(v) = backend.decode::<V>(raw.borrow())
50        {
51            results.push((k, v));
52        }
53    }
54
55    Ok(results)
56}
57
58pub fn map_len<B>(backend: &B, path: &str) -> ReactiveMapResult<usize, B::Error>
59where
60    B: AmeBackendSync,
61{
62    Ok(backend
63        .scan_prefix(&format!("{}.", path))
64        .map(|kvs| kvs.len())?)
65}
66
67pub fn map_set_existing<B, K, V>(
68    backend: &B,
69    core: &ReactiveMapCore<K, V>,
70    path: Arc<str>,
71    key: K,
72    value: &V,
73    notify_after_commit: bool,
74    source: Option<Uuid>,
75) -> ReactiveMapResult<(), B::Error>
76where
77    B: AmeBackendSync,
78    K: ReactiveMapKey,
79    V: ReactiveMapValue,
80{
81    let full_path = format!("{}.{}", path, key);
82    let old_value = match backend.get::<V>(&full_path)? {
83        Some(old_value) => old_value,
84        None => return Err(ReactiveMapError::KeyNotFound(key.to_string())),
85    };
86
87    let change = MapChange::Update {
88        key,
89        old_value,
90        new_value: value.clone(),
91        source,
92    };
93
94    map_apply_change(backend, core, path, change, notify_after_commit)
95}
96
97pub fn map_set_or_create<B, K, V>(
98    backend: &B,
99    core: &ReactiveMapCore<K, V>,
100    path: Arc<str>,
101    key: K,
102    value: &V,
103    notify_after_commit: bool,
104    source: Option<Uuid>,
105) -> ReactiveMapResult<(), B::Error>
106where
107    B: AmeBackendSync,
108    K: ReactiveMapKey,
109    V: ReactiveMapValue,
110{
111    let full_path = format!("{}.{}", path, key);
112    let old_value = backend.get::<V>(&full_path)?;
113    let change = if let Some(old_value) = old_value {
114        MapChange::Update {
115            key,
116            old_value,
117            new_value: value.clone(),
118            source,
119        }
120    } else {
121        MapChange::Insert {
122            key,
123            value: value.clone(),
124            source,
125        }
126    };
127
128    map_apply_change(backend, core, path, change, notify_after_commit)
129}
130
131pub fn map_remove<B, K, V>(
132    backend: &B,
133    core: &ReactiveMapCore<K, V>,
134    path: Arc<str>,
135    key: K,
136    notify_after_commit: bool,
137    source: Option<Uuid>,
138) -> ReactiveMapResult<Option<V>, B::Error>
139where
140    B: AmeBackendSync,
141    K: ReactiveMapKey,
142    V: ReactiveMapValue,
143{
144    let exists = core.cache.lock().unwrap().contains_key(&key);
145    if !exists {
146        return Ok(None);
147    }
148
149    let full_path = format!("{}.{}", path, key);
150    let old_value = backend.get::<V>(&full_path)?;
151    if let Some(old_value) = old_value {
152        let change = MapChange::Remove {
153            key,
154            old_value: old_value.clone(),
155            source,
156        };
157        map_apply_change(backend, core, path, change, notify_after_commit)?;
158        Ok(Some(old_value))
159    } else {
160        core.cache.lock().unwrap().remove(&key);
161        Ok(None)
162    }
163}
164
165pub fn map_clear<B, K, V>(
166    backend: &B,
167    core: &ReactiveMapCore<K, V>,
168    path: Arc<str>,
169    notify_after_commit: bool,
170    source: Option<Uuid>,
171) -> ReactiveMapResult<(), B::Error>
172where
173    B: AmeBackendSync,
174    K: ReactiveMapKey,
175    V: ReactiveMapValue,
176{
177    map_apply_change(
178        backend,
179        core,
180        path,
181        MapChange::Clear { source },
182        notify_after_commit,
183    )
184}
185
186pub fn map_apply_change<B, K, V>(
187    backend: &B,
188    core: &ReactiveMapCore<K, V>,
189    path: Arc<str>,
190    change: MapChange<K, V>,
191    notify_after_commit: bool,
192) -> ReactiveMapResult<(), B::Error>
193where
194    B: AmeBackendSync,
195    K: ReactiveMapKey,
196    V: ReactiveMapValue,
197{
198    let context_path: Arc<str> = match change.key() {
199        Some(key) => format!("{}.{}", path, key).into(),
200        None => path.clone(),
201    };
202
203    let processed = core
204        .run_interceptors(context_path, change)
205        .map_err(|_| ReactiveMapError::Intercepted)?;
206
207    match &processed {
208        MapChange::Insert { key, value, .. }
209        | MapChange::Update {
210            key,
211            new_value: value,
212            ..
213        } => {
214            backend.set_with_source(&format!("{}.{}", path, key), value, processed.source())?;
215        }
216        MapChange::Remove { key, .. } => {
217            backend.delete_with_source(&format!("{}.{}", path, key), processed.source())?;
218        }
219        MapChange::Clear { .. } => {
220            let prefix = format!("{}.", path);
221            let kvs = backend.scan_prefix(&prefix)?;
222            for (full_path, _) in kvs {
223                backend.delete_with_source(&full_path, processed.source())?;
224            }
225        }
226    }
227
228    map_apply_remote_change(core, &processed);
229    if notify_after_commit {
230        core.notify(&processed);
231    }
232
233    Ok(())
234}
235
236pub fn map_apply_remote_change<K, V>(core: &ReactiveMapCore<K, V>, change: &MapChange<K, V>)
237where
238    K: ReactiveMapKey,
239    V: ReactiveMapValue,
240{
241    let mut keys = core.cache.lock().unwrap();
242    match change {
243        MapChange::Insert { key, value, .. }
244        | MapChange::Update {
245            key,
246            new_value: value,
247            ..
248        } => {
249            keys.insert(key.clone(), value.clone());
250        }
251        MapChange::Remove { key, .. } => {
252            keys.remove(key);
253        }
254        MapChange::Clear { .. } => {
255            keys.clear();
256        }
257    }
258}