datex_core/references/
mutations.rs

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