use crate::path::{JsonPath, PathElement};
use crate::tokenizer::{Token, TokenSink};
use crate::{JsonError, JsonErrorKind};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ValuePath {
segments: Vec<PathElement>,
}
impl ValuePath {
#[must_use]
pub fn root() -> Self {
Self::default()
}
#[must_use]
pub fn segments(&self) -> &[PathElement] {
&self.segments
}
pub(crate) fn segments_mut(&mut self) -> &mut Vec<PathElement> {
&mut self.segments
}
}
impl std::fmt::Display for ValuePath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("$")?;
for segment in &self.segments {
write!(f, "{segment}")?;
}
Ok(())
}
}
pub trait SelectionHandler {
fn handle_selection(
&mut self,
selector: usize,
path: &ValuePath,
token: Token<'_>,
) -> Result<(), JsonError>;
}
pub struct SelectingSink<H> {
selectors: Box<[JsonPath]>,
handler: H,
stack: Vec<Frame>,
path: ValuePath,
}
impl<H> SelectingSink<H> {
#[must_use]
pub fn new(selectors: impl IntoIterator<Item = JsonPath>, handler: H) -> Self {
Self {
selectors: selectors.into_iter().collect(),
handler,
stack: Vec::new(),
path: ValuePath::root(),
}
}
#[must_use]
pub fn into_handler(self) -> H {
self.handler
}
}
impl<H: SelectionHandler> TokenSink for SelectingSink<H> {
fn token(&mut self, token: Token<'_>) -> Result<(), JsonError> {
match token {
Token::ObjectKey(key) => {
let decoded = key.decode()?;
let Some(Frame::Object { pending_key, .. }) = self.stack.last_mut() else {
return Err(JsonError::new(JsonErrorKind::UnexpectedToken("object key")));
};
*pending_key = Some(decoded.into_boxed_str());
}
Token::StartObject(_) | Token::StartArray(_) => {
let parent_path_len = self.push_value_path()?;
self.report(token)?;
match token {
Token::StartObject(_) => self.stack.push(Frame::Object {
parent_path_len,
pending_key: None,
}),
Token::StartArray(_) => self.stack.push(Frame::Array {
parent_path_len,
next_index: 0,
}),
_ => {}
}
}
Token::EndObject(_) | Token::EndArray(_) => {
let Some(frame) = self.stack.pop() else {
return Err(JsonError::new(JsonErrorKind::UnexpectedToken(
"container end",
)));
};
self.finish_value_after_path(frame.parent_path_len());
}
Token::String(_)
| Token::Number(_)
| Token::True(_)
| Token::False(_)
| Token::Null(_) => {
let parent_path_len = self.push_value_path()?;
self.report(token)?;
self.finish_value_after_path(parent_path_len);
}
Token::Whitespace(_) | Token::Colon(_) | Token::Comma(_) => {}
}
Ok(())
}
}
impl<H: SelectionHandler> SelectingSink<H> {
fn report(&mut self, token: Token<'_>) -> Result<(), JsonError> {
for index in 0..self.selectors.len() {
for _ in 0..self.selectors[index].match_count(self.path.segments()) {
self.handler.handle_selection(index, &self.path, token)?;
}
}
Ok(())
}
}
impl<H> SelectingSink<H> {
fn push_value_path(&mut self) -> Result<usize, JsonError> {
let parent_path_len = self.path.segments.len();
match self.stack.last_mut() {
None => {}
Some(Frame::Object { pending_key, .. }) => {
let Some(key) = pending_key.take() else {
return Err(JsonError::new(JsonErrorKind::UnexpectedToken(
"object value",
)));
};
self.path.segments.push(PathElement::Member(key));
}
Some(Frame::Array { next_index, .. }) => {
self.path.segments.push(PathElement::Index(*next_index));
}
}
Ok(parent_path_len)
}
fn finish_value_after_path(&mut self, parent_path_len: usize) {
self.path.segments.truncate(parent_path_len);
match self.stack.last_mut() {
Some(Frame::Array { next_index, .. }) => {
*next_index += 1;
}
Some(Frame::Object { .. }) | None => {}
}
}
}
#[derive(Debug, Clone)]
enum Frame {
Object {
parent_path_len: usize,
pending_key: Option<Box<str>>,
},
Array {
parent_path_len: usize,
next_index: usize,
},
}
impl Frame {
fn parent_path_len(&self) -> usize {
match self {
Self::Object {
parent_path_len, ..
}
| Self::Array {
parent_path_len, ..
} => *parent_path_len,
}
}
}
impl<F> SelectionHandler for F
where
F: FnMut(usize, &ValuePath, Token<'_>) -> Result<(), JsonError>,
{
fn handle_selection(
&mut self,
selector: usize,
path: &ValuePath,
token: Token<'_>,
) -> Result<(), JsonError> {
self(selector, path, token)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tokenizer::{Tokenizer, tokenize};
#[derive(Default)]
struct Hits {
hits: Vec<(usize, String, Vec<u8>)>,
}
impl SelectionHandler for Hits {
fn handle_selection(
&mut self,
selector: usize,
path: &ValuePath,
token: Token<'_>,
) -> Result<(), JsonError> {
self.hits
.push((selector, path.to_string(), token.raw().to_vec()));
Ok(())
}
}
#[test]
fn selects_matching_values() {
let selectors = vec![
"$.store.book[*].author".parse().unwrap(),
"$..price".parse().unwrap(),
];
let mut sink = SelectingSink::new(selectors, Hits::default());
tokenize(
br#"{"store":{"book":[{"author":"a","price":1},{"author":"b","price":2}]}}"#,
&mut sink,
)
.unwrap();
let hits = sink.into_handler().hits;
assert_eq!(
hits,
vec![
(0, "$.store.book[0].author".to_owned(), br#""a""#.to_vec()),
(1, "$.store.book[0].price".to_owned(), b"1".to_vec()),
(0, "$.store.book[1].author".to_owned(), br#""b""#.to_vec()),
(1, "$.store.book[1].price".to_owned(), b"2".to_vec()),
]
);
}
#[test]
fn closure_selection_handler_is_called() {
let selectors = vec!["$..id".parse().unwrap()];
let hits = std::cell::RefCell::new(Vec::new());
let mut sink = SelectingSink::new(
selectors,
|selector: usize, path: &ValuePath, token: Token<'_>| {
hits.borrow_mut()
.push((selector, path.to_string(), token.raw().to_vec()));
Ok(())
},
);
tokenize(br#"{"items":[{"id":1},{"id":2}]}"#, &mut sink).unwrap();
assert_eq!(
hits.into_inner(),
vec![
(0, "$.items[0].id".to_owned(), b"1".to_vec()),
(0, "$.items[1].id".to_owned(), b"2".to_vec()),
]
);
}
#[test]
fn selection_survives_chunk_boundaries() {
let selectors = vec!["$..id".parse().unwrap()];
let mut sink = SelectingSink::new(selectors, Hits::default());
let mut tokenizer = Tokenizer::new();
for chunk in br#"{"items":[{"id":1},{"id":2}]}"#.chunks(3) {
tokenizer.write(chunk, &mut sink).unwrap();
}
tokenizer.end(&mut sink).unwrap();
let paths: Vec<_> = sink
.into_handler()
.hits
.into_iter()
.map(|(_, path, raw)| (path, raw))
.collect();
assert_eq!(
paths,
vec![
("$.items[0].id".to_owned(), b"1".to_vec()),
("$.items[1].id".to_owned(), b"2".to_vec()),
]
);
}
#[test]
fn selects_union_and_slice_values() {
let selectors = vec![
"$.items[1:4:2].id".parse().unwrap(),
"$.items[0,2].id".parse().unwrap(),
"$..[1].id".parse().unwrap(),
"$..[\"missing\",3].id".parse().unwrap(),
];
let mut sink = SelectingSink::new(selectors, Hits::default());
tokenize(
br#"{"items":[{"id":0},{"id":1},{"id":2},{"id":3}]}"#,
&mut sink,
)
.unwrap();
let hits = sink.into_handler().hits;
assert_eq!(
hits,
vec![
(1, "$.items[0].id".to_owned(), b"0".to_vec()),
(0, "$.items[1].id".to_owned(), b"1".to_vec()),
(2, "$.items[1].id".to_owned(), b"1".to_vec()),
(1, "$.items[2].id".to_owned(), b"2".to_vec()),
(0, "$.items[3].id".to_owned(), b"3".to_vec()),
(3, "$.items[3].id".to_owned(), b"3".to_vec()),
]
);
}
}