Skip to content

Commit 35428e9

Browse files
committed
fix default slice start in parser
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 35428e9

File tree

4 files changed

+70
-9
lines changed

4 files changed

+70
-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

+5-2
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,8 @@ pub struct Slice {
542542
}
543543

544544
impl Slice {
545-
const DEFAULT_START: Index = Index::FromStart(num::JsonUInt::ZERO);
545+
const DEFAULT_START_FORWARDS: Index = Index::FromStart(num::JsonUInt::ZERO);
546+
const DEFAULT_START_BACKWARDS: Index = Index::FromEnd(num::JsonNonZeroUInt::ONE);
546547
const DEFAULT_STEP: Step = Step::Forward(num::JsonUInt::ONE);
547548

548549
/// Create a new [`Slice`] from given bounds and step.
@@ -1180,7 +1181,9 @@ impl Display for Step {
11801181
impl Display for Slice {
11811182
#[inline]
11821183
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1183-
if self.start != Self::DEFAULT_START {
1184+
if (self.step.is_forward() && self.start != Self::DEFAULT_START_FORWARDS)
1185+
|| (self.step.is_backward() && self.start != Self::DEFAULT_START_BACKWARDS)
1186+
{
11841187
write!(f, "{}", self.start)?;
11851188
}
11861189
write!(f, ":")?;

crates/rsonpath-syntax/src/num.rs

+10
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,16 @@ impl JsonUInt {
458458
}
459459

460460
impl JsonNonZeroUInt {
461+
/// A constant value of one.
462+
///
463+
/// # Examples
464+
/// ```
465+
/// # use rsonpath_syntax::num::JsonNonZeroUInt;
466+
/// assert_eq!(JsonNonZeroUInt::ONE.as_u64(), 1);
467+
/// assert_eq!(JsonNonZeroUInt::ONE.as_non_zero_u64(), std::num::NonZeroU64::try_from(1).unwrap());
468+
/// ```
469+
pub const ONE: Self = Self(NonZeroU64::MIN);
470+
461471
#[must_use]
462472
const fn new(value: NonZeroU64) -> Self {
463473
Self(value)

crates/rsonpath-syntax/src/parser.rs

+13-5
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 = Index::FromEnd(JsonNonZeroUInt::ONE);
342+
}
343+
339344
Ok((rest, Selector::Slice(slice)))
340345
}
341346

@@ -917,7 +922,7 @@ fn fail<T>(kind: SyntaxErrorKind, rev_idx: usize, err_len: usize, rest: &str) ->
917922
#[cfg(test)]
918923
mod tests {
919924
use crate::{
920-
num::{JsonFloat, JsonInt, JsonNumber},
925+
num::{JsonFloat, JsonInt, JsonNonZeroUInt, JsonNumber},
921926
str::JsonString,
922927
Comparable, ComparisonExpr, ComparisonOp, Index, Literal, LogicalExpr, ParserOptions, Selector,
923928
SingularJsonPathQuery, SingularSegment, Step,
@@ -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(JsonNonZeroUInt::ONE), Some(Index::FromStart(4.into())), Step::Backward(5.try_into().unwrap()); "test c4cm5")]
1029+
#[test_case(":-4:-5", Index::FromEnd(JsonNonZeroUInt::ONE), 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(JsonNonZeroUInt::ONE), 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)