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
19use crate::utils::parse_path;
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::from("foo");
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::from("foo").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::from("foo.bar.baz");
63/// let expected = VariantPath::from("foo").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::from("foo")
73/// .join("bar")
74/// .join("baz");
75/// assert_eq!(path[1], VariantPathElement::field("bar"));
76/// ```
77#[derive(Debug, Clone, PartialEq, Default)]
78pub struct VariantPath<'a>(Vec<VariantPathElement<'a>>);
79
80impl<'a> VariantPath<'a> {
81 /// Create a new `VariantPath` from a vector of `VariantPathElement`.
82 pub fn new(path: Vec<VariantPathElement<'a>>) -> Self {
83 Self(path)
84 }
85
86 /// Return the inner path elements.
87 pub fn path(&self) -> &Vec<VariantPathElement<'_>> {
88 &self.0
89 }
90
91 /// Return a new `VariantPath` with element appended
92 pub fn join(mut self, element: impl Into<VariantPathElement<'a>>) -> Self {
93 self.push(element);
94 self
95 }
96
97 /// Append a new element to the path
98 pub fn push(&mut self, element: impl Into<VariantPathElement<'a>>) {
99 self.0.push(element.into());
100 }
101
102 /// Returns whether [`VariantPath`] has no path elements
103 pub fn is_empty(&self) -> bool {
104 self.0.is_empty()
105 }
106}
107
108impl<'a> From<Vec<VariantPathElement<'a>>> for VariantPath<'a> {
109 fn from(value: Vec<VariantPathElement<'a>>) -> Self {
110 Self::new(value)
111 }
112}
113
114/// Create from &str with support for dot notation
115impl<'a> From<&'a str> for VariantPath<'a> {
116 fn from(path: &'a str) -> Self {
117 VariantPath::new(path.split(".").flat_map(parse_path).collect())
118 }
119}
120
121/// Create from usize
122impl<'a> From<usize> for VariantPath<'a> {
123 fn from(index: usize) -> Self {
124 VariantPath::new(vec![VariantPathElement::index(index)])
125 }
126}
127
128impl<'a> From<&[VariantPathElement<'a>]> for VariantPath<'a> {
129 fn from(elements: &[VariantPathElement<'a>]) -> Self {
130 VariantPath::new(elements.to_vec())
131 }
132}
133
134/// Create from iter
135impl<'a> FromIterator<VariantPathElement<'a>> for VariantPath<'a> {
136 fn from_iter<T: IntoIterator<Item = VariantPathElement<'a>>>(iter: T) -> Self {
137 VariantPath::new(Vec::from_iter(iter))
138 }
139}
140
141impl<'a> Deref for VariantPath<'a> {
142 type Target = [VariantPathElement<'a>];
143
144 fn deref(&self) -> &Self::Target {
145 &self.0
146 }
147}
148
149/// Element of a [`VariantPath`] that can be a field name or an index.
150///
151/// See [`VariantPath`] for more details and examples.
152#[derive(Debug, Clone, PartialEq)]
153pub enum VariantPathElement<'a> {
154 /// Access field with name `name`
155 Field { name: Cow<'a, str> },
156 /// Access the list element at `index`
157 Index { index: usize },
158}
159
160impl<'a> VariantPathElement<'a> {
161 pub fn field(name: impl Into<Cow<'a, str>>) -> VariantPathElement<'a> {
162 let name = name.into();
163 VariantPathElement::Field { name }
164 }
165
166 pub fn index(index: usize) -> VariantPathElement<'a> {
167 VariantPathElement::Index { index }
168 }
169}
170
171// Conversion utilities for `VariantPathElement` from string types
172impl<'a> From<Cow<'a, str>> for VariantPathElement<'a> {
173 fn from(name: Cow<'a, str>) -> Self {
174 VariantPathElement::field(name)
175 }
176}
177
178impl<'a> From<&'a str> for VariantPathElement<'a> {
179 fn from(name: &'a str) -> Self {
180 VariantPathElement::field(Cow::Borrowed(name))
181 }
182}
183
184impl<'a> From<String> for VariantPathElement<'a> {
185 fn from(name: String) -> Self {
186 VariantPathElement::field(Cow::Owned(name))
187 }
188}
189
190impl<'a> From<&'a String> for VariantPathElement<'a> {
191 fn from(name: &'a String) -> Self {
192 VariantPathElement::field(Cow::Borrowed(name.as_str()))
193 }
194}
195
196impl<'a> From<usize> for VariantPathElement<'a> {
197 fn from(index: usize) -> Self {
198 VariantPathElement::index(index)
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205
206 #[test]
207 fn test_variant_path_empty() {
208 let path = VariantPath::from_iter([]);
209 assert!(path.is_empty());
210 }
211
212 #[test]
213 fn test_variant_path_empty_str() {
214 let path = VariantPath::from("");
215 assert!(path.is_empty());
216 }
217
218 #[test]
219 fn test_variant_path_non_empty() {
220 let p = VariantPathElement::from("a");
221 let path = VariantPath::from_iter([p]);
222 assert!(!path.is_empty());
223 }
224
225 #[test]
226 fn test_variant_path_dot_notation_with_array_index() {
227 let path = VariantPath::from("city.store.books[3].title");
228
229 let expected = VariantPath::from("city")
230 .join("store")
231 .join("books")
232 .join(3)
233 .join("title");
234
235 assert_eq!(path, expected);
236 }
237
238 #[test]
239 fn test_variant_path_dot_notation_with_only_array_index() {
240 let path = VariantPath::from("[3]");
241
242 let expected = VariantPath::from(3);
243
244 assert_eq!(path, expected);
245 }
246
247 #[test]
248 fn test_variant_path_dot_notation_with_starting_array_index() {
249 let path = VariantPath::from("[3].title");
250
251 let expected = VariantPath::from(3).join("title");
252
253 assert_eq!(path, expected);
254 }
255}