From 9093325d2802ac4fa99ba5d22642da06bb46c0c4 Mon Sep 17 00:00:00 2001 From: Fabrice Reix Date: Tue, 5 Oct 2021 17:05:00 +0200 Subject: [PATCH] Improve jsonpath support --- packages/hurl/src/jsonpath/ast.rs | 13 +- packages/hurl/src/jsonpath/eval.rs | 167 ++++--- packages/hurl/src/jsonpath/mod.rs | 28 ++ packages/hurl/src/jsonpath/parser/parse.rs | 171 +++++++- .../hurl/src/jsonpath/parser/primitives.rs | 42 +- packages/hurl/tests/jsonpath.rs | 409 +++++++++++++++--- 6 files changed, 688 insertions(+), 142 deletions(-) diff --git a/packages/hurl/src/jsonpath/ast.rs b/packages/hurl/src/jsonpath/ast.rs index 0b8e40231..4dae3afd3 100644 --- a/packages/hurl/src/jsonpath/ast.rs +++ b/packages/hurl/src/jsonpath/ast.rs @@ -26,13 +26,24 @@ pub struct Query { #[derive(Clone, Debug, PartialEq, Eq)] pub enum Selector { + Wildcard, NameChild(String), - ArrayIndex(usize), + ArrayIndex(Vec), // 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, + pub end: Option, +} + #[derive(Clone, Debug, PartialEq, Eq)] pub struct Predicate { pub key: String, diff --git a/packages/hurl/src/jsonpath/eval.rs b/packages/hurl/src/jsonpath/eval.rs index 8158cbb62..6c60284ab 100644 --- a/packages/hurl/src/jsonpath/eval.rs +++ b/packages/hurl/src/jsonpath/eval.rs @@ -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()), diff --git a/packages/hurl/src/jsonpath/mod.rs b/packages/hurl/src/jsonpath/mod.rs index a2f44e1ed..2d2154993 100644 --- a/packages/hurl/src/jsonpath/mod.rs +++ b/packages/hurl/src/jsonpath/mod.rs @@ -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; diff --git a/packages/hurl/src/jsonpath/parser/parse.rs b/packages/hurl/src/jsonpath/parser/parse.rs index f638ace7b..72afb8608 100644 --- a/packages/hurl/src/jsonpath/parser/parse.rs +++ b/packages/hurl/src/jsonpath/parser/parse.rs @@ -46,12 +46,15 @@ fn query(reader: &mut Reader) -> ParseResult { fn selector(reader: &mut Reader) -> ParseResult { 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 { fn selector_array_index(reader: &mut Reader) -> Result { 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 { } 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 { @@ -80,6 +104,37 @@ fn selector_array_wildcard(reader: &mut Reader) -> Result { Ok(Selector::ArrayWildcard {}) } +fn selector_array_slice(reader: &mut Reader) -> Result { + 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 { try_literal("[?(", reader)?; let pred = predicate(reader)?; @@ -119,7 +174,7 @@ fn selector_object_key(reader: &mut Reader) -> Result { }); }; - 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 { Ok(Selector::NameChild(s)) } +fn selector_wildcard(reader: &mut Reader) -> Result { + try_literal(".*", reader)?; + Ok(Selector::Wildcard {}) +} + +fn selector_recursive_wildcard(reader: &mut Reader) -> Result { + try_literal("..*", reader)?; + Ok(Selector::RecursiveWildcard {}) +} + fn selector_recursive_key(reader: &mut Reader) -> Result { try_literal("..", reader)?; let k = key_name(reader)?; @@ -163,7 +228,14 @@ fn predicate(reader: &mut Reader) -> ParseResult { // @.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] diff --git a/packages/hurl/src/jsonpath/parser/primitives.rs b/packages/hurl/src/jsonpath/parser/primitives.rs index bebd86ce2..9365b9bb4 100644 --- a/packages/hurl/src/jsonpath/parser/primitives.rs +++ b/packages/hurl/src/jsonpath/parser/primitives.rs @@ -99,8 +99,41 @@ pub fn number(reader: &mut Reader) -> ParseResult<'static, Number> { pub fn string_value(reader: &mut Reader) -> Result { 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 { pub fn key_name(reader: &mut Reader) -> Result { // // 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!( diff --git a/packages/hurl/tests/jsonpath.rs b/packages/hurl/tests/jsonpath.rs index a122f02af..ca8c7476c 100644 --- a/packages/hurl/tests/jsonpath.rs +++ b/packages/hurl/tests/jsonpath.rs @@ -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 { - 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 = 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::>(); + 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::>(); + results.sort_unstable(); + assert_eq!(results, values); +} + +#[test] +fn test_parsing_error() { + // not supported yet + assert!(jsonpath::parse("$..book[(@.length-1)]").is_err()); }