datex_core/references/
mutations.rs

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