Skip to content

Commit 094ae20

Browse files
authored
fix default slice start in parser (#559)
According to the spec the start of a slice when not explicitly specified depends on the sign of the step. The current implementation always used FromStart(0), which is correct for forward steps, but for backward steps it's supposed to be starting from the end - FromEnd(1). This affects only `rsonpath-syntax`.
1 parent ac85aee commit 094ae20

File tree

3 files changed

+64
-9
lines changed

3 files changed

+64
-9
lines changed

crates/rsonpath-syntax/src/builder.rs

+42-2
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,8 @@ impl From<JsonPathQueryBuilder> for JsonPathQuery {
417417
/// ```
418418
pub struct SliceBuilder {
419419
inner: Slice,
420+
/// We need to track if start is explicit because the default depends on step sign.
421+
start_was_explicitly_given: bool,
420422
}
421423

422424
impl SliceBuilder {
@@ -432,13 +434,15 @@ impl SliceBuilder {
432434
pub fn new() -> Self {
433435
Self {
434436
inner: Slice::default(),
437+
start_was_explicitly_given: false,
435438
}
436439
}
437440

438441
/// Set the start of the [`Slice`].
439442
#[inline]
440443
pub fn with_start<N: Into<JsonInt>>(&mut self, start: N) -> &mut Self {
441444
self.inner.start = start.into().into();
445+
self.start_was_explicitly_given = true;
442446
self
443447
}
444448

@@ -462,15 +466,23 @@ impl SliceBuilder {
462466
#[inline]
463467
#[must_use]
464468
pub fn to_slice(&mut self) -> Slice {
469+
if !self.start_was_explicitly_given {
470+
if self.inner.step.is_forward() {
471+
self.inner.start = Slice::DEFAULT_START_FORWARDS;
472+
} else {
473+
self.inner.start = Slice::default_start_backwards();
474+
}
475+
}
476+
465477
self.inner.clone()
466478
}
467479
}
468480

469481
impl From<SliceBuilder> for Slice {
470482
#[inline]
471483
#[must_use]
472-
fn from(value: SliceBuilder) -> Self {
473-
value.inner
484+
fn from(mut value: SliceBuilder) -> Self {
485+
value.to_slice()
474486
}
475487
}
476488

@@ -805,3 +817,31 @@ impl From<LogicalExprBuilder> for LogicalExpr {
805817
value.current
806818
}
807819
}
820+
821+
#[cfg(test)]
822+
mod tests {
823+
use super::SliceBuilder;
824+
use crate::{Index, Slice, Step};
825+
826+
#[test]
827+
fn slice_builder_default_start_forward() {
828+
let mut builder = SliceBuilder::new();
829+
builder.with_end(3).with_step(4);
830+
let slice: Slice = builder.into();
831+
832+
assert_eq!(slice.start(), Index::FromStart(0.into()));
833+
assert_eq!(slice.end(), Some(Index::FromStart(3.into())));
834+
assert_eq!(slice.step(), Step::Forward(4.into()));
835+
}
836+
837+
#[test]
838+
fn slice_builder_default_start_backward() {
839+
let mut builder = SliceBuilder::new();
840+
builder.with_end(3).with_step(-4);
841+
let slice: Slice = builder.into();
842+
843+
assert_eq!(slice.start(), Index::FromEnd(1.try_into().unwrap()));
844+
assert_eq!(slice.end(), Some(Index::FromStart(3.into())));
845+
assert_eq!(slice.step(), Step::Backward(4.try_into().unwrap()));
846+
}
847+
}

crates/rsonpath-syntax/src/lib.rs

+10-3
Original file line numberDiff line numberDiff line change
@@ -542,8 +542,13 @@ pub struct Slice {
542542
}
543543

544544
impl Slice {
545-
const DEFAULT_START: Index = Index::FromStart(num::JsonUInt::ZERO);
546-
const DEFAULT_STEP: Step = Step::Forward(num::JsonUInt::ONE);
545+
pub(crate) const DEFAULT_START_FORWARDS: Index = Index::FromStart(num::JsonUInt::ZERO);
546+
/// This is not const because the required NonZeroU64::MIN is from Rust 1.70.
547+
#[inline(always)]
548+
pub(crate) fn default_start_backwards() -> Index {
549+
Index::FromEnd(1.try_into().expect("const 1 is nonzero"))
550+
}
551+
pub(crate) const DEFAULT_STEP: Step = Step::Forward(num::JsonUInt::ONE);
547552

548553
/// Create a new [`Slice`] from given bounds and step.
549554
#[inline(always)]
@@ -1180,7 +1185,9 @@ impl Display for Step {
11801185
impl Display for Slice {
11811186
#[inline]
11821187
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1183-
if self.start != Self::DEFAULT_START {
1188+
if (self.step.is_forward() && self.start != Self::DEFAULT_START_FORWARDS)
1189+
|| (self.step.is_backward() && self.start != Self::default_start_backwards())
1190+
{
11841191
write!(f, "{}", self.start)?;
11851192
}
11861193
write!(f, ":")?;

crates/rsonpath-syntax/src/parser.rs

+12-4
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,11 @@ fn slice_selector(q: &str) -> IResult<&str, Selector, InternalParseError> {
336336
};
337337
}
338338

339+
// Fixup the bounds - if start was not given and step is negative, the default must be reversed.
340+
if slice.step.is_backward() && opt_start.is_none() {
341+
slice.start = crate::Slice::default_start_backwards();
342+
}
343+
339344
Ok((rest, Selector::Slice(slice)))
340345
}
341346

@@ -1020,8 +1025,8 @@ mod tests {
10201025
#[test_case("-3:-4:-5", Index::FromEnd(3.try_into().unwrap()), Some(Index::FromEnd(4.try_into().unwrap())), Step::Backward(5.try_into().unwrap()); "test m3cm4cm5")]
10211026
#[test_case(":4:5", Index::FromStart(0.into()), Some(Index::FromStart(4.into())), Step::Forward(5.into()); "test c4c5")]
10221027
#[test_case(":-4:5", Index::FromStart(0.into()), Some(Index::FromEnd(4.try_into().unwrap())), Step::Forward(5.into()); "test cm4c5")]
1023-
#[test_case(":4:-5", Index::FromStart(0.into()), Some(Index::FromStart(4.into())), Step::Backward(5.try_into().unwrap()); "test c4cm5")]
1024-
#[test_case(":-4:-5", Index::FromStart(0.into()), Some(Index::FromEnd(4.try_into().unwrap())), Step::Backward(5.try_into().unwrap()); "test cm4cm5")]
1028+
#[test_case(":4:-5", Index::FromEnd(1.try_into().unwrap()), Some(Index::FromStart(4.into())), Step::Backward(5.try_into().unwrap()); "test c4cm5")]
1029+
#[test_case(":-4:-5", Index::FromEnd(1.try_into().unwrap()), Some(Index::FromEnd(4.try_into().unwrap())), Step::Backward(5.try_into().unwrap()); "test cm4cm5")]
10251030
#[test_case("3::5", Index::FromStart(3.into()), None, Step::Forward(5.into()); "test 3cc5")]
10261031
#[test_case("-3::5", Index::FromEnd(3.try_into().unwrap()), None, Step::Forward(5.into()); "test m3cc5")]
10271032
#[test_case("3::-5", Index::FromStart(3.into()), None, Step::Backward(5.try_into().unwrap()); "test 3ccm5")]
@@ -1039,9 +1044,12 @@ mod tests {
10391044
#[test_case(":4", Index::FromStart(0.into()), Some(Index::FromStart(4.into())), Step::Forward(1.into()); "test c4")]
10401045
#[test_case(":-4", Index::FromStart(0.into()), Some(Index::FromEnd(4.try_into().unwrap())), Step::Forward(1.into()); "test cm4")]
10411046
#[test_case("::5", Index::FromStart(0.into()), None, Step::Forward(5.into()); "test cc5")]
1042-
#[test_case("::-5", Index::FromStart(0.into()), None, Step::Backward(5.try_into().unwrap()); "test ccm5")]
1047+
#[test_case("::-5", Index::FromEnd(1.try_into().unwrap()), None, Step::Backward(5.try_into().unwrap()); "test ccm5")]
10431048
#[test_case("::", Index::FromStart(0.into()), None, Step::Forward(1.into()); "test cc")]
1044-
fn full_positive_slice(input: &str, exp_start: Index, exp_end: Option<Index>, exp_step: Step) {
1049+
#[test_case("::-1", Index::FromEnd(1.try_into().unwrap()), None, Step::Backward(1.try_into().unwrap()); "test ccm1")]
1050+
#[test_case("0::-1", Index::FromStart(0.into()), None, Step::Backward(1.try_into().unwrap()); "test 0ccm1")]
1051+
#[test_case("0:0:-1", Index::FromStart(0.into()), Some(Index::FromStart(0.into())), Step::Backward(1.try_into().unwrap()); "test 0c0cm1")]
1052+
fn slice(input: &str, exp_start: Index, exp_end: Option<Index>, exp_step: Step) {
10451053
let (rest, selector) = super::slice_selector(input).expect("should parse");
10461054
assert_eq!("", rest);
10471055
match selector {

0 commit comments

Comments
 (0)