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