mirror of
https://github.com/Orange-OpenSource/hurl.git
synced 2024-12-25 12:05:32 +03:00
Improve jsonpath support
This commit is contained in:
parent
dab4006346
commit
9093325d28
@ -26,13 +26,24 @@ pub struct Query {
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Selector {
|
||||
Wildcard,
|
||||
NameChild(String),
|
||||
ArrayIndex(usize),
|
||||
ArrayIndex(Vec<usize>), // one or more indexes (separated by comma)
|
||||
ArraySlice(Slice),
|
||||
ArrayWildcard,
|
||||
Filter(Predicate),
|
||||
RecursiveWildcard,
|
||||
RecursiveKey(String),
|
||||
}
|
||||
|
||||
// For the time-being
|
||||
// use simple slice start:end (without the step)
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Slice {
|
||||
pub start: Option<i64>,
|
||||
pub end: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Predicate {
|
||||
pub key: String,
|
||||
|
@ -37,20 +37,50 @@ impl Query {
|
||||
impl Selector {
|
||||
pub fn eval(self, root: serde_json::Value) -> JsonpathResult {
|
||||
match self {
|
||||
Selector::NameChild(field) => match root.get(field) {
|
||||
None => vec![],
|
||||
Some(value) => vec![value.clone()],
|
||||
},
|
||||
Selector::ArrayIndex(index) => match root.get(index) {
|
||||
None => vec![],
|
||||
Some(value) => vec![value.clone()],
|
||||
},
|
||||
Selector::ArrayWildcard {} => {
|
||||
Selector::Wildcard | Selector::ArrayWildcard => {
|
||||
let mut elements = vec![];
|
||||
if let serde_json::Value::Array(values) = root {
|
||||
for value in values {
|
||||
elements.push(value);
|
||||
}
|
||||
} else if let serde_json::Value::Object(key_values) = root {
|
||||
for value in key_values.values() {
|
||||
elements.push(value.clone());
|
||||
}
|
||||
}
|
||||
elements
|
||||
}
|
||||
Selector::NameChild(field) => match root.get(field) {
|
||||
None => vec![],
|
||||
Some(value) => vec![value.clone()],
|
||||
},
|
||||
Selector::ArrayIndex(indexes) => {
|
||||
let mut values = vec![];
|
||||
for index in indexes {
|
||||
if let Some(value) = root.get(index) {
|
||||
values.push(value.clone())
|
||||
}
|
||||
}
|
||||
values
|
||||
}
|
||||
Selector::ArraySlice(Slice { start, end }) => {
|
||||
let mut elements = vec![];
|
||||
if let serde_json::Value::Array(values) = root {
|
||||
for (i, value) in values.iter().enumerate() {
|
||||
if let Some(n) = start {
|
||||
let n = if n < 0 { values.len() as i64 + n } else { n };
|
||||
if (i as i64) < n {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if let Some(n) = end {
|
||||
let n = if n < 0 { values.len() as i64 + n } else { n };
|
||||
if (i as i64) >= n {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
elements.push(value.clone());
|
||||
}
|
||||
}
|
||||
elements
|
||||
}
|
||||
@ -86,6 +116,30 @@ impl Selector {
|
||||
}
|
||||
elements
|
||||
}
|
||||
Selector::RecursiveWildcard => {
|
||||
let mut elements = vec![];
|
||||
match root {
|
||||
serde_json::Value::Object(map) => {
|
||||
for elem in map.values() {
|
||||
elements.push(elem.clone());
|
||||
for element in Selector::RecursiveWildcard.eval(elem.clone()) {
|
||||
elements.push(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
serde_json::Value::Array(values) => {
|
||||
for elem in values {
|
||||
elements.push(elem.clone());
|
||||
for element in Selector::RecursiveWildcard.eval(elem.clone()) {
|
||||
elements.push(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
elements
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -95,6 +149,7 @@ impl Predicate {
|
||||
match elem {
|
||||
serde_json::Value::Object(ref obj) => {
|
||||
match (obj.get(self.key.as_str()), self.func.clone()) {
|
||||
(Some(_), PredicateFunc::KeyExist {}) => true,
|
||||
(Some(serde_json::Value::Number(v)), PredicateFunc::Equal(ref num)) => {
|
||||
approx_eq!(f64, v.as_f64().unwrap(), num.to_f64(), ulps = 2)
|
||||
} //v.as_f64().unwrap() == num.to_f64(),
|
||||
@ -137,7 +192,6 @@ mod tests {
|
||||
json!({
|
||||
"book": json_books(),
|
||||
"bicycle": [
|
||||
|
||||
]
|
||||
})
|
||||
}
|
||||
@ -153,23 +207,25 @@ mod tests {
|
||||
|
||||
pub fn json_first_book() -> serde_json::Value {
|
||||
json!({
|
||||
"category": "reference",
|
||||
"author": "Nigel Rees",
|
||||
"title": "Sayings of the Century",
|
||||
"price": 8.95
|
||||
})
|
||||
"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
|
||||
json!({
|
||||
"category": "fiction",
|
||||
"author": "Evelyn Waugh",
|
||||
"title": "Sword of Honour",
|
||||
"price": 12.99
|
||||
})
|
||||
}
|
||||
|
||||
pub fn json_third_book() -> serde_json::Value {
|
||||
json!( { "category": "fiction",
|
||||
json!({
|
||||
"category": "fiction",
|
||||
"author": "Herman Melville",
|
||||
"title": "Moby Dick",
|
||||
"isbn": "0-553-21311-3",
|
||||
@ -178,11 +234,12 @@ mod tests {
|
||||
}
|
||||
|
||||
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
|
||||
json!({
|
||||
"category": "fiction",
|
||||
"author": "J. R. R. Tolkien",
|
||||
"title": "The Lord of the Rings",
|
||||
"isbn": "0-395-19395-8",
|
||||
"price": 22.99
|
||||
})
|
||||
}
|
||||
|
||||
@ -205,7 +262,7 @@ mod tests {
|
||||
selectors: vec![
|
||||
Selector::NameChild("store".to_string()),
|
||||
Selector::NameChild("book".to_string()),
|
||||
Selector::ArrayIndex(0),
|
||||
Selector::ArrayIndex(vec![0]),
|
||||
Selector::NameChild("title".to_string()),
|
||||
],
|
||||
};
|
||||
@ -271,9 +328,13 @@ mod tests {
|
||||
#[test]
|
||||
pub fn test_selector_array_index() {
|
||||
assert_eq!(
|
||||
Selector::ArrayIndex(0).eval(json_books()),
|
||||
Selector::ArrayIndex(vec![0]).eval(json_books()),
|
||||
vec![json_first_book()]
|
||||
);
|
||||
assert_eq!(
|
||||
Selector::ArrayIndex(vec![1, 2]).eval(json_books()),
|
||||
vec![json_second_book(), json_third_book()]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -290,37 +351,19 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_bookstore() {
|
||||
// assert_eq!(Selector::NameChild("store".to_string()).eval(json_root()),
|
||||
// vec![json_store()]
|
||||
// );
|
||||
// assert_eq!(Selector::NameChild("book".to_string()).eval(json_store()),
|
||||
// vec![json_books()]
|
||||
// );
|
||||
//
|
||||
// assert_eq!(Selector::ArrayIndex(0).eval(json_books()),
|
||||
// vec![json_first_book()]
|
||||
// );
|
||||
//
|
||||
// assert_eq!(Selector::NameChild("title".to_string()).eval(json_first_book()),
|
||||
// vec![json!("Sayings of the Century")]
|
||||
// );
|
||||
//
|
||||
// assert_eq!(Selector::ArrayIndex(0).eval(json_books()),
|
||||
// vec![json_first_book()]
|
||||
// );
|
||||
// assert_eq!(Selector::Filter(Predicate::KeyEqualStringValue("category".to_string(), "reference".to_string())).eval(json_books()),
|
||||
// vec![json_first_book()]
|
||||
// );
|
||||
//
|
||||
// assert_eq!(Selector::Filter(Predicate::KeySmallerThanIntValue("price".to_string(), 10)).eval(json_books()),
|
||||
// vec![json_first_book(), json_third_book()]
|
||||
// );
|
||||
//
|
||||
// assert_eq!(Selector::RecursiveKey("book".to_string()).eval(json_root()),
|
||||
// vec![json_books()]
|
||||
// );
|
||||
pub fn test_selector_array_slice() {
|
||||
assert_eq!(
|
||||
Selector::ArraySlice(Slice {
|
||||
start: None,
|
||||
end: Some(2)
|
||||
})
|
||||
.eval(json_books()),
|
||||
vec![json_first_book(), json_second_book(),]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_recursive_key() {
|
||||
assert_eq!(
|
||||
Selector::RecursiveKey("author".to_string()).eval(json_root()),
|
||||
vec![
|
||||
@ -336,11 +379,19 @@ mod tests {
|
||||
#[test]
|
||||
pub fn test_array_index() {
|
||||
let value = json!(["first", "second", "third", "forth", "fifth"]);
|
||||
assert_eq!(Selector::ArrayIndex(2).eval(value), vec![json!("third")]);
|
||||
assert_eq!(
|
||||
Selector::ArrayIndex(vec![2]).eval(value),
|
||||
vec![json!("third")]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_predicate() {
|
||||
assert!(Predicate {
|
||||
key: "key".to_string(),
|
||||
func: PredicateFunc::KeyExist {},
|
||||
}
|
||||
.eval(json!({"key": "value"})));
|
||||
assert!(Predicate {
|
||||
key: "key".to_string(),
|
||||
func: PredicateFunc::EqualString("value".to_string()),
|
||||
|
@ -16,6 +16,34 @@
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* jsonpath specs
|
||||
* There is no proper specifications for jsonpath.
|
||||
* The defacto one is still https://goessner.net/articles/JsonPath/
|
||||
* Hurl will try to follow this one as closely as possible
|
||||
*
|
||||
* There are a few edge cases for which several implementations differ
|
||||
* The online app https://jsonpath.herokuapp.com/ might be used to test them
|
||||
* We describe below the behaviour that we expect in Hurl.
|
||||
*
|
||||
* Specify a field key in a subscript operator: $['name']
|
||||
* The key must be enclosed within single quotes only.
|
||||
* The following expressions will not be valid: $["name"] and $[name]
|
||||
*
|
||||
* Accessing a key containing a single quote must be escape: $['\'']
|
||||
* Key with unicode are supported: $['✈']
|
||||
*
|
||||
* Any character within these quote won't have a specific meaning:
|
||||
* $['*'] selects the element with key '*'. It is different from $[*] which selects all elements
|
||||
* $['.'] selects the element with key '.'.
|
||||
*
|
||||
* The dot notation is usually more readable the the bracket notation
|
||||
* but it is more limited in terms of allowed characters
|
||||
* The following characters are allowed:
|
||||
* alphanumeric
|
||||
* _ (underscore)
|
||||
*/
|
||||
|
||||
pub use self::parser::parse;
|
||||
|
||||
mod ast;
|
||||
|
@ -46,12 +46,15 @@ fn query(reader: &mut Reader) -> ParseResult<Query> {
|
||||
fn selector(reader: &mut Reader) -> ParseResult<Selector> {
|
||||
choice(
|
||||
vec![
|
||||
selector_filter,
|
||||
selector_wildcard,
|
||||
selector_recursive_wildcard,
|
||||
selector_recursive_key,
|
||||
selector_array_index,
|
||||
selector_array_wildcard,
|
||||
selector_array_slice,
|
||||
selector_object_key_bracket,
|
||||
selector_object_key,
|
||||
selector_filter,
|
||||
],
|
||||
reader,
|
||||
)
|
||||
@ -59,6 +62,7 @@ fn selector(reader: &mut Reader) -> ParseResult<Selector> {
|
||||
|
||||
fn selector_array_index(reader: &mut Reader) -> Result<Selector, Error> {
|
||||
try_left_bracket(reader)?;
|
||||
let mut indexes = vec![];
|
||||
let i = match natural(reader) {
|
||||
Err(e) => {
|
||||
return Err(Error {
|
||||
@ -69,8 +73,28 @@ fn selector_array_index(reader: &mut Reader) -> Result<Selector, Error> {
|
||||
}
|
||||
Ok(v) => v,
|
||||
};
|
||||
indexes.push(i);
|
||||
loop {
|
||||
let state = reader.state.clone();
|
||||
if try_literal(",", reader).is_ok() {
|
||||
let i = match natural(reader) {
|
||||
Err(e) => {
|
||||
return Err(Error {
|
||||
pos: e.pos,
|
||||
recoverable: true,
|
||||
inner: e.inner,
|
||||
})
|
||||
}
|
||||
Ok(v) => v,
|
||||
};
|
||||
indexes.push(i);
|
||||
} else {
|
||||
reader.state = state;
|
||||
break;
|
||||
}
|
||||
}
|
||||
literal("]", reader)?;
|
||||
Ok(Selector::ArrayIndex(i))
|
||||
Ok(Selector::ArrayIndex(indexes))
|
||||
}
|
||||
|
||||
fn selector_array_wildcard(reader: &mut Reader) -> Result<Selector, Error> {
|
||||
@ -80,6 +104,37 @@ fn selector_array_wildcard(reader: &mut Reader) -> Result<Selector, Error> {
|
||||
Ok(Selector::ArrayWildcard {})
|
||||
}
|
||||
|
||||
fn selector_array_slice(reader: &mut Reader) -> Result<Selector, Error> {
|
||||
try_left_bracket(reader)?;
|
||||
let state = reader.state.clone();
|
||||
let start = match integer(reader) {
|
||||
Err(_) => {
|
||||
reader.state = state.clone();
|
||||
None
|
||||
}
|
||||
Ok(v) => Some(v),
|
||||
};
|
||||
if try_literal(":", reader).is_err() {
|
||||
return Err(Error {
|
||||
pos: state.pos,
|
||||
recoverable: true,
|
||||
inner: ParseError::Expecting {
|
||||
value: ":".to_string(),
|
||||
},
|
||||
});
|
||||
};
|
||||
let state = reader.state.clone();
|
||||
let end = match integer(reader) {
|
||||
Err(_) => {
|
||||
reader.state = state;
|
||||
None
|
||||
}
|
||||
Ok(v) => Some(v),
|
||||
};
|
||||
literal("]", reader)?;
|
||||
Ok(Selector::ArraySlice(Slice { start, end }))
|
||||
}
|
||||
|
||||
fn selector_filter(reader: &mut Reader) -> Result<Selector, Error> {
|
||||
try_literal("[?(", reader)?;
|
||||
let pred = predicate(reader)?;
|
||||
@ -119,7 +174,7 @@ fn selector_object_key(reader: &mut Reader) -> Result<Selector, Error> {
|
||||
});
|
||||
};
|
||||
|
||||
let s = reader.read_while(|c| c.is_alphanumeric());
|
||||
let s = reader.read_while(|c| c.is_alphanumeric() || *c == '_');
|
||||
if s.is_empty() {
|
||||
return Err(Error {
|
||||
pos: reader.state.pos.clone(),
|
||||
@ -136,6 +191,16 @@ fn selector_object_key(reader: &mut Reader) -> Result<Selector, Error> {
|
||||
Ok(Selector::NameChild(s))
|
||||
}
|
||||
|
||||
fn selector_wildcard(reader: &mut Reader) -> Result<Selector, Error> {
|
||||
try_literal(".*", reader)?;
|
||||
Ok(Selector::Wildcard {})
|
||||
}
|
||||
|
||||
fn selector_recursive_wildcard(reader: &mut Reader) -> Result<Selector, Error> {
|
||||
try_literal("..*", reader)?;
|
||||
Ok(Selector::RecursiveWildcard {})
|
||||
}
|
||||
|
||||
fn selector_recursive_key(reader: &mut Reader) -> Result<Selector, Error> {
|
||||
try_literal("..", reader)?;
|
||||
let k = key_name(reader)?;
|
||||
@ -163,7 +228,14 @@ fn predicate(reader: &mut Reader) -> ParseResult<Predicate> {
|
||||
// @.key>=value GreaterThanOrEqual(Key, Value)
|
||||
literal("@.", reader)?; // assume key value for the time being
|
||||
let key = key_name(reader)?;
|
||||
let func = predicate_func(reader)?;
|
||||
let state = reader.state.clone();
|
||||
let func = match predicate_func(reader) {
|
||||
Ok(f) => f,
|
||||
Err(_) => {
|
||||
reader.state = state;
|
||||
PredicateFunc::KeyExist {}
|
||||
}
|
||||
};
|
||||
Ok(Predicate { key, func })
|
||||
}
|
||||
|
||||
@ -248,7 +320,7 @@ mod tests {
|
||||
#[test]
|
||||
pub fn test_query() {
|
||||
let expected_query = Query {
|
||||
selectors: vec![Selector::ArrayIndex(2)],
|
||||
selectors: vec![Selector::ArrayIndex(vec![2])],
|
||||
};
|
||||
assert_eq!(query(&mut Reader::init("$[2]")).unwrap(), expected_query);
|
||||
|
||||
@ -265,7 +337,7 @@ mod tests {
|
||||
selectors: vec![
|
||||
Selector::NameChild("store".to_string()),
|
||||
Selector::NameChild("book".to_string()),
|
||||
Selector::ArrayIndex(0),
|
||||
Selector::ArrayIndex(vec![0]),
|
||||
Selector::NameChild("title".to_string()),
|
||||
],
|
||||
};
|
||||
@ -281,7 +353,7 @@ mod tests {
|
||||
let expected_query = Query {
|
||||
selectors: vec![
|
||||
Selector::RecursiveKey("book".to_string()),
|
||||
Selector::ArrayIndex(2),
|
||||
Selector::ArrayIndex(vec![2]),
|
||||
],
|
||||
};
|
||||
assert_eq!(
|
||||
@ -301,6 +373,17 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
pub fn test_selector_filter() {
|
||||
// Filter exist value
|
||||
let mut reader = Reader::init("[?(@.isbn)]");
|
||||
assert_eq!(
|
||||
selector(&mut reader).unwrap(),
|
||||
Selector::Filter(Predicate {
|
||||
key: "isbn".to_string(),
|
||||
func: PredicateFunc::KeyExist {},
|
||||
})
|
||||
);
|
||||
assert_eq!(reader.state.cursor, 11);
|
||||
|
||||
// Filter equal on string with single quotes
|
||||
let mut reader = Reader::init("[?(@.key=='value')]");
|
||||
assert_eq!(
|
||||
@ -311,6 +394,19 @@ mod tests {
|
||||
})
|
||||
);
|
||||
assert_eq!(reader.state.cursor, 19);
|
||||
|
||||
let mut reader = Reader::init("[?(@.price<10)]");
|
||||
assert_eq!(
|
||||
selector(&mut reader).unwrap(),
|
||||
Selector::Filter(Predicate {
|
||||
key: "price".to_string(),
|
||||
func: PredicateFunc::LessThan(Number {
|
||||
int: 10,
|
||||
decimal: 0
|
||||
}),
|
||||
})
|
||||
);
|
||||
assert_eq!(reader.state.cursor, 15);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -326,13 +422,26 @@ mod tests {
|
||||
#[test]
|
||||
pub fn test_selector_array_index() {
|
||||
let mut reader = Reader::init("[2]");
|
||||
assert_eq!(selector(&mut reader).unwrap(), Selector::ArrayIndex(2));
|
||||
assert_eq!(
|
||||
selector(&mut reader).unwrap(),
|
||||
Selector::ArrayIndex(vec![2])
|
||||
);
|
||||
assert_eq!(reader.state.cursor, 3);
|
||||
|
||||
let mut reader = Reader::init("[0,1]");
|
||||
assert_eq!(
|
||||
selector(&mut reader).unwrap(),
|
||||
Selector::ArrayIndex(vec![0, 1])
|
||||
);
|
||||
assert_eq!(reader.state.cursor, 5);
|
||||
|
||||
// you don't need to keep the exact string
|
||||
// this is not part of the AST
|
||||
let mut reader = Reader::init(".[2]");
|
||||
assert_eq!(selector(&mut reader).unwrap(), Selector::ArrayIndex(2));
|
||||
assert_eq!(
|
||||
selector(&mut reader).unwrap(),
|
||||
Selector::ArrayIndex(vec![2])
|
||||
);
|
||||
assert_eq!(reader.state.cursor, 4);
|
||||
}
|
||||
|
||||
@ -349,6 +458,29 @@ mod tests {
|
||||
assert_eq!(reader.state.cursor, 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_selector_array_slice() {
|
||||
let mut reader = Reader::init("[-1:]");
|
||||
assert_eq!(
|
||||
selector(&mut reader).unwrap(),
|
||||
Selector::ArraySlice(Slice {
|
||||
start: Some(-1),
|
||||
end: None
|
||||
})
|
||||
);
|
||||
assert_eq!(reader.state.cursor, 5);
|
||||
|
||||
let mut reader = Reader::init("[:2]");
|
||||
assert_eq!(
|
||||
selector(&mut reader).unwrap(),
|
||||
Selector::ArraySlice(Slice {
|
||||
start: None,
|
||||
end: Some(2)
|
||||
})
|
||||
);
|
||||
assert_eq!(reader.state.cursor, 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_key_bracket_selector() {
|
||||
let mut reader = Reader::init("['key']");
|
||||
@ -392,6 +524,15 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
pub fn test_predicate() {
|
||||
// Key exists
|
||||
assert_eq!(
|
||||
predicate(&mut Reader::init("@.isbn")).unwrap(),
|
||||
Predicate {
|
||||
key: "isbn".to_string(),
|
||||
func: PredicateFunc::KeyExist {},
|
||||
}
|
||||
);
|
||||
|
||||
// Filter equal on string with single quotes
|
||||
assert_eq!(
|
||||
predicate(&mut Reader::init("@.key=='value'")).unwrap(),
|
||||
@ -409,6 +550,18 @@ mod tests {
|
||||
func: PredicateFunc::Equal(Number { int: 1, decimal: 0 }),
|
||||
}
|
||||
);
|
||||
|
||||
// Filter less than int
|
||||
assert_eq!(
|
||||
predicate(&mut Reader::init("@.price<10")).unwrap(),
|
||||
Predicate {
|
||||
key: "price".to_string(),
|
||||
func: PredicateFunc::LessThan(Number {
|
||||
int: 10,
|
||||
decimal: 0
|
||||
}),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -99,8 +99,41 @@ pub fn number(reader: &mut Reader) -> ParseResult<'static, Number> {
|
||||
|
||||
pub fn string_value(reader: &mut Reader) -> Result<String, Error> {
|
||||
try_literal("'", reader)?;
|
||||
let s = reader.read_while(|c| *c != '\'');
|
||||
literal("'", reader)?;
|
||||
let mut s = "".to_string();
|
||||
loop {
|
||||
match reader.read() {
|
||||
None => {
|
||||
return Err(Error {
|
||||
pos: reader.state.pos.clone(),
|
||||
recoverable: false,
|
||||
inner: ParseError::Expecting {
|
||||
value: String::from("'"),
|
||||
},
|
||||
})
|
||||
}
|
||||
Some('\'') => break,
|
||||
Some('\\') => {
|
||||
// only single quote can be escaped
|
||||
match reader.read() {
|
||||
Some('\'') => {
|
||||
s.push('\'');
|
||||
}
|
||||
_ => {
|
||||
return Err(Error {
|
||||
pos: reader.state.pos.clone(),
|
||||
recoverable: false,
|
||||
inner: ParseError::Expecting {
|
||||
value: String::from("'"),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(c) => {
|
||||
s.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
whitespace(reader);
|
||||
Ok(s)
|
||||
}
|
||||
@ -108,7 +141,7 @@ pub fn string_value(reader: &mut Reader) -> Result<String, Error> {
|
||||
pub fn key_name(reader: &mut Reader) -> Result<String, Error> {
|
||||
// // test python or javascript
|
||||
//// subset that can used for dot notation
|
||||
let s = reader.read_while(|c| c.is_alphabetic() || *c == '-' || *c == '_');
|
||||
let s = reader.read_while(|c| c.is_alphabetic() || *c == '_');
|
||||
whitespace(reader);
|
||||
Ok(s)
|
||||
}
|
||||
@ -377,6 +410,9 @@ mod tests {
|
||||
let mut reader = Reader::init("'hello'");
|
||||
assert_eq!(string_value(&mut reader).unwrap(), "hello".to_string());
|
||||
|
||||
let mut reader = Reader::init("'\\''");
|
||||
assert_eq!(string_value(&mut reader).unwrap(), "'".to_string());
|
||||
|
||||
let mut reader = Reader::init("1");
|
||||
let error = string_value(&mut reader).err().unwrap();
|
||||
assert_eq!(
|
||||
|
@ -17,93 +17,360 @@
|
||||
*/
|
||||
extern crate hurl;
|
||||
|
||||
use hurl::jsonpath;
|
||||
use serde_json::json;
|
||||
use std::fs::read_to_string;
|
||||
|
||||
use serde_json::json;
|
||||
fn bookstore_value() -> serde_json::Value {
|
||||
let s = read_to_string("tests/bookstore.json").expect("could not read string from file");
|
||||
serde_json::from_str(s.as_str()).expect("could not parse json file")
|
||||
}
|
||||
|
||||
use hurl::jsonpath;
|
||||
fn store_value() -> serde_json::Value {
|
||||
serde_json::from_str(
|
||||
r#"
|
||||
{
|
||||
"book": [
|
||||
{
|
||||
"category": "reference",
|
||||
"author": "Nigel Rees",
|
||||
"title": "Sayings of the Century",
|
||||
"price": 8.95
|
||||
},
|
||||
{
|
||||
"category": "fiction",
|
||||
"author": "Evelyn Waugh",
|
||||
"title": "Sword of Honour",
|
||||
"price": 12.99
|
||||
},
|
||||
{
|
||||
"category": "fiction",
|
||||
"author": "Herman Melville",
|
||||
"title": "Moby Dick",
|
||||
"isbn": "0-553-21311-3",
|
||||
"price": 8.99
|
||||
},
|
||||
{
|
||||
"category": "fiction",
|
||||
"author": "J. R. R. Tolkien",
|
||||
"title": "The Lord of the Rings",
|
||||
"isbn": "0-395-19395-8",
|
||||
"price": 22.99
|
||||
}
|
||||
],
|
||||
"bicycle": {
|
||||
"color": "red",
|
||||
"price": 19.95
|
||||
}
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn test_ok(s: &str, value: serde_json::Value) -> Vec<serde_json::Value> {
|
||||
return match jsonpath::parse(s) {
|
||||
Ok(expr) => expr.eval(value),
|
||||
Err(e) => panic!("{:?}", e),
|
||||
};
|
||||
fn book_value() -> serde_json::Value {
|
||||
serde_json::from_str(
|
||||
r#"
|
||||
[
|
||||
{
|
||||
"category": "reference",
|
||||
"author": "Nigel Rees",
|
||||
"title": "Sayings of the Century",
|
||||
"price": 8.95
|
||||
},
|
||||
{
|
||||
"category": "fiction",
|
||||
"author": "Evelyn Waugh",
|
||||
"title": "Sword of Honour",
|
||||
"price": 12.99
|
||||
},
|
||||
{
|
||||
"category": "fiction",
|
||||
"author": "Herman Melville",
|
||||
"title": "Moby Dick",
|
||||
"isbn": "0-553-21311-3",
|
||||
"price": 8.99
|
||||
},
|
||||
{
|
||||
"category": "fiction",
|
||||
"author": "J. R. R. Tolkien",
|
||||
"title": "The Lord of the Rings",
|
||||
"isbn": "0-395-19395-8",
|
||||
"price": 22.99
|
||||
}
|
||||
]
|
||||
"#,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn bicycle_value() -> serde_json::Value {
|
||||
serde_json::from_str(
|
||||
r#"
|
||||
{
|
||||
"color": "red",
|
||||
"price": 19.95
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn book0_value() -> serde_json::Value {
|
||||
json!( { "category": "reference",
|
||||
"author": "Nigel Rees",
|
||||
"title": "Sayings of the Century",
|
||||
"price": 8.95
|
||||
})
|
||||
}
|
||||
|
||||
fn book1_value() -> serde_json::Value {
|
||||
json!( { "category": "fiction",
|
||||
"author": "Evelyn Waugh",
|
||||
"title": "Sword of Honour",
|
||||
"price": 12.99
|
||||
})
|
||||
}
|
||||
|
||||
fn book2_value() -> serde_json::Value {
|
||||
json!( {
|
||||
"category": "fiction",
|
||||
"author": "Herman Melville",
|
||||
"title": "Moby Dick",
|
||||
"isbn": "0-553-21311-3",
|
||||
"price": 8.99
|
||||
})
|
||||
}
|
||||
|
||||
fn book3_value() -> 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]
|
||||
fn test_bookstore_path() {
|
||||
// examples from https://goessner.net/articles/JsonPath/
|
||||
|
||||
//the authors of all books in the store
|
||||
let expr = jsonpath::parse("$.store.book[*].author").unwrap();
|
||||
assert_eq!(
|
||||
expr.eval(bookstore_value()),
|
||||
vec![
|
||||
json!("Nigel Rees"),
|
||||
json!("Evelyn Waugh"),
|
||||
json!("Herman Melville"),
|
||||
json!("J. R. R. Tolkien")
|
||||
]
|
||||
);
|
||||
|
||||
// all authors
|
||||
let expr = jsonpath::parse("$..author").unwrap();
|
||||
assert_eq!(
|
||||
expr.eval(bookstore_value()),
|
||||
vec![
|
||||
json!("Nigel Rees"),
|
||||
json!("Evelyn Waugh"),
|
||||
json!("Herman Melville"),
|
||||
json!("J. R. R. Tolkien")
|
||||
]
|
||||
);
|
||||
|
||||
// all things in store, which are some books and a red bicycle.
|
||||
let expr = jsonpath::parse("$.store.*").unwrap();
|
||||
// Attention, there is no ordering on object keys with serde_json
|
||||
// But you expect that order stays the same
|
||||
// that's why bicycle and boot are inverted
|
||||
assert_eq!(
|
||||
expr.eval(bookstore_value()),
|
||||
vec![bicycle_value(), book_value(),]
|
||||
);
|
||||
|
||||
// the price of everything in the store.
|
||||
let expr = jsonpath::parse("$.store..price").unwrap();
|
||||
// Attention, there is no ordering on object keys with serde_json
|
||||
// But you expect that order stays the same
|
||||
assert_eq!(
|
||||
expr.eval(bookstore_value()),
|
||||
vec![
|
||||
json!(19.95),
|
||||
json!(8.95),
|
||||
json!(12.99),
|
||||
json!(8.99),
|
||||
json!(22.99),
|
||||
]
|
||||
);
|
||||
|
||||
// the third book
|
||||
let expr = jsonpath::parse("$..book[2]").unwrap();
|
||||
assert_eq!(expr.eval(bookstore_value()), vec![book2_value()]);
|
||||
|
||||
// the last book in order
|
||||
// The following expression is not supported
|
||||
// (@.length-1)
|
||||
// use python-like indexing instead
|
||||
let expr = jsonpath::parse("$..book[-1:]").unwrap();
|
||||
assert_eq!(expr.eval(bookstore_value()), vec![book3_value()]);
|
||||
|
||||
// the first two books
|
||||
let expr = jsonpath::parse("$..book[0,1]").unwrap();
|
||||
assert_eq!(
|
||||
expr.eval(bookstore_value()),
|
||||
vec![book0_value(), book1_value()]
|
||||
);
|
||||
let expr = jsonpath::parse("$..book[:2]").unwrap();
|
||||
assert_eq!(
|
||||
expr.eval(bookstore_value()),
|
||||
vec![book0_value(), book1_value()]
|
||||
);
|
||||
|
||||
// filter all books with isbn number
|
||||
let expr = jsonpath::parse("$..book[?(@.isbn)]").unwrap();
|
||||
assert_eq!(
|
||||
expr.eval(bookstore_value()),
|
||||
vec![book2_value(), book3_value(),]
|
||||
);
|
||||
|
||||
// filter all books cheapier than 10
|
||||
let expr = jsonpath::parse("$..book[?(@.price<10)]").unwrap();
|
||||
assert_eq!(
|
||||
expr.eval(bookstore_value()),
|
||||
vec![book0_value(), book2_value(),]
|
||||
);
|
||||
|
||||
// All members of JSON structure
|
||||
let expr = jsonpath::parse("$..*").unwrap();
|
||||
// Order is reproducible
|
||||
// but does not keep same order of json input!
|
||||
assert_eq!(
|
||||
expr.eval(bookstore_value()),
|
||||
vec![
|
||||
store_value(),
|
||||
bicycle_value(),
|
||||
json!("red"),
|
||||
json!(19.95),
|
||||
book_value(),
|
||||
book0_value(),
|
||||
json!("Nigel Rees"),
|
||||
json!("reference"),
|
||||
json!(8.95),
|
||||
json!("Sayings of the Century"),
|
||||
book1_value(),
|
||||
json!("Evelyn Waugh"),
|
||||
json!("fiction"),
|
||||
json!(12.99),
|
||||
json!("Sword of Honour"),
|
||||
book2_value(),
|
||||
json!("Herman Melville"),
|
||||
json!("fiction"),
|
||||
json!("0-553-21311-3"),
|
||||
json!(8.99),
|
||||
json!("Moby Dick"),
|
||||
book3_value(),
|
||||
json!("J. R. R. Tolkien"),
|
||||
json!("fiction"),
|
||||
json!("0-395-19395-8"),
|
||||
json!(22.99),
|
||||
json!("The Lord of the Rings"),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bookstore_additional() {
|
||||
let no_result: Vec<serde_json::Value> = vec![];
|
||||
let s = read_to_string("tests/bookstore.json").expect("could not read string from file");
|
||||
let value: serde_json::Value =
|
||||
serde_json::from_str(s.as_str()).expect("could not parse json file");
|
||||
|
||||
// $.store.book[*].author
|
||||
assert_eq!(
|
||||
test_ok("$.store.book[*].author", value.clone()),
|
||||
vec![
|
||||
json!("Nigel Rees"),
|
||||
json!("Evelyn Waugh"),
|
||||
json!("Herman Melville"),
|
||||
json!("J. R. R. Tolkien")
|
||||
]
|
||||
);
|
||||
// Find books more expensive than 100
|
||||
let expr = jsonpath::parse("$.store.book[?(@.price>100)]").unwrap();
|
||||
assert_eq!(expr.eval(bookstore_value()), no_result);
|
||||
|
||||
assert_eq!(
|
||||
test_ok("$.store.book[0].title", value.clone()),
|
||||
vec![json!("Sayings of the Century")]
|
||||
);
|
||||
assert_eq!(
|
||||
test_ok("$.store.book.[0].title", value.clone()),
|
||||
vec![json!("Sayings of the Century")]
|
||||
);
|
||||
assert_eq!(
|
||||
test_ok("$.store.book[0].title", value.clone()),
|
||||
vec![json!("Sayings of the Century")]
|
||||
);
|
||||
assert_eq!(
|
||||
test_ok("$.store.book[?(@.price<10)].title", value.clone()),
|
||||
vec![json!("Sayings of the Century"), json!("Moby Dick")]
|
||||
);
|
||||
assert_eq!(
|
||||
test_ok("$.store.book[?(@.price < 10)].title", value.clone()),
|
||||
vec![json!("Sayings of the Century"), json!("Moby Dick")]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
test_ok("$..book[2]", value.clone()),
|
||||
vec![json!({
|
||||
"category": "fiction",
|
||||
"author": "Herman Melville",
|
||||
"title": "Moby Dick",
|
||||
"isbn": "0-553-21311-3",
|
||||
"price": 8.99
|
||||
})]
|
||||
);
|
||||
assert_eq!(
|
||||
test_ok("$..author", value.clone()),
|
||||
vec![
|
||||
json!("Nigel Rees"),
|
||||
json!("Evelyn Waugh"),
|
||||
json!("Herman Melville"),
|
||||
json!("J. R. R. Tolkien")
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
test_ok("$.store.book[?(@.price>100)]", value.clone()),
|
||||
no_result
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
test_ok("$..book[?(@.category=='reference')].author", value),
|
||||
vec![json!("Nigel Rees")]
|
||||
);
|
||||
// find all authors for reference book
|
||||
let expr = jsonpath::parse("$..book[?(@.category=='reference')].author").unwrap();
|
||||
assert_eq!(expr.eval(bookstore_value()), vec![json!("Nigel Rees")]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_array() {
|
||||
let array = json!([0, 1, 2, 3]);
|
||||
assert_eq!(test_ok("$[2]", array), vec![json!(2)]);
|
||||
let expr = jsonpath::parse("$[2]").unwrap();
|
||||
assert_eq!(expr.eval(array), vec![json!(2)]);
|
||||
|
||||
let expr = jsonpath::parse("$[0].name").unwrap();
|
||||
let array = json!([{"name": "Bob"},{"name": "Bill"}]);
|
||||
assert_eq!(test_ok("$[0].name", array), vec![json!("Bob")]);
|
||||
assert_eq!(expr.eval(array), vec![json!("Bob")]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_key_access() {
|
||||
let obj = json!({
|
||||
"_": "underscore",
|
||||
"-": "hyphen",
|
||||
"*": "asterisk",
|
||||
"'": "single_quote",
|
||||
"\"": "double_quote",
|
||||
"✈": "plane"
|
||||
});
|
||||
|
||||
// Bracket notation
|
||||
let expr = jsonpath::parse("$['-']").unwrap();
|
||||
assert_eq!(expr.eval(obj.clone()), vec![json!("hyphen")]);
|
||||
|
||||
let expr = jsonpath::parse("$['_']").unwrap();
|
||||
assert_eq!(expr.eval(obj.clone()), vec![json!("underscore")]);
|
||||
|
||||
let expr = jsonpath::parse("$['*']").unwrap();
|
||||
assert_eq!(expr.eval(obj.clone()), vec![json!("asterisk")]);
|
||||
|
||||
let expr = jsonpath::parse("$['\\'']").unwrap();
|
||||
assert_eq!(expr.eval(obj.clone()), vec![json!("single_quote")]);
|
||||
|
||||
let expr = jsonpath::parse("$['\"']").unwrap();
|
||||
assert_eq!(expr.eval(obj.clone()), vec![json!("double_quote")]);
|
||||
|
||||
let expr = jsonpath::parse("$['✈']").unwrap();
|
||||
assert_eq!(expr.eval(obj.clone()), vec![json!("plane")]);
|
||||
|
||||
// Dot notation
|
||||
let expr = jsonpath::parse("$._").unwrap();
|
||||
assert_eq!(expr.eval(obj.clone()), vec![json!("underscore")]);
|
||||
|
||||
// Asterisk
|
||||
// return all elements
|
||||
// There is no ordering in JSON keys
|
||||
// You must compare with their string values sorted
|
||||
let values = vec![
|
||||
"asterisk",
|
||||
"double_quote",
|
||||
"hyphen",
|
||||
"plane",
|
||||
"single_quote",
|
||||
"underscore",
|
||||
];
|
||||
|
||||
let expr = jsonpath::parse("$.*").unwrap();
|
||||
let results = expr.eval(obj.clone());
|
||||
let mut results = results
|
||||
.iter()
|
||||
.map(|e| e.as_str().unwrap())
|
||||
.collect::<Vec<&str>>();
|
||||
results.sort_unstable();
|
||||
assert_eq!(results, values);
|
||||
|
||||
let expr = jsonpath::parse("$[*]").unwrap();
|
||||
let results = expr.eval(obj);
|
||||
let mut results = results
|
||||
.iter()
|
||||
.map(|e| e.as_str().unwrap())
|
||||
.collect::<Vec<&str>>();
|
||||
results.sort_unstable();
|
||||
assert_eq!(results, values);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parsing_error() {
|
||||
// not supported yet
|
||||
assert!(jsonpath::parse("$..book[(@.length-1)]").is_err());
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user