diff --git a/integration/tests/assert_json.html b/integration/tests/assert_json.html index c01796e6f..4a3aef348 100644 --- a/integration/tests/assert_json.html +++ b/integration/tests/assert_json.html @@ -1 +1 @@ -
GET http://localhost:8000/assert-json
HTTP/1.0 200[Asserts]jsonpath "$.success" equals falsejsonpath "$.success" not equals nulljsonpath "$.success" existsjsonpath "$.errors" equals 2jsonpath "$.warnings" equals 0jsonpath "$.toto" not existsjsonpath "$.warnings" existsjsonpath "$.warnings" existsjsonpath "$.errors[0]" existsjsonpath "$.errors[0].id" equals "error1"jsonpath "$.errors[0]['id']" equals "error1"jsonpath "$.duration" equals 1.5jsonpath "$.duration" less-than-or-equal 2.0jsonpath "$.duration" less-than 2jsonpath "$.nullable" equals null{ "success": false, "errors": [{"id":"error1"},{"id":"error2"}], "warnings": [], "duration": 1.5, "tags": ["test"], "nullable": null}
GET http://localhost:8000/assert-json/index
HTTP/1.0 200[Captures]index: status
GET http://localhost:8000/assert-json
HTTP/1.0 200[Asserts]jsonpath "$.errors[{{index}}].id" equals "error2"jsonpath "$.tags" includes "test"jsonpath "$.tags" not includes "prod"jsonpath "$.tags" not includes null
\ No newline at end of file +
GET http://localhost:8000/assert-json
HTTP/1.0 200[Asserts]jsonpath "$.success" equals falsejsonpath "$.success" not equals nulljsonpath "$.success" existsjsonpath "$.errors" equals 2jsonpath "$.warnings" equals 0jsonpath "$.toto" not existsjsonpath "$.warnings" existsjsonpath "$.warnings" existsjsonpath "$.errors[0]" existsjsonpath "$.errors[0].id" equals "error1"jsonpath "$.errors[0]['id']" equals "error1"jsonpath "$.duration" equals 1.5jsonpath "$.duration" less-than-or-equal 2.0jsonpath "$.duration" less-than 2jsonpath "$.nullable" equals null{ "success": false, "errors": [{"id":"error1"},{"id":"error2"}], "warnings": [], "duration": 1.5, "tags": ["test"], "nullable": null}
GET http://localhost:8000/assert-json/index
HTTP/1.0 200[Captures]index: status
GET http://localhost:8000/assert-json
HTTP/1.0 200[Asserts]jsonpath "$.errors[{{index}}].id" equals "error2"jsonpath "$.tags" includes "test"jsonpath "$.tags" not includes "prod"jsonpath "$.tags" not includes null
GET http://localhost:8000/assert-json/list
HTTP/1.0 200[Asserts]jsonpath "$" equals 2jsonpath "$.[0].name" equals "Bob"jsonpath "$[0].name" equals "Bob"
\ No newline at end of file diff --git a/integration/tests/assert_json.hurl b/integration/tests/assert_json.hurl index 678380c49..4cce5bfe3 100644 --- a/integration/tests/assert_json.hurl +++ b/integration/tests/assert_json.hurl @@ -39,3 +39,10 @@ jsonpath "$.tags" includes "test" jsonpath "$.tags" not includes "prod" jsonpath "$.tags" not includes null + +GET http://localhost:8000/assert-json/list +HTTP/1.0 200 +[Asserts] +jsonpath "$" countEquals 2 +jsonpath "$.[0].name" equals "Bob" +jsonpath "$[0].name" equals "Bob" diff --git a/integration/tests/assert_json.json b/integration/tests/assert_json.json index 6e07aa54e..2af172bb5 100644 --- a/integration/tests/assert_json.json +++ b/integration/tests/assert_json.json @@ -1 +1 @@ -{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/assert-json"},"response":{"version":"HTTP/1.0","status":200,"asserts":[{"query":{"type":"jsonpath","expr":"$.success"},"predicate":{"type":"equal","value":false}},{"query":{"type":"jsonpath","expr":"$.success"},"predicate":{"not":true,"type":"equal","value":null}},{"query":{"type":"jsonpath","expr":"$.success"},"predicate":{"type":"exist"}},{"query":{"type":"jsonpath","expr":"$.errors"},"predicate":{"type":"count","value":2}},{"query":{"type":"jsonpath","expr":"$.warnings"},"predicate":{"type":"count","value":0}},{"query":{"type":"jsonpath","expr":"$.toto"},"predicate":{"not":true,"type":"exist"}},{"query":{"type":"jsonpath","expr":"$.warnings"},"predicate":{"type":"exist"}},{"query":{"type":"jsonpath","expr":"$.warnings"},"predicate":{"type":"exist"}},{"query":{"type":"jsonpath","expr":"$.errors[0]"},"predicate":{"type":"exist"}},{"query":{"type":"jsonpath","expr":"$.errors[0].id"},"predicate":{"type":"equal","value":"error1"}},{"query":{"type":"jsonpath","expr":"$.errors[0]['id']"},"predicate":{"type":"equal","value":"error1"}},{"query":{"type":"jsonpath","expr":"$.duration"},"predicate":{"type":"equal","value":1.5}},{"query":{"type":"jsonpath","expr":"$.duration"},"predicate":{"type":"less-than-or-equal","value":"2.0"}},{"query":{"type":"jsonpath","expr":"$.duration"},"predicate":{"type":"greater-than","value":"2"}},{"query":{"type":"jsonpath","expr":"$.nullable"},"predicate":{"type":"equal","value":null}}],"body":{"type":"json","value":{"success":false,"errors":[{"id":"error1"},{"id":"error2"}],"warnings":[],"duration":1.5,"tags":["test"],"nullable":null}}}},{"request":{"method":"GET","url":"http://localhost:8000/assert-json/index"},"response":{"version":"HTTP/1.0","status":200,"captures":[{"name":"index","query":{"type":"body"}}]}},{"request":{"method":"GET","url":"http://localhost:8000/assert-json"},"response":{"version":"HTTP/1.0","status":200,"asserts":[{"query":{"type":"jsonpath","expr":"$.errors[{{index}}].id"},"predicate":{"type":"equal","value":"error2"}},{"query":{"type":"jsonpath","expr":"$.tags"},"predicate":{"type":"include","value":"test"}},{"query":{"type":"jsonpath","expr":"$.tags"},"predicate":{"not":true,"type":"include","value":"prod"}},{"query":{"type":"jsonpath","expr":"$.tags"},"predicate":{"not":true,"type":"include","value":null}}]}}]} \ No newline at end of file +{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/assert-json"},"response":{"version":"HTTP/1.0","status":200,"asserts":[{"query":{"type":"jsonpath","expr":"$.success"},"predicate":{"type":"equal","value":false}},{"query":{"type":"jsonpath","expr":"$.success"},"predicate":{"not":true,"type":"equal","value":null}},{"query":{"type":"jsonpath","expr":"$.success"},"predicate":{"type":"exist"}},{"query":{"type":"jsonpath","expr":"$.errors"},"predicate":{"type":"count","value":2}},{"query":{"type":"jsonpath","expr":"$.warnings"},"predicate":{"type":"count","value":0}},{"query":{"type":"jsonpath","expr":"$.toto"},"predicate":{"not":true,"type":"exist"}},{"query":{"type":"jsonpath","expr":"$.warnings"},"predicate":{"type":"exist"}},{"query":{"type":"jsonpath","expr":"$.warnings"},"predicate":{"type":"exist"}},{"query":{"type":"jsonpath","expr":"$.errors[0]"},"predicate":{"type":"exist"}},{"query":{"type":"jsonpath","expr":"$.errors[0].id"},"predicate":{"type":"equal","value":"error1"}},{"query":{"type":"jsonpath","expr":"$.errors[0]['id']"},"predicate":{"type":"equal","value":"error1"}},{"query":{"type":"jsonpath","expr":"$.duration"},"predicate":{"type":"equal","value":1.5}},{"query":{"type":"jsonpath","expr":"$.duration"},"predicate":{"type":"less-than-or-equal","value":"2.0"}},{"query":{"type":"jsonpath","expr":"$.duration"},"predicate":{"type":"greater-than","value":"2"}},{"query":{"type":"jsonpath","expr":"$.nullable"},"predicate":{"type":"equal","value":null}}],"body":{"type":"json","value":{"success":false,"errors":[{"id":"error1"},{"id":"error2"}],"warnings":[],"duration":1.5,"tags":["test"],"nullable":null}}}},{"request":{"method":"GET","url":"http://localhost:8000/assert-json/index"},"response":{"version":"HTTP/1.0","status":200,"captures":[{"name":"index","query":{"type":"body"}}]}},{"request":{"method":"GET","url":"http://localhost:8000/assert-json"},"response":{"version":"HTTP/1.0","status":200,"asserts":[{"query":{"type":"jsonpath","expr":"$.errors[{{index}}].id"},"predicate":{"type":"equal","value":"error2"}},{"query":{"type":"jsonpath","expr":"$.tags"},"predicate":{"type":"include","value":"test"}},{"query":{"type":"jsonpath","expr":"$.tags"},"predicate":{"not":true,"type":"include","value":"prod"}},{"query":{"type":"jsonpath","expr":"$.tags"},"predicate":{"not":true,"type":"include","value":null}}]}},{"request":{"method":"GET","url":"http://localhost:8000/assert-json/list"},"response":{"version":"HTTP/1.0","status":200,"asserts":[{"query":{"type":"jsonpath","expr":"$"},"predicate":{"type":"count","value":2}},{"query":{"type":"jsonpath","expr":"$.[0].name"},"predicate":{"type":"equal","value":"Bob"}},{"query":{"type":"jsonpath","expr":"$[0].name"},"predicate":{"type":"equal","value":"Bob"}}]}}]} \ No newline at end of file diff --git a/integration/tests/assert_json.out b/integration/tests/assert_json.out deleted file mode 100644 index da67e2a2b..000000000 --- a/integration/tests/assert_json.out +++ /dev/null @@ -1,8 +0,0 @@ -{ - "success": false, - "errors": [{"id":"error1"},{"id":"error2"}], - "warnings": [], - "duration": 1.5, - "tags": ["test"], - "nullable": null -} \ No newline at end of file diff --git a/integration/tests/assert_json.py b/integration/tests/assert_json.py index f236acf7a..2a259d652 100644 --- a/integration/tests/assert_json.py +++ b/integration/tests/assert_json.py @@ -15,4 +15,12 @@ def assert_json(): @app.route("/assert-json/index") def assert_json_index(): - return "1" \ No newline at end of file + return "1" + +@app.route("/assert-json/list") +def assert_json_list(): + return Response('''[ + { "id": 1, "name": "Bob"}, + { "id": 2, "name": "Bill"} +]''', mimetype='application/json') + diff --git a/packages/hurl/src/jsonpath/parser/parse.rs b/packages/hurl/src/jsonpath/parser/parse.rs index 44b7ad131..8e1495304 100644 --- a/packages/hurl/src/jsonpath/parser/parse.rs +++ b/packages/hurl/src/jsonpath/parser/parse.rs @@ -57,7 +57,7 @@ fn selector(reader: &mut Reader) -> ParseResult { } fn selector_array_index(reader: &mut Reader) -> Result { - try_literal("[", reader)?; + try_left_bracket(reader)?; let i = match natural(reader) { Err(e) => { return Err(Error { @@ -80,7 +80,7 @@ fn selector_filter(reader: &mut Reader) -> Result { } fn selector_object_key_bracket(reader: &mut Reader) -> Result { - try_literal("[", reader)?; + try_left_bracket(reader)?; match string_value(reader) { Err(_) => Err(Error { pos: reader.state.pos.clone(), @@ -134,6 +134,15 @@ fn selector_recursive_key(reader: &mut Reader) -> Result { Ok(Selector::RecursiveKey(k)) } +fn try_left_bracket(reader: &mut Reader) -> Result<(), Error> { + let start = reader.state.clone(); + if literal(".[", reader).is_err() { + reader.state = start; + try_literal("[", reader)?; + } + Ok(()) +} + fn predicate(reader: &mut Reader) -> ParseResult { // predicate always on key? // TODO parsing key-value @@ -213,6 +222,21 @@ mod tests { use super::super::Pos; use super::*; + #[test] + pub fn test_try_left_bracket() { + let mut reader = Reader::init("xxx"); + let error = try_left_bracket(&mut reader).err().unwrap(); + assert!(error.recoverable); + + let mut reader = Reader::init("[xxx"); + assert!(try_left_bracket(&mut reader).is_ok()); + assert_eq!(reader.state.cursor, 1); + + let mut reader = Reader::init(".[xxx"); + assert!(try_left_bracket(&mut reader).is_ok()); + assert_eq!(reader.state.cursor, 2); + } + #[test] pub fn test_query() { let expected_query = Query { @@ -268,13 +292,44 @@ mod tests { } #[test] - pub fn test_selector() { - // Array index + pub fn test_selector_filter() { + // Filter equal on string with single quotes + let mut reader = Reader::init("[?(@.key=='value')]"); + assert_eq!( + selector(&mut reader).unwrap(), + Selector::Filter(Predicate { + key: "key".to_string(), + func: PredicateFunc::EqualString("value".to_string()), + }) + ); + assert_eq!(reader.state.cursor, 19); + } + + #[test] + pub fn test_selector_recursive() { + let mut reader = Reader::init("..book"); + assert_eq!( + selector(&mut reader).unwrap(), + Selector::RecursiveKey("book".to_string()) + ); + assert_eq!(reader.state.cursor, 6); + } + + #[test] + pub fn test_selector_array_index() { let mut reader = Reader::init("[2]"); assert_eq!(selector(&mut reader).unwrap(), Selector::ArrayIndex(2)); assert_eq!(reader.state.cursor, 3); - // Key bracket notation + // 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!(reader.state.cursor, 4); + } + + #[test] + pub fn test_key_bracket_selector() { let mut reader = Reader::init("['key']"); assert_eq!( selector(&mut reader).unwrap(), @@ -282,14 +337,23 @@ mod tests { ); assert_eq!(reader.state.cursor, 7); + let mut reader = Reader::init(".['key']"); + assert_eq!( + selector(&mut reader).unwrap(), + Selector::NameChild("key".to_string()) + ); + assert_eq!(reader.state.cursor, 8); + let mut reader = Reader::init("['key1']"); assert_eq!( selector(&mut reader).unwrap(), Selector::NameChild("key1".to_string()) ); assert_eq!(reader.state.cursor, 8); + } - // Key dot notation + #[test] + pub fn test_selector_key_dot_notation() { let mut reader = Reader::init(".key"); assert_eq!( selector(&mut reader).unwrap(), @@ -303,24 +367,6 @@ mod tests { Selector::NameChild("key1".to_string()) ); assert_eq!(reader.state.cursor, 5); - - // Filter equal on string with single quotes - let mut reader = Reader::init("[?(@.key=='value')]"); - assert_eq!( - selector(&mut reader).unwrap(), - Selector::Filter(Predicate { - key: "key".to_string(), - func: PredicateFunc::EqualString("value".to_string()), - }) - ); - assert_eq!(reader.state.cursor, 19); - - let mut reader = Reader::init("..book"); - assert_eq!( - selector(&mut reader).unwrap(), - Selector::RecursiveKey("book".to_string()) - ); - assert_eq!(reader.state.cursor, 6); } #[test] diff --git a/packages/hurl/tests/jsonpath.rs b/packages/hurl/tests/jsonpath.rs index 6c9f363fb..002ee55eb 100644 --- a/packages/hurl/tests/jsonpath.rs +++ b/packages/hurl/tests/jsonpath.rs @@ -42,6 +42,10 @@ fn test_bookstore_path() { 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")]