use core::fmt;
use std::fmt::{Display, Formatter};
use std::sync::Arc;
use itertools::Itertools;
use vortex_error::{vortex_err, VortexResult};
use crate::FieldNames;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Field {
Name(Arc<str>),
Index(usize),
}
impl From<&str> for Field {
fn from(value: &str) -> Self {
Field::Name(value.into())
}
}
impl From<String> for Field {
fn from(value: String) -> Self {
Field::Name(value.into())
}
}
impl From<usize> for Field {
fn from(value: usize) -> Self {
Field::Index(value)
}
}
impl Display for Field {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Field::Name(name) => write!(f, "${name}"),
Field::Index(idx) => write!(f, "[{idx}]"),
}
}
}
impl Field {
pub fn is_named(&self) -> bool {
matches!(self, Field::Name(_))
}
pub fn is_indexed(&self) -> bool {
matches!(self, Field::Index(_))
}
pub fn into_named_field(self, field_names: &FieldNames) -> VortexResult<Self> {
match self {
Field::Index(idx) => field_names
.get(idx)
.ok_or_else(|| {
vortex_err!(
"Field index {} out of bounds, it has names {:?}",
idx,
field_names
)
})
.cloned()
.map(Field::Name),
Field::Name(_) => Ok(self),
}
}
pub fn into_index_field(self, field_names: &FieldNames) -> VortexResult<Self> {
match self {
Field::Name(name) => field_names
.iter()
.position(|n| *n == name)
.ok_or_else(|| {
vortex_err!(
"Field name {} not found, it has names {:?}",
name,
field_names
)
})
.map(Field::Index),
Field::Index(_) => Ok(self),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct FieldPath(Vec<Field>);
impl FieldPath {
pub fn root() -> Self {
Self(vec![])
}
pub fn from_name<F: Into<Field>>(name: F) -> Self {
Self(vec![name.into()])
}
pub fn path(&self) -> &[Field] {
&self.0
}
pub fn is_root(&self) -> bool {
self.0.is_empty()
}
pub fn push<F: Into<Field>>(mut self, field: F) -> Self {
self.0.push(field.into());
self
}
pub fn starts_with_field(&self, field: &Field) -> bool {
assert!(matches!(field, Field::Name(_)));
let first = self.0.first();
assert!(matches!(first, Some(Field::Name(_))));
first.is_some_and(|f| f == field)
}
pub fn step_into(mut self) -> Option<Self> {
if self.0.is_empty() {
return None;
}
self.0 = self.0.iter().skip(1).cloned().collect();
Some(self)
}
}
impl FromIterator<Field> for FieldPath {
fn from_iter<T: IntoIterator<Item = Field>>(iter: T) -> Self {
FieldPath(iter.into_iter().collect())
}
}
impl From<Field> for FieldPath {
fn from(value: Field) -> Self {
FieldPath(vec![value])
}
}
impl From<Vec<Field>> for FieldPath {
fn from(value: Vec<Field>) -> Self {
FieldPath(value)
}
}
impl Display for FieldPath {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0.iter().format("."), f)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_field_path() {
let path = FieldPath::from_name("A").push("B").push("C");
assert_eq!(path.to_string(), "$A.$B.$C");
let fields = vec!["A", "B", "C"]
.into_iter()
.map(Field::from)
.collect_vec();
assert_eq!(path.path(), &fields);
let vec_path = FieldPath::from(fields);
assert_eq!(vec_path.to_string(), "$A.$B.$C");
assert_eq!(path, vec_path);
}
}