Add ObjectPath query language
This commit is contained in:
95
crates/pile-config/src/objectpath/mod.rs
Normal file
95
crates/pile-config/src/objectpath/mod.rs
Normal file
@@ -0,0 +1,95 @@
|
||||
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 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user