Refacto JSONPath literal parsing
This commit is contained in:
parent
f6d8c88969
commit
e00f6bc34e
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* Hurl (https://hurl.dev)
|
||||
* Copyright (C) 2025 Orange
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
pub(crate) mod number;
|
||||
pub(crate) mod string;
|
||||
|
||||
use crate::jsonpath2::ast::literal::Literal;
|
||||
use crate::jsonpath2::parser::primitives::match_str;
|
||||
use crate::jsonpath2::parser::{ParseError, ParseErrorKind, ParseResult};
|
||||
use hurl_core::reader::Reader;
|
||||
use number::try_number;
|
||||
use string::try_parse as try_string;
|
||||
|
||||
/// Parse a literal
|
||||
/// This includes standard JSON primitives (number, string, bool or null)
|
||||
/// with the addition of string literals with quotes
|
||||
///
|
||||
/// Number can be either integer, or floats
|
||||
/// If the number contains a decimal point or an exponent, it is parsed as a float (Like Serde::json)
|
||||
/// 110 is an integer, but 110.0 or 1.1e2 are floats
|
||||
#[allow(dead_code)]
|
||||
pub fn parse(reader: &mut Reader) -> ParseResult<Literal> {
|
||||
if try_null(reader) {
|
||||
Ok(Literal::Null)
|
||||
} else if let Some(value) = try_boolean(reader) {
|
||||
Ok(Literal::Bool(value))
|
||||
} else if let Some(value) = number::try_number(reader)? {
|
||||
Ok(Literal::Number(value))
|
||||
} else if let Some(value) = string::try_parse(reader)? {
|
||||
Ok(Literal::String(value))
|
||||
} else {
|
||||
Err(ParseError::new(
|
||||
reader.cursor().pos,
|
||||
ParseErrorKind::Expecting("a literal".to_string()),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn try_parse(reader: &mut Reader) -> ParseResult<Option<Literal>> {
|
||||
if try_null(reader) {
|
||||
Ok(Some(Literal::Null))
|
||||
} else if let Some(value) = try_boolean(reader) {
|
||||
Ok(Some(Literal::Bool(value)))
|
||||
} else if let Some(value) = try_number(reader)? {
|
||||
Ok(Some(Literal::Number(value)))
|
||||
} else if let Some(value) = try_string(reader)? {
|
||||
Ok(Some(Literal::String(value)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to parse a boolean literal
|
||||
#[allow(dead_code)]
|
||||
fn try_boolean(reader: &mut Reader) -> Option<bool> {
|
||||
if match_str("true", reader) {
|
||||
Some(true)
|
||||
} else if match_str("false", reader) {
|
||||
Some(false)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to parse a null literal
|
||||
#[allow(dead_code)]
|
||||
fn try_null(reader: &mut Reader) -> bool {
|
||||
match_str("null", reader)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
use hurl_core::reader::{CharPos, Pos, Reader};
|
||||
|
||||
#[test]
|
||||
pub fn test_literal() {
|
||||
let mut reader = Reader::new("null");
|
||||
assert_eq!(try_parse(&mut reader).unwrap().unwrap(), Literal::Null);
|
||||
assert_eq!(reader.cursor().index, CharPos(4));
|
||||
|
||||
let mut reader = Reader::new("true");
|
||||
assert_eq!(
|
||||
try_parse(&mut reader).unwrap().unwrap(),
|
||||
Literal::Bool(true)
|
||||
);
|
||||
assert_eq!(reader.cursor().index, CharPos(4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_literal_error() {
|
||||
let mut reader = Reader::new("NULL");
|
||||
assert_eq!(
|
||||
parse(&mut reader).unwrap_err(),
|
||||
ParseError::new(
|
||||
Pos::new(1, 1),
|
||||
ParseErrorKind::Expecting("a literal".to_string())
|
||||
)
|
||||
);
|
||||
assert_eq!(reader.cursor().index, CharPos(0));
|
||||
}
|
||||
}
|
||||
|
|
@ -18,69 +18,9 @@
|
|||
|
||||
use hurl_core::reader::Reader;
|
||||
|
||||
use crate::jsonpath2::ast::literal::{Literal, Number};
|
||||
use crate::jsonpath2::parser::{
|
||||
primitives::{expect_str, match_str},
|
||||
ParseError, ParseErrorKind, ParseResult,
|
||||
};
|
||||
|
||||
/// Parse a literal
|
||||
/// This includes standard JSON primitives (number, string, bool or null)
|
||||
/// with the addition of string literals with quotes
|
||||
///
|
||||
/// Number can be either integer, or floats
|
||||
/// If the number contains a decimal point or an exponent, it is parsed as a float (Like Serde::json)
|
||||
/// 110 is an integer, but 110.0 or 1.1e2 are floats
|
||||
#[allow(dead_code)]
|
||||
pub fn parse(reader: &mut Reader) -> ParseResult<Literal> {
|
||||
if try_null(reader) {
|
||||
Ok(Literal::Null)
|
||||
} else if let Some(value) = try_boolean(reader) {
|
||||
Ok(Literal::Bool(value))
|
||||
} else if let Some(value) = try_number(reader)? {
|
||||
Ok(Literal::Number(value))
|
||||
} else if let Some(value) = try_string_literal(reader)? {
|
||||
Ok(Literal::String(value))
|
||||
} else {
|
||||
Err(ParseError::new(
|
||||
reader.cursor().pos,
|
||||
ParseErrorKind::Expecting("a literal".to_string()),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn try_parse(reader: &mut Reader) -> ParseResult<Option<Literal>> {
|
||||
if try_null(reader) {
|
||||
Ok(Some(Literal::Null))
|
||||
} else if let Some(value) = try_boolean(reader) {
|
||||
Ok(Some(Literal::Bool(value)))
|
||||
} else if let Some(value) = try_number(reader)? {
|
||||
Ok(Some(Literal::Number(value)))
|
||||
} else if let Some(value) = try_string_literal(reader)? {
|
||||
Ok(Some(Literal::String(value)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to parse a boolean literal
|
||||
#[allow(dead_code)]
|
||||
fn try_boolean(reader: &mut Reader) -> Option<bool> {
|
||||
if match_str("true", reader) {
|
||||
Some(true)
|
||||
} else if match_str("false", reader) {
|
||||
Some(false)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to parse a null literal
|
||||
#[allow(dead_code)]
|
||||
fn try_null(reader: &mut Reader) -> bool {
|
||||
match_str("null", reader)
|
||||
}
|
||||
use crate::jsonpath2::ast::literal::Number;
|
||||
use crate::jsonpath2::parser::primitives::match_str;
|
||||
use crate::jsonpath2::parser::{ParseError, ParseErrorKind, ParseResult};
|
||||
|
||||
/// Try to parse a decimal integer
|
||||
/// if it does not start with a minus sign or a digit
|
||||
|
|
@ -171,82 +111,11 @@ fn try_exponent(reader: &mut Reader) -> ParseResult<Option<i32>> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Try to parse a string literal
|
||||
/// if it does not start with a quote it returns `None` rather than a `ParseError`
|
||||
///
|
||||
// TODO: implement full spec with double-quoted and single-quoted parser
|
||||
pub fn try_string_literal(reader: &mut Reader) -> ParseResult<Option<String>> {
|
||||
if match_str("\"", reader) {
|
||||
let s = reader.read_while(|c| c != '"');
|
||||
expect_str("\"", reader)?;
|
||||
Ok(Some(s))
|
||||
} else if match_str("'", reader) {
|
||||
let s = reader.read_while(|c| c != '\'');
|
||||
expect_str("'", reader)?;
|
||||
Ok(Some(s))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
use hurl_core::reader::{CharPos, Pos, Reader};
|
||||
|
||||
#[test]
|
||||
pub fn test_literal() {
|
||||
let mut reader = Reader::new("null");
|
||||
assert_eq!(parse(&mut reader).unwrap(), Literal::Null);
|
||||
assert_eq!(reader.cursor().index, CharPos(4));
|
||||
|
||||
let mut reader = Reader::new("true");
|
||||
assert_eq!(parse(&mut reader).unwrap(), Literal::Bool(true));
|
||||
assert_eq!(reader.cursor().index, CharPos(4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_literal_error() {
|
||||
let mut reader = Reader::new("NULL");
|
||||
assert_eq!(
|
||||
parse(&mut reader).unwrap_err(),
|
||||
ParseError::new(
|
||||
Pos::new(1, 1),
|
||||
ParseErrorKind::Expecting("a literal".to_string())
|
||||
)
|
||||
);
|
||||
assert_eq!(reader.cursor().index, CharPos(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_literal() {
|
||||
let mut reader = Reader::new("'store'");
|
||||
assert_eq!(
|
||||
try_string_literal(&mut reader).unwrap().unwrap(),
|
||||
"store".to_string()
|
||||
);
|
||||
assert_eq!(reader.cursor().index, CharPos(7));
|
||||
let mut reader = Reader::new("\"store\"");
|
||||
assert_eq!(
|
||||
try_string_literal(&mut reader).unwrap().unwrap(),
|
||||
"store".to_string()
|
||||
);
|
||||
assert_eq!(reader.cursor().index, CharPos(7));
|
||||
|
||||
let mut reader = Reader::new("0");
|
||||
assert!(try_string_literal(&mut reader).unwrap().is_none());
|
||||
assert_eq!(reader.cursor().index, CharPos(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_literal_error() {
|
||||
let mut reader = Reader::new("'store");
|
||||
assert_eq!(
|
||||
try_string_literal(&mut reader).unwrap_err(),
|
||||
ParseError::new(Pos::new(1, 7), ParseErrorKind::Expecting("'".to_string()))
|
||||
);
|
||||
}
|
||||
use hurl_core::reader::{CharPos, Reader};
|
||||
|
||||
#[test]
|
||||
fn test_number() {
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Hurl (https://hurl.dev)
|
||||
* Copyright (C) 2025 Orange
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
use crate::jsonpath2::parser::primitives::{expect_str, match_str};
|
||||
use crate::jsonpath2::parser::ParseResult;
|
||||
use hurl_core::reader::Reader;
|
||||
|
||||
/// Try to parse a string literal
|
||||
/// if it does not start with a quote it returns `None` rather than a `ParseError`
|
||||
pub fn try_parse(reader: &mut Reader) -> ParseResult<Option<String>> {
|
||||
if let Some(s) = try_double_quoted_string(reader)? {
|
||||
Ok(Some(s))
|
||||
} else if let Some(s) = try_single_quoted_string(reader)? {
|
||||
Ok(Some(s))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn try_double_quoted_string(reader: &mut Reader) -> ParseResult<Option<String>> {
|
||||
if match_str("\"", reader) {
|
||||
let s = reader.read_while(|c| c != '"');
|
||||
expect_str("\"", reader)?;
|
||||
Ok(Some(s))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn try_single_quoted_string(reader: &mut Reader) -> ParseResult<Option<String>> {
|
||||
if match_str("\'", reader) {
|
||||
let s = reader.read_while(|c| c != '\'');
|
||||
expect_str("\'", reader)?;
|
||||
Ok(Some(s))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::jsonpath2::parser::{ParseError, ParseErrorKind};
|
||||
use hurl_core::reader::{CharPos, Pos, Reader};
|
||||
|
||||
#[test]
|
||||
fn test_string_literal() {
|
||||
let mut reader = Reader::new("'store'");
|
||||
assert_eq!(
|
||||
try_parse(&mut reader).unwrap().unwrap(),
|
||||
"store".to_string()
|
||||
);
|
||||
assert_eq!(reader.cursor().index, CharPos(7));
|
||||
let mut reader = Reader::new("\"store\"");
|
||||
assert_eq!(
|
||||
try_parse(&mut reader).unwrap().unwrap(),
|
||||
"store".to_string()
|
||||
);
|
||||
assert_eq!(reader.cursor().index, CharPos(7));
|
||||
|
||||
let mut reader = Reader::new("0");
|
||||
assert!(try_parse(&mut reader).unwrap().is_none());
|
||||
assert_eq!(reader.cursor().index, CharPos(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_literal_error() {
|
||||
let mut reader = Reader::new("'store");
|
||||
assert_eq!(
|
||||
try_parse(&mut reader).unwrap_err(),
|
||||
ParseError::new(Pos::new(1, 7), ParseErrorKind::Expecting("'".to_string()))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -21,7 +21,8 @@ use crate::jsonpath2::ast::selector::{
|
|||
ArraySliceSelector, FilterSelector, IndexSelector, NameSelector, Selector, WildcardSelector,
|
||||
};
|
||||
use crate::jsonpath2::parser::expr::logical_or_expr;
|
||||
use crate::jsonpath2::parser::literal::{try_integer, try_string_literal};
|
||||
use crate::jsonpath2::parser::literal::number::try_integer;
|
||||
use crate::jsonpath2::parser::literal::string::try_parse as try_string_literal;
|
||||
use crate::jsonpath2::parser::primitives::{match_str, skip_whitespace};
|
||||
use hurl_core::reader::Reader;
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ use crate::jsonpath2::ast::selector::{IndexSelector, NameSelector};
|
|||
use crate::jsonpath2::ast::singular_query::{
|
||||
AbsoluteSingularQuery, RelativeSingularQuery, SingularQuery, SingularQuerySegment,
|
||||
};
|
||||
use crate::jsonpath2::parser::literal::try_integer;
|
||||
use crate::jsonpath2::parser::literal::number::try_integer;
|
||||
use crate::jsonpath2::parser::primitives::expect_str;
|
||||
use crate::jsonpath2::parser::primitives::match_str;
|
||||
use crate::jsonpath2::parser::selectors::try_name_selector;
|
||||
|
|
|
|||
Loading…
Reference in New Issue