Files
pile/crates/pile-config/src/objectpath/mod.rs
rm-dr a9e402bc83
Some checks failed
CI / Typos (push) Successful in 19s
CI / Build and test (push) Failing after 40s
CI / Clippy (push) Failing after 53s
Add ObjectPath query language
2026-03-05 21:43:59 -08:00

96 lines
2.1 KiB
Rust

use std::{fmt, str::FromStr};
use serde::{
Deserialize, Deserializer,
de::{self, Visitor},
};
use smartstring::{LazyCompact, SmartString};
use thiserror::Error;
use crate::Label;
mod parser;
mod tokenizer;
#[derive(Debug, Error, PartialEq)]
pub enum PathParseError {
#[error("invalid syntax at index {position}")]
Syntax { position: usize },
#[error("path string must start with $")]
MustStartWithRoot { position: usize },
#[error("invalid field {str:?} at {position}")]
InvalidField {
position: usize,
str: SmartString<LazyCompact>,
},
#[error("invalid index {str:?} at {position}")]
InvalidIndexString {
position: usize,
str: SmartString<LazyCompact>,
},
#[error("non-ascii character {char:?} at index {position}")]
NonAsciiChar { position: usize, char: char },
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PathSegment {
/// Go to root node (`$` identifier)
Root,
/// Go to a child of the current object
Field(Label),
/// Go to an element of the current list
Index(i64),
}
/// A path to aPathSegment::Field inside a nested object,
/// This is a subset of the rfc9535 jsonpath.
///
/// Format:
/// - `$` refers to the root object
/// - `.<name>` selects aPathSegment::Field of an object
/// - `[n]` selects an item of an array
#[derive(Debug, Clone)]
pub struct ObjectPath {
pub segments: Vec<PathSegment>,
}
impl<'de> Deserialize<'de> for ObjectPath {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct PathVisitor;
impl Visitor<'_> for PathVisitor {
type Value = ObjectPath;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("an objectpath")
}
fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
v.parse().map_err(de::Error::custom)
}
}
deserializer.deserialize_str(PathVisitor)
}
}
impl FromStr for ObjectPath {
type Err = PathParseError;
fn from_str(source: &str) -> Result<Self, Self::Err> {
let tk = tokenizer::Tokenizer::new();
let tk = tk.tokenize(source)?;
let ps = parser::Parser::new();
let segments = ps.parse(source, &tk)?;
return Ok(Self { segments });
}
}