loose_liquid_core/model/
find.rs

1//! Find a `ValueView` nested in an `ObjectView`
2
3use 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/// Path to a value in an `Object`.
15///
16/// There is guaranteed always at least one element.
17#[derive(Clone, Debug, PartialEq, Eq)]
18pub struct Path<'s>(Vec<ScalarCow<'s>>);
19
20impl<'s> Path<'s> {
21    /// Create a `Value` reference.
22    pub fn with_index<I: Into<ScalarCow<'s>>>(value: I) -> Self {
23        let indexes = vec![value.into()];
24        Path(indexes)
25    }
26
27    /// Append an index.
28    pub fn push<I: Into<ScalarCow<'s>>>(&mut self, value: I) {
29        self.0.push(value.into());
30    }
31
32    /// Reserves capacity for at least `additional` more elements to be inserted
33    /// in the given `Path`. The `Path` may reserve more space to avoid
34    /// frequent reallocations. After calling `reserve`, capacity will be
35    /// greater than or equal to `self.len() + additional`. Does nothing if
36    /// capacity is already sufficient.
37    pub fn reserve(&mut self, additional: usize) {
38        self.0.reserve(additional);
39    }
40
41    /// Access the `Value` reference.
42    pub fn iter(&self) -> PathIter<'_, '_> {
43        PathIter(self.0.iter())
44    }
45
46    /// Extracts a slice containing the entire vector.
47    #[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/// Iterate over indexes in a `Value`'s `Path`.
90#[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
119/// Find a `ValueView` nested in an `ObjectView`
120pub 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
194/// Find a `ValueView` nested in an `ObjectView`
195pub 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}