datalogic_rs/
context.rs

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