Skip to main content

parquet_variant/
path.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17use crate::utils::parse_path;
18use arrow_schema::ArrowError;
19use std::{borrow::Cow, ops::Deref};
20
21/// Represents a qualified path to a potential subfield or index of a variant
22/// value.
23///
24/// Can be used with [`Variant::get_path`] to retrieve a specific subfield of
25/// a variant value.
26///
27/// [`Variant::get_path`]: crate::Variant::get_path
28///
29/// Create a [`VariantPath`] from a vector of [`VariantPathElement`], or
30/// from a single field name or index.
31///
32/// # Example: Simple paths
33/// ```rust
34/// # use parquet_variant::{VariantPath, VariantPathElement};
35/// // access the field "foo" in a variant object value
36/// let path = VariantPath::try_from("foo").unwrap();
37/// // access the first element in a variant list vale
38/// let path = VariantPath::from(0);
39/// ```
40///
41/// # Example: Compound paths
42/// ```
43/// # use parquet_variant::{VariantPath, VariantPathElement};
44/// /// You can also create a path by joining elements together:
45/// // access the field "foo" and then the first element in a variant list value
46/// let path = VariantPath::try_from("foo").unwrap().join(0);
47/// // this is the same as the previous one
48/// let path2 = VariantPath::from_iter(["foo".into(), 0.into()]);
49/// assert_eq!(path, path2);
50/// // you can also create a path from a vector of `VariantPathElement` directly
51/// let path3 = [
52///   VariantPathElement::field("foo"),
53///   VariantPathElement::index(0)
54/// ].into_iter().collect::<VariantPath>();
55/// assert_eq!(path, path3);
56/// ```
57///
58/// # Example: From Dot notation strings
59/// ```
60/// # use parquet_variant::{VariantPath, VariantPathElement};
61/// /// You can also convert strings directly into paths using dot notation
62/// let path = VariantPath::try_from("foo.bar.baz").unwrap();
63/// let expected = VariantPath::try_from("foo").unwrap().join("bar").join("baz");
64/// assert_eq!(path, expected);
65/// ```
66///
67/// # Example: Accessing Compound paths
68/// ```
69/// # use parquet_variant::{VariantPath, VariantPathElement};
70/// /// You can access the paths using slices
71/// // access the field "foo" and then the first element in a variant list value
72/// let path = VariantPath::try_from("foo").unwrap()
73///   .join("bar")
74///   .join("baz");
75/// assert_eq!(path[1], VariantPathElement::field("bar"));
76/// ```
77///
78/// # Example: Accessing field with bracket
79/// ```
80/// # use parquet_variant::{VariantPath, VariantPathElement};
81/// let path = VariantPath::try_from("a['b.c'].d[2]['3']").unwrap();
82/// let expected = VariantPath::from_iter([VariantPathElement::field("a"),
83///     VariantPathElement::field("b.c"),
84///     VariantPathElement::field("d"),
85///     VariantPathElement::index(2),
86///     VariantPathElement::field("3")]);
87/// assert_eq!(path, expected)
88/// ```
89#[derive(Debug, Clone, PartialEq, Default)]
90pub struct VariantPath<'a>(Vec<VariantPathElement<'a>>);
91
92impl<'a> VariantPath<'a> {
93    /// Create a new `VariantPath` from a vector of `VariantPathElement`.
94    pub fn new(path: Vec<VariantPathElement<'a>>) -> Self {
95        Self(path)
96    }
97
98    /// Return the inner path elements.
99    pub fn path(&self) -> &Vec<VariantPathElement<'_>> {
100        &self.0
101    }
102
103    /// Return a new `VariantPath` with element appended
104    pub fn join(mut self, element: impl Into<VariantPathElement<'a>>) -> Self {
105        self.push(element);
106        self
107    }
108
109    /// Append a new element to the path
110    pub fn push(&mut self, element: impl Into<VariantPathElement<'a>>) {
111        self.0.push(element.into());
112    }
113
114    /// Returns whether [`VariantPath`] has no path elements
115    pub fn is_empty(&self) -> bool {
116        self.0.is_empty()
117    }
118}
119
120impl<'a> From<Vec<VariantPathElement<'a>>> for VariantPath<'a> {
121    fn from(value: Vec<VariantPathElement<'a>>) -> Self {
122        Self::new(value)
123    }
124}
125
126/// Create from &str with support for dot notation
127impl<'a> TryFrom<&'a str> for VariantPath<'a> {
128    type Error = ArrowError;
129
130    fn try_from(path: &'a str) -> Result<Self, Self::Error> {
131        parse_path(path).map(VariantPath::new)
132    }
133}
134
135/// Create from usize
136impl From<usize> for VariantPath<'_> {
137    fn from(index: usize) -> Self {
138        VariantPath::new(vec![VariantPathElement::index(index)])
139    }
140}
141
142impl<'a> From<&[VariantPathElement<'a>]> for VariantPath<'a> {
143    fn from(elements: &[VariantPathElement<'a>]) -> Self {
144        VariantPath::new(elements.to_vec())
145    }
146}
147
148/// Create from iter
149impl<'a> FromIterator<VariantPathElement<'a>> for VariantPath<'a> {
150    fn from_iter<T: IntoIterator<Item = VariantPathElement<'a>>>(iter: T) -> Self {
151        VariantPath::new(Vec::from_iter(iter))
152    }
153}
154
155impl<'a> Deref for VariantPath<'a> {
156    type Target = [VariantPathElement<'a>];
157
158    fn deref(&self) -> &Self::Target {
159        &self.0
160    }
161}
162
163/// Element of a [`VariantPath`] that can be a field name, an index, or a list element wildcard.
164///
165/// See [`VariantPath`] for more details and examples.
166#[derive(Debug, Clone, PartialEq)]
167pub enum VariantPathElement<'a> {
168    /// Access field with name `name`
169    Field { name: Cow<'a, str> },
170    /// Access the list element at `index`
171    Index { index: usize },
172    /// Match the shared element schema of a list (`[*]`)
173    ListElement,
174}
175
176impl<'a> VariantPathElement<'a> {
177    pub fn field(name: impl Into<Cow<'a, str>>) -> VariantPathElement<'a> {
178        let name = name.into();
179        VariantPathElement::Field { name }
180    }
181
182    pub fn index(index: usize) -> VariantPathElement<'a> {
183        VariantPathElement::Index { index }
184    }
185
186    pub fn list_element() -> VariantPathElement<'a> {
187        VariantPathElement::ListElement
188    }
189}
190
191// Conversion utilities for `VariantPathElement` from string types
192impl<'a> From<Cow<'a, str>> for VariantPathElement<'a> {
193    fn from(name: Cow<'a, str>) -> Self {
194        VariantPathElement::field(name)
195    }
196}
197
198impl<'a> From<&'a str> for VariantPathElement<'a> {
199    fn from(name: &'a str) -> Self {
200        VariantPathElement::field(Cow::Borrowed(name))
201    }
202}
203
204impl From<String> for VariantPathElement<'_> {
205    fn from(name: String) -> Self {
206        VariantPathElement::field(Cow::Owned(name))
207    }
208}
209
210impl<'a> From<&'a String> for VariantPathElement<'a> {
211    fn from(name: &'a String) -> Self {
212        VariantPathElement::field(Cow::Borrowed(name.as_str()))
213    }
214}
215
216impl From<usize> for VariantPathElement<'_> {
217    fn from(index: usize) -> Self {
218        VariantPathElement::index(index)
219    }
220}
221
222#[cfg(test)]
223mod tests {
224    use super::*;
225
226    #[test]
227    fn test_variant_path_empty() {
228        let path = VariantPath::from_iter([]);
229        assert!(path.is_empty());
230    }
231
232    #[test]
233    fn test_variant_path_empty_str() {
234        let path = VariantPath::try_from("").unwrap();
235        assert!(path.is_empty());
236    }
237
238    #[test]
239    fn test_variant_path_non_empty() {
240        let p = VariantPathElement::from("a");
241        let path = VariantPath::from_iter([p]);
242        assert!(!path.is_empty());
243    }
244
245    #[test]
246    fn test_variant_path_dot_notation_with_array_index() {
247        let path = VariantPath::try_from("city.store.books[3].title").unwrap();
248
249        let expected = VariantPath::try_from("city")
250            .unwrap()
251            .join("store")
252            .join("books")
253            .join(3)
254            .join("title");
255
256        assert_eq!(path, expected);
257    }
258
259    #[test]
260    fn test_variant_path_dot_notation_with_only_array_index() {
261        let path = VariantPath::try_from("[3]").unwrap();
262
263        let expected = VariantPath::from(3);
264
265        assert_eq!(path, expected);
266    }
267
268    #[test]
269    fn test_variant_path_dot_notation_with_starting_array_index() {
270        let path = VariantPath::try_from("[3].title").unwrap();
271
272        let expected = VariantPath::from(3).join("title");
273
274        assert_eq!(path, expected);
275    }
276
277    #[test]
278    fn test_variant_path_field_in_bracket() {
279        // field with index
280        let path = VariantPath::try_from("foo[0].bar").unwrap();
281        let expected = VariantPath::from_iter([
282            VariantPathElement::field("foo"),
283            VariantPathElement::index(0),
284            VariantPathElement::field("bar"),
285        ]);
286        assert_eq!(path, expected);
287
288        // index in the end
289        let path = VariantPath::try_from("foo.bar[42]").unwrap();
290        let expected = VariantPath::from_iter([
291            VariantPathElement::field("foo"),
292            VariantPathElement::field("bar"),
293            VariantPathElement::index(42),
294        ]);
295        assert_eq!(path, expected);
296
297        // list wildcard is distinct from a quoted field named "*"
298        let path = VariantPath::try_from("foo[*]['*']").unwrap();
299        let expected = VariantPath::from_iter([
300            VariantPathElement::field("foo"),
301            VariantPathElement::list_element(),
302            VariantPathElement::field("*"),
303        ]);
304        assert_eq!(path, expected);
305
306        // invalid index will be treated as field
307        let path = VariantPath::try_from("foo.bar['abc'][\"def\"]").unwrap();
308        let expected = VariantPath::from_iter([
309            VariantPathElement::field("foo"),
310            VariantPathElement::field("bar"),
311            VariantPathElement::field("abc"),
312            VariantPathElement::field("def"),
313        ]);
314        assert_eq!(path, expected);
315
316        // a number quoted with `'` is treated as field, not index
317        let path = VariantPath::try_from("foo['0'].bar[\"1\"]").unwrap();
318        let expected = VariantPath::from_iter([
319            VariantPathElement::field("foo"),
320            VariantPathElement::field("0"),
321            VariantPathElement::field("bar"),
322            VariantPathElement::field("1"),
323        ]);
324        assert_eq!(path, expected);
325    }
326
327    #[test]
328    fn test_invalid_path_parse() {
329        // Leading dot
330        let err = VariantPath::try_from(".foo.bar").unwrap_err();
331        assert_eq!(err.to_string(), "Parser error: Unexpected leading '.'");
332
333        // Trailing dot
334        let err = VariantPath::try_from("foo.bar.").unwrap_err();
335        assert_eq!(err.to_string(), "Parser error: Unexpected trailing '.'");
336
337        // No ']' will be treated as error
338        let err = VariantPath::try_from("foo.bar[2.baz").unwrap_err();
339        assert_eq!(err.to_string(), "Parser error: Unclosed '[' at byte 7");
340
341        // No ']' because of escaped.
342        let err = VariantPath::try_from("foo.bar[2\\].fds").unwrap_err();
343        assert_eq!(err.to_string(), "Parser error: Unclosed '[' at byte 7");
344
345        // Trailing backslash in bracket
346        let err = VariantPath::try_from("foo.bar[fdafa\\").unwrap_err();
347        assert_eq!(err.to_string(), "Parser error: Unclosed '[' at byte 7");
348
349        // No '[' before ']'
350        let err = VariantPath::try_from("foo.bar]baz").unwrap_err();
351        assert_eq!(err.to_string(), "Parser error: Unexpected ']' at byte 7");
352
353        // Invalid number(without quote) parse
354        let err = VariantPath::try_from("foo.bar[123abc]").unwrap_err();
355        assert_eq!(
356            err.to_string(),
357            "Parser error: Invalid token in bracket request: `123abc`. Expected `*`, a quoted string, or a number(e.g., `[*]`, `['field']`, or `[123]`)"
358        );
359
360        let err = VariantPath::try_from("foo.bar[abc]").unwrap_err();
361        assert_eq!(
362            err.to_string(),
363            "Parser error: Invalid token in bracket request: `abc`. Expected `*`, a quoted string, or a number(e.g., `[*]`, `['field']`, or `[123]`)"
364        );
365
366        // Out-of-range integer indexes are invalid path tokens.
367        let too_large_index = (usize::MAX as u128) + 1;
368        let err = VariantPath::try_from(format!("[{too_large_index}]").as_str()).unwrap_err();
369        assert!(
370            err.to_string()
371                .contains("Parser error: Invalid token in bracket request"),
372            "{err}"
373        );
374    }
375}