Parse singular segment

This commit is contained in:
Fabrice Reix 2025-09-21 10:10:46 +02:00 committed by hurl-bot
parent fb3a67531a
commit 1df5d7e70a
No known key found for this signature in database
GPG Key ID: 1283A2B4A0DCAF8D
3 changed files with 110 additions and 32 deletions

View File

@ -18,15 +18,42 @@
use hurl_core::reader::Reader;
use crate::jsonpath2::ast::query::AbsoluteQuery;
use crate::jsonpath2::parser::{primitives::expect_str, segments, ParseResult};
use crate::jsonpath2::ast::query::{AbsoluteQuery, Query, RelativeQuery};
use crate::jsonpath2::parser::primitives::{expect_str, match_str};
use crate::jsonpath2::parser::{segments, ParseError, ParseErrorKind, ParseResult};
pub fn parse(reader: &mut Reader) -> ParseResult<AbsoluteQuery> {
expect_str("$", reader)?;
let segments = segments::parse(reader)?;
let segments = segments::parse(reader, false)?;
Ok(AbsoluteQuery::new(segments))
}
/// Parse a singular query.
/// Regardless of the input value, the expression produces a nodelist containing at most one node
#[allow(dead_code)]
pub fn singular_query(reader: &mut Reader) -> ParseResult<Query> {
if match_str("$", reader) {
let segments = segments::parse(reader, true)?;
Ok(Query::AbsoluteQuery(AbsoluteQuery::new(segments)))
} else if match_str("@", reader) {
let segments = segments::parse(reader, true)?;
Ok(Query::RelativeQuery(RelativeQuery::new(segments)))
} else {
let pos = reader.cursor().pos;
let kind = ParseErrorKind::Expecting("a singular query".to_string());
Err(ParseError::new(pos, kind))
}
}
pub fn try_relative_query(reader: &mut Reader) -> ParseResult<Option<RelativeQuery>> {
if match_str("@", reader) {
let segments = segments::parse(reader, false)?;
Ok(Some(RelativeQuery::new(segments)))
} else {
Ok(None)
}
}
#[cfg(test)]
mod tests {
@ -91,4 +118,16 @@ mod tests {
);
assert_eq!(reader.cursor().index, CharPos(6));
}
#[test]
pub fn test_relative_query() {
let mut reader = Reader::new("@['isbn']");
assert_eq!(
try_relative_query(&mut reader).unwrap().unwrap(),
RelativeQuery::new(vec![Segment::Child(ChildSegment::new(vec![
Selector::Name(NameSelector::new("isbn".to_string()))
]))])
);
assert_eq!(reader.cursor().index, CharPos(9));
}
}

View File

@ -22,14 +22,44 @@ use crate::jsonpath2::ast::selector::{NameSelector, Selector, WildcardSelector};
use crate::jsonpath2::parser::{selectors, ParseError, ParseErrorKind};
use hurl_core::reader::Reader;
pub fn parse(reader: &mut Reader) -> ParseResult<Vec<Segment>> {
/// Parse segments
/// Only parse singular segments by setting `only_singular` to true
pub fn parse(reader: &mut Reader, only_singular: bool) -> ParseResult<Vec<Segment>> {
let mut segments = vec![];
let mut current_pos = reader.cursor().pos;
// Parsing singular segments
// In the spec, it is defined as parsing only name and index segments
// For reporting error, we will first parse any segment instead, and then check whether the segment is singular
while let Some(segment) = try_segment(reader)? {
if only_singular && !segment.is_singular() {
return Err(ParseError::new(
current_pos,
ParseErrorKind::Expecting("singular segment".to_string()),
));
}
segments.push(segment);
current_pos = reader.cursor().pos;
}
Ok(segments)
}
impl Segment {
fn is_singular(&self) -> bool {
if let Segment::Child(child_segment) = self {
for selector in child_segment.selectors() {
if !matches!(selector, Selector::Name(_)) && !matches!(selector, Selector::Index(_))
{
return false;
}
}
true
} else {
false
}
}
}
fn try_segment(reader: &mut Reader) -> ParseResult<Option<Segment>> {
if let Some(segment) = try_segment_shorthand(reader)? {
return Ok(Some(segment));
@ -52,7 +82,7 @@ fn try_segment(reader: &mut Reader) -> ParseResult<Option<Segment>> {
Ok(Some(segment))
}
// try to parse a shorthand notation
/// try to parse a shorthand notation
fn try_segment_shorthand(reader: &mut Reader) -> ParseResult<Option<Segment>> {
if match_str(".*", reader) {
Ok(Some(Segment::Child(ChildSegment::new(vec![
@ -102,7 +132,7 @@ fn alpha(reader: &mut Reader) -> ParseResult<char> {
mod tests {
use crate::jsonpath2::ast::selector::{NameSelector, Selector, WildcardSelector};
use hurl_core::reader::{CharPos, Reader};
use hurl_core::reader::{CharPos, Pos, Reader};
use super::*;
@ -111,7 +141,7 @@ mod tests {
let mut reader = Reader::new("['isbn']]");
assert_eq!(
parse(&mut reader).unwrap(),
parse(&mut reader, false).unwrap(),
vec![Segment::Child(ChildSegment::new(vec![Selector::Name(
NameSelector::new("isbn".to_string())
)]))]
@ -178,4 +208,32 @@ mod tests {
);
assert_eq!(reader.cursor().index, CharPos(3));
}
#[test]
pub fn test_singular_segments() {
let mut reader = Reader::new(".*");
assert_eq!(
parse(&mut reader, true).unwrap_err(),
ParseError::new(
Pos::new(1, 1),
ParseErrorKind::Expecting("singular segment".to_string())
)
);
assert_eq!(reader.cursor().index, CharPos(2));
}
#[test]
pub fn test_is_singular() {
assert!(
Segment::Child(ChildSegment::new(vec![Selector::Name(NameSelector::new(
"name".to_string()
))]))
.is_singular()
);
assert!(!Segment::Child(ChildSegment::new(vec![Selector::Wildcard(
WildcardSelector
)]))
.is_singular());
}
}

View File

@ -18,13 +18,12 @@
use super::{ParseError, ParseErrorKind};
use crate::jsonpath2::ast::expr::LogicalExpr;
use crate::jsonpath2::ast::query::RelativeQuery;
use crate::jsonpath2::ast::selector::{
ArraySliceSelector, FilterSelector, IndexSelector, NameSelector, Selector, WildcardSelector,
};
use crate::jsonpath2::parser::literal::{try_integer, try_string_literal};
use crate::jsonpath2::parser::primitives::{expect_str, match_str};
use crate::jsonpath2::parser::segments;
use crate::jsonpath2::parser::query::try_relative_query;
use hurl_core::reader::Reader;
use super::ParseResult;
@ -106,7 +105,7 @@ fn try_array_slice_selector(reader: &mut Reader) -> ParseResult<Option<ArraySlic
/// Try to parse a filter selector
fn try_filter_selector(reader: &mut Reader) -> ParseResult<Option<FilterSelector>> {
if match_str("?", reader) {
let rel_query = try_rel_query(reader)?.unwrap();
let rel_query = try_relative_query(reader)?.unwrap();
let expr = LogicalExpr::new(rel_query);
Ok(Some(FilterSelector::new(expr)))
} else {
@ -114,18 +113,12 @@ fn try_filter_selector(reader: &mut Reader) -> ParseResult<Option<FilterSelector
}
}
fn try_rel_query(reader: &mut Reader) -> ParseResult<Option<RelativeQuery>> {
if match_str("@", reader) {
let segments = segments::parse(reader)?;
Ok(Some(RelativeQuery::new(segments)))
} else {
Ok(None)
}
}
#[cfg(test)]
mod tests {
use crate::jsonpath2::ast::segment::{ChildSegment, Segment};
use crate::jsonpath2::ast::{
query::RelativeQuery,
segment::{ChildSegment, Segment},
};
use super::*;
use hurl_core::reader::{CharPos, Reader};
@ -242,16 +235,4 @@ mod tests {
);
assert_eq!(reader.cursor().index, CharPos(10));
}
#[test]
pub fn test_rel_query() {
let mut reader = Reader::new("@['isbn']");
assert_eq!(
try_rel_query(&mut reader).unwrap().unwrap(),
RelativeQuery::new(vec![Segment::Child(ChildSegment::new(vec![
Selector::Name(NameSelector::new("isbn".to_string()))
]))])
);
assert_eq!(reader.cursor().index, CharPos(9));
}
}