Add JSONPath functions
This commit is contained in:
parent
ea87045f7c
commit
6b1ff56bc1
|
|
@ -16,6 +16,7 @@
|
|||
*
|
||||
*/
|
||||
|
||||
use crate::jsonpath2::ast::function::functions::ValueTypeFunction;
|
||||
use crate::jsonpath2::ast::literal::Literal;
|
||||
use crate::jsonpath2::ast::singular_query::SingularQuery;
|
||||
|
||||
|
|
@ -52,6 +53,7 @@ impl ComparisonExpr {
|
|||
pub enum Comparable {
|
||||
Literal(Literal),
|
||||
SingularQuery(SingularQuery),
|
||||
Function(ValueTypeFunction),
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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 super::functions::LogicalTypeFunction;
|
||||
use super::functions::ValueTypeFunction;
|
||||
use crate::jsonpath2::ast::expr::LogicalExpr;
|
||||
use crate::jsonpath2::ast::literal::Literal;
|
||||
use crate::jsonpath2::ast::query::Query;
|
||||
use crate::jsonpath2::ast::singular_query::SingularQuery;
|
||||
|
||||
/// Arguments with ValueType
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[allow(dead_code)]
|
||||
pub enum ValueTypeArgument {
|
||||
Literal(Literal),
|
||||
SingularQuery(SingularQuery),
|
||||
Function(Box<ValueTypeFunction>),
|
||||
}
|
||||
|
||||
/// Arguments with LogicalType
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum LogicalTypeArgument {
|
||||
LogicalExpr(LogicalExpr),
|
||||
LogicalTypeFunction(Box<LogicalTypeFunction>),
|
||||
}
|
||||
|
||||
/// Arguments with NodesType
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum NodesTypeArgument {
|
||||
FilterQuery(Query),
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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 super::argument::{NodesTypeArgument, ValueTypeArgument};
|
||||
|
||||
/// Functions returning a LogicalType
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum LogicalTypeFunction {
|
||||
Match(ValueTypeArgument, ValueTypeArgument),
|
||||
Search(ValueTypeArgument, ValueTypeArgument),
|
||||
}
|
||||
|
||||
/// Functions returning a ValueType
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ValueTypeFunction {
|
||||
Length(ValueTypeArgument),
|
||||
Count(NodesTypeArgument),
|
||||
Value(NodesTypeArgument),
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
/// Function extensions:
|
||||
/// <https://www.rfc-editor.org/rfc/rfc9535.html#name-function-extensions>
|
||||
/// This module implements the functions defined in the RFC.
|
||||
/// It defines the function types and their arguments.
|
||||
pub mod argument;
|
||||
pub mod functions;
|
||||
|
||||
use crate::jsonpath2::eval::NodeList;
|
||||
|
||||
pub type ValueType = Option<serde_json::Value>;
|
||||
pub type LogicalType = bool;
|
||||
pub type NodesType = NodeList;
|
||||
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
pub(crate) mod comparison;
|
||||
pub(crate) mod expr;
|
||||
pub(crate) mod function;
|
||||
pub(crate) mod literal;
|
||||
pub(crate) mod query;
|
||||
pub(crate) mod segment;
|
||||
|
|
|
|||
|
|
@ -88,6 +88,9 @@ impl Comparable {
|
|||
Comparable::SingularQuery(singular_query) => {
|
||||
singular_query.eval(current_value, root_value)
|
||||
}
|
||||
Comparable::Function(value_type_function) => {
|
||||
value_type_function.eval(current_value, root_value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* 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::ast::function::argument::{NodesTypeArgument, ValueTypeArgument};
|
||||
use crate::jsonpath2::ast::function::{NodesType, ValueType};
|
||||
|
||||
impl ValueTypeArgument {
|
||||
pub fn eval(
|
||||
&self,
|
||||
current_value: &serde_json::Value,
|
||||
root_value: &serde_json::Value,
|
||||
) -> ValueType {
|
||||
match self {
|
||||
ValueTypeArgument::SingularQuery(filter_query) => {
|
||||
filter_query.eval(current_value, root_value)
|
||||
}
|
||||
ValueTypeArgument::Function(value_type_function) => {
|
||||
value_type_function.eval(current_value, root_value)
|
||||
}
|
||||
ValueTypeArgument::Literal(value) => Some(value.eval()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NodesTypeArgument {
|
||||
pub fn eval(
|
||||
&self,
|
||||
current_value: &serde_json::Value,
|
||||
root_value: &serde_json::Value,
|
||||
) -> NodesType {
|
||||
match self {
|
||||
NodesTypeArgument::FilterQuery(query) => query.eval(current_value, root_value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::jsonpath2::ast::literal::{Literal, Number};
|
||||
use crate::jsonpath2::ast::query::{Query, RelativeQuery};
|
||||
use crate::jsonpath2::ast::segment::{ChildSegment, Segment};
|
||||
use crate::jsonpath2::ast::selector::{NameSelector, Selector};
|
||||
use crate::jsonpath2::ast::singular_query::{
|
||||
RelativeSingularQuery, SingularQuery, SingularQuerySegment,
|
||||
};
|
||||
use crate::jsonpath2::eval::function::argument::{NodesTypeArgument, ValueTypeArgument};
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn test_value_type_argument() {
|
||||
// "hello"
|
||||
let argument = ValueTypeArgument::Literal(Literal::String("hello".to_string()));
|
||||
assert_eq!(argument.eval(&json!({}), &json!({})), Some(json!("hello")));
|
||||
|
||||
// 1
|
||||
let argument = ValueTypeArgument::Literal(Literal::Number(Number::Integer(1)));
|
||||
assert_eq!(argument.eval(&json!({}), &json!({})), Some(json!(1)));
|
||||
|
||||
// @.authors
|
||||
let argument = ValueTypeArgument::SingularQuery(SingularQuery::Relative(
|
||||
RelativeSingularQuery::new(vec![SingularQuerySegment::Name(NameSelector::new(
|
||||
"authors".to_string(),
|
||||
))]),
|
||||
));
|
||||
assert_eq!(
|
||||
argument.eval(&json!({"authors": ["a", "b", "c"]}), &json!({})),
|
||||
Some(json!(["a", "b", "c"]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nodes_type_argument() {
|
||||
// @.books
|
||||
let argument =
|
||||
NodesTypeArgument::FilterQuery(Query::RelativeQuery(RelativeQuery::new(vec![
|
||||
Segment::Child(ChildSegment::new(vec![Selector::Name(NameSelector::new(
|
||||
"books".to_string(),
|
||||
))])),
|
||||
])));
|
||||
assert_eq!(
|
||||
argument.eval(&json!({"books": ["book1", "book2"]}), &json!({})),
|
||||
vec![json!(["book1", "book2"])]
|
||||
);
|
||||
assert!(argument.eval(&json!({}), &json!({})).is_empty());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,251 @@
|
|||
/*
|
||||
* 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 regex::Regex;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::jsonpath2::ast::function::functions::{LogicalTypeFunction, ValueTypeFunction};
|
||||
use crate::jsonpath2::ast::function::LogicalType;
|
||||
|
||||
impl ValueTypeFunction {
|
||||
#[allow(dead_code)]
|
||||
pub fn eval(
|
||||
&self,
|
||||
current_value: &serde_json::Value,
|
||||
root_value: &serde_json::Value,
|
||||
) -> Option<serde_json::Value> {
|
||||
match self {
|
||||
ValueTypeFunction::Length(argument) => {
|
||||
let argument = argument.eval(current_value, root_value)?;
|
||||
length(&argument).map(|n| {
|
||||
let number = serde_json::Number::from_i128(n as i128).unwrap();
|
||||
serde_json::Value::Number(number)
|
||||
})
|
||||
}
|
||||
ValueTypeFunction::Count(argument) => {
|
||||
let nodelist = argument.eval(current_value, root_value);
|
||||
Some(serde_json::Value::Number(serde_json::Number::from(
|
||||
nodelist.len(),
|
||||
)))
|
||||
}
|
||||
ValueTypeFunction::Value(argument) => {
|
||||
let nodelist = argument.eval(current_value, root_value);
|
||||
if nodelist.len() == 1 {
|
||||
Some(nodelist[0].clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LogicalTypeFunction {
|
||||
#[allow(dead_code)]
|
||||
pub fn eval(
|
||||
&self,
|
||||
current_value: &serde_json::Value,
|
||||
root_value: &serde_json::Value,
|
||||
) -> LogicalType {
|
||||
match self {
|
||||
LogicalTypeFunction::Match(string_argument, pattern_argument) => {
|
||||
let string = string_argument.eval(current_value, root_value);
|
||||
let pattern = pattern_argument.eval(current_value, root_value);
|
||||
|
||||
let s = if let Some(Value::String(value)) = string {
|
||||
value
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
let p = if let Some(Value::String(value)) = pattern {
|
||||
value
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
|
||||
// use total match
|
||||
let p = format!("^{}$", p);
|
||||
if let Ok(regex) = Regex::new(&p) {
|
||||
regex.is_match(&s)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
LogicalTypeFunction::Search(string_argument, pattern_argument) => {
|
||||
let string = string_argument.eval(current_value, root_value);
|
||||
let pattern = pattern_argument.eval(current_value, root_value);
|
||||
|
||||
let s = if let Some(Value::String(value)) = string {
|
||||
value
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
let p = if let Some(Value::String(value)) = pattern {
|
||||
value
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if let Ok(regex) = Regex::new(&p) {
|
||||
regex.is_match(&s)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn length(value: &serde_json::Value) -> Option<usize> {
|
||||
match value {
|
||||
serde_json::Value::Array(arr) => Some(arr.len()),
|
||||
serde_json::Value::Object(map) => Some(map.len()),
|
||||
serde_json::Value::String(s) => Some(s.chars().count()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::jsonpath2::ast::function::argument::{NodesTypeArgument, ValueTypeArgument};
|
||||
use crate::jsonpath2::ast::literal::{Literal, Number};
|
||||
use crate::jsonpath2::ast::query::{Query, RelativeQuery};
|
||||
use crate::jsonpath2::ast::segment::{ChildSegment, DescendantSegment, Segment};
|
||||
use crate::jsonpath2::ast::selector::{NameSelector, Selector, WildcardSelector};
|
||||
use crate::jsonpath2::ast::singular_query::{
|
||||
RelativeSingularQuery, SingularQuery, SingularQuerySegment,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn test_length() {
|
||||
// length("abc")
|
||||
let length_function = ValueTypeFunction::Length(ValueTypeArgument::Literal(
|
||||
Literal::String("abc".to_string()),
|
||||
));
|
||||
assert_eq!(
|
||||
length_function.eval(&json!({}), &json!({})).unwrap(),
|
||||
json!(3)
|
||||
);
|
||||
|
||||
// length(1)
|
||||
let length_function = ValueTypeFunction::Length(ValueTypeArgument::Literal(
|
||||
Literal::Number(Number::Integer(1)),
|
||||
));
|
||||
assert!(length_function.eval(&json!({}), &json!({})).is_none());
|
||||
|
||||
// length(@.authors)
|
||||
let length_function = ValueTypeFunction::Length(ValueTypeArgument::SingularQuery(
|
||||
SingularQuery::Relative(RelativeSingularQuery::new(vec![
|
||||
SingularQuerySegment::Name(NameSelector::new("authors".to_string())),
|
||||
])),
|
||||
));
|
||||
assert_eq!(
|
||||
length_function
|
||||
.eval(&json!({"authors": ["a", "b", "c"]}), &json!({}))
|
||||
.unwrap(),
|
||||
json!(3)
|
||||
);
|
||||
assert_eq!(
|
||||
length_function
|
||||
.eval(
|
||||
&json!({"authors": {"a":"a", "b": "b", "c": "c"}}),
|
||||
&json!({})
|
||||
)
|
||||
.unwrap(),
|
||||
json!(3)
|
||||
);
|
||||
assert!(length_function.eval(&json!({}), &json!({})).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_count() {
|
||||
// count(@.*.author)
|
||||
let count_function = ValueTypeFunction::Count(NodesTypeArgument::FilterQuery(
|
||||
Query::RelativeQuery(RelativeQuery::new(vec![
|
||||
Segment::Child(ChildSegment::new(vec![Selector::Wildcard(
|
||||
WildcardSelector,
|
||||
)])),
|
||||
Segment::Child(ChildSegment::new(vec![Selector::Name(NameSelector::new(
|
||||
"author".to_string(),
|
||||
))])),
|
||||
])),
|
||||
));
|
||||
assert_eq!(
|
||||
count_function
|
||||
.eval(&json!([{"author": "Bob"},{"author": "Bill"}]), &json!({}))
|
||||
.unwrap(),
|
||||
json!(2)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_match() {
|
||||
// match(@.date, "1974-05-..")
|
||||
let string_argument = ValueTypeArgument::SingularQuery(SingularQuery::Relative(
|
||||
RelativeSingularQuery::new(vec![SingularQuerySegment::Name(NameSelector::new(
|
||||
"date".to_string(),
|
||||
))]),
|
||||
));
|
||||
let pattern_argument =
|
||||
ValueTypeArgument::Literal(Literal::String("1974-05-..".to_string()));
|
||||
let match_function = LogicalTypeFunction::Match(string_argument, pattern_argument);
|
||||
|
||||
assert!(match_function.eval(&json!({"date": "1974-05-01"}), &json!({})));
|
||||
assert!(!match_function.eval(&json!({"date": "74-05-01"}), &json!({})));
|
||||
assert!(!match_function.eval(&json!({"date": "1974-04-01"}), &json!({})));
|
||||
assert!(!match_function.eval(&json!({"_date": "1974-05-01"}), &json!({})));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_search() {
|
||||
// search(@.author, "[BR]ob")
|
||||
let string_argument = ValueTypeArgument::SingularQuery(SingularQuery::Relative(
|
||||
RelativeSingularQuery::new(vec![SingularQuerySegment::Name(NameSelector::new(
|
||||
"author".to_string(),
|
||||
))]),
|
||||
));
|
||||
let pattern_argument = ValueTypeArgument::Literal(Literal::String("[BR]ob".to_string()));
|
||||
let search_function = LogicalTypeFunction::Search(string_argument, pattern_argument);
|
||||
|
||||
assert!(search_function.eval(&json!({"author": "Bob Dylan"}), &json!({})));
|
||||
assert!(search_function.eval(&json!({"author": "Robert De Niro"}), &json!({})));
|
||||
assert!(!search_function.eval(&json!({"author": "Christiano Ronaldo"}), &json!({})));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_value() {
|
||||
// value(@..color)
|
||||
let value_function =
|
||||
ValueTypeFunction::Value(NodesTypeArgument::FilterQuery(Query::RelativeQuery(
|
||||
RelativeQuery::new(vec![Segment::Descendant(DescendantSegment::new(vec![
|
||||
Selector::Name(NameSelector::new("color".to_string())),
|
||||
]))]),
|
||||
)));
|
||||
assert_eq!(
|
||||
value_function
|
||||
.eval(&json!({"color": "red"}), &json!({}))
|
||||
.unwrap(),
|
||||
json!("red")
|
||||
);
|
||||
assert!(value_function.eval(&json!({}), &json!({})).is_none());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
mod argument;
|
||||
mod functions;
|
||||
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
mod comparison;
|
||||
mod expr;
|
||||
mod function;
|
||||
mod literal;
|
||||
mod query;
|
||||
mod segment;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
use crate::jsonpath2::ast::comparison::Comparable;
|
||||
use crate::jsonpath2::ast::comparison::ComparisonExpr;
|
||||
use crate::jsonpath2::ast::comparison::ComparisonOp;
|
||||
use crate::jsonpath2::parser::function::functions::try_value_type_function;
|
||||
use crate::jsonpath2::parser::literal;
|
||||
use crate::jsonpath2::parser::primitives::{match_str, skip_whitespace};
|
||||
use crate::jsonpath2::parser::singular_query::try_parse as try_singular_query;
|
||||
|
|
@ -66,6 +67,8 @@ fn try_comparable(reader: &mut Reader) -> ParseResult<Option<Comparable>> {
|
|||
Ok(Some(Comparable::Literal(literal)))
|
||||
} else if let Some(singular_query) = try_singular_query(reader)? {
|
||||
Ok(Some(Comparable::SingularQuery(singular_query)))
|
||||
} else if let Some(function) = try_value_type_function(reader)? {
|
||||
Ok(Some(Comparable::Function(function)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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::ast::function::argument::{NodesTypeArgument, ValueTypeArgument};
|
||||
use crate::jsonpath2::parser::literal;
|
||||
use crate::jsonpath2::parser::query::try_filter_query;
|
||||
use crate::jsonpath2::parser::singular_query;
|
||||
use crate::jsonpath2::parser::{ParseError, ParseErrorKind, ParseResult};
|
||||
use hurl_core::reader::Reader;
|
||||
|
||||
/// Parse an argument with ValueType
|
||||
#[allow(dead_code)]
|
||||
pub fn value_type(reader: &mut Reader) -> ParseResult<ValueTypeArgument> {
|
||||
if let Some(v) = literal::try_parse(reader)? {
|
||||
Ok(ValueTypeArgument::Literal(v))
|
||||
} else if let Some(query) = singular_query::try_parse(reader)? {
|
||||
Ok(ValueTypeArgument::SingularQuery(query))
|
||||
} else {
|
||||
Err(ParseError::new(
|
||||
reader.cursor().pos,
|
||||
ParseErrorKind::Expecting("a ValueType argument".to_string()),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse an argument with NodeType
|
||||
#[allow(dead_code)]
|
||||
pub fn nodes_type(reader: &mut Reader) -> ParseResult<NodesTypeArgument> {
|
||||
if let Some(filter_query) = try_filter_query(reader)? {
|
||||
Ok(NodesTypeArgument::FilterQuery(filter_query))
|
||||
} else {
|
||||
Err(ParseError::new(
|
||||
reader.cursor().pos,
|
||||
ParseErrorKind::Expecting("a NodesType argument".to_string()),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::jsonpath2::ast::literal::{Literal, Number};
|
||||
use crate::jsonpath2::ast::query::{Query, RelativeQuery};
|
||||
use crate::jsonpath2::ast::segment::{ChildSegment, Segment};
|
||||
use crate::jsonpath2::ast::selector::{NameSelector, Selector, WildcardSelector};
|
||||
use hurl_core::reader::CharPos;
|
||||
|
||||
#[test]
|
||||
pub fn test_value_type() {
|
||||
let mut reader = Reader::new("42");
|
||||
assert_eq!(
|
||||
value_type(&mut reader).unwrap(),
|
||||
ValueTypeArgument::Literal(Literal::Number(Number::Integer(42)))
|
||||
);
|
||||
assert_eq!(reader.cursor().index, CharPos(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_nodes_type() {
|
||||
let mut reader = Reader::new("@.*.author");
|
||||
assert_eq!(
|
||||
nodes_type(&mut reader).unwrap(),
|
||||
NodesTypeArgument::FilterQuery(Query::RelativeQuery(RelativeQuery::new(vec![
|
||||
Segment::Child(ChildSegment::new(vec![Selector::Wildcard(
|
||||
WildcardSelector
|
||||
)])),
|
||||
Segment::Child(ChildSegment::new(vec![Selector::Name(NameSelector::new(
|
||||
"author".to_string()
|
||||
)),]))
|
||||
])))
|
||||
);
|
||||
assert_eq!(reader.cursor().index, CharPos(10));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
* 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 super::argument;
|
||||
use crate::jsonpath2::ast::function::functions::{LogicalTypeFunction, ValueTypeFunction};
|
||||
use crate::jsonpath2::parser::primitives::{expect_str, match_str, skip_whitespace};
|
||||
use crate::jsonpath2::parser::ParseResult;
|
||||
use hurl_core::reader::Reader;
|
||||
|
||||
/// Try to parse a function return a ValueType
|
||||
#[allow(dead_code)]
|
||||
pub fn try_value_type_function(reader: &mut Reader) -> ParseResult<Option<ValueTypeFunction>> {
|
||||
//let initial_state = reader.cursor();
|
||||
if match_str("length", reader) {
|
||||
expect_str("(", reader)?;
|
||||
let argument = argument::value_type(reader)?;
|
||||
expect_str(")", reader)?;
|
||||
Ok(Some(ValueTypeFunction::Length(argument)))
|
||||
} else if match_str("value", reader) {
|
||||
expect_str("(", reader)?;
|
||||
let argument = argument::nodes_type(reader)?;
|
||||
expect_str(")", reader)?;
|
||||
Ok(Some(ValueTypeFunction::Value(argument)))
|
||||
} else if match_str("count", reader) {
|
||||
expect_str("(", reader)?;
|
||||
let argument = argument::nodes_type(reader)?;
|
||||
expect_str(")", reader)?;
|
||||
Ok(Some(ValueTypeFunction::Count(argument)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to parse a function return a LogicalType
|
||||
#[allow(dead_code)]
|
||||
pub fn try_logical_type_function(reader: &mut Reader) -> ParseResult<Option<LogicalTypeFunction>> {
|
||||
//let initial_state = reader.cursor();
|
||||
if match_str("match", reader) {
|
||||
expect_str("(", reader)?;
|
||||
let argument1 = argument::value_type(reader)?;
|
||||
skip_whitespace(reader);
|
||||
expect_str(",", reader)?;
|
||||
skip_whitespace(reader);
|
||||
let argument2 = argument::value_type(reader)?;
|
||||
expect_str(")", reader)?;
|
||||
Ok(Some(LogicalTypeFunction::Match(argument1, argument2)))
|
||||
} else if match_str("search", reader) {
|
||||
expect_str("(", reader)?;
|
||||
let argument1 = argument::value_type(reader)?;
|
||||
skip_whitespace(reader);
|
||||
expect_str(",", reader)?;
|
||||
skip_whitespace(reader);
|
||||
let argument2 = argument::value_type(reader)?;
|
||||
expect_str(")", reader)?;
|
||||
Ok(Some(LogicalTypeFunction::Search(argument1, argument2)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::jsonpath2::ast::function::argument::{NodesTypeArgument, ValueTypeArgument};
|
||||
use crate::jsonpath2::ast::literal::Literal;
|
||||
use crate::jsonpath2::ast::query::{Query, RelativeQuery};
|
||||
use crate::jsonpath2::ast::segment::{ChildSegment, DescendantSegment, Segment};
|
||||
use crate::jsonpath2::ast::selector::{NameSelector, Selector, WildcardSelector};
|
||||
use crate::jsonpath2::ast::singular_query::{
|
||||
RelativeSingularQuery, SingularQuery, SingularQuerySegment,
|
||||
};
|
||||
use crate::jsonpath2::parser::{ParseError, ParseErrorKind};
|
||||
use hurl_core::reader::{CharPos, Pos};
|
||||
//use serde_json::json;
|
||||
|
||||
#[test]
|
||||
pub fn test_length_function() {
|
||||
let mut reader = Reader::new("length(\"abc\")");
|
||||
assert_eq!(
|
||||
try_value_type_function(&mut reader).unwrap().unwrap(),
|
||||
ValueTypeFunction::Length(ValueTypeArgument::Literal(Literal::String(
|
||||
"abc".to_string()
|
||||
)))
|
||||
);
|
||||
assert_eq!(reader.cursor().index, CharPos(13));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_length_function_error() {
|
||||
// not well-typed since @.* is a non-singular query
|
||||
let mut reader = Reader::new("length(@.*)");
|
||||
assert_eq!(
|
||||
try_value_type_function(&mut reader).unwrap_err(),
|
||||
ParseError::new(Pos::new(1, 9), ParseErrorKind::Expecting(")".to_string()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_count_function() {
|
||||
let mut reader = Reader::new("count(@.*.author)");
|
||||
assert_eq!(
|
||||
try_value_type_function(&mut reader).unwrap().unwrap(),
|
||||
ValueTypeFunction::Count(NodesTypeArgument::FilterQuery(Query::RelativeQuery(
|
||||
RelativeQuery::new(vec![
|
||||
Segment::Child(ChildSegment::new(vec![Selector::Wildcard(
|
||||
WildcardSelector
|
||||
)])),
|
||||
Segment::Child(ChildSegment::new(vec![Selector::Name(NameSelector::new(
|
||||
"author".to_string()
|
||||
)),]))
|
||||
])
|
||||
)))
|
||||
);
|
||||
assert_eq!(reader.cursor().index, CharPos(17));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_count_function_error() {
|
||||
// not well-typed since 1 is not a query or function expression
|
||||
let mut reader = Reader::new("count(1)");
|
||||
assert_eq!(
|
||||
try_value_type_function(&mut reader).unwrap_err(),
|
||||
ParseError::new(
|
||||
Pos::new(1, 7),
|
||||
ParseErrorKind::Expecting("a NodesType argument".to_string())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_match_function() {
|
||||
let mut reader = Reader::new("match(@.date, \"1974-05-01\")");
|
||||
assert_eq!(
|
||||
try_logical_type_function(&mut reader).unwrap().unwrap(),
|
||||
LogicalTypeFunction::Match(
|
||||
ValueTypeArgument::SingularQuery(SingularQuery::Relative(
|
||||
RelativeSingularQuery::new(vec![SingularQuerySegment::Name(
|
||||
NameSelector::new("date".to_string())
|
||||
)])
|
||||
)),
|
||||
ValueTypeArgument::Literal(Literal::String("1974-05-01".to_string()))
|
||||
)
|
||||
);
|
||||
assert_eq!(reader.cursor().index, CharPos(27));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_search_function() {
|
||||
let mut reader = Reader::new("search(@.author, \"[BR]ob\")");
|
||||
assert_eq!(
|
||||
try_logical_type_function(&mut reader).unwrap().unwrap(),
|
||||
LogicalTypeFunction::Search(
|
||||
ValueTypeArgument::SingularQuery(SingularQuery::Relative(
|
||||
RelativeSingularQuery::new(vec![SingularQuerySegment::Name(
|
||||
NameSelector::new("author".to_string())
|
||||
)])
|
||||
)),
|
||||
ValueTypeArgument::Literal(Literal::String("[BR]ob".to_string()))
|
||||
)
|
||||
);
|
||||
assert_eq!(reader.cursor().index, CharPos(26));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_value_function() {
|
||||
let mut reader = Reader::new("value(@..color)");
|
||||
assert_eq!(
|
||||
try_value_type_function(&mut reader).unwrap().unwrap(),
|
||||
ValueTypeFunction::Value(NodesTypeArgument::FilterQuery(Query::RelativeQuery(
|
||||
RelativeQuery::new(vec![Segment::Descendant(DescendantSegment::new(vec![
|
||||
Selector::Name(NameSelector::new("color".to_string()))
|
||||
]))])
|
||||
)))
|
||||
);
|
||||
assert_eq!(reader.cursor().index, CharPos(15));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 mod argument;
|
||||
pub mod functions;
|
||||
|
|
@ -49,6 +49,21 @@ pub fn parse(reader: &mut Reader) -> ParseResult<Literal> {
|
|||
}
|
||||
}
|
||||
|
||||
#[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> {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
mod comparison;
|
||||
mod error;
|
||||
mod expr;
|
||||
mod function;
|
||||
mod literal;
|
||||
mod primitives;
|
||||
mod query;
|
||||
|
|
|
|||
|
|
@ -273,7 +273,7 @@ fn load_testcases() -> Vec<TestCase> {
|
|||
fn run() {
|
||||
let testcases = load_testcases();
|
||||
// TODO: Remove Limit when spec is fully implemented
|
||||
let testcases = testcases.iter().take(190);
|
||||
let testcases = testcases.iter().take(200);
|
||||
let count_total = testcases.len();
|
||||
let testcases = testcases.filter(|tc| !IGNORED_TESTS.contains(&tc.name.as_str()));
|
||||
let count_ignored = count_total - testcases.clone().count();
|
||||
|
|
|
|||
Loading…
Reference in New Issue