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 std::{borrow::Cow, ops::Deref};
18
19/// Represents a qualified path to a potential subfield or index of a variant
20/// value.
21///
22/// Can be used with [`Variant::get_path`] to retrieve a specific subfield of
23/// a variant value.
24///
25/// [`Variant::get_path`]: crate::Variant::get_path
26///
27/// Create a [`VariantPath`] from a vector of [`VariantPathElement`], or
28/// from a single field name or index.
29///
30/// # Example: Simple paths
31/// ```rust
32/// # use parquet_variant::{VariantPath, VariantPathElement};
33/// // access the field "foo" in a variant object value
34/// let path = VariantPath::from("foo");
35/// // access the first element in a variant list vale
36/// let path = VariantPath::from(0);
37/// ```
38///
39/// # Example: Compound paths
40/// ```
41/// # use parquet_variant::{VariantPath, VariantPathElement};
42/// /// You can also create a path by joining elements together:
43/// // access the field "foo" and then the first element in a variant list value
44/// let path = VariantPath::from("foo").join(0);
45/// // this is the same as the previous one
46/// let path2 = VariantPath::from_iter(["foo".into(), 0.into()]);
47/// assert_eq!(path, path2);
48/// // you can also create a path from a vector of `VariantPathElement` directly
49/// let path3 = [
50///   VariantPathElement::field("foo"),
51///   VariantPathElement::index(0)
52/// ].into_iter().collect::<VariantPath>();
53/// assert_eq!(path, path3);
54/// ```
55///
56/// # Example: From Dot notation strings
57/// ```
58/// # use parquet_variant::{VariantPath, VariantPathElement};
59/// /// You can also convert strings directly into paths using dot notation
60/// let path = VariantPath::from("foo.bar.baz");
61/// let expected = VariantPath::from("foo").join("bar").join("baz");
62/// assert_eq!(path, expected);
63/// ```
64///
65/// # Example: Accessing Compound paths
66/// ```
67/// # use parquet_variant::{VariantPath, VariantPathElement};
68/// /// You can access the paths using slices
69/// // access the field "foo" and then the first element in a variant list value
70/// let path = VariantPath::from("foo")
71///   .join("bar")
72///   .join("baz");
73/// assert_eq!(path[1], VariantPathElement::field("bar"));
74/// ```
75#[derive(Debug, Clone, PartialEq, Default)]
76pub struct VariantPath<'a>(Vec<VariantPathElement<'a>>);
77
78impl<'a> VariantPath<'a> {
79    /// Create a new `VariantPath` from a vector of `VariantPathElement`.
80    pub fn new(path: Vec<VariantPathElement<'a>>) -> Self {
81        Self(path)
82    }
83
84    /// Return the inner path elements.
85    pub fn path(&self) -> &Vec<VariantPathElement<'_>> {
86        &self.0
87    }
88
89    /// Return a new `VariantPath` with element appended
90    pub fn join(mut self, element: impl Into<VariantPathElement<'a>>) -> Self {
91        self.push(element);
92        self
93    }
94
95    /// Append a new element to the path
96    pub fn push(&mut self, element: impl Into<VariantPathElement<'a>>) {
97        self.0.push(element.into());
98    }
99
100    /// Returns whether [`VariantPath`] has no path elements
101    pub fn is_empty(&self) -> bool {
102        self.0.is_empty()
103    }
104}
105
106impl<'a> From<Vec<VariantPathElement<'a>>> for VariantPath<'a> {
107    fn from(value: Vec<VariantPathElement<'a>>) -> Self {
108        Self::new(value)
109    }
110}
111
112/// Create from &str with support for dot notation
113impl<'a> From<&'a str> for VariantPath<'a> {
114    fn from(path: &'a str) -> Self {
115        if path.is_empty() {
116            VariantPath::new(vec![])
117        } else {
118            VariantPath::new(path.split('.').map(Into::into).collect())
119        }
120    }
121}
122
123/// Create from usize
124impl<'a> From<usize> for VariantPath<'a> {
125    fn from(index: usize) -> Self {
126        VariantPath::new(vec![VariantPathElement::index(index)])
127    }
128}
129
130impl<'a> From<&[VariantPathElement<'a>]> for VariantPath<'a> {
131    fn from(elements: &[VariantPathElement<'a>]) -> Self {
132        VariantPath::new(elements.to_vec())
133    }
134}
135
136/// Create from iter
137impl<'a> FromIterator<VariantPathElement<'a>> for VariantPath<'a> {
138    fn from_iter<T: IntoIterator<Item = VariantPathElement<'a>>>(iter: T) -> Self {
139        VariantPath::new(Vec::from_iter(iter))
140    }
141}
142
143impl<'a> Deref for VariantPath<'a> {
144    type Target = [VariantPathElement<'a>];
145
146    fn deref(&self) -> &Self::Target {
147        &self.0
148    }
149}
150
151/// Element of a [`VariantPath`] that can be a field name or an index.
152///
153/// See [`VariantPath`] for more details and examples.
154#[derive(Debug, Clone, PartialEq)]
155pub enum VariantPathElement<'a> {
156    /// Access field with name `name`
157    Field { name: Cow<'a, str> },
158    /// Access the list element at `index`
159    Index { index: usize },
160}
161
162impl<'a> VariantPathElement<'a> {
163    pub fn field(name: impl Into<Cow<'a, str>>) -> VariantPathElement<'a> {
164        let name = name.into();
165        VariantPathElement::Field { name }
166    }
167
168    pub fn index(index: usize) -> VariantPathElement<'a> {
169        VariantPathElement::Index { index }
170    }
171}
172
173// Conversion utilities for `VariantPathElement` from string types
174impl<'a> From<Cow<'a, str>> for VariantPathElement<'a> {
175    fn from(name: Cow<'a, str>) -> Self {
176        VariantPathElement::field(name)
177    }
178}
179
180impl<'a> From<&'a str> for VariantPathElement<'a> {
181    fn from(name: &'a str) -> Self {
182        VariantPathElement::field(Cow::Borrowed(name))
183    }
184}
185
186impl<'a> From<String> for VariantPathElement<'a> {
187    fn from(name: String) -> Self {
188        VariantPathElement::field(Cow::Owned(name))
189    }
190}
191
192impl<'a> From<&'a String> for VariantPathElement<'a> {
193    fn from(name: &'a String) -> Self {
194        VariantPathElement::field(Cow::Borrowed(name.as_str()))
195    }
196}
197
198impl<'a> From<usize> for VariantPathElement<'a> {
199    fn from(index: usize) -> Self {
200        VariantPathElement::index(index)
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207
208    #[test]
209    fn test_variant_path_empty() {
210        let path = VariantPath::from_iter([]);
211        assert!(path.is_empty());
212    }
213
214    #[test]
215    fn test_variant_path_empty_str() {
216        let path = VariantPath::from("");
217        assert!(path.is_empty());
218    }
219
220    #[test]
221    fn test_variant_path_non_empty() {
222        let p = VariantPathElement::from("a");
223        let path = VariantPath::from_iter([p]);
224        assert!(!path.is_empty());
225    }
226}