datalogic_rs/context.rs
1use serde_json::Value;
2use std::collections::HashMap;
3use std::sync::Arc;
4
5// Static string constants for common metadata keys
6pub const INDEX_KEY: &str = "index";
7pub const KEY_KEY: &str = "key";
8pub const CURRENT_KEY: &str = "current";
9pub const ACCUMULATOR_KEY: &str = "accumulator";
10
11/// A single frame in the context stack (for temporary/nested contexts)
12pub struct ContextFrame {
13 /// The data value at this context level
14 pub data: Value,
15 /// Optional index for array iteration (avoids HashMap allocation)
16 pub index: Option<usize>,
17 /// Optional metadata for this frame (e.g., "key" in map operations)
18 /// Only used when additional metadata beyond index is needed
19 pub metadata: Option<HashMap<String, Value>>,
20}
21
22/// Reference to a context frame (either actual frame or root)
23pub enum ContextFrameRef<'a> {
24 /// Reference to an actual frame
25 Frame(&'a ContextFrame),
26 /// Reference to the root data
27 Root(&'a Arc<Value>),
28}
29
30impl<'a> ContextFrameRef<'a> {
31 /// Get the data value
32 pub fn data(&self) -> &Value {
33 match self {
34 ContextFrameRef::Frame(frame) => &frame.data,
35 ContextFrameRef::Root(root) => root,
36 }
37 }
38
39 /// Get the index if available (fast path, no HashMap lookup)
40 #[inline]
41 pub fn get_index(&self) -> Option<usize> {
42 match self {
43 ContextFrameRef::Frame(frame) => frame.index,
44 ContextFrameRef::Root(_) => None,
45 }
46 }
47
48 /// Get the metadata (only available for frames)
49 pub fn metadata(&self) -> Option<&HashMap<String, Value>> {
50 match self {
51 ContextFrameRef::Frame(frame) => frame.metadata.as_ref(),
52 ContextFrameRef::Root(_) => None,
53 }
54 }
55}
56
57/// Context stack for nested evaluations
58pub struct ContextStack {
59 /// Immutable root data (never changes during evaluation)
60 root: Arc<Value>,
61 /// Stack of temporary frames (excludes root)
62 frames: Vec<ContextFrame>,
63}
64
65impl ContextStack {
66 /// Create a new context stack with Arc root data
67 pub fn new(root: Arc<Value>) -> Self {
68 Self {
69 root,
70 frames: Vec::new(),
71 }
72 }
73
74 /// Pushes a new context frame for nested evaluation.
75 ///
76 /// Used by operators that need to create a nested data context without metadata.
77 ///
78 /// # Arguments
79 ///
80 /// * `data` - The data value for the new context frame
81 pub fn push(&mut self, data: Value) {
82 self.frames.push(ContextFrame {
83 data,
84 index: None,
85 metadata: None,
86 });
87 }
88
89 /// Pushes a frame with just an index (optimized path - no HashMap allocation).
90 ///
91 /// Used by array operators like `map` and `filter` for simple iteration
92 /// where only index access is needed.
93 ///
94 /// # Arguments
95 ///
96 /// * `data` - The current item being processed
97 /// * `index` - The array index of the current item
98 #[inline]
99 pub fn push_with_index(&mut self, data: Value, index: usize) {
100 self.frames.push(ContextFrame {
101 data,
102 index: Some(index),
103 metadata: None,
104 });
105 }
106
107 /// Pushes a frame with metadata for iteration operations.
108 ///
109 /// Used by array operators like `map`, `filter`, and `reduce` to provide
110 /// iteration context including index and key information.
111 ///
112 /// # Arguments
113 ///
114 /// * `data` - The current item being processed
115 /// * `metadata` - Iteration metadata (typically includes "index" and optionally "key")
116 ///
117 /// # Example
118 ///
119 /// During array iteration:
120 /// ```rust,ignore
121 /// let mut metadata = HashMap::new();
122 /// metadata.insert("index".to_string(), json!(0));
123 /// context.push_with_metadata(item_value, metadata);
124 /// ```
125 pub fn push_with_metadata(&mut self, data: Value, metadata: HashMap<String, Value>) {
126 // Extract index from metadata if present
127 let index = metadata
128 .get(INDEX_KEY)
129 .and_then(|v| v.as_u64())
130 .map(|i| i as usize);
131
132 self.frames.push(ContextFrame {
133 data,
134 index,
135 metadata: Some(metadata),
136 });
137 }
138
139 /// Pops the current context frame from the stack.
140 ///
141 /// Restores the previous context after nested evaluation completes.
142 /// Returns None if there are no frames to pop (root is never popped).
143 ///
144 /// # Returns
145 ///
146 /// The popped context frame, or None if the stack is empty.
147 pub fn pop(&mut self) -> Option<ContextFrame> {
148 // Only pop if there are frames (root is separate)
149 self.frames.pop()
150 }
151
152 /// Accesses data at a context level relative to current.
153 ///
154 /// This method enables access to parent contexts during nested operations,
155 /// which is essential for complex JSONLogic expressions.
156 ///
157 /// # Arguments
158 ///
159 /// * `level` - The number of levels to traverse up the context stack
160 /// - 0: returns the current context
161 /// - N (positive or negative): goes up N levels from current
162 ///
163 /// # Returns
164 ///
165 /// A reference to the context frame at the specified level,
166 /// or None if the level exceeds the stack depth.
167 ///
168 /// # Note
169 ///
170 /// The sign of the level is ignored; both positive and negative values
171 /// traverse up the stack the same way. This maintains backward compatibility
172 /// with existing usage patterns.
173 ///
174 /// # Returns
175 /// The context frame at the specified level, or the root if level exceeds stack depth
176 pub fn get_at_level(&self, level: isize) -> Option<ContextFrameRef<'_>> {
177 // Get absolute value - sign doesn't matter (for backward compatibility)
178 let levels_up = level.unsigned_abs();
179
180 if levels_up == 0 {
181 // 0 means current context
182 return Some(self.current());
183 }
184
185 let frame_count = self.frames.len();
186
187 // If going up more levels than or equal to the total frame count,
188 // we reach the root (since root is not in frames)
189 if levels_up >= frame_count {
190 return Some(ContextFrameRef::Root(&self.root));
191 }
192
193 // Calculate target index by going up from current
194 let target_index = frame_count - levels_up;
195 self.frames.get(target_index).map(ContextFrameRef::Frame)
196 }
197
198 /// Get the current context frame (top of stack)
199 /// Returns a temporary frame for root if no frames are pushed
200 pub fn current(&self) -> ContextFrameRef<'_> {
201 if let Some(frame) = self.frames.last() {
202 ContextFrameRef::Frame(frame)
203 } else {
204 ContextFrameRef::Root(&self.root)
205 }
206 }
207
208 /// Get the root context frame
209 pub fn root(&self) -> ContextFrameRef<'_> {
210 ContextFrameRef::Root(&self.root)
211 }
212
213 /// Get the current depth (number of frames)
214 pub fn depth(&self) -> usize {
215 self.frames.len()
216 }
217}