Skip to main content

datex_core/shared_values/
mutations.rs

1use crate::{
2    dif::{
3        update::{DIFKey, DIFUpdateData},
4        value::DIFValueContainer,
5    },
6    runtime::memory::Memory,
7    shared_values::{
8        observers::TransceiverId,
9        shared_container::{AccessError, SharedContainer},
10    },
11    values::{
12        core_value::CoreValue,
13        value_container::{ValueContainer, ValueKey},
14    },
15};
16use core::{cell::RefCell, ops::FnOnce, prelude::rust_2024::*};
17
18use crate::prelude::*;
19pub enum DIFUpdateDataOrMemory<'a> {
20    Update(&'a DIFUpdateData),
21    Memory(&'a RefCell<Memory>),
22}
23
24impl<'a> From<&'a DIFUpdateData> for DIFUpdateDataOrMemory<'a> {
25    fn from(update: &'a DIFUpdateData) -> Self {
26        DIFUpdateDataOrMemory::Update(update)
27    }
28}
29
30impl<'a> From<&'a RefCell<Memory>> for DIFUpdateDataOrMemory<'a> {
31    fn from(memory: &'a RefCell<Memory>) -> Self {
32        DIFUpdateDataOrMemory::Memory(memory)
33    }
34}
35
36impl SharedContainer {
37    /// Internal function that handles updates
38    /// - Checks if the reference is mutable
39    /// - Calls the provided handler to perform the update and get the DIFUpdateData
40    /// - Notifies observers with the update data
41    /// - Returns any AccessError encountered
42    fn handle_update<'a>(
43        &self,
44        _source_id: TransceiverId,
45        handler: impl FnOnce() -> Result<&'a DIFUpdateData, AccessError>,
46    ) -> Result<(), AccessError> {
47        if !self.is_mutable() {
48            return Err(AccessError::ImmutableReference);
49        }
50        let _update_data = handler()?;
51        // self.notify_observers(update_data.with_source(source_id));
52        Ok(())
53    }
54
55    fn assert_mutable(&self) -> Result<(), AccessError> {
56        if !self.is_mutable() {
57            return Err(AccessError::ImmutableReference);
58        }
59        Ok(())
60    }
61
62    /// Sets a property on the value if applicable (e.g. for maps)
63    pub fn try_set_property<'a>(
64        &self,
65        source_id: TransceiverId,
66        maybe_dif_update_data: Option<&DIFUpdateData>,
67        key: impl Into<ValueKey<'a>>,
68        val: ValueContainer,
69    ) -> Result<(), AccessError> {
70        self.assert_mutable()?;
71
72        let key = key.into();
73
74        let dif_update = match maybe_dif_update_data {
75            Some(update) => update,
76            None => &DIFUpdateData::set(
77                DIFKey::from_value_key(&key),
78                DIFValueContainer::from_value_container(&val),
79            ),
80        };
81
82        self.with_value_unchecked(|value| {
83            value.try_set_property(key, val.clone())
84        })?;
85
86        self.notify_observers(&dif_update.with_source(source_id));
87        Ok(())
88    }
89
90    /// Sets a value on the reference if it is mutable and the type is compatible.
91    pub fn try_replace(
92        &self,
93        source_id: TransceiverId,
94        maybe_dif_update_data: Option<&DIFUpdateData>,
95        value: impl Into<ValueContainer>,
96    ) -> Result<(), AccessError> {
97        self.assert_mutable()?;
98
99        // TODO #306: ensure type compatibility with allowed_type
100        let value_container = &value.into();
101
102        let dif_update = match maybe_dif_update_data {
103            Some(update) => update,
104            None => &DIFUpdateData::replace(
105                DIFValueContainer::from_value_container(value_container),
106            ),
107        };
108
109        self.with_value_unchecked(|core_value| {
110            // Set the value directly, ensuring it is a ValueContainer
111            core_value.inner =
112                value_container.to_value().borrow().inner.clone();
113        });
114
115        self.notify_observers(&dif_update.with_source(source_id));
116        Ok(())
117    }
118
119    /// Pushes a value to the reference if it is a list.
120    pub fn try_append_value<'a>(
121        &self,
122        source_id: TransceiverId,
123        maybe_dif_update_data: Option<&DIFUpdateData>,
124        value: impl Into<ValueContainer>,
125    ) -> Result<(), AccessError> {
126        self.assert_mutable()?;
127        let value_container = value.into();
128
129        let dif_update = match maybe_dif_update_data {
130            Some(update) => update,
131            None => &DIFUpdateData::append(
132                DIFValueContainer::from_value_container(&value_container),
133            ),
134        };
135
136        self.with_value_unchecked(move |core_value| {
137            match &mut core_value.inner {
138                CoreValue::List(list) => {
139                    list.push(value_container);
140                }
141                _ => {
142                    return Err(AccessError::InvalidOperation(format!(
143                        "Cannot push value to non-list value: {:?}",
144                        core_value
145                    )));
146                }
147            }
148
149            Ok(())
150        })?;
151
152        self.notify_observers(&dif_update.with_source(source_id));
153        Ok(())
154    }
155
156    /// Tries to delete a property from the reference if it is a map.
157    /// Notifies observers if successful.
158    pub fn try_delete_property<'a>(
159        &self,
160        source_id: TransceiverId,
161        maybe_dif_update_data: Option<&DIFUpdateData>,
162        key: impl Into<ValueKey<'a>>,
163    ) -> Result<(), AccessError> {
164        self.assert_mutable()?;
165        let key = key.into();
166
167        let dif_update = match maybe_dif_update_data {
168            Some(update) => update,
169            None => &DIFUpdateData::delete(DIFKey::from_value_key(&key)),
170        };
171
172        self.with_value_unchecked(|value| {
173            match value.inner {
174                CoreValue::Map(ref mut map) => {
175                    key.with_value_container(|key| map.delete(key))?;
176                }
177                CoreValue::List(ref mut list) => {
178                    if let Some(index) = key.try_as_index() {
179                        list.delete(index).map_err(|err| {
180                            AccessError::IndexOutOfBounds(err)
181                        })?;
182                    } else {
183                        return Err(AccessError::InvalidIndexKey);
184                    }
185                }
186                _ => {
187                    return Err(AccessError::InvalidOperation(format!(
188                        "Cannot delete property '{:?}' on non-map value: {:?}",
189                        key, value
190                    )));
191                }
192            }
193
194            Ok(())
195        })?;
196
197        self.notify_observers(&dif_update.with_source(source_id));
198        Ok(())
199    }
200
201    pub fn try_clear(
202        &self,
203        source_id: TransceiverId,
204    ) -> Result<(), AccessError> {
205        self.assert_mutable()?;
206
207        self.with_value_unchecked(|value| {
208            match value.inner {
209                CoreValue::Map(ref mut map) => {
210                    map.clear()?;
211                }
212                CoreValue::List(ref mut list) => {
213                    list.clear();
214                }
215                _ => {
216                    return Err(AccessError::InvalidOperation(format!(
217                        "Cannot clear non-list/map value: {:?}",
218                        value
219                    )));
220                }
221            }
222
223            Ok(())
224        })?;
225
226        self.notify_observers(&DIFUpdateData::clear().with_source(source_id));
227        Ok(())
228    }
229
230    pub fn try_list_splice<'a>(
231        &self,
232        source_id: TransceiverId,
233        maybe_dif_update_data: Option<&DIFUpdateData>,
234        range: core::ops::Range<u32>,
235        items: Vec<ValueContainer>,
236    ) -> Result<(), AccessError> {
237        self.assert_mutable()?;
238
239        let dif_update = match maybe_dif_update_data {
240            Some(update) => update,
241            None => &DIFUpdateData::list_splice(
242                range.clone(),
243                items
244                    .iter()
245                    .map(DIFValueContainer::from_value_container)
246                    .collect(),
247            ),
248        };
249
250        self.with_value_unchecked(|value| {
251            match value.inner {
252                CoreValue::List(ref mut list) => {
253                    list.splice(range, items);
254                }
255                _ => {
256                    return Err(AccessError::InvalidOperation(format!(
257                        "Cannot apply splice operation on non-list value: {:?}",
258                        value
259                    )));
260                }
261            }
262
263            Ok(())
264        })?;
265
266        self.notify_observers(&dif_update.with_source(source_id));
267        Ok(())
268    }
269}
270
271#[cfg(test)]
272mod tests {
273    use crate::{
274        prelude::*,
275        runtime::memory::Memory,
276        shared_values::{
277            pointer::Pointer,
278            shared_container::{
279                AccessError, IndexOutOfBoundsError, SharedContainer,
280                SharedContainerMutability,
281            },
282        },
283        values::{
284            core_values::{list::List, map::Map},
285            value_container::ValueContainer,
286        },
287    };
288    use core::{assert_matches, cell::RefCell};
289
290    #[test]
291    fn push() {
292        let list = vec![
293            ValueContainer::from(1),
294            ValueContainer::from(2),
295            ValueContainer::from(3),
296        ];
297        let list_ref =
298            SharedContainer::boxed_mut(List::from(list).into(), Pointer::NULL)
299                .unwrap();
300        list_ref
301            .try_append_value(0, None, ValueContainer::from(4))
302            .expect("Failed to push value to list");
303        let updated_value = list_ref.try_get_property(3).unwrap();
304        assert_eq!(updated_value, ValueContainer::from(4));
305
306        // Try to push to immutable value
307        let int_ref = SharedContainer::boxed(
308            List::from(vec![ValueContainer::from(42)]),
309            Pointer::NULL,
310        );
311        let result =
312            int_ref.try_append_value(0, None, ValueContainer::from(99));
313        assert_matches!(result, Err(AccessError::ImmutableReference));
314
315        // Try to push to non-list value
316        let int_ref =
317            SharedContainer::boxed_mut(42.into(), Pointer::NULL).unwrap();
318        let result =
319            int_ref.try_append_value(0, None, ValueContainer::from(99));
320        assert_matches!(result, Err(AccessError::InvalidOperation(_)));
321    }
322
323    #[test]
324    fn property() {
325        let map = Map::from(vec![
326            ("key1".to_string(), ValueContainer::from(1)),
327            ("key2".to_string(), ValueContainer::from(2)),
328        ]);
329        let map_ref = SharedContainer::boxed_mut(
330            ValueContainer::from(map),
331            Pointer::NULL,
332        )
333        .unwrap();
334        // Set existing property
335        map_ref
336            .try_set_property(0, None, "key1", ValueContainer::from(42))
337            .expect("Failed to set existing property");
338        let updated_value = map_ref.try_get_property("key1").unwrap();
339        assert_eq!(updated_value, 42.into());
340
341        // Set new property
342        let result =
343            map_ref.try_set_property(0, None, "new", ValueContainer::from(99));
344        assert!(result.is_ok());
345        let new_value = map_ref.try_get_property("new").unwrap();
346        assert_eq!(new_value, 99.into());
347    }
348
349    #[test]
350    fn numeric_property() {
351        let list = vec![
352            ValueContainer::from(1),
353            ValueContainer::from(2),
354            ValueContainer::from(3),
355        ];
356        let list_ref = SharedContainer::boxed_mut(
357            ValueContainer::from(list),
358            Pointer::NULL,
359        )
360        .unwrap();
361
362        // Set existing index
363        list_ref
364            .try_set_property(0, None, 1, ValueContainer::from(42))
365            .expect("Failed to set existing index");
366        let updated_value = list_ref.try_get_property(1).unwrap();
367        assert_eq!(updated_value, ValueContainer::from(42));
368
369        // Try to set out-of-bounds index
370        let result =
371            list_ref.try_set_property(0, None, 5, ValueContainer::from(99));
372        assert_matches!(
373            result,
374            Err(AccessError::IndexOutOfBounds(IndexOutOfBoundsError {
375                index: 5
376            }))
377        );
378
379        // Try to set index on non-map value
380        let int_ref =
381            SharedContainer::boxed_mut(42.into(), Pointer::NULL).unwrap();
382        let result =
383            int_ref.try_set_property(0, None, 0, ValueContainer::from(99));
384        assert_matches!(result, Err(AccessError::InvalidOperation(_)));
385    }
386
387    #[test]
388    fn text_property() {
389        let struct_val = Map::from(vec![
390            (ValueContainer::from("name"), ValueContainer::from("Alice")),
391            (ValueContainer::from("age"), ValueContainer::from(30)),
392        ]);
393        let struct_ref = SharedContainer::boxed_mut(
394            ValueContainer::from(struct_val),
395            Pointer::NULL,
396        )
397        .unwrap();
398
399        // Set existing property
400        struct_ref
401            .try_set_property(0, None, "name", ValueContainer::from("Bob"))
402            .expect("Failed to set existing property");
403        let name = struct_ref.try_get_property("name").unwrap();
404        assert_eq!(name, "Bob".into());
405
406        // Try to set non-existing property
407        let result = struct_ref.try_set_property(
408            0,
409            None,
410            "nonexistent",
411            ValueContainer::from("Value"),
412        );
413        assert_matches!(result, Ok(()));
414
415        // // Try to set property on non-struct value
416        let int_ref =
417            SharedContainer::boxed_mut(42.into(), Pointer::NULL).unwrap();
418        let result = int_ref.try_set_property(
419            0,
420            None,
421            "name",
422            ValueContainer::from("Bob"),
423        );
424        assert_matches!(result, Err(AccessError::InvalidOperation(_)));
425    }
426
427    #[test]
428    fn immutable_reference_fails() {
429        let r = SharedContainer::boxed(42, Pointer::NULL);
430        assert_matches!(
431            r.try_replace(0, None, 43),
432            Err(AccessError::ImmutableReference)
433        );
434
435        let r = SharedContainer::try_boxed(
436            42.into(),
437            None,
438            Pointer::NULL,
439            SharedContainerMutability::Immutable,
440        )
441        .unwrap();
442        assert_matches!(
443            r.try_replace(0, None, 43),
444            Err(AccessError::ImmutableReference)
445        );
446    }
447}