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 clone_data_without(&self, exclude: &[&str]) -> Value {
107 match &*self.data {
108 Value::Object(map) => {
109 let mut new_map = Map::new();
110 for (k, v) in map {
111 if !exclude.contains(&k.as_str()) {
112 new_map.insert(k.clone(), v.clone());
113 }
114 }
115 Value::Object(new_map)
116 }
117 other => other.clone(),
118 }
119 }
120
121 /// Set a field value and increment version
122 /// Accepts both dotted notation (user.name) and JSON pointer format (/user/name)
123 /// Uses CoW: clones data only if shared
124 pub fn set(&mut self, path: &str, value: Value) {
125 // Normalize to JSON pointer format internally
126 let pointer = path_utils::normalize_to_json_pointer(path);
127 let data = Arc::make_mut(&mut self.data); // CoW: clone only if shared
128 Self::set_by_pointer(data, &pointer, value);
129 }
130
131 /// Append to an array field without full clone (optimized for table building)
132 /// Accepts both dotted notation (items) and JSON pointer format (/items)
133 /// Uses CoW: clones data only if shared
134 pub fn push_to_array(&mut self, path: &str, value: Value) {
135 // Normalize to JSON pointer format internally
136 let pointer = path_utils::normalize_to_json_pointer(path);
137 let data = Arc::make_mut(&mut self.data); // CoW: clone only if shared
138 if let Some(arr) = data.pointer_mut(&pointer) {
139 if let Some(array) = arr.as_array_mut() {
140 array.push(value);
141 }
142 }
143 }
144
145 /// Get a field value
146 /// Accepts both dotted notation (user.name) and JSON pointer format (/user/name)
147 #[inline]
148 pub fn get(&self, path: &str) -> Option<&Value> {
149 // Normalize to JSON pointer format internally
150 let pointer = path_utils::normalize_to_json_pointer(path);
151 // Use native serde_json pointer access for best performance
152 path_utils::get_value_by_pointer(&self.data, &pointer)
153 }
154
155 #[inline]
156 pub fn get_without_properties(&self, path: &str) -> Option<&Value> {
157 // Normalize to JSON pointer format internally
158 let pointer = path_utils::normalize_to_json_pointer(path);
159 // Use native serde_json pointer access for best performance
160 path_utils::get_value_by_pointer_without_properties(&self.data, &pointer)
161 }
162
163 /// OPTIMIZED: Fast array element access
164 #[inline]
165 pub fn get_array_element(&self, array_path: &str, index: usize) -> Option<&Value> {
166 let pointer = path_utils::normalize_to_json_pointer(array_path);
167 path_utils::get_array_element_by_pointer(&self.data, &pointer, index)
168 }
169
170 /// Get a mutable reference to a field value
171 /// Accepts both dotted notation and JSON pointer format
172 /// Uses CoW: clones data only if shared
173 /// Note: Caller must manually increment version after mutation
174 pub fn get_mut(&mut self, path: &str) -> Option<&mut Value> {
175 // Normalize to JSON pointer format internally
176 let pointer = path_utils::normalize_to_json_pointer(path);
177 let data = Arc::make_mut(&mut self.data); // CoW: clone only if shared
178 if pointer.is_empty() {
179 Some(data)
180 } else {
181 data.pointer_mut(&pointer)
182 }
183 }
184
185 /// Get a mutable reference to a table row object at path[index]
186 /// Accepts both dotted notation and JSON pointer format
187 /// Uses CoW: clones data only if shared
188 /// Returns None if path is not an array or row is not an object
189 #[inline(always)]
190 pub fn get_table_row_mut(
191 &mut self,
192 path: &str,
193 index: usize,
194 ) -> Option<&mut Map<String, Value>> {
195 // Normalize to JSON pointer format internally
196 let pointer = path_utils::normalize_to_json_pointer(path);
197 let data = Arc::make_mut(&mut self.data); // CoW: clone only if shared
198 let array = if pointer.is_empty() {
199 data
200 } else {
201 data.pointer_mut(&pointer)?
202 };
203 array.as_array_mut()?.get_mut(index)?.as_object_mut()
204 }
205
206 /// Get multiple field values efficiently (for cache key generation)
207 /// OPTIMIZED: Use batch pointer resolution for better performance
208 pub fn get_values<'a>(&'a self, paths: &'a [String]) -> Vec<Cow<'a, Value>> {
209 // Convert all paths to JSON pointers for batch processing
210 let pointers: Vec<String> = paths
211 .iter()
212 .map(|path| path_utils::normalize_to_json_pointer(path))
213 .collect();
214
215 // Batch pointer resolution
216 path_utils::get_values_by_pointers(&self.data, &pointers)
217 .into_iter()
218 .map(|opt_val| {
219 opt_val
220 .map(Cow::Borrowed)
221 .unwrap_or(Cow::Owned(Value::Null))
222 })
223 .collect()
224 }
225
226 /// Set a value by JSON pointer, creating intermediate structures as needed
227 fn set_by_pointer(data: &mut Value, pointer: &str, new_value: Value) {
228 if pointer.is_empty() {
229 return;
230 }
231
232 // Split pointer into segments (remove leading /)
233 let path = &pointer[1..];
234 let segments: Vec<&str> = path.split('/').collect();
235
236 if segments.is_empty() {
237 return;
238 }
239
240 // Navigate to parent, creating intermediate structures
241 let mut current = data;
242 for (i, segment) in segments.iter().enumerate() {
243 let is_last = i == segments.len() - 1;
244
245 // Try to parse as array index
246 if let Ok(index) = segment.parse::<usize>() {
247 // Current should be an array
248 if !current.is_array() {
249 return; // Cannot index into non-array
250 }
251
252 let arr = current.as_array_mut().unwrap();
253
254 // Extend array if needed
255 while arr.len() <= index {
256 arr.push(if is_last {
257 Value::Null
258 } else {
259 Value::Object(Map::new())
260 });
261 }
262
263 if is_last {
264 arr[index] = new_value;
265 return;
266 } else {
267 current = &mut arr[index];
268 }
269 } else {
270 // Object key access
271 if !current.is_object() {
272 return; // Cannot access key on non-object
273 }
274
275 let map = current.as_object_mut().unwrap();
276
277 if is_last {
278 map.insert(segment.to_string(), new_value);
279 return;
280 } else {
281 current = map
282 .entry(segment.to_string())
283 .or_insert_with(|| Value::Object(Map::new()));
284 }
285 }
286 }
287 }
288}
289
290impl From<Value> for EvalData {
291 fn from(value: Value) -> Self {
292 Self::new(value)
293 }
294}
295
296impl Clone for EvalData {
297 fn clone(&self) -> Self {
298 Self {
299 instance_id: self.instance_id, // Keep same ID for clones
300 data: Arc::clone(&self.data), // CoW: cheap Arc clone (ref count only)
301 }
302 }
303}