loose_liquid_core/runtime/
stack.rs

1use crate::error::Error;
2use crate::error::Result;
3use crate::model::{Object, ObjectView, ScalarCow, Value, ValueCow, ValueView};
4
5use super::Registers;
6
7/// Layer variables on top of the existing runtime
8pub struct StackFrame<P, O> {
9    parent: P,
10    name: Option<crate::model::KString>,
11    data: O,
12}
13
14impl<P: super::Runtime, O: ObjectView> StackFrame<P, O> {
15    /// Layer variables on top of the existing runtime
16    pub fn new(parent: P, data: O) -> Self {
17        Self {
18            parent,
19            name: None,
20            data,
21        }
22    }
23
24    /// Name the current context
25    pub fn with_name<S: Into<crate::model::KString>>(mut self, name: S) -> Self {
26        self.name = Some(name.into());
27        self
28    }
29}
30
31impl<P: super::Runtime, O: ObjectView> super::Runtime for StackFrame<P, O> {
32    fn partials(&self) -> &dyn super::PartialStore {
33        self.parent.partials()
34    }
35
36    fn name(&self) -> Option<crate::model::KStringRef<'_>> {
37        self.name
38            .as_ref()
39            .map(|n| n.as_ref())
40            .or_else(|| self.parent.name())
41    }
42
43    fn roots(&self) -> std::collections::BTreeSet<crate::model::KStringCow<'_>> {
44        let mut roots = self.parent.roots();
45        roots.extend(self.data.keys());
46        roots
47    }
48
49    fn try_get(&self, path: &[ScalarCow<'_>]) -> Option<ValueCow<'_>> {
50        let key = path.first()?;
51        let key = key.to_kstr();
52        let data = &self.data;
53        if data.contains_key(key.as_str()) {
54            crate::model::try_find(data.as_value(), path)
55        } else {
56            self.parent.try_get(path)
57        }
58    }
59
60    fn get(&self, path: &[ScalarCow<'_>]) -> Result<ValueCow<'_>> {
61        let key = path.first().ok_or_else(|| Error::unknown_variable("nil"))?;
62        let key = key.to_kstr();
63        let data = &self.data;
64        if data.contains_key(key.as_str()) {
65            crate::model::find(data.as_value(), path).map(|v| v.into_owned().into())
66        } else {
67            self.parent.get(path)
68        }
69    }
70
71    fn set_global(
72        &self,
73        name: crate::model::KString,
74        val: crate::model::Value,
75    ) -> Option<crate::model::Value> {
76        self.parent.set_global(name, val)
77    }
78
79    fn set_index(&self, name: crate::model::KString, val: Value) -> Option<Value> {
80        self.parent.set_index(name, val)
81    }
82
83    fn get_index<'a>(&'a self, name: &str) -> Option<ValueCow<'a>> {
84        self.parent.get_index(name)
85    }
86
87    fn registers(&self) -> &super::Registers {
88        self.parent.registers()
89    }
90}
91
92/// A stack frame that only provides a sandboxed set of globals
93pub struct GlobalFrame<P> {
94    parent: P,
95    data: std::cell::RefCell<Object>,
96}
97
98impl<P: super::Runtime> GlobalFrame<P> {
99    /// Override globals for `parent`
100    pub fn new(parent: P) -> Self {
101        Self {
102            parent,
103            data: Default::default(),
104        }
105    }
106}
107
108impl<P: super::Runtime> super::Runtime for GlobalFrame<P> {
109    fn partials(&self) -> &dyn super::PartialStore {
110        self.parent.partials()
111    }
112
113    fn name(&self) -> Option<crate::model::KStringRef<'_>> {
114        self.parent.name()
115    }
116
117    fn roots(&self) -> std::collections::BTreeSet<crate::model::KStringCow<'_>> {
118        let mut roots = self.parent.roots();
119        roots.extend(self.data.borrow().keys().map(|k| k.clone().into()));
120        roots
121    }
122
123    fn try_get(&self, path: &[ScalarCow<'_>]) -> Option<ValueCow<'_>> {
124        let key = path.first()?;
125        let key = key.to_kstr();
126        let data = self.data.borrow();
127        if data.contains_key(key.as_str()) {
128            crate::model::try_find(data.as_value(), path).map(|v| v.into_owned().into())
129        } else {
130            self.parent.try_get(path)
131        }
132    }
133
134    fn get(&self, path: &[ScalarCow<'_>]) -> Result<ValueCow<'_>> {
135        let key = path.first().ok_or_else(|| Error::unknown_variable("nil"))?;
136        let key = key.to_kstr();
137        let data = self.data.borrow();
138        if data.contains_key(key.as_str()) {
139            crate::model::find(data.as_value(), path).map(|v| v.into_owned().into())
140        } else {
141            self.parent.get(path)
142        }
143    }
144
145    fn set_global(
146        &self,
147        name: crate::model::KString,
148        val: crate::model::Value,
149    ) -> Option<crate::model::Value> {
150        let mut data = self.data.borrow_mut();
151        data.insert(name, val)
152    }
153
154    fn set_index(&self, name: crate::model::KString, val: Value) -> Option<Value> {
155        self.parent.set_index(name, val)
156    }
157
158    fn get_index<'a>(&'a self, name: &str) -> Option<ValueCow<'a>> {
159        self.parent.get_index(name)
160    }
161
162    fn registers(&self) -> &super::Registers {
163        self.parent.registers()
164    }
165}
166
167pub(crate) struct IndexFrame<P> {
168    parent: P,
169    data: std::cell::RefCell<Object>,
170}
171
172impl<P: super::Runtime> IndexFrame<P> {
173    pub fn new(parent: P) -> Self {
174        Self {
175            parent,
176            data: Default::default(),
177        }
178    }
179}
180
181impl<P: super::Runtime> super::Runtime for IndexFrame<P> {
182    fn partials(&self) -> &dyn super::PartialStore {
183        self.parent.partials()
184    }
185
186    fn name(&self) -> Option<crate::model::KStringRef<'_>> {
187        self.parent.name()
188    }
189
190    fn roots(&self) -> std::collections::BTreeSet<crate::model::KStringCow<'_>> {
191        let mut roots = self.parent.roots();
192        roots.extend(self.data.borrow().keys().map(|k| k.clone().into()));
193        roots
194    }
195
196    fn try_get(&self, path: &[ScalarCow<'_>]) -> Option<ValueCow<'_>> {
197        let key = path.first()?;
198        let key = key.to_kstr();
199        let data = self.data.borrow();
200        if data.contains_key(key.as_str()) {
201            crate::model::try_find(data.as_value(), path).map(|v| v.into_owned().into())
202        } else {
203            self.parent.try_get(path)
204        }
205    }
206
207    fn get(&self, path: &[ScalarCow<'_>]) -> Result<ValueCow<'_>> {
208        let key = path.first().ok_or_else(|| Error::unknown_variable("nil"))?;
209        let key = key.to_kstr();
210        let data = self.data.borrow();
211        if data.contains_key(key.as_str()) {
212            crate::model::find(data.as_value(), path).map(|v| v.into_owned().into())
213        } else {
214            self.parent.get(path)
215        }
216    }
217
218    fn set_global(
219        &self,
220        name: crate::model::KString,
221        val: crate::model::Value,
222    ) -> Option<crate::model::Value> {
223        self.parent.set_global(name, val)
224    }
225
226    fn set_index(&self, name: crate::model::KString, val: Value) -> Option<Value> {
227        let mut data = self.data.borrow_mut();
228        data.insert(name, val)
229    }
230
231    fn get_index<'a>(&'a self, name: &str) -> Option<ValueCow<'a>> {
232        self.data.borrow().get(name).map(|v| v.to_value().into())
233    }
234
235    fn registers(&self) -> &super::Registers {
236        self.parent.registers()
237    }
238}
239
240/// A [`StackFrame`] where variables are not recursively searched for,
241/// However, you can still access the parent's partials.
242pub struct SandboxedStackFrame<P, O> {
243    parent: P,
244    name: Option<crate::model::KString>,
245    data: O,
246    registers: Registers,
247}
248
249impl<P: super::Runtime, O: ObjectView> SandboxedStackFrame<P, O> {
250    /// Create a new [`SandboxedStackFrame`] from a parent and some data
251    pub fn new(parent: P, data: O) -> Self {
252        Self {
253            parent,
254            name: None,
255            data,
256            registers: Default::default(),
257        }
258    }
259
260    /// Name the current context
261    pub fn with_name<S: Into<crate::model::KString>>(mut self, name: S) -> Self {
262        self.name = Some(name.into());
263        self
264    }
265}
266
267impl<P: super::Runtime, O: ObjectView> super::Runtime for SandboxedStackFrame<P, O> {
268    fn partials(&self) -> &dyn super::PartialStore {
269        self.parent.partials()
270    }
271
272    fn name(&self) -> Option<crate::model::KStringRef<'_>> {
273        self.name
274            .as_ref()
275            .map(|n| n.as_ref())
276            .or_else(|| self.parent.name())
277    }
278
279    fn roots(&self) -> std::collections::BTreeSet<crate::model::KStringCow<'_>> {
280        let mut roots = std::collections::BTreeSet::new();
281        roots.extend(self.data.keys());
282        roots
283    }
284
285    fn try_get(&self, path: &[ScalarCow<'_>]) -> Option<ValueCow<'_>> {
286        let key = path.first()?;
287        let key = key.to_kstr();
288        let data = &self.data;
289        data.get(key.as_str())
290            .and_then(|_| crate::model::try_find(data.as_value(), path))
291    }
292
293    fn get(&self, path: &[ScalarCow<'_>]) -> Result<ValueCow<'_>> {
294        let key = path.first().ok_or_else(|| {
295            Error::with_msg("Unknown variable").context("requested variable", "nil")
296        })?;
297        let key = key.to_kstr();
298        let data = &self.data;
299        data.get(key.as_str())
300            .and_then(|_| crate::model::try_find(data.as_value(), path))
301            .map(|v| v.into_owned().into())
302            .ok_or_else(|| Error::with_msg("Unknown variable").context("requested variable", key))
303    }
304
305    fn set_global(
306        &self,
307        name: crate::model::KString,
308        val: crate::model::Value,
309    ) -> Option<crate::model::Value> {
310        self.parent.set_global(name, val)
311    }
312
313    fn set_index(&self, name: crate::model::KString, val: Value) -> Option<Value> {
314        self.parent.set_index(name, val)
315    }
316
317    fn get_index<'a>(&'a self, name: &str) -> Option<ValueCow<'a>> {
318        self.parent.get_index(name)
319    }
320
321    fn registers(&self) -> &super::Registers {
322        &self.registers
323    }
324}
325
326#[cfg(test)]
327mod tests {
328    use crate::{runtime::RuntimeBuilder, Runtime};
329
330    use super::*;
331
332    #[test]
333    fn test_opaque_stack_frame_try_get() {
334        let globals = {
335            let mut o = Object::new();
336            o.insert("a".into(), Value::Scalar(1i64.into()));
337            o
338        };
339        let runtime = RuntimeBuilder::new().set_globals(&globals).build();
340        let opaque_stack_frame = SandboxedStackFrame::new(&runtime, {
341            let mut o = Object::new();
342            o.insert("b".into(), Value::Scalar(2i64.into()));
343            o
344        });
345
346        // Testing that you can access variables in the current frame, but not the parent
347        assert!(opaque_stack_frame.try_get(&["a".into()]).is_none());
348        assert!(opaque_stack_frame.try_get(&["b".into()]).is_some());
349
350        let stack_frame = StackFrame::new(opaque_stack_frame, {
351            let mut o = Object::new();
352            o.insert("c".into(), Value::Scalar(1i64.into()));
353            o
354        });
355
356        // Testing that a child of a OpaqueStackFrame can access access OpaqueStackFrame's variables but not the parent
357        assert!(stack_frame.try_get(&["a".into()]).is_none());
358        assert!(stack_frame.try_get(&["b".into()]).is_some());
359        assert!(stack_frame.try_get(&["c".into()]).is_some());
360    }
361
362    #[test]
363    fn test_opaque_stack_frame_get() {
364        let globals = {
365            let mut o = Object::new();
366            o.insert("a".into(), Value::Scalar(1i64.into()));
367            o
368        };
369        let runtime = RuntimeBuilder::new().set_globals(&globals).build();
370        let opaque_stack_frame = SandboxedStackFrame::new(&runtime, {
371            let mut o = Object::new();
372            o.insert("b".into(), Value::Scalar(2i64.into()));
373            o
374        });
375
376        // Testing that you can access variables in the current frame, but not the parent
377        assert!(opaque_stack_frame.get(&["a".into()]).is_err());
378        assert!(opaque_stack_frame.get(&["b".into()]).is_ok());
379
380        let stack_frame = StackFrame::new(opaque_stack_frame, {
381            let mut o = Object::new();
382            o.insert("c".into(), Value::Scalar(1i64.into()));
383            o
384        });
385
386        // Testing that a child of a OpaqueStackFrame can access access OpaqueStackFrame's variables but not the parent
387        assert!(stack_frame.get(&["a".into()]).is_err());
388        assert!(stack_frame.get(&["b".into()]).is_ok());
389        assert!(stack_frame.get(&["c".into()]).is_ok());
390    }
391
392    #[test]
393    fn test_opaque_stack_frame_roots() {
394        let globals = {
395            let mut o = Object::new();
396            o.insert("a".into(), Value::Scalar(1i64.into()));
397            o
398        };
399        let runtime = RuntimeBuilder::new().set_globals(&globals).build();
400        let opaque_stack_frame = SandboxedStackFrame::new(&runtime, {
401            let mut o = Object::new();
402            o.insert("b".into(), Value::Scalar(2i64.into()));
403            o
404        });
405        let roots = opaque_stack_frame.roots();
406
407        // Testing that the roots are not copied from the parent
408        assert!(!roots.contains("a"));
409        assert!(roots.contains("b"));
410    }
411}