mirror of
https://github.com/Orange-OpenSource/hurl.git
synced 2024-11-11 14:11:26 +03:00
Split jsonpath eval module into query/selector
This commit is contained in:
parent
479e9e97cd
commit
fc9e8a97f6
27
packages/hurl/src/jsonpath/eval/mod.rs
Normal file
27
packages/hurl/src/jsonpath/eval/mod.rs
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Hurl (https://hurl.dev)
|
||||
* Copyright (C) 2023 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 selector;
|
||||
pub mod query;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum JsonpathResult {
|
||||
SingleEntry(serde_json::Value), // returned by a "definite" path
|
||||
Collection(Vec<serde_json::Value>), // returned by a "indefinite" path
|
||||
}
|
||||
|
199
packages/hurl/src/jsonpath/eval/query.rs
Normal file
199
packages/hurl/src/jsonpath/eval/query.rs
Normal file
@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Hurl (https://hurl.dev)
|
||||
* Copyright (C) 2023 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::jsonpath::ast::Query;
|
||||
use crate::jsonpath::JsonpathResult;
|
||||
|
||||
impl Query {
|
||||
pub fn eval(&self, value: &serde_json::Value) -> Option<JsonpathResult> {
|
||||
let mut result = JsonpathResult::SingleEntry(value.clone());
|
||||
for selector in &self.selectors {
|
||||
match result.clone() {
|
||||
JsonpathResult::SingleEntry(value) => {
|
||||
result = selector.eval(&value)?;
|
||||
}
|
||||
JsonpathResult::Collection(values) => {
|
||||
let mut elements = vec![];
|
||||
for value in values {
|
||||
match selector.eval(&value)? {
|
||||
JsonpathResult::SingleEntry(new_value) => {
|
||||
elements.push(new_value);
|
||||
}
|
||||
JsonpathResult::Collection(mut new_values) => {
|
||||
elements.append(&mut new_values);
|
||||
}
|
||||
}
|
||||
result = JsonpathResult::Collection(elements.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(result)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::jsonpath::ast::{Number, Predicate, PredicateFunc, Query, Selector};
|
||||
use crate::jsonpath::JsonpathResult;
|
||||
use serde_json::json;
|
||||
|
||||
|
||||
|
||||
pub fn json_root() -> serde_json::Value {
|
||||
json!({ "store": json_store() })
|
||||
}
|
||||
|
||||
pub fn json_store() -> serde_json::Value {
|
||||
json!({
|
||||
"book": json_books(),
|
||||
"bicycle": [
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
pub fn json_books() -> serde_json::Value {
|
||||
json!([
|
||||
json_first_book(),
|
||||
json_second_book(),
|
||||
json_third_book(),
|
||||
json_fourth_book()
|
||||
])
|
||||
}
|
||||
|
||||
pub fn json_first_book() -> serde_json::Value {
|
||||
json!({
|
||||
"category": "reference",
|
||||
"author": "Nigel Rees",
|
||||
"title": "Sayings of the Century",
|
||||
"price": 8.95
|
||||
})
|
||||
}
|
||||
|
||||
pub fn json_second_book() -> serde_json::Value {
|
||||
json!({
|
||||
"category": "fiction",
|
||||
"author": "Evelyn Waugh",
|
||||
"title": "Sword of Honour",
|
||||
"price": 12.99
|
||||
})
|
||||
}
|
||||
|
||||
pub fn json_third_book() -> serde_json::Value {
|
||||
json!({
|
||||
"category": "fiction",
|
||||
"author": "Herman Melville",
|
||||
"title": "Moby Dick",
|
||||
"isbn": "0-553-21311-3",
|
||||
"price": 8.99
|
||||
})
|
||||
}
|
||||
|
||||
pub fn json_fourth_book() -> serde_json::Value {
|
||||
json!({
|
||||
"category": "fiction",
|
||||
"author": "J. R. R. Tolkien",
|
||||
"title": "The Lord of the Rings",
|
||||
"isbn": "0-395-19395-8",
|
||||
"price": 22.99
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_query() {
|
||||
assert_eq!(
|
||||
Query { selectors: vec![] }.eval(&json_root()).unwrap(),
|
||||
JsonpathResult::SingleEntry(json_root())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Query {
|
||||
selectors: vec![Selector::NameChild("store".to_string())]
|
||||
}
|
||||
.eval(&json_root())
|
||||
.unwrap(),
|
||||
JsonpathResult::SingleEntry(json_store())
|
||||
);
|
||||
|
||||
let query = Query {
|
||||
selectors: vec![
|
||||
Selector::NameChild("store".to_string()),
|
||||
Selector::NameChild("book".to_string()),
|
||||
Selector::ArrayIndex(0),
|
||||
Selector::NameChild("title".to_string()),
|
||||
],
|
||||
};
|
||||
assert_eq!(
|
||||
query.eval(&json_root()).unwrap(),
|
||||
JsonpathResult::SingleEntry(json!("Sayings of the Century"))
|
||||
);
|
||||
|
||||
// $.store.book[?(@.price<10)].title
|
||||
let query = Query {
|
||||
selectors: vec![
|
||||
Selector::NameChild("store".to_string()),
|
||||
Selector::NameChild("book".to_string()),
|
||||
Selector::Filter(Predicate {
|
||||
key: vec!["price".to_string()],
|
||||
func: PredicateFunc::LessThan(Number {
|
||||
int: 10,
|
||||
decimal: 0,
|
||||
}),
|
||||
}),
|
||||
Selector::NameChild("title".to_string()),
|
||||
],
|
||||
};
|
||||
assert_eq!(
|
||||
query.eval(&json_root()).unwrap(),
|
||||
JsonpathResult::Collection(vec![json!("Sayings of the Century"), json!("Moby Dick")])
|
||||
);
|
||||
|
||||
// $..author
|
||||
let query = Query {
|
||||
selectors: vec![Selector::RecursiveKey("author".to_string())],
|
||||
};
|
||||
assert_eq!(
|
||||
query.eval(&json_root()).unwrap(),
|
||||
JsonpathResult::Collection(vec![
|
||||
json!("Nigel Rees"),
|
||||
json!("Evelyn Waugh"),
|
||||
json!("Herman Melville"),
|
||||
json!("J. R. R. Tolkien")
|
||||
])
|
||||
);
|
||||
|
||||
// $.store.book[*].author
|
||||
let query = Query {
|
||||
selectors: vec![
|
||||
Selector::NameChild("store".to_string()),
|
||||
Selector::NameChild("book".to_string()),
|
||||
Selector::ArrayWildcard {},
|
||||
Selector::NameChild("author".to_string()),
|
||||
],
|
||||
};
|
||||
assert_eq!(
|
||||
query.eval(&json_root()).unwrap(),
|
||||
JsonpathResult::Collection(vec![
|
||||
json!("Nigel Rees"),
|
||||
json!("Evelyn Waugh"),
|
||||
json!("Herman Melville"),
|
||||
json!("J. R. R. Tolkien")
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
@ -16,44 +16,10 @@
|
||||
*
|
||||
*/
|
||||
|
||||
use crate::jsonpath::ast::{Predicate, PredicateFunc, Selector, Slice};
|
||||
use crate::jsonpath::JsonpathResult;
|
||||
use float_cmp::approx_eq;
|
||||
|
||||
use super::ast::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum JsonpathResult {
|
||||
SingleEntry(serde_json::Value), // returned by a "definite" path
|
||||
Collection(Vec<serde_json::Value>), // returned by a "indefinite" path
|
||||
}
|
||||
|
||||
impl Query {
|
||||
pub fn eval(&self, value: &serde_json::Value) -> Option<JsonpathResult> {
|
||||
let mut result = JsonpathResult::SingleEntry(value.clone());
|
||||
for selector in &self.selectors {
|
||||
match result.clone() {
|
||||
JsonpathResult::SingleEntry(value) => {
|
||||
result = selector.eval(&value)?;
|
||||
}
|
||||
JsonpathResult::Collection(values) => {
|
||||
let mut elements = vec![];
|
||||
for value in values {
|
||||
match selector.eval(&value)? {
|
||||
JsonpathResult::SingleEntry(new_value) => {
|
||||
elements.push(new_value);
|
||||
}
|
||||
JsonpathResult::Collection(mut new_values) => {
|
||||
elements.append(&mut new_values);
|
||||
}
|
||||
}
|
||||
result = JsonpathResult::Collection(elements.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl Selector {
|
||||
pub fn eval(&self, root: &serde_json::Value) -> Option<JsonpathResult> {
|
||||
match self {
|
||||
@ -234,6 +200,7 @@ fn extract_value(obj: serde_json::Value, key_path: Vec<String>) -> Option<serde_
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::jsonpath::ast::Number;
|
||||
use serde_json::json;
|
||||
|
||||
use super::*;
|
||||
@ -297,89 +264,6 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_query() {
|
||||
assert_eq!(
|
||||
Query { selectors: vec![] }.eval(&json_root()).unwrap(),
|
||||
JsonpathResult::SingleEntry(json_root())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Query {
|
||||
selectors: vec![Selector::NameChild("store".to_string())]
|
||||
}
|
||||
.eval(&json_root())
|
||||
.unwrap(),
|
||||
JsonpathResult::SingleEntry(json_store())
|
||||
);
|
||||
|
||||
let query = Query {
|
||||
selectors: vec![
|
||||
Selector::NameChild("store".to_string()),
|
||||
Selector::NameChild("book".to_string()),
|
||||
Selector::ArrayIndex(0),
|
||||
Selector::NameChild("title".to_string()),
|
||||
],
|
||||
};
|
||||
assert_eq!(
|
||||
query.eval(&json_root()).unwrap(),
|
||||
JsonpathResult::SingleEntry(json!("Sayings of the Century"))
|
||||
);
|
||||
|
||||
// $.store.book[?(@.price<10)].title
|
||||
let query = Query {
|
||||
selectors: vec![
|
||||
Selector::NameChild("store".to_string()),
|
||||
Selector::NameChild("book".to_string()),
|
||||
Selector::Filter(Predicate {
|
||||
key: vec!["price".to_string()],
|
||||
func: PredicateFunc::LessThan(Number {
|
||||
int: 10,
|
||||
decimal: 0,
|
||||
}),
|
||||
}),
|
||||
Selector::NameChild("title".to_string()),
|
||||
],
|
||||
};
|
||||
assert_eq!(
|
||||
query.eval(&json_root()).unwrap(),
|
||||
JsonpathResult::Collection(vec![json!("Sayings of the Century"), json!("Moby Dick")])
|
||||
);
|
||||
|
||||
// $..author
|
||||
let query = Query {
|
||||
selectors: vec![Selector::RecursiveKey("author".to_string())],
|
||||
};
|
||||
assert_eq!(
|
||||
query.eval(&json_root()).unwrap(),
|
||||
JsonpathResult::Collection(vec![
|
||||
json!("Nigel Rees"),
|
||||
json!("Evelyn Waugh"),
|
||||
json!("Herman Melville"),
|
||||
json!("J. R. R. Tolkien")
|
||||
])
|
||||
);
|
||||
|
||||
// $.store.book[*].author
|
||||
let query = Query {
|
||||
selectors: vec![
|
||||
Selector::NameChild("store".to_string()),
|
||||
Selector::NameChild("book".to_string()),
|
||||
Selector::ArrayWildcard {},
|
||||
Selector::NameChild("author".to_string()),
|
||||
],
|
||||
};
|
||||
assert_eq!(
|
||||
query.eval(&json_root()).unwrap(),
|
||||
JsonpathResult::Collection(vec![
|
||||
json!("Nigel Rees"),
|
||||
json!("Evelyn Waugh"),
|
||||
json!("Herman Melville"),
|
||||
json!("J. R. R. Tolkien")
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_selector_name_child() {
|
||||
assert_eq!(
|
@ -25,7 +25,7 @@ use std::fs::read_to_string;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::jsonpath;
|
||||
use crate::jsonpath::eval::JsonpathResult;
|
||||
use crate::jsonpath::JsonpathResult;
|
||||
|
||||
fn bookstore_value() -> serde_json::Value {
|
||||
let s = read_to_string("tests/bookstore.json").expect("could not read string from file");
|
||||
|
Loading…
Reference in New Issue
Block a user