Skip to main content

json_eval_rs/jsoneval/
eval_data.rs

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