Skip to main content

datex_core/references/
mutations.rs

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