liquid_interpreter/
store.rs

1use std::fmt;
2
3use itertools;
4use liquid_error::{Error, Result};
5use liquid_value::Object;
6use liquid_value::PathRef;
7use liquid_value::ScalarCow;
8use liquid_value::Value;
9
10/// Immutable view into a template's global variables.
11pub trait ValueStore: fmt::Debug {
12    /// Check if root variable exists.
13    fn contains_root(&self, name: &str) -> bool;
14
15    /// Enumerate all root variables.
16    fn roots(&self) -> Vec<&str>;
17
18    /// Check if variable exists.
19    ///
20    /// Notes to implementers:
21    /// - Don't forget to reverse-index on negative array indexes
22    /// - Don't forget about arr.first, arr.last.
23    fn contains_variable(&self, path: PathRef) -> bool;
24
25    /// Access a variable.
26    ///
27    /// Notes to implementers:
28    /// - Don't forget to reverse-index on negative array indexes
29    /// - Don't forget about arr.first, arr.last.
30    fn try_get_variable<'a>(&'a self, path: PathRef) -> Option<&'a Value>;
31
32    /// Access a variable.
33    ///
34    /// Notes to implementers:
35    /// - Don't forget to reverse-index on negative array indexes
36    /// - Don't forget about arr.first, arr.last.
37    fn get_variable<'a>(&'a self, path: PathRef) -> Result<&'a Value>;
38}
39
40impl ValueStore for Object {
41    fn contains_root(&self, name: &str) -> bool {
42        self.contains_key(name)
43    }
44
45    fn roots(&self) -> Vec<&str> {
46        self.keys().map(|s| s.as_ref()).collect()
47    }
48
49    fn contains_variable(&self, path: PathRef) -> bool {
50        get_variable_option(self, path).is_some()
51    }
52
53    fn try_get_variable<'a>(&'a self, path: PathRef) -> Option<&'a Value> {
54        get_variable_option(self, path)
55    }
56
57    fn get_variable<'a>(&'a self, path: PathRef) -> Result<&'a Value> {
58        if let Some(res) = self.try_get_variable(path) {
59            return Ok(res);
60        } else {
61            for cur_idx in 1..path.len() {
62                let subpath_end = path.len() - cur_idx;
63                let subpath = &path[0..subpath_end];
64                if let Some(parent) = self.try_get_variable(subpath) {
65                    let subpath = itertools::join(subpath.iter().map(ScalarCow::render), ".");
66                    let requested = &path[subpath_end];
67                    let available: Vec<_> = parent.keys().collect();
68                    let available = itertools::join(available.iter().map(ScalarCow::render), ", ");
69                    return Error::with_msg("Unknown index")
70                        .context("variable", subpath)
71                        .context("requested index", format!("{}", requested.render()))
72                        .context("available indexes", available)
73                        .into_err();
74                }
75            }
76
77            let requested = path
78                .get(0)
79                .expect("`Path` guarantees at least one element")
80                .to_str()
81                .into_owned();
82            let available = itertools::join(self.keys(), ", ");
83            return Error::with_msg("Unknown variable")
84                .context("requested variable", requested)
85                .context("available variables", available)
86                .into_err();
87        }
88    }
89}
90
91fn get_variable_option<'o>(obj: &'o Object, path: PathRef) -> Option<&'o Value> {
92    let mut indexes = path.iter();
93    let key = indexes.next()?;
94    let key = key.to_str();
95    let value = obj.get(key.as_ref())?;
96
97    indexes.fold(Some(value), |value, index| {
98        let value = value?;
99        value.get(index)
100    })
101}