json_eval_rs/
eval_data.rs

1use serde_json::{Map, Value};
2use std::borrow::Cow;
3use std::sync::{atomic::{AtomicU64, Ordering}, Arc};
4
5use crate::path_utils;
6
7static NEXT_INSTANCE_ID: AtomicU64 = AtomicU64::new(0);
8
9/// Version tracker for data mutations
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub struct DataVersion(pub u64);
12
13/// Tracked data wrapper that gates all mutations for safety
14/// 
15/// # Design Philosophy
16/// 
17/// EvalData serves as the single gatekeeper for all data mutations in the system.
18/// All write operations (set, push_to_array, get_mut, etc.) MUST go through this
19/// type to ensure proper version tracking and mutation safety.
20/// 
21/// This design provides:
22/// - Thread-safe mutation tracking via version numbers
23/// - Copy-on-Write (CoW) semantics via Arc for efficient cloning
24/// - Single point of control for all data state changes
25/// - Prevention of untracked mutations that could cause race conditions
26/// 
27/// # CoW Behavior
28/// 
29/// - Read operations are zero-cost (direct Arc dereference)
30/// - Clone operations are cheap (Arc reference counting)
31/// - First mutation triggers deep clone via Arc::make_mut
32/// - Subsequent mutations on exclusive owner are zero-cost
33pub struct EvalData {
34    instance_id: u64,
35    data: Arc<Value>,
36}
37
38impl EvalData {
39    /// Create a new tracked data wrapper
40    pub fn new(data: Value) -> Self {
41        Self {
42            instance_id: NEXT_INSTANCE_ID.fetch_add(1, Ordering::Relaxed),
43            data: Arc::new(data),
44        }
45    }
46    
47    /// Initialize eval data with zero-copy references to evaluated_schema, input_data, and context_data
48    /// This avoids cloning by directly constructing the data structure with borrowed references
49    pub fn with_schema_data_context(
50        evaluated_schema: &Value,
51        input_data: &Value,
52        context_data: &Value,
53    ) -> Self {
54        let mut data_map = Map::new();
55        
56        // Insert $params from evaluated_schema (clone only the reference, not deep clone)
57        if let Some(params) = evaluated_schema.get("$params") {
58            data_map.insert("$params".to_string(), params.clone());
59        }
60        
61        // Merge input_data into the root level
62        if let Value::Object(input_obj) = input_data {
63            for (key, value) in input_obj {
64                data_map.insert(key.clone(), value.clone());
65            }
66        }
67        
68        // Insert context
69        data_map.insert("$context".to_string(), context_data.clone());
70        
71        Self::new(Value::Object(data_map))
72    }
73    
74    /// Replace data and context in existing EvalData (for evaluation updates)
75    /// Uses CoW: replaces Arc, no clone needed if not shared
76    pub fn replace_data_and_context(&mut self, input_data: Value, context_data: Value) {
77        let data = Arc::make_mut(&mut self.data);  // CoW: clone only if shared
78        input_data.as_object().unwrap().iter().for_each(|(key, value)| {
79            Self::set_by_pointer(data, &format!("/{key}"), value.clone());
80        });
81        Self::set_by_pointer(data, "/$context", context_data);
82    }
83    
84    /// Get the unique instance ID
85    #[inline(always)]
86    pub fn instance_id(&self) -> u64 {
87        self.instance_id
88    }
89   
90    /// Get a reference to the underlying data (read-only)
91    /// Zero-cost access via Arc dereference
92    #[inline(always)]
93    pub fn data(&self) -> &Value {
94        &*self.data
95    }
96
97    /// Clone a Value without certain keys
98    #[inline(always)]
99    pub fn clone_data_without(&self, exclude: &[&str]) -> Value {
100        match &*self.data {
101            Value::Object(map) => {
102                let mut new_map = Map::new();
103                for (k, v) in map {
104                    if !exclude.contains(&k.as_str()) {
105                        new_map.insert(k.clone(), v.clone());
106                    }
107                }
108                Value::Object(new_map)
109            }
110            other => other.clone(),
111        }
112    }
113    
114    /// Set a field value and increment version
115    /// Accepts both dotted notation (user.name) and JSON pointer format (/user/name)
116    /// Uses CoW: clones data only if shared
117    pub fn set(&mut self, path: &str, value: Value) {
118        // Normalize to JSON pointer format internally
119        let pointer = path_utils::normalize_to_json_pointer(path);
120        let data = Arc::make_mut(&mut self.data);  // CoW: clone only if shared
121        Self::set_by_pointer(data, &pointer, value);
122    }
123
124    /// Append to an array field without full clone (optimized for table building)
125    /// Accepts both dotted notation (items) and JSON pointer format (/items)
126    /// Uses CoW: clones data only if shared
127    pub fn push_to_array(&mut self, path: &str, value: Value) {
128        // Normalize to JSON pointer format internally
129        let pointer = path_utils::normalize_to_json_pointer(path);
130        let data = Arc::make_mut(&mut self.data);  // CoW: clone only if shared
131        if let Some(arr) = data.pointer_mut(&pointer) {
132            if let Some(array) = arr.as_array_mut() {
133                array.push(value);
134            }
135        }
136    }
137    
138    /// Get a field value
139    /// Accepts both dotted notation (user.name) and JSON pointer format (/user/name)
140    #[inline]
141    pub fn get(&self, path: &str) -> Option<&Value> {
142        // Normalize to JSON pointer format internally
143        let pointer = path_utils::normalize_to_json_pointer(path);
144        // Use native serde_json pointer access for best performance
145        path_utils::get_value_by_pointer(&self.data, &pointer)
146    }
147
148    #[inline]
149    pub fn get_without_properties(&self, path: &str) -> Option<&Value> {
150        // Normalize to JSON pointer format internally
151        let pointer = path_utils::normalize_to_json_pointer(path);
152        // Use native serde_json pointer access for best performance
153        path_utils::get_value_by_pointer_without_properties(&self.data, &pointer)
154    }
155    
156    /// OPTIMIZED: Fast array element access
157    #[inline]
158    pub fn get_array_element(&self, array_path: &str, index: usize) -> Option<&Value> {
159        let pointer = path_utils::normalize_to_json_pointer(array_path);
160        path_utils::get_array_element_by_pointer(&self.data, &pointer, index)
161    }
162    
163    /// Get a mutable reference to a field value
164    /// Accepts both dotted notation and JSON pointer format
165    /// Uses CoW: clones data only if shared
166    /// Note: Caller must manually increment version after mutation
167    pub fn get_mut(&mut self, path: &str) -> Option<&mut Value> {
168        // Normalize to JSON pointer format internally
169        let pointer = path_utils::normalize_to_json_pointer(path);
170        let data = Arc::make_mut(&mut self.data);  // CoW: clone only if shared
171        if pointer.is_empty() {
172            Some(data)
173        } else {
174            data.pointer_mut(&pointer)
175        }
176    }
177
178    /// Get a mutable reference to a table row object at path[index]
179    /// Accepts both dotted notation and JSON pointer format
180    /// Uses CoW: clones data only if shared
181    /// Returns None if path is not an array or row is not an object
182    #[inline(always)]
183    pub fn get_table_row_mut(&mut self, path: &str, index: usize) -> Option<&mut Map<String, Value>> {
184        // Normalize to JSON pointer format internally
185        let pointer = path_utils::normalize_to_json_pointer(path);
186        let data = Arc::make_mut(&mut self.data);  // CoW: clone only if shared
187        let array = if pointer.is_empty() {
188            data
189        } else {
190            data.pointer_mut(&pointer)?
191        };
192        array.as_array_mut()?
193            .get_mut(index)?
194            .as_object_mut()
195    }
196    
197    /// Get multiple field values efficiently (for cache key generation)
198    /// OPTIMIZED: Use batch pointer resolution for better performance
199    pub fn get_values<'a>(&'a self, paths: &'a [String]) -> Vec<Cow<'a, Value>> {
200        // Convert all paths to JSON pointers for batch processing
201        let pointers: Vec<String> = paths.iter()
202            .map(|path| path_utils::normalize_to_json_pointer(path))
203            .collect();
204        
205        // Batch pointer resolution
206        path_utils::get_values_by_pointers(&self.data, &pointers)
207            .into_iter()
208            .map(|opt_val| {
209                opt_val.map(Cow::Borrowed).unwrap_or(Cow::Owned(Value::Null))
210            })
211            .collect()
212    }
213
214    /// Set a value by JSON pointer, creating intermediate structures as needed
215    fn set_by_pointer(data: &mut Value, pointer: &str, new_value: Value) {
216        if pointer.is_empty() {
217            return;
218        }
219
220        // Split pointer into segments (remove leading /)
221        let path = &pointer[1..];
222        let segments: Vec<&str> = path.split('/').collect();
223        
224        if segments.is_empty() {
225            return;
226        }
227
228        // Navigate to parent, creating intermediate structures
229        let mut current = data;
230        for (i, segment) in segments.iter().enumerate() {
231            let is_last = i == segments.len() - 1;
232            
233            // Try to parse as array index
234            if let Ok(index) = segment.parse::<usize>() {
235                // Current should be an array
236                if !current.is_array() {
237                    return; // Cannot index into non-array
238                }
239                
240                let arr = current.as_array_mut().unwrap();
241                
242                // Extend array if needed
243                while arr.len() <= index {
244                    arr.push(if is_last { Value::Null } else { Value::Object(Map::new()) });
245                }
246                
247                if is_last {
248                    arr[index] = new_value;
249                    return;
250                } else {
251                    current = &mut arr[index];
252                }
253            } else {
254                // Object key access
255                if !current.is_object() {
256                    return; // Cannot access key on non-object
257                }
258                
259                let map = current.as_object_mut().unwrap();
260                
261                if is_last {
262                    map.insert(segment.to_string(), new_value);
263                    return;
264                } else {
265                    current = map.entry(segment.to_string())
266                        .or_insert_with(|| Value::Object(Map::new()));
267                }
268            }
269        }
270    }
271}
272
273impl From<Value> for EvalData {
274    fn from(value: Value) -> Self {
275        Self::new(value)
276    }
277}
278
279impl Clone for EvalData {
280    fn clone(&self) -> Self {
281        Self {
282            instance_id: self.instance_id, // Keep same ID for clones
283            data: Arc::clone(&self.data),  // CoW: cheap Arc clone (ref count only)
284        }
285    }
286}