loose_liquid_core/model/
find.rs1use std::fmt;
4use std::slice;
5
6use crate::error::{Error, Result};
7use crate::model::KStringCow;
8
9use super::ScalarCow;
10use super::Value;
11use super::ValueCow;
12use super::ValueView;
13
14#[derive(Clone, Debug, PartialEq, Eq)]
18pub struct Path<'s>(Vec<ScalarCow<'s>>);
19
20impl<'s> Path<'s> {
21 pub fn with_index<I: Into<ScalarCow<'s>>>(value: I) -> Self {
23 let indexes = vec![value.into()];
24 Path(indexes)
25 }
26
27 pub fn push<I: Into<ScalarCow<'s>>>(&mut self, value: I) {
29 self.0.push(value.into());
30 }
31
32 pub fn reserve(&mut self, additional: usize) {
38 self.0.reserve(additional);
39 }
40
41 pub fn iter(&self) -> PathIter<'_, '_> {
43 PathIter(self.0.iter())
44 }
45
46 #[inline]
48 pub fn as_slice(&self) -> &[ScalarCow<'s>] {
49 self.0.as_slice()
50 }
51}
52
53impl<'s> Extend<ScalarCow<'s>> for Path<'s> {
54 fn extend<T: IntoIterator<Item = ScalarCow<'s>>>(&mut self, iter: T) {
55 self.0.extend(iter);
56 }
57}
58
59impl<'s> ::std::ops::Deref for Path<'s> {
60 type Target = [ScalarCow<'s>];
61
62 #[inline]
63 fn deref(&self) -> &Self::Target {
64 &self.0
65 }
66}
67
68impl<'s> ::std::borrow::Borrow<[ScalarCow<'s>]> for Path<'s> {
69 #[inline]
70 fn borrow(&self) -> &[ScalarCow<'s>] {
71 self
72 }
73}
74
75impl<'s> AsRef<[ScalarCow<'s>]> for Path<'s> {
76 #[inline]
77 fn as_ref(&self) -> &[ScalarCow<'s>] {
78 self
79 }
80}
81
82impl<'s> fmt::Display for Path<'s> {
83 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84 let data = itertools::join(self.iter().map(ValueView::render), ".");
85 write!(f, "{}", data)
86 }
87}
88
89#[derive(Debug)]
91pub struct PathIter<'i, 's>(slice::Iter<'i, ScalarCow<'s>>);
92
93impl<'i, 's: 'i> Iterator for PathIter<'i, 's> {
94 type Item = &'i ScalarCow<'s>;
95
96 #[inline]
97 fn next(&mut self) -> Option<&'i ScalarCow<'s>> {
98 self.0.next()
99 }
100
101 #[inline]
102 fn size_hint(&self) -> (usize, Option<usize>) {
103 self.0.size_hint()
104 }
105
106 #[inline]
107 fn count(self) -> usize {
108 self.0.count()
109 }
110}
111
112impl<'i, 's: 'i> ExactSizeIterator for PathIter<'i, 's> {
113 #[inline]
114 fn len(&self) -> usize {
115 self.0.len()
116 }
117}
118
119pub fn try_find<'o>(value: &'o dyn ValueView, path: &[ScalarCow<'_>]) -> Option<ValueCow<'o>> {
121 let indexes = path.iter();
122 try_find_borrowed(value, indexes)
123}
124
125fn try_find_borrowed<'o, 'i>(
126 value: &'o dyn ValueView,
127 mut path: impl Iterator<Item = &'i ScalarCow<'i>>,
128) -> Option<ValueCow<'o>> {
129 let index = match path.next() {
130 Some(index) => index,
131 None => {
132 return Some(ValueCow::Borrowed(value));
133 }
134 };
135 let child = augmented_get(value, index)?;
136 match child {
137 ValueCow::Owned(child) => try_find_owned(child, path),
138 ValueCow::Borrowed(child) => try_find_borrowed(child, path),
139 }
140}
141
142fn try_find_owned<'o, 'i>(
143 value: Value,
144 mut path: impl Iterator<Item = &'i ScalarCow<'i>>,
145) -> Option<ValueCow<'o>> {
146 let index = match path.next() {
147 Some(index) => index,
148 None => {
149 return Some(ValueCow::Owned(value));
150 }
151 };
152 let child = augmented_get(&value, index)?;
153 match child {
154 ValueCow::Owned(child) => try_find_owned(child, path),
155 ValueCow::Borrowed(child) => {
156 try_find_borrowed(child, path).map(|v| ValueCow::Owned(v.into_owned()))
157 }
158 }
159}
160
161fn augmented_get<'o>(value: &'o dyn ValueView, index: &ScalarCow<'_>) -> Option<ValueCow<'o>> {
162 if let Some(arr) = value.as_array() {
163 if let Some(index) = index.to_integer() {
164 arr.get(index).map(ValueCow::Borrowed)
165 } else {
166 match &*index.to_kstr() {
167 "first" => arr.first().map(ValueCow::Borrowed),
168 "last" => arr.last().map(ValueCow::Borrowed),
169 "size" => Some(ValueCow::Owned(Value::scalar(arr.size()))),
170 _ => None,
171 }
172 }
173 } else if let Some(obj) = value.as_object() {
174 let index = index.to_kstr();
175 obj.get(index.as_str())
176 .map(ValueCow::Borrowed)
177 .or_else(|| match index.as_str() {
178 "size" => Some(ValueCow::Owned(Value::scalar(obj.size()))),
179 _ => None,
180 })
181 } else if let Some(scalar) = value.as_scalar() {
182 let index = index.to_kstr();
183 match index.as_str() {
184 "size" => Some(ValueCow::Owned(Value::scalar(
185 scalar.to_kstr().as_str().len() as i64,
186 ))),
187 _ => None,
188 }
189 } else {
190 None
191 }
192}
193
194pub fn find<'o>(value: &'o dyn ValueView, path: &[ScalarCow<'_>]) -> Result<ValueCow<'o>> {
196 if let Some(res) = try_find(value, path) {
197 Ok(res)
198 } else {
199 for cur_idx in 1..path.len() {
200 let subpath_end = path.len() - cur_idx;
201 let subpath = &path[0..subpath_end];
202 if let Some(parent) = try_find(value, subpath) {
203 let subpath = itertools::join(subpath.iter().map(ValueView::render), ".");
204 let requested = &path[subpath_end];
205 let available = if let Some(arr) = parent.as_array() {
206 let mut available = vec![
207 KStringCow::from_static("first"),
208 KStringCow::from_static("last"),
209 ];
210 if 0 < arr.size() {
211 available
212 .insert(0, KStringCow::from_string(format!("0..{}", arr.size() - 1)));
213 }
214 available
215 } else if let Some(obj) = parent.as_object() {
216 let available: Vec<_> = obj.keys().collect();
217 available
218 } else {
219 Vec::new()
220 };
221 let available = itertools::join(available.iter(), ", ");
222 return Error::with_kind(crate::ErrorKind::UnknownIndex)
223 .context("variable", subpath)
224 .context("requested index", format!("{}", requested.render()))
225 .context("available indexes", available)
226 .into_err();
227 }
228 }
229
230 panic!(
231 "Should have already errored for `{}` with path {:?}",
232 value.source(),
233 path
234 );
235 }
236}