liquid_core/runtime/
variable.rs1use 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#[derive(Clone, Debug, PartialEq)]
13pub struct Variable {
14 variable: Scalar,
15 indexes: Vec<Expression>,
16}
17
18impl Variable {
19 pub fn with_literal<S: Into<Scalar>>(value: S) -> Self {
21 Self {
22 variable: value.into(),
23 indexes: Default::default(),
24 }
25 }
26
27 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 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 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}