liquid_core/runtime/
variable.rs

1use std::fmt;
2
3use crate::error::{Error, Result};
4use crate::model::Path;
5use crate::model::Scalar;
6use crate::model::{ValueCow, ValueView};
7
8use super::Expression;
9use super::Runtime;
10
11/// A `Value` reference.
12#[derive(Clone, Debug, PartialEq)]
13pub struct Variable {
14    variable: Scalar,
15    indexes: Vec<Expression>,
16}
17
18impl Variable {
19    /// Create a `Value` reference.
20    pub fn with_literal<S: Into<Scalar>>(value: S) -> Self {
21        Self {
22            variable: value.into(),
23            indexes: Default::default(),
24        }
25    }
26
27    /// Append a literal.
28    pub fn push_literal<S: Into<Scalar>>(mut self, value: S) -> Self {
29        self.indexes.push(Expression::with_literal(value));
30        self
31    }
32
33    /// Convert to a `Path`.
34    pub fn try_evaluate<'c>(&'c self, runtime: &'c dyn Runtime) -> Option<Path<'c>> {
35        let mut path = Path::with_index(self.variable.as_ref());
36        path.reserve(self.indexes.len());
37        for expr in &self.indexes {
38            let v = expr.try_evaluate(runtime)?;
39            let s = match v {
40                ValueCow::Owned(v) => v.into_scalar(),
41                ValueCow::Borrowed(v) => v.as_scalar(),
42            }?;
43            path.push(s);
44        }
45        Some(path)
46    }
47
48    /// Convert to a `Path`.
49    pub fn evaluate<'c>(&'c self, runtime: &'c dyn Runtime) -> Result<Path<'c>> {
50        let mut path = Path::with_index(self.variable.as_ref());
51        path.reserve(self.indexes.len());
52        for expr in &self.indexes {
53            let v = expr.evaluate(runtime)?;
54            let s = match v {
55                ValueCow::Owned(v) => v.into_scalar(),
56                ValueCow::Borrowed(v) => v.as_scalar(),
57            }
58            .ok_or_else(|| {
59                let v = expr.evaluate(runtime).expect("lookup already verified");
60                let v = v.source();
61                let msg = format!("Expected scalar, found `{}`", v);
62                Error::with_msg(msg)
63            })?;
64            path.push(s);
65        }
66        Ok(path)
67    }
68}
69
70impl Extend<Scalar> for Variable {
71    fn extend<T: IntoIterator<Item = Scalar>>(&mut self, iter: T) {
72        let path = iter.into_iter().map(Expression::with_literal);
73        self.indexes.extend(path);
74    }
75}
76
77impl Extend<Expression> for Variable {
78    fn extend<T: IntoIterator<Item = Expression>>(&mut self, iter: T) {
79        let path = iter.into_iter();
80        self.indexes.extend(path);
81    }
82}
83
84impl fmt::Display for Variable {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        write!(f, "{}", self.variable.render())?;
87        for index in self.indexes.iter() {
88            write!(f, "[{}]", index)?;
89        }
90        Ok(())
91    }
92}
93
94#[cfg(test)]
95mod test {
96    use super::*;
97
98    use crate::model::Object;
99    use crate::model::ValueViewCmp;
100
101    use super::super::RuntimeBuilder;
102    use super::super::StackFrame;
103
104    #[test]
105    fn identifier_path_array_index() {
106        let globals: Object = serde_yaml::from_str(
107            r#"
108test_a: ["test"]
109"#,
110        )
111        .unwrap();
112        let mut var = Variable::with_literal("test_a");
113        let index = vec![Scalar::new(0)];
114        var.extend(index);
115
116        let runtime = RuntimeBuilder::new().build();
117        let runtime = StackFrame::new(&runtime, &globals);
118        let actual = var.evaluate(&runtime).unwrap();
119        let actual = runtime.get(&actual).unwrap();
120        assert_eq!(actual, ValueViewCmp::new(&"test"));
121    }
122
123    #[test]
124    fn identifier_path_array_index_negative() {
125        let globals: Object = serde_yaml::from_str(
126            r#"
127test_a: ["test1", "test2"]
128"#,
129        )
130        .unwrap();
131        let mut var = Variable::with_literal("test_a");
132        let index = vec![Scalar::new(-1)];
133        var.extend(index);
134
135        let runtime = RuntimeBuilder::new().build();
136        let runtime = StackFrame::new(&runtime, &globals);
137        let actual = var.evaluate(&runtime).unwrap();
138        let actual = runtime.get(&actual).unwrap();
139        assert_eq!(actual, ValueViewCmp::new(&"test2"));
140    }
141
142    #[test]
143    fn identifier_path_object() {
144        let globals: Object = serde_yaml::from_str(
145            r#"
146test_a:
147  - test_h: 5
148"#,
149        )
150        .unwrap();
151        let mut var = Variable::with_literal("test_a");
152        let index = vec![Scalar::new(0), Scalar::new("test_h")];
153        var.extend(index);
154
155        let runtime = RuntimeBuilder::new().build();
156        let runtime = StackFrame::new(&runtime, &globals);
157        let actual = var.evaluate(&runtime).unwrap();
158        let actual = runtime.get(&actual).unwrap();
159        assert_eq!(actual, ValueViewCmp::new(&5));
160    }
161}