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    /// Initialize eval data with zero-copy references to evaluated_schema, input_data, and context_data
51    /// This avoids cloning by directly constructing the data structure with borrowed references
52    pub fn with_schema_data_context(
53        evaluated_schema: &Value,
54        input_data: &Value,
55        context_data: &Value,
56    ) -> Self {
57        let mut data_map = Map::new();
58
59        // Insert $params from evaluated_schema (clone only the reference, not deep clone)
60        if let Some(params) = evaluated_schema.get("$params") {
61            data_map.insert("$params".to_string(), params.clone());
62        }
63
64        // Merge input_data into the root level
65        if let Value::Object(input_obj) = input_data {
66            for (key, value) in input_obj {
67                data_map.insert(key.clone(), value.clone());
68            }
69        }
70
71        // Insert context
72        data_map.insert("$context".to_string(), context_data.clone());
73
74        Self::new(Value::Object(data_map))
75    }
76
77    /// Replace data and context in existing EvalData (for evaluation updates)
78    /// Uses CoW: replaces Arc, no clone needed if not shared
79    pub fn replace_data_and_context(&mut self, input_data: Value, context_data: Value) {
80        let data = Arc::make_mut(&mut self.data); // CoW: clone only if shared
81        input_data
82            .as_object()
83            .unwrap()
84            .iter()
85            .for_each(|(key, value)| {
86                Self::set_by_pointer(data, &format!("/{key}"), value.clone());
87            });
88        Self::set_by_pointer(data, "/$context", context_data);
89    }
90
91    /// Get the unique instance ID
92    #[inline(always)]
93    pub fn instance_id(&self) -> u64 {
94        self.instance_id
95    }
96
97    /// Get a reference to the underlying data (read-only)
98    /// Zero-cost access via Arc dereference
99    #[inline(always)]
100    pub fn data(&self) -> &Value {
101        &*self.data
102    }
103
104    /// Clone a Value without certain keys
105    #[inline(always)]
106    pub fn snapshot_data(&self) -> Arc<Value> {
107        Arc::clone(&self.data)
108    }
109
110    /// Returns a deep clone of the current data for diffing before it gets replaced
111    #[inline]
112    pub fn snapshot_data_clone(&self) -> Value {
113        (*self.data).clone()
114    }
115
116    /// Deep-clone into a new, exclusive EvalData (Arc strong count = 1).
117    ///
118    /// Unlike `clone()` which bumps the Arc reference count (causing `Arc::make_mut`
119    /// to reallocate on the first mutation), this copies the inner Value once and
120    /// wraps it in a fresh Arc. All subsequent `set()` / `push_to_array()` calls
121    /// on the returned instance are zero-cost because the Arc is always exclusive.
122    #[inline]
123    pub fn exclusive_clone(&self) -> Self {
124        Self::new((*self.data).clone())
125    }
126
127    /// Set a field value and increment version
128    /// Accepts both dotted notation (user.name) and JSON pointer format (/user/name)
129    /// Uses CoW: clones data only if shared
130    pub fn set(&mut self, path: &str, value: Value) {
131        // Normalize to JSON pointer format internally
132        let pointer = path_utils::normalize_to_json_pointer(path);
133        let data = Arc::make_mut(&mut self.data); // CoW: clone only if shared
134        Self::set_by_pointer(data, &pointer, value);
135    }
136
137    /// Append to an array field without full clone (optimized for table building)
138    /// Accepts both dotted notation (items) and JSON pointer format (/items)
139    /// Uses CoW: clones data only if shared
140    pub fn push_to_array(&mut self, path: &str, value: Value) {
141        // Normalize to JSON pointer format internally
142        let pointer = path_utils::normalize_to_json_pointer(path);
143        let data = Arc::make_mut(&mut self.data); // CoW: clone only if shared
144        if let Some(arr) = data.pointer_mut(&pointer) {
145            if let Some(array) = arr.as_array_mut() {
146                array.push(value);
147            }
148        }
149    }
150
151    /// Get a field value
152    /// Accepts both dotted notation (user.name) and JSON pointer format (/user/name)
153    #[inline]
154    pub fn get(&self, path: &str) -> Option<&Value> {
155        // Normalize to JSON pointer format internally
156        let pointer = path_utils::normalize_to_json_pointer(path);
157        // Use native serde_json pointer access for best performance
158        path_utils::get_value_by_pointer(&self.data, &pointer)
159    }
160
161    #[inline]
162    pub fn get_without_properties(&self, path: &str) -> Option<&Value> {
163        // Normalize to JSON pointer format internally
164        let pointer = path_utils::normalize_to_json_pointer(path);
165        // Use native serde_json pointer access for best performance
166        path_utils::get_value_by_pointer_without_properties(&self.data, &pointer)
167    }
168
169    /// OPTIMIZED: Fast array element access
170    #[inline]
171    pub fn get_array_element(&self, array_path: &str, index: usize) -> Option<&Value> {
172        let pointer = path_utils::normalize_to_json_pointer(array_path);
173        path_utils::get_array_element_by_pointer(&self.data, &pointer, index)
174    }
175
176    /// Get a mutable reference to a field value
177    /// Accepts both dotted notation and JSON pointer format
178    /// Uses CoW: clones data only if shared
179    /// Note: Caller must manually increment version after mutation
180    pub fn get_mut(&mut self, path: &str) -> Option<&mut Value> {
181        // Normalize to JSON pointer format internally
182        let pointer = path_utils::normalize_to_json_pointer(path);
183        let data = Arc::make_mut(&mut self.data); // CoW: clone only if shared
184        if pointer.is_empty() {
185            Some(data)
186        } else {
187            data.pointer_mut(&pointer)
188        }
189    }
190
191    /// Get a mutable reference to a table row object at path[index]
192    /// Accepts both dotted notation and JSON pointer format
193    /// Uses CoW: clones data only if shared
194    /// Returns None if path is not an array or row is not an object
195    #[inline(always)]
196    pub fn get_table_row_mut(
197        &mut self,
198        path: &str,
199        index: usize,
200    ) -> Option<&mut Map<String, Value>> {
201        // Normalize to JSON pointer format internally
202        let pointer = path_utils::normalize_to_json_pointer(path);
203        let data = Arc::make_mut(&mut self.data); // CoW: clone only if shared
204        let array = if pointer.is_empty() {
205            data
206        } else {
207            data.pointer_mut(&pointer)?
208        };
209        array.as_array_mut()?.get_mut(index)?.as_object_mut()
210    }
211
212    /// Get a mutable reference to a table row object using pre-parsed segments
213    /// This bypasses repetitive JSON pointer string parsing and allocation
214    #[inline(always)]
215    pub fn get_table_row_mut_by_segments(
216        &mut self,
217        segments: &[&str],
218        index: usize,
219    ) -> Option<&mut Map<String, Value>> {
220        let mut target = Arc::make_mut(&mut self.data);
221        for &segment in segments {
222            target = match target {
223                Value::Object(map) => map.get_mut(segment)?,
224                Value::Array(list) => {
225                    let idx = segment.parse::<usize>().ok()?;
226                    list.get_mut(idx)?
227                }
228                _ => return None,
229            };
230        }
231        target.as_array_mut()?.get_mut(index)?.as_object_mut()
232    }
233
234    /// Get multiple field values efficiently (for cache key generation)
235    /// OPTIMIZED: Use batch pointer resolution for better performance
236    pub fn get_values<'a>(&'a self, paths: &'a [String]) -> Vec<Cow<'a, Value>> {
237        // Convert all paths to JSON pointers for batch processing
238        let pointers: Vec<String> = paths
239            .iter()
240            .map(|path| path_utils::normalize_to_json_pointer(path).into_owned())
241            .collect();
242
243        // Batch pointer resolution
244        path_utils::get_values_by_pointers(&self.data, &pointers)
245            .into_iter()
246            .map(|opt_val| {
247                opt_val
248                    .map(Cow::Borrowed)
249                    .unwrap_or(Cow::Owned(Value::Null))
250            })
251            .collect()
252    }
253
254    /// Set a value by JSON pointer, creating intermediate structures as needed
255    fn set_by_pointer(data: &mut Value, pointer: &str, new_value: Value) {
256        if pointer.is_empty() {
257            return;
258        }
259
260        // Split pointer into segments (remove leading /)
261        let path = &pointer[1..];
262        let segments: Vec<&str> = path.split('/').collect();
263
264        if segments.is_empty() {
265            return;
266        }
267
268        // Navigate to parent, creating intermediate structures
269        let mut current = data;
270        for (i, segment) in segments.iter().enumerate() {
271            let is_last = i == segments.len() - 1;
272
273            // Try to parse as array index
274            if let Ok(index) = segment.parse::<usize>() {
275                // Current should be an array
276                if !current.is_array() {
277                    return; // Cannot index into non-array
278                }
279
280                let arr = current.as_array_mut().unwrap();
281
282                // Extend array if needed
283                while arr.len() <= index {
284                    arr.push(if is_last {
285                        Value::Null
286                    } else {
287                        Value::Object(Map::new())
288                    });
289                }
290
291                if is_last {
292                    arr[index] = new_value;
293                    return;
294                } else {
295                    current = &mut arr[index];
296                }
297            } else {
298                // Object key access
299                if !current.is_object() {
300                    return; // Cannot access key on non-object
301                }
302
303                let map = current.as_object_mut().unwrap();
304
305                if is_last {
306                    map.insert(segment.to_string(), new_value);
307                    return;
308                } else {
309                    let next_segment = segments[i + 1];
310                    let is_array_next = next_segment.parse::<usize>().is_ok();
311
312                    current = map.entry(segment.to_string()).or_insert_with(|| {
313                        if is_array_next {
314                            Value::Array(Vec::new())
315                        } else {
316                            Value::Object(Map::new())
317                        }
318                    });
319                }
320            }
321        }
322    }
323}
324
325impl From<Value> for EvalData {
326    fn from(value: Value) -> Self {
327        Self::new(value)
328    }
329}
330
331impl Clone for EvalData {
332    fn clone(&self) -> Self {
333        Self {
334            instance_id: self.instance_id, // Keep same ID for clones
335            data: Arc::clone(&self.data),  // CoW: cheap Arc clone (ref count only)
336        }
337    }
338}