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 if let Some(existing) = self.data.pointer(&pointer) {
149 if existing == &value {
150 return;
151 }
152 }
153 let data = Arc::make_mut(&mut self.data); // CoW: clone only if shared
154 Self::set_by_pointer(data, &pointer, value);
155 }
156
157 /// Append to an array field without full clone (optimized for table building)
158 /// Accepts both dotted notation (items) and JSON pointer format (/items)
159 /// Uses CoW: clones data only if shared
160 pub fn push_to_array(&mut self, path: &str, value: Value) {
161 // Normalize to JSON pointer format internally
162 let pointer = path_utils::normalize_to_json_pointer(path);
163 let data = Arc::make_mut(&mut self.data); // CoW: clone only if shared
164 if let Some(arr) = data.pointer_mut(&pointer) {
165 if let Some(array) = arr.as_array_mut() {
166 array.push(value);
167 }
168 }
169 }
170
171 /// Get a field value
172 /// Accepts both dotted notation (user.name) and JSON pointer format (/user/name)
173 #[inline]
174 pub fn get(&self, path: &str) -> Option<&Value> {
175 // Normalize to JSON pointer format internally
176 let pointer = path_utils::normalize_to_json_pointer(path);
177 // Use native serde_json pointer access for best performance
178 path_utils::get_value_by_pointer(&self.data, &pointer)
179 }
180
181 #[inline]
182 pub fn get_without_properties(&self, path: &str) -> Option<&Value> {
183 // Normalize to JSON pointer format internally
184 let pointer = path_utils::normalize_to_json_pointer(path);
185 // Use native serde_json pointer access for best performance
186 path_utils::get_value_by_pointer_without_properties(&self.data, &pointer)
187 }
188
189 /// OPTIMIZED: Fast array element access
190 #[inline]
191 pub fn get_array_element(&self, array_path: &str, index: usize) -> Option<&Value> {
192 let pointer = path_utils::normalize_to_json_pointer(array_path);
193 path_utils::get_array_element_by_pointer(&self.data, &pointer, index)
194 }
195
196 /// Get a mutable reference to a field value
197 /// Accepts both dotted notation and JSON pointer format
198 /// Uses CoW: clones data only if shared
199 /// Note: Caller must manually increment version after mutation
200 pub fn get_mut(&mut self, path: &str) -> Option<&mut 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 if pointer.is_empty() {
205 Some(data)
206 } else {
207 data.pointer_mut(&pointer)
208 }
209 }
210
211 /// Get a mutable reference to a table row object at path[index]
212 /// Accepts both dotted notation and JSON pointer format
213 /// Uses CoW: clones data only if shared
214 /// Returns None if path is not an array or row is not an object
215 #[inline(always)]
216 pub fn get_table_row_mut(
217 &mut self,
218 path: &str,
219 index: usize,
220 ) -> Option<&mut Map<String, Value>> {
221 // Normalize to JSON pointer format internally
222 let pointer = path_utils::normalize_to_json_pointer(path);
223 let data = Arc::make_mut(&mut self.data); // CoW: clone only if shared
224 let array = if pointer.is_empty() {
225 data
226 } else {
227 data.pointer_mut(&pointer)?
228 };
229 array.as_array_mut()?.get_mut(index)?.as_object_mut()
230 }
231
232 /// Get a mutable reference to a table row object using pre-parsed segments
233 /// This bypasses repetitive JSON pointer string parsing and allocation
234 #[inline(always)]
235 pub fn get_table_row_mut_by_segments(
236 &mut self,
237 segments: &[&str],
238 index: usize,
239 ) -> Option<&mut Map<String, Value>> {
240 let mut target = Arc::make_mut(&mut self.data);
241 for &segment in segments {
242 target = match target {
243 Value::Object(map) => map.get_mut(segment)?,
244 Value::Array(list) => {
245 let idx = segment.parse::<usize>().ok()?;
246 list.get_mut(idx)?
247 }
248 _ => return None,
249 };
250 }
251 target.as_array_mut()?.get_mut(index)?.as_object_mut()
252 }
253
254 /// Get multiple field values efficiently (for cache key generation)
255 /// OPTIMIZED: Use batch pointer resolution for better performance
256 pub fn get_values<'a>(&'a self, paths: &'a [String]) -> Vec<Cow<'a, Value>> {
257 // Convert all paths to JSON pointers for batch processing
258 let pointers: Vec<String> = paths
259 .iter()
260 .map(|path| path_utils::normalize_to_json_pointer(path).into_owned())
261 .collect();
262
263 // Batch pointer resolution
264 path_utils::get_values_by_pointers(&self.data, &pointers)
265 .into_iter()
266 .map(|opt_val| {
267 opt_val
268 .map(Cow::Borrowed)
269 .unwrap_or(Cow::Owned(Value::Null))
270 })
271 .collect()
272 }
273
274 /// Set a value by JSON pointer, creating intermediate structures as needed
275 pub(crate) fn set_by_pointer(data: &mut Value, pointer: &str, new_value: Value) {
276 if pointer.is_empty() {
277 return;
278 }
279
280 // Split pointer into segments (remove leading /)
281 let path = &pointer[1..];
282 let segments: Vec<&str> = path.split('/').collect();
283
284 if segments.is_empty() {
285 return;
286 }
287
288 // Navigate to parent, creating intermediate structures
289 let mut current = data;
290 for (i, segment) in segments.iter().enumerate() {
291 let is_last = i == segments.len() - 1;
292
293 // Try to parse as array index
294 if let Ok(index) = segment.parse::<usize>() {
295 // Current should be an array
296 if !current.is_array() {
297 return; // Cannot index into non-array
298 }
299
300 let arr = current.as_array_mut().unwrap();
301
302 // Extend array if needed
303 while arr.len() <= index {
304 arr.push(if is_last {
305 Value::Null
306 } else {
307 Value::Object(Map::new())
308 });
309 }
310
311 if is_last {
312 arr[index] = new_value;
313 return;
314 } else {
315 current = &mut arr[index];
316 }
317 } else {
318 // Object key access
319 if !current.is_object() {
320 return; // Cannot access key on non-object
321 }
322
323 let map = current.as_object_mut().unwrap();
324
325 if is_last {
326 map.insert(segment.to_string(), new_value);
327 return;
328 } else {
329 let next_segment = segments[i + 1];
330 let is_array_next = next_segment.parse::<usize>().is_ok();
331
332 current = map.entry(segment.to_string()).or_insert_with(|| {
333 if is_array_next {
334 Value::Array(Vec::new())
335 } else {
336 Value::Object(Map::new())
337 }
338 });
339 }
340 }
341 }
342 }
343}
344
345impl From<Value> for EvalData {
346 fn from(value: Value) -> Self {
347 Self::new(value)
348 }
349}
350
351impl Clone for EvalData {
352 fn clone(&self) -> Self {
353 Self {
354 instance_id: self.instance_id, // Keep same ID for clones
355 data: Arc::clone(&self.data), // CoW: cheap Arc clone (ref count only)
356 }
357 }
358}