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