pub enum Variant<'m, 'v> {
Show 20 variants
Null,
Int8(i8),
Int16(i16),
Int32(i32),
Int64(i64),
Date(NaiveDate),
TimestampMicros(DateTime<Utc>),
TimestampNtzMicros(NaiveDateTime),
Decimal4(VariantDecimal4),
Decimal8(VariantDecimal8),
Decimal16(VariantDecimal16),
Float(f32),
Double(f64),
BooleanTrue,
BooleanFalse,
Binary(&'v [u8]),
String(&'v str),
ShortString(ShortString<'v>),
Object(VariantObject<'m, 'v>),
List(VariantList<'m, 'v>),
}
Expand description
Represents a Parquet Variant
The lifetimes 'm
and 'v
are for metadata and value buffers, respectively.
§Background
The specification says:
The Variant Binary Encoding allows representation of semi-structured data (e.g. JSON) in a form that can be efficiently queried by path. The design is intended to allow efficient access to nested data even in the presence of very wide or deep structures.
Another motivation for the representation is that (aside from metadata) each nested Variant value is contiguous and self-contained. For example, in a Variant containing an Array of Variant values, the representation of an inner Variant value, when paired with the metadata of the full variant, is itself a valid Variant.
When stored in Parquet files, Variant fields can also be shredded. Shredding refers to extracting some elements of the variant into separate columns for more efficient extraction/filter pushdown. The Variant Shredding specification describes the details of shredding Variant values as typed Parquet columns.
A Variant represents a type that contains one of:
-
Primitive: A type and corresponding value (e.g. INT, STRING)
-
Array: An ordered list of Variant values
-
Object: An unordered collection of string/Variant pairs (i.e. key/value pairs). An object may not contain duplicate keys.
§Encoding
A Variant is encoded with 2 binary values, the value and the metadata. The metadata stores a header and an optional dictionary of field names which are referred to by offset in the value. The value is a binary representation of the actual data, and varies depending on the type.
§Design Goals
The design goals of the Rust API are as follows:
- Speed / Zero copy access (no
clone
ing is required) - Safety
- Follow standard Rust conventions
§Examples:
§Creating Variant
from Rust Types
use parquet_variant::Variant;
// variants can be directly constructed
let variant = Variant::Int32(123);
// or constructed via `From` impls
assert_eq!(variant, Variant::from(123i32));
§Creating Variant
from metadata and value
let metadata = [0x01, 0x00, 0x00];
let value = [0x09, 0x48, 0x49];
// parse the header metadata
assert_eq!(
Variant::from("HI"),
Variant::new(&metadata, &value)
);
§Using Variant
values
// variants can be used in match statements like normal enums
match variant {
Variant::Int32(i) => println!("Integer: {}", i),
Variant::String(s) => println!("String: {}", s),
_ => println!("Other variant"),
}
§Validation
Every instance of variant is either valid or invalid. depending on whether the underlying bytes are a valid encoding of a variant value (see below).
Instances produced by Self::try_new
, Self::try_new_with_metadata
, or Self::with_full_validation
are fully validated. They always contain valid data, and infallible accesses such as
iteration and indexing are panic-free. The validation cost is O(m + v)
where m
and
v
are the number of bytes in the metadata and value buffers, respectively.
Instances produced by Self::new
and Self::new_with_metadata
are unvalidated and so
they may contain either valid or invalid data. Infallible accesses to variant objects and
arrays, such as iteration and indexing will panic if the underlying bytes are invalid, and
fallible alternatives are provided as panic-free alternatives. Self::with_full_validation
can also be
used to validate an unvalidated instance, if desired.
Unvalidated instances can be constructed in constant time. This can be useful if the caller knows the underlying bytes were already validated previously, or if the caller intends to perform a small number of (fallible) accesses to a large variant value.
A validated variant value guarantees that the associated metadata and all nested object and array values are valid. Primitive variant subtypes are always valid by construction.
§Safety
Even an invalid variant value is still safe to use in the Rust sense. Accessing it with infallible methods may cause panics but will never lead to undefined behavior.
Variants§
Null
Primitive type: Null
Int8(i8)
Primitive (type_id=1): INT(8, SIGNED)
Int16(i16)
Primitive (type_id=1): INT(16, SIGNED)
Int32(i32)
Primitive (type_id=1): INT(32, SIGNED)
Int64(i64)
Primitive (type_id=1): INT(64, SIGNED)
Date(NaiveDate)
Primitive (type_id=1): DATE
TimestampMicros(DateTime<Utc>)
Primitive (type_id=1): TIMESTAMP(isAdjustedToUTC=true, MICROS)
TimestampNtzMicros(NaiveDateTime)
Primitive (type_id=1): TIMESTAMP(isAdjustedToUTC=false, MICROS)
Decimal4(VariantDecimal4)
Primitive (type_id=1): DECIMAL(precision, scale) 32-bits
Decimal8(VariantDecimal8)
Primitive (type_id=1): DECIMAL(precision, scale) 64-bits
Decimal16(VariantDecimal16)
Primitive (type_id=1): DECIMAL(precision, scale) 128-bits
Float(f32)
Primitive (type_id=1): FLOAT
Double(f64)
Primitive (type_id=1): DOUBLE
BooleanTrue
Primitive (type_id=1): BOOLEAN (true)
BooleanFalse
Primitive (type_id=1): BOOLEAN (false)
Binary(&'v [u8])
Primitive (type_id=1): BINARY
String(&'v str)
Primitive (type_id=1): STRING
ShortString(ShortString<'v>)
Short String (type_id=2): STRING
Object(VariantObject<'m, 'v>)
Object (type_id=3): N/A
List(VariantList<'m, 'v>)
Array (type_id=4): N/A
Implementations§
Source§impl<'m, 'v> Variant<'m, 'v>
impl<'m, 'v> Variant<'m, 'v>
Sourcepub fn try_new(metadata: &'m [u8], value: &'v [u8]) -> Result<Self, ArrowError>
pub fn try_new(metadata: &'m [u8], value: &'v [u8]) -> Result<Self, ArrowError>
Attempts to interpret a metadata and value buffer pair as a new Variant
.
The instance is fully validated.
§Example
use parquet_variant::{Variant, VariantMetadata};
let metadata = [0x01, 0x00, 0x00];
let value = [0x09, 0x48, 0x49];
// parse the header metadata
assert_eq!(
Variant::from("HI"),
Variant::try_new(&metadata, &value).unwrap()
);
Sourcepub fn new(metadata: &'m [u8], value: &'v [u8]) -> Self
pub fn new(metadata: &'m [u8], value: &'v [u8]) -> Self
Attempts to interpret a metadata and value buffer pair as a new Variant
.
The instance is unvalidated.
§Example
use parquet_variant::{Variant, VariantMetadata};
let metadata = [0x01, 0x00, 0x00];
let value = [0x09, 0x48, 0x49];
// parse the header metadata
assert_eq!(
Variant::from("HI"),
Variant::new(&metadata, &value)
);
Sourcepub fn try_new_with_metadata(
metadata: VariantMetadata<'m>,
value: &'v [u8],
) -> Result<Self, ArrowError>
pub fn try_new_with_metadata( metadata: VariantMetadata<'m>, value: &'v [u8], ) -> Result<Self, ArrowError>
Create a new variant with existing metadata.
The instance is fully validated.
§Example
let metadata = [0x01, 0x00, 0x00];
let value = [0x09, 0x48, 0x49];
// parse the header metadata first
let metadata = VariantMetadata::new(&metadata);
assert_eq!(
Variant::from("HI"),
Variant::try_new_with_metadata(metadata, &value).unwrap()
);
Sourcepub fn new_with_metadata(metadata: VariantMetadata<'m>, value: &'v [u8]) -> Self
pub fn new_with_metadata(metadata: VariantMetadata<'m>, value: &'v [u8]) -> Self
Similar to Self::try_new_with_metadata
, but unvalidated.
fn try_new_with_metadata_and_shallow_validation( metadata: VariantMetadata<'m>, value: &'v [u8], ) -> Result<Self, ArrowError>
Sourcepub fn is_fully_validated(&self) -> bool
pub fn is_fully_validated(&self) -> bool
True if this variant instance has already been validated.
Sourcepub fn with_full_validation(self) -> Result<Self, ArrowError>
pub fn with_full_validation(self) -> Result<Self, ArrowError>
Recursively validates this variant value, ensuring that infallible access will not panic due to invalid bytes.
Variant leaf values are always valid by construction, but objects and arrays can be constructed in unvalidated (and potentially invalid) state.
If Self::is_fully_validated
is true, validation is a no-op. Otherwise, the cost is O(m + v)
where m
and v
are the sizes of metadata and value buffers, respectively.
Sourcepub fn as_null(&self) -> Option<()>
pub fn as_null(&self) -> Option<()>
Converts this variant to ()
if it is null.
Returns Some(())
for null variants,
None
for non-null variants.
§Examples
use parquet_variant::Variant;
// you can extract `()` from a null variant
let v1 = Variant::from(());
assert_eq!(v1.as_null(), Some(()));
// but not from other variants
let v2 = Variant::from("hello!");
assert_eq!(v2.as_null(), None);
Sourcepub fn as_boolean(&self) -> Option<bool>
pub fn as_boolean(&self) -> Option<bool>
Converts this variant to a bool
if possible.
Returns Some(bool)
for boolean variants,
None
for non-boolean variants.
§Examples
use parquet_variant::Variant;
// you can extract a bool from the true variant
let v1 = Variant::from(true);
assert_eq!(v1.as_boolean(), Some(true));
// and the false variant
let v2 = Variant::from(false);
assert_eq!(v2.as_boolean(), Some(false));
// but not from other variants
let v3 = Variant::from("hello!");
assert_eq!(v3.as_boolean(), None);
Sourcepub fn as_naive_date(&self) -> Option<NaiveDate>
pub fn as_naive_date(&self) -> Option<NaiveDate>
Converts this variant to a NaiveDate
if possible.
Returns Some(NaiveDate)
for date variants,
None
for non-date variants.
§Examples
use parquet_variant::Variant;
use chrono::NaiveDate;
// you can extract a NaiveDate from a date variant
let date = NaiveDate::from_ymd_opt(2025, 4, 12).unwrap();
let v1 = Variant::from(date);
assert_eq!(v1.as_naive_date(), Some(date));
// but not from other variants
let v2 = Variant::from("hello!");
assert_eq!(v2.as_naive_date(), None);
Sourcepub fn as_datetime_utc(&self) -> Option<DateTime<Utc>>
pub fn as_datetime_utc(&self) -> Option<DateTime<Utc>>
Converts this variant to a DateTime<Utc>
if possible.
Returns Some(DateTime<Utc>)
for timestamp variants,
None
for non-timestamp variants.
§Examples
use parquet_variant::Variant;
use chrono::NaiveDate;
// you can extract a DateTime<Utc> from a UTC-adjusted variant
let datetime = NaiveDate::from_ymd_opt(2025, 4, 16).unwrap().and_hms_milli_opt(12, 34, 56, 780).unwrap().and_utc();
let v1 = Variant::from(datetime);
assert_eq!(v1.as_datetime_utc(), Some(datetime));
// or a non-UTC-adjusted variant
let datetime = NaiveDate::from_ymd_opt(2025, 4, 16).unwrap().and_hms_milli_opt(12, 34, 56, 780).unwrap();
let v2 = Variant::from(datetime);
assert_eq!(v2.as_datetime_utc(), Some(datetime.and_utc()));
// but not from other variants
let v3 = Variant::from("hello!");
assert_eq!(v3.as_datetime_utc(), None);
Sourcepub fn as_naive_datetime(&self) -> Option<NaiveDateTime>
pub fn as_naive_datetime(&self) -> Option<NaiveDateTime>
Converts this variant to a NaiveDateTime
if possible.
Returns Some(NaiveDateTime)
for timestamp variants,
None
for non-timestamp variants.
§Examples
use parquet_variant::Variant;
use chrono::NaiveDate;
// you can extract a NaiveDateTime from a non-UTC-adjusted variant
let datetime = NaiveDate::from_ymd_opt(2025, 4, 16).unwrap().and_hms_milli_opt(12, 34, 56, 780).unwrap();
let v1 = Variant::from(datetime);
assert_eq!(v1.as_naive_datetime(), Some(datetime));
// or a UTC-adjusted variant
let datetime = NaiveDate::from_ymd_opt(2025, 4, 16).unwrap().and_hms_milli_opt(12, 34, 56, 780).unwrap().and_utc();
let v2 = Variant::from(datetime);
assert_eq!(v2.as_naive_datetime(), Some(datetime.naive_utc()));
// but not from other variants
let v3 = Variant::from("hello!");
assert_eq!(v3.as_naive_datetime(), None);
Sourcepub fn as_u8_slice(&'v self) -> Option<&'v [u8]>
pub fn as_u8_slice(&'v self) -> Option<&'v [u8]>
Converts this variant to a &[u8]
if possible.
Returns Some(&[u8])
for binary variants,
None
for non-binary variants.
§Examples
use parquet_variant::Variant;
// you can extract a byte slice from a binary variant
let data = b"hello!";
let v1 = Variant::Binary(data);
assert_eq!(v1.as_u8_slice(), Some(data.as_slice()));
// but not from other variant types
let v2 = Variant::from(123i64);
assert_eq!(v2.as_u8_slice(), None);
Sourcepub fn as_string(&'v self) -> Option<&'v str>
pub fn as_string(&'v self) -> Option<&'v str>
Converts this variant to a &str
if possible.
Returns Some(&str)
for string variants (both regular and short strings),
None
for non-string variants.
§Examples
use parquet_variant::Variant;
// you can extract a string from string variants
let s = "hello!";
let v1 = Variant::from(s);
assert_eq!(v1.as_string(), Some(s));
// but not from other variants
let v2 = Variant::from(123i64);
assert_eq!(v2.as_string(), None);
Sourcepub fn as_int8(&self) -> Option<i8>
pub fn as_int8(&self) -> Option<i8>
Converts this variant to an i8
if possible.
Returns Some(i8)
for integer variants that fit in i8
range,
None
for non-integer variants or values that would overflow.
§Examples
use parquet_variant::Variant;
// you can read an int64 variant into an i8 if it fits
let v1 = Variant::from(123i64);
assert_eq!(v1.as_int8(), Some(123i8));
// but not if it would overflow
let v2 = Variant::from(1234i64);
assert_eq!(v2.as_int8(), None);
// or if the variant cannot be cast into an integer
let v3 = Variant::from("hello!");
assert_eq!(v3.as_int8(), None);
Sourcepub fn as_int16(&self) -> Option<i16>
pub fn as_int16(&self) -> Option<i16>
Converts this variant to an i16
if possible.
Returns Some(i16)
for integer variants that fit in i16
range,
None
for non-integer variants or values that would overflow.
§Examples
use parquet_variant::Variant;
// you can read an int64 variant into an i16 if it fits
let v1 = Variant::from(123i64);
assert_eq!(v1.as_int16(), Some(123i16));
// but not if it would overflow
let v2 = Variant::from(123456i64);
assert_eq!(v2.as_int16(), None);
// or if the variant cannot be cast into an integer
let v3 = Variant::from("hello!");
assert_eq!(v3.as_int16(), None);
Sourcepub fn as_int32(&self) -> Option<i32>
pub fn as_int32(&self) -> Option<i32>
Converts this variant to an i32
if possible.
Returns Some(i32)
for integer variants that fit in i32
range,
None
for non-integer variants or values that would overflow.
§Examples
use parquet_variant::Variant;
// you can read an int64 variant into an i32 if it fits
let v1 = Variant::from(123i64);
assert_eq!(v1.as_int32(), Some(123i32));
// but not if it would overflow
let v2 = Variant::from(12345678901i64);
assert_eq!(v2.as_int32(), None);
// or if the variant cannot be cast into an integer
let v3 = Variant::from("hello!");
assert_eq!(v3.as_int32(), None);
Sourcepub fn as_int64(&self) -> Option<i64>
pub fn as_int64(&self) -> Option<i64>
Converts this variant to an i64
if possible.
Returns Some(i64)
for integer variants that fit in i64
range,
None
for non-integer variants or values that would overflow.
§Examples
use parquet_variant::Variant;
// you can read an int64 variant into an i64
let v1 = Variant::from(123i64);
assert_eq!(v1.as_int64(), Some(123i64));
// but not a variant that cannot be cast into an integer
let v2 = Variant::from("hello!");
assert_eq!(v2.as_int64(), None);
Sourcepub fn as_decimal4(&self) -> Option<VariantDecimal4>
pub fn as_decimal4(&self) -> Option<VariantDecimal4>
Converts this variant to tuple with a 4-byte unscaled value if possible.
Returns Some((i32, u8))
for decimal variants where the unscaled value
fits in i32
range,
None
for non-decimal variants or decimal values that would overflow.
§Examples
use parquet_variant::{Variant, VariantDecimal4, VariantDecimal8};
// you can extract decimal parts from smaller or equally-sized decimal variants
let v1 = Variant::from(VariantDecimal4::try_new(1234_i32, 2).unwrap());
assert_eq!(v1.as_decimal4(), VariantDecimal4::try_new(1234_i32, 2).ok());
// and from larger decimal variants if they fit
let v2 = Variant::from(VariantDecimal8::try_new(1234_i64, 2).unwrap());
assert_eq!(v2.as_decimal4(), VariantDecimal4::try_new(1234_i32, 2).ok());
// but not if the value would overflow i32
let v3 = Variant::from(VariantDecimal8::try_new(12345678901i64, 2).unwrap());
assert_eq!(v3.as_decimal4(), None);
// or if the variant is not a decimal
let v4 = Variant::from("hello!");
assert_eq!(v4.as_decimal4(), None);
Sourcepub fn as_decimal8(&self) -> Option<VariantDecimal8>
pub fn as_decimal8(&self) -> Option<VariantDecimal8>
Converts this variant to tuple with an 8-byte unscaled value if possible.
Returns Some((i64, u8))
for decimal variants where the unscaled value
fits in i64
range,
None
for non-decimal variants or decimal values that would overflow.
§Examples
use parquet_variant::{Variant, VariantDecimal4, VariantDecimal8, VariantDecimal16};
// you can extract decimal parts from smaller or equally-sized decimal variants
let v1 = Variant::from(VariantDecimal4::try_new(1234_i32, 2).unwrap());
assert_eq!(v1.as_decimal8(), VariantDecimal8::try_new(1234_i64, 2).ok());
// and from larger decimal variants if they fit
let v2 = Variant::from(VariantDecimal16::try_new(1234_i128, 2).unwrap());
assert_eq!(v2.as_decimal8(), VariantDecimal8::try_new(1234_i64, 2).ok());
// but not if the value would overflow i64
let v3 = Variant::from(VariantDecimal16::try_new(2e19 as i128, 2).unwrap());
assert_eq!(v3.as_decimal8(), None);
// or if the variant is not a decimal
let v4 = Variant::from("hello!");
assert_eq!(v4.as_decimal8(), None);
Sourcepub fn as_decimal16(&self) -> Option<VariantDecimal16>
pub fn as_decimal16(&self) -> Option<VariantDecimal16>
Converts this variant to tuple with a 16-byte unscaled value if possible.
Returns Some((i128, u8))
for decimal variants where the unscaled value
fits in i128
range,
None
for non-decimal variants or decimal values that would overflow.
§Examples
use parquet_variant::{Variant, VariantDecimal16, VariantDecimal4};
// you can extract decimal parts from smaller or equally-sized decimal variants
let v1 = Variant::from(VariantDecimal4::try_new(1234_i32, 2).unwrap());
assert_eq!(v1.as_decimal16(), VariantDecimal16::try_new(1234_i128, 2).ok());
// but not if the variant is not a decimal
let v2 = Variant::from("hello!");
assert_eq!(v2.as_decimal16(), None);
Sourcepub fn as_f32(&self) -> Option<f32>
pub fn as_f32(&self) -> Option<f32>
Converts this variant to an f32
if possible.
Returns Some(f32)
for float and double variants,
None
for non-floating-point variants.
§Examples
use parquet_variant::Variant;
// you can extract an f32 from a float variant
let v1 = Variant::from(std::f32::consts::PI);
assert_eq!(v1.as_f32(), Some(std::f32::consts::PI));
// and from a double variant (with loss of precision to nearest f32)
let v2 = Variant::from(std::f64::consts::PI);
assert_eq!(v2.as_f32(), Some(std::f32::consts::PI));
// but not from other variants
let v3 = Variant::from("hello!");
assert_eq!(v3.as_f32(), None);
Sourcepub fn as_f64(&self) -> Option<f64>
pub fn as_f64(&self) -> Option<f64>
Converts this variant to an f64
if possible.
Returns Some(f64)
for float and double variants,
None
for non-floating-point variants.
§Examples
use parquet_variant::Variant;
// you can extract an f64 from a float variant
let v1 = Variant::from(std::f32::consts::PI);
assert_eq!(v1.as_f64(), Some(std::f32::consts::PI as f64));
// and from a double variant
let v2 = Variant::from(std::f64::consts::PI);
assert_eq!(v2.as_f64(), Some(std::f64::consts::PI));
// but not from other variants
let v3 = Variant::from("hello!");
assert_eq!(v3.as_f64(), None);
Sourcepub fn as_object(&'m self) -> Option<&'m VariantObject<'m, 'v>>
pub fn as_object(&'m self) -> Option<&'m VariantObject<'m, 'v>>
Converts this variant to an Object
if it is an VariantObject
.
Returns Some(&VariantObject)
for object variants,
None
for non-object variants.
See Self::get_path
to dynamically traverse objects
§Examples
// object that is {"name": "John"}
let variant = Variant::new(&metadata, &value);
// use the `as_object` method to access the object
let obj = variant.as_object().expect("variant should be an object");
assert_eq!(obj.get("name"), Some(Variant::from("John")));
Sourcepub fn get_object_field(&self, field_name: &str) -> Option<Self>
pub fn get_object_field(&self, field_name: &str) -> Option<Self>
If this is an object and the requested field name exists, retrieves the corresponding field value. Otherwise, returns None.
This is shorthand for Self::as_object
followed by VariantObject::get
.
§Examples
// object that is {"name": "John"}
let variant = Variant::new(&metadata, &value);
// use the `get_object_field` method to access the object
let obj = variant.get_object_field("name");
assert_eq!(obj, Some(Variant::from("John")));
let obj = variant.get_object_field("foo");
assert!(obj.is_none());
Sourcepub fn as_list(&'m self) -> Option<&'m VariantList<'m, 'v>>
pub fn as_list(&'m self) -> Option<&'m VariantList<'m, 'v>>
Converts this variant to a List
if it is a VariantList
.
Returns Some(&VariantList)
for list variants,
None
for non-list variants.
See Self::get_path
to dynamically traverse lists
§Examples
// list that is ["John", "Doe"]
let variant = Variant::new(&metadata, &value);
// use the `as_list` method to access the list
let list = variant.as_list().expect("variant should be a list");
assert_eq!(list.len(), 2);
assert_eq!(list.get(0).unwrap(), Variant::from("John"));
assert_eq!(list.get(1).unwrap(), Variant::from("Doe"));
Sourcepub fn get_list_element(&self, index: usize) -> Option<Self>
pub fn get_list_element(&self, index: usize) -> Option<Self>
If this is a list and the requested index is in bounds, retrieves the corresponding element. Otherwise, returns None.
This is shorthand for Self::as_list
followed by VariantList::get
.
§Examples
// list that is ["John", "Doe"]
let variant = Variant::new(&metadata, &value);
// use the `get_list_element` method to access the list
assert_eq!(variant.get_list_element(0), Some(Variant::from("John")));
assert_eq!(variant.get_list_element(1), Some(Variant::from("Doe")));
assert!(variant.get_list_element(2).is_none());
Sourcepub fn metadata(&self) -> Option<&'m VariantMetadata<'_>>
pub fn metadata(&self) -> Option<&'m VariantMetadata<'_>>
Return the metadata associated with this variant, if any.
Returns Some(&VariantMetadata)
for object and list variants,
Sourcepub fn get_path(&self, path: &VariantPath<'_>) -> Option<Variant<'_, '_>>
pub fn get_path(&self, path: &VariantPath<'_>) -> Option<Variant<'_, '_>>
Return a new Variant with the path followed.
If the path is not found, None
is returned.
§Example
// given a variant like `{"foo": ["bar", "baz"]}`
let variant = Variant::new(&metadata, &value);
// Accessing a non existent path returns None
assert_eq!(variant.get_path(&VariantPath::from("non_existent")), None);
// Access obj["foo"]
let path = VariantPath::from("foo");
let foo = variant.get_path(&path).expect("field `foo` should exist");
assert!(foo.as_list().is_some(), "field `foo` should be a list");
// Access foo[0]
let path = VariantPath::from(0);
let bar = foo.get_path(&path).expect("element 0 should exist");
// bar is a string
assert_eq!(bar.as_string(), Some("bar"));
// You can also access nested paths
let path = VariantPath::from("foo").join(0);
assert_eq!(variant.get_path(&path).unwrap(), bar);