Slice arrays
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
use std::{fmt, str::FromStr};
|
||||
|
||||
use serde::{
|
||||
Deserialize, Deserializer,
|
||||
Deserialize, Deserializer, Serialize, Serializer,
|
||||
de::{self, Visitor},
|
||||
};
|
||||
use smartstring::{LazyCompact, SmartString};
|
||||
@@ -49,6 +49,13 @@ pub enum PathSegment {
|
||||
|
||||
/// Go to an element of the current list
|
||||
Index(i64),
|
||||
|
||||
/// Go to a slice of the current list
|
||||
Range {
|
||||
start: i64,
|
||||
end: i64,
|
||||
inclusive: bool,
|
||||
},
|
||||
}
|
||||
|
||||
/// A path to aPathSegment::Field inside a nested object,
|
||||
@@ -63,6 +70,39 @@ pub struct ObjectPath {
|
||||
pub segments: Vec<PathSegment>,
|
||||
}
|
||||
|
||||
impl fmt::Display for ObjectPath {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for seg in &self.segments {
|
||||
match seg {
|
||||
PathSegment::Root => write!(f, "$")?,
|
||||
PathSegment::Field { name, args: None } => write!(f, ".{name}")?,
|
||||
PathSegment::Field {
|
||||
name,
|
||||
args: Some(a),
|
||||
} => write!(f, ".{name}({a})")?,
|
||||
PathSegment::Index(i) => write!(f, "[{i}]")?,
|
||||
PathSegment::Range {
|
||||
start,
|
||||
end,
|
||||
inclusive: false,
|
||||
} => write!(f, "[{start}..{end}]")?,
|
||||
PathSegment::Range {
|
||||
start,
|
||||
end,
|
||||
inclusive: true,
|
||||
} => write!(f, "[{start}..={end}]")?,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for ObjectPath {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
serializer.serialize_str(&self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ObjectPath {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
struct PathVisitor;
|
||||
|
||||
@@ -87,6 +87,15 @@ enum State {
|
||||
/// We are indexing an array, waiting for a number
|
||||
Index,
|
||||
|
||||
/// We parsed the start index, waiting for `]` or the first `.` of `..`
|
||||
IndexAfterStart(i64),
|
||||
|
||||
/// We saw one `.` after the start index, waiting for the second `.`
|
||||
IndexRangeDot1(i64),
|
||||
|
||||
/// We saw `..`, waiting for the end index (optionally prefixed with `=`)
|
||||
IndexRangeDot2(i64),
|
||||
|
||||
/// We are indexing an array, waiting for a close-bracket
|
||||
IndexClose,
|
||||
}
|
||||
@@ -164,8 +173,7 @@ impl Parser {
|
||||
}
|
||||
})?;
|
||||
|
||||
self.segments.push(PathSegment::Index(idx));
|
||||
self.state = State::IndexClose;
|
||||
self.state = State::IndexAfterStart(idx);
|
||||
}
|
||||
|
||||
(State::Index, (p, Token::Root))
|
||||
@@ -175,6 +183,49 @@ impl Parser {
|
||||
return Err(PathParseError::Syntax { position: *p });
|
||||
}
|
||||
|
||||
(State::IndexAfterStart(idx), (_, Token::SqbClose)) => {
|
||||
self.segments.push(PathSegment::Index(idx));
|
||||
self.state = State::Selected;
|
||||
}
|
||||
(State::IndexAfterStart(idx), (_, Token::Dot)) => {
|
||||
self.state = State::IndexRangeDot1(idx);
|
||||
}
|
||||
(State::IndexAfterStart(_), (p, _)) => {
|
||||
return Err(PathParseError::Syntax { position: *p });
|
||||
}
|
||||
|
||||
(State::IndexRangeDot1(idx), (_, Token::Dot)) => {
|
||||
self.state = State::IndexRangeDot2(idx);
|
||||
}
|
||||
(State::IndexRangeDot1(_), (p, _)) => {
|
||||
return Err(PathParseError::Syntax { position: *p });
|
||||
}
|
||||
|
||||
(State::IndexRangeDot2(start), (p, Token::Ident(ident))) => {
|
||||
let (end_str, inclusive) = if let Some(stripped) = ident.strip_prefix('=') {
|
||||
(stripped, true)
|
||||
} else {
|
||||
(*ident, false)
|
||||
};
|
||||
|
||||
let end: i64 = i64::from_str(end_str).map_err(|_err| {
|
||||
PathParseError::InvalidIndexString {
|
||||
position: *p,
|
||||
str: (*ident).into(),
|
||||
}
|
||||
})?;
|
||||
|
||||
self.segments.push(PathSegment::Range {
|
||||
start,
|
||||
end,
|
||||
inclusive,
|
||||
});
|
||||
self.state = State::IndexClose;
|
||||
}
|
||||
(State::IndexRangeDot2(_), (p, _)) => {
|
||||
return Err(PathParseError::Syntax { position: *p });
|
||||
}
|
||||
|
||||
(State::IndexClose, (_, Token::SqbClose)) => self.state = State::Selected,
|
||||
(State::IndexClose, (p, _)) => {
|
||||
return Err(PathParseError::Syntax { position: *p });
|
||||
@@ -187,6 +238,9 @@ impl Parser {
|
||||
State::Start => Err(PathParseError::Syntax { position: 0 }),
|
||||
State::Dot => Err(PathParseError::Syntax { position }),
|
||||
State::Index => Err(PathParseError::Syntax { position }),
|
||||
State::IndexAfterStart(_) => Err(PathParseError::Syntax { position }),
|
||||
State::IndexRangeDot1(_) => Err(PathParseError::Syntax { position }),
|
||||
State::IndexRangeDot2(_) => Err(PathParseError::Syntax { position }),
|
||||
State::IndexClose => Err(PathParseError::Syntax { position }),
|
||||
State::Selected => Ok(()),
|
||||
}?;
|
||||
@@ -387,4 +441,46 @@ mod tests {
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// MARK: range
|
||||
|
||||
fn range(start: i64, end: i64, inclusive: bool) -> PathSegment {
|
||||
PathSegment::Range {
|
||||
start,
|
||||
end,
|
||||
inclusive,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exclusive_range() {
|
||||
parse_test(
|
||||
"$.a[0..5]",
|
||||
Ok(&[PathSegment::Root, field("a"), range(0, 5, false)]),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inclusive_range() {
|
||||
parse_test(
|
||||
"$.a[1..=2]",
|
||||
Ok(&[PathSegment::Root, field("a"), range(1, 2, true)]),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_with_negative_end() {
|
||||
parse_test(
|
||||
"$.a[0..-1]",
|
||||
Ok(&[PathSegment::Root, field("a"), range(0, -1, false)]),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_with_negative_start() {
|
||||
parse_test(
|
||||
"$.a[-3..-1]",
|
||||
Ok(&[PathSegment::Root, field("a"), range(-3, -1, false)]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user