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