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