Skip to content

Commit 969bbc7

Browse files
committed
feat: parsing for the slice selector
1 parent 91dd197 commit 969bbc7

5 files changed

Lines changed: 154 additions & 268 deletions

File tree

crates/rsonpath-lib/src/error.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ impl UnsupportedFeatureError {
147147
}
148148

149149
/// Slice Selector – supporting slice selectors.
150-
/// https://github.com/V0ldek/rsonpath/issues/152
150+
/// <https://github.com/V0ldek/rsonpath/issues/152>
151151
#[must_use]
152152
#[inline(always)]
153153
pub fn slice_selector() -> Self {

crates/rsonpath-syntax/src/builder.rs

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Utility for building a [`JsonPathQuery`](`crate::JsonPathQuery`)
22
//! programmatically.
3-
use crate::{num::JsonInt, str::JsonString, Index, JsonPathQuery, Segment, Selector, Selectors};
3+
use crate::{num::JsonInt, str::JsonString, Index, JsonPathQuery, Segment, Selector, Selectors, SliceBuilder};
44

55
/// Builder for [`JsonPathQuery`] instances.
66
///
@@ -13,12 +13,13 @@ use crate::{num::JsonInt, str::JsonString, Index, JsonPathQuery, Segment, Select
1313
/// .descendant_name("b")
1414
/// .child_wildcard()
1515
/// .child_name("c")
16-
/// .descendant_wildcard();
16+
/// .descendant_wildcard()
17+
/// .child_slice(|x| x.with_start(3).with_end(-7).with_step(2));
1718
///
1819
/// // Can also use `builder.build()` as a non-consuming version.
1920
/// let query: JsonPathQuery = builder.into();
2021
///
21-
/// assert_eq!(query.to_string(), "$['a']..['b'][*]['c']..[*]");
22+
/// assert_eq!(query.to_string(), "$['a']..['b'][*]['c']..[*][3:-7:2]");
2223
/// ```
2324
pub struct JsonPathQueryBuilder {
2425
segments: Vec<Segment>,
@@ -133,6 +134,17 @@ impl JsonPathQueryBuilder {
133134
self.child(|x| x.index(idx))
134135
}
135136

137+
/// Add a child segment with a single slice selector.
138+
///
139+
/// This is a shorthand for `.child(|x| x.slice(slice_builder))`.
140+
#[inline(always)]
141+
pub fn child_slice<F>(&mut self, slice_builder: F) -> &mut Self
142+
where
143+
F: FnOnce(&mut SliceBuilder) -> &mut SliceBuilder,
144+
{
145+
self.child(|x| x.slice(slice_builder))
146+
}
147+
136148
/// Add a descendant segment with a single name selector.
137149
///
138150
/// This is a shorthand for `.descendant(|x| x.name(name))`.
@@ -157,6 +169,17 @@ impl JsonPathQueryBuilder {
157169
self.descendant(|x| x.index(idx))
158170
}
159171

172+
/// Add a descendant segment with a single slice selector.
173+
///
174+
/// This is a shorthand for `.descendant(|x| x.slice(slice_builder))`.
175+
#[inline(always)]
176+
pub fn descendant_slice<F>(&mut self, slice_builder: F) -> &mut Self
177+
where
178+
F: FnOnce(&mut SliceBuilder) -> &mut SliceBuilder,
179+
{
180+
self.descendant(|x| x.slice(slice_builder))
181+
}
182+
160183
/// Produce a [`JsonPathQuery`] from the builder.
161184
///
162185
/// This clones all data in the builder to create the query.
@@ -225,6 +248,47 @@ impl JsonPathSelectorsBuilder {
225248
self
226249
}
227250

251+
/// Add a slice selector based on a given start, end, and step integers.
252+
///
253+
/// The result is a [`Selector::Slice`] with given `start`, `end`, and `step`.
254+
///
255+
/// ## Examples
256+
///
257+
/// ```rust
258+
/// # use rsonpath_syntax::{Selector, SliceBuilder, Index, Step, num::{JsonNonZeroUInt, JsonUInt}, builder::JsonPathQueryBuilder};
259+
/// let mut builder = JsonPathQueryBuilder::new();
260+
/// builder.child(|x| x
261+
/// .slice(|s| s.with_start(10).with_end(-20).with_step(5))
262+
/// .slice(|s| s.with_start(-20).with_step(-30)));
263+
/// let result = builder.into_query();
264+
///
265+
/// assert_eq!(result.segments().len(), 1);
266+
/// let segment = &result.segments()[0];
267+
/// let selectors = segment.selectors().as_slice();
268+
/// match (&selectors[0], &selectors[1]) {
269+
/// (Selector::Slice(s1), Selector::Slice(s2)) => {
270+
/// assert_eq!(s1.start(), Index::FromStart(10.into()));
271+
/// assert_eq!(s1.end(), Some(Index::FromEnd(JsonNonZeroUInt::try_from(20).unwrap())));
272+
/// assert_eq!(s1.step(), Step::Forward(5.into()));
273+
/// assert_eq!(s2.start(), Index::FromEnd(JsonNonZeroUInt::try_from(20).unwrap()));
274+
/// assert_eq!(s2.end(), None);
275+
/// assert_eq!(s2.step(), Step::Backward(JsonNonZeroUInt::try_from(30).unwrap()));
276+
/// }
277+
/// _ => unreachable!()
278+
/// }
279+
/// ```
280+
#[inline(always)]
281+
pub fn slice<F>(&mut self, slice_builder: F) -> &mut Self
282+
where
283+
F: FnOnce(&mut SliceBuilder) -> &mut SliceBuilder,
284+
{
285+
let mut slice = SliceBuilder::new();
286+
slice_builder(&mut slice);
287+
let slice = slice.into();
288+
self.selectors.push(Selector::Slice(slice));
289+
self
290+
}
291+
228292
/// Add a wildcard selector.
229293
#[inline(always)]
230294
pub fn wildcard(&mut self) -> &mut Self {

crates/rsonpath-syntax/src/lib.rs

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -351,8 +351,8 @@ pub enum Selector {
351351
/// An index selector matches at most one array element value,
352352
/// depending on the selector's [`Index`].
353353
Index(Index),
354-
// A slice selector matches elements from arrays starting at a given index,
355-
// ending at a given index, and incrementing with a specified step.
354+
/// A slice selector matches elements from arrays starting at a given index,
355+
/// ending at a given index, and incrementing with a specified step.
356356
Slice(Slice),
357357
}
358358

@@ -392,7 +392,7 @@ impl From<num::JsonInt> for Index {
392392
/// Directional step offset within a JSON array.
393393
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
394394
pub enum Step {
395-
// Step forward by a given offset amount.
395+
/// Step forward by a given offset amount.
396396
Forward(num::JsonUInt),
397397
/// Step backward by a given offset amount.
398398
Backward(num::JsonNonZeroUInt),
@@ -455,10 +455,7 @@ pub struct Slice {
455455
/// # use rsonpath_syntax::{Slice, SliceBuilder, Index, Step, num::JsonUInt};
456456
/// let mut builder = SliceBuilder::new();
457457
///
458-
/// builder
459-
/// .with_start(Index::FromEnd(3.try_into().unwrap()))
460-
/// .with_end(Index::FromStart(1.into()))
461-
/// .with_step(Step::Backward(7.try_into().unwrap()));
458+
/// builder.with_start(-3).with_end(1).with_step(-7);
462459
///
463460
/// let slice: Slice = builder.into();
464461
/// assert_eq!(slice.to_string(), "-3:1:-7");
@@ -523,22 +520,22 @@ impl SliceBuilder {
523520

524521
/// Set the start of the [`Slice`].
525522
#[inline]
526-
pub fn with_start(&mut self, start: Index) -> &mut Self {
527-
self.inner.start = start;
523+
pub fn with_start<N: Into<num::JsonInt>>(&mut self, start: N) -> &mut Self {
524+
self.inner.start = start.into().into();
528525
self
529526
}
530527

531528
/// Set the end of the [`Slice`].
532529
#[inline]
533-
pub fn with_end(&mut self, end: Index) -> &mut Self {
534-
self.inner.end = Some(end);
530+
pub fn with_end<N: Into<num::JsonInt>>(&mut self, end: N) -> &mut Self {
531+
self.inner.end = Some(end.into().into());
535532
self
536533
}
537534

538535
/// Set the step of the [`Slice`].
539536
#[inline]
540-
pub fn with_step(&mut self, step: Step) -> &mut Self {
541-
self.inner.step = step;
537+
pub fn with_step<N: Into<num::JsonInt>>(&mut self, step: N) -> &mut Self {
538+
self.inner.step = step.into().into();
542539
self
543540
}
544541

crates/rsonpath-syntax/src/parser.rs

Lines changed: 75 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::{
22
error::{InternalParseError, ParseErrorBuilder, SyntaxError, SyntaxErrorKind},
33
num::{error::JsonIntParseError, JsonInt, JsonNonZeroUInt, JsonUInt},
44
str::{JsonString, JsonStringBuilder},
5-
Index, JsonPathQuery, ParserOptions, Result, Segment, Selector, Selectors, SliceBuilder, Step,
5+
Index, JsonPathQuery, ParserOptions, Result, Segment, Selector, Selectors, Step,
66
};
77
use nom::{branch::*, bytes::complete::*, character::complete::*, combinator::*, multi::*, sequence::*, *};
88
use std::{iter::Peekable, str::FromStr};
@@ -254,12 +254,12 @@ fn wildcard_selector(q: &str) -> IResult<&str, Selector, InternalParseError> {
254254
fn slice_selector(q: &str) -> IResult<&str, Selector, InternalParseError> {
255255
let (rest, opt_start) = terminated(opt(int), ignore_whitespace(char(':')))(q)?;
256256
// We have parsed a ':', so this *must* be a slice selector. Any errors after here are fatal.
257-
let mut slice = SliceBuilder::new();
257+
let mut slice = crate::Slice::default();
258258

259259
if let Some(start_str) = opt_start {
260260
match parse_directional_int(start_str) {
261-
DirectionalInt::Plus(int) => slice.with_start(Index::FromStart(int)),
262-
DirectionalInt::Minus(int) => slice.with_start(Index::FromEnd(int)),
261+
DirectionalInt::Plus(int) => slice.start = Index::FromStart(int),
262+
DirectionalInt::Minus(int) => slice.start = Index::FromEnd(int),
263263
DirectionalInt::Error(err) => {
264264
return fail(
265265
SyntaxErrorKind::SliceStartParseError(err),
@@ -275,8 +275,8 @@ fn slice_selector(q: &str) -> IResult<&str, Selector, InternalParseError> {
275275

276276
if let Some(end_str) = opt_end {
277277
match parse_directional_int(end_str) {
278-
DirectionalInt::Plus(int) => slice.with_end(Index::FromStart(int)),
279-
DirectionalInt::Minus(int) => slice.with_end(Index::FromEnd(int)),
278+
DirectionalInt::Plus(int) => slice.end = Some(Index::FromStart(int)),
279+
DirectionalInt::Minus(int) => slice.end = Some(Index::FromEnd(int)),
280280
DirectionalInt::Error(err) => {
281281
return fail(SyntaxErrorKind::SliceEndParseError(err), q.len(), end_str.len(), rest);
282282
}
@@ -288,15 +288,15 @@ fn slice_selector(q: &str) -> IResult<&str, Selector, InternalParseError> {
288288

289289
if let Some(Some(step_str)) = opt_step {
290290
match parse_directional_int(step_str) {
291-
DirectionalInt::Plus(int) => slice.with_step(Step::Forward(int)),
292-
DirectionalInt::Minus(int) => slice.with_step(Step::Backward(int)),
291+
DirectionalInt::Plus(int) => slice.step = Step::Forward(int),
292+
DirectionalInt::Minus(int) => slice.step = Step::Backward(int),
293293
DirectionalInt::Error(err) => {
294294
return fail(SyntaxErrorKind::SliceStepParseError(err), q.len(), step_str.len(), rest);
295295
}
296296
};
297297
}
298298

299-
Ok((rest, Selector::Slice(slice.into())))
299+
Ok((rest, Selector::Slice(slice)))
300300
}
301301

302302
fn index_selector(q: &str) -> IResult<&str, Selector, InternalParseError> {
@@ -655,11 +655,13 @@ mod tests {
655655
#[derive(Debug, Clone)]
656656
enum SelectorTag {
657657
WildcardChild,
658-
Child(String),
659-
WildcardDescendant,
660-
Descendant(String),
658+
NameChild(String),
661659
ArrayIndexChild(JsonUInt),
660+
ArraySliceChild(JsonUInt, Option<JsonUInt>, JsonUInt),
661+
WildcardDescendant,
662+
NameDescendant(String),
662663
ArrayIndexDescendant(JsonUInt),
664+
ArraySliceDescendant(JsonUInt, Option<JsonUInt>, JsonUInt),
663665
}
664666

665667
#[derive(Debug, Clone)]
@@ -724,11 +726,13 @@ mod tests {
724726
fn any_selector() -> impl Strategy<Value = Selector> {
725727
prop_oneof![
726728
any_wildcard_child(),
727-
child_any(),
728-
any_wildcard_descendant(),
729-
descendant_any(),
729+
any_child_name(),
730730
any_array_index_child(),
731+
any_array_slice_child(),
732+
any_wildcard_descendant(),
733+
any_descendant_name(),
731734
any_array_index_descendant(),
735+
any_array_slice_descendant(),
732736
]
733737
}
734738

@@ -749,18 +753,18 @@ mod tests {
749753
}
750754

751755
// .label or ['label']
752-
fn child_any() -> impl Strategy<Value = Selector> {
756+
fn any_child_name() -> impl Strategy<Value = Selector> {
753757
prop_oneof![any_short_name().prop_map(|x| (format!(".{x}"), x)), any_name(),].prop_map(|(s, l)| Selector {
754758
string: s,
755-
tag: SelectorTag::Child(l),
759+
tag: SelectorTag::NameChild(l),
756760
})
757761
}
758762

759763
// ..label or ..['label']
760-
fn descendant_any() -> impl Strategy<Value = Selector> {
764+
fn any_descendant_name() -> impl Strategy<Value = Selector> {
761765
prop_oneof![any_short_name().prop_map(|x| (x.clone(), x)), any_name(),].prop_map(|(x, l)| Selector {
762766
string: format!("..{x}"),
763-
tag: SelectorTag::Descendant(l),
767+
tag: SelectorTag::NameDescendant(l),
764768
})
765769
}
766770

@@ -771,13 +775,45 @@ mod tests {
771775
})
772776
}
773777

778+
fn any_array_slice_child() -> impl Strategy<Value = Selector> {
779+
(
780+
any_non_negative_array_index(),
781+
proptest::option::of(any_non_negative_array_index()),
782+
any_non_negative_array_index(),
783+
)
784+
.prop_map(|(start, end, step)| Selector {
785+
string: if let Some(end) = end {
786+
format!("[{}:{}:{}]", start.as_u64(), end.as_u64(), step.as_u64())
787+
} else {
788+
format!("[{}::{}]", start.as_u64(), step.as_u64())
789+
},
790+
tag: SelectorTag::ArraySliceChild(start, end, step),
791+
})
792+
}
793+
774794
fn any_array_index_descendant() -> impl Strategy<Value = Selector> {
775795
any_non_negative_array_index().prop_map(|i| Selector {
776796
string: format!("..[{}]", i.as_u64()),
777797
tag: SelectorTag::ArrayIndexDescendant(i),
778798
})
779799
}
780800

801+
fn any_array_slice_descendant() -> impl Strategy<Value = Selector> {
802+
(
803+
any_non_negative_array_index(),
804+
proptest::option::of(any_non_negative_array_index()),
805+
any_non_negative_array_index(),
806+
)
807+
.prop_map(|(start, end, step)| Selector {
808+
string: if let Some(end) = end {
809+
format!("..[{}:{}:{}]", start.as_u64(), end.as_u64(), step.as_u64())
810+
} else {
811+
format!("..[{}::{}]", start.as_u64(), step.as_u64())
812+
},
813+
tag: SelectorTag::ArraySliceDescendant(start, end, step),
814+
})
815+
}
816+
781817
fn any_short_name() -> impl Strategy<Value = String> {
782818
r"([A-Za-z]|_|[^\u0000-\u007F])([A-Za-z0-9]|_|[^\u0000-\u007F])*"
783819
}
@@ -831,12 +867,20 @@ mod tests {
831867
result += &selector.string;
832868

833869
match selector.tag {
870+
SelectorTag::NameChild(name) => query.child_name(JsonString::new(&name)),
871+
SelectorTag::ArrayIndexChild(idx) => query.child_index(idx),
872+
SelectorTag::ArraySliceChild(start, None, step) =>
873+
query.child_slice(|x| x.with_start(start).with_step(step)),
874+
SelectorTag::ArraySliceChild(start, Some(end), step) =>
875+
query.child_slice(|x| x.with_start(start).with_end(end).with_step(step)),
834876
SelectorTag::WildcardChild => query.child_wildcard(),
835-
SelectorTag::Child(name) => query.child_name(JsonString::new(&name)),
877+
SelectorTag::NameDescendant(name) => query.descendant_name(JsonString::new(&name)),
878+
SelectorTag::ArrayIndexDescendant(idx) => query.descendant_index(idx),
879+
SelectorTag::ArraySliceDescendant(start, None, step) =>
880+
query.descendant_slice(|x| x.with_start(start).with_step(step)),
881+
SelectorTag::ArraySliceDescendant(start, Some(end), step) =>
882+
query.descendant_slice(|x| x.with_start(start).with_end(end).with_step(step)),
836883
SelectorTag::WildcardDescendant => query.descendant_wildcard(),
837-
SelectorTag::Descendant(name) => query.descendant_name(JsonString::new(&name)),
838-
SelectorTag::ArrayIndexChild(idx) => query.child_index(idx),
839-
SelectorTag::ArrayIndexDescendant(idx) => query.descendant_index(idx)
840884
};
841885
}
842886

@@ -855,6 +899,14 @@ mod tests {
855899

856900
assert_eq!(expected, result);
857901
}
902+
903+
#[test]
904+
fn round_trip((_, query) in any_valid_query()) {
905+
let input = query.to_string();
906+
let result = crate::parse(&input).expect("expected Ok");
907+
908+
assert_eq!(query, result);
909+
}
858910
}
859911
}
860912
}

0 commit comments

Comments
 (0)