diff --git a/ci/check.sh b/ci/check.sh index b61c99741..8712d471b 100755 --- a/ci/check.sh +++ b/ci/check.sh @@ -4,7 +4,7 @@ set -e shellcheck ./*.sh ./*/*.sh # watch eprintln!/eprintln! -find src -name "*.rs" | grep -E -v 'logger|hurlfmt|http/client' | while read -r f; do +find src -name "*.rs" | grep -E -v 'logger|hurlfmt|http/client|hurl_file' | while read -r f; do if grep -q eprintln "$f"; then echo "file '$f' contains a println!" exit 1 diff --git a/integration/report/html/index.html b/integration/report/html/index.html index a861aa04e..06a24ae01 100644 --- a/integration/report/html/index.html +++ b/integration/report/html/index.html @@ -1,2 +1,2 @@ -Hurl Report

Hurl Report

Thu, 24 Sep 2020 21:02:09 +0200
filenameduration
tests/assert_base64.hurl0.002s
tests/assert_header.hurl0.003s
tests/assert_json.hurl0.009s
tests/assert_match.hurl0.016s
tests/assert_regex.hurl0.004s
tests/assert_xpath.hurl0.002s
tests/bytes.hurl0.002s
tests/capture_and_assert.hurl0.002s
tests/captures.hurl0.008s
tests/cookies.hurl0.016s
tests/cookie_storage.hurl0.003s
tests/delete.hurl0.002s
tests/empty.hurl0s
tests/encoding.hurl0.006s
tests/error_assert_base64.hurl0.002s
tests/error_assert_file.hurl0.002s
tests/error_assert_header_not_found.hurl0.002s
tests/error_assert_header_value.hurl0.001s
tests/error_assert_http_version.hurl0.001s
tests/error_assert_invalid_predicate_type.hurl0.002s
tests/error_assert_match_utf8.hurl0.002s
tests/error_assert_query_cookie.hurl0.004s
tests/error_assert_query_invalid_regex.hurl0.002s
tests/error_assert_query_invalid_xpath.hurl0.002s
tests/error_assert_status.hurl0.002s
tests/error_assert_template_variable_not_found.hurl0.002s
tests/error_assert_value_error.hurl0.004s
tests/error_assert_variable.hurl0.005s
tests/error_file_read_access.hurl0s
tests/error_http_connection.hurl0.006s
tests/error_invalid_jsonpath.hurl0.002s
tests/error_invalid_url.hurl0s
tests/error_invalid_xml.hurl0.002s
tests/error_multipart_form_data.hurl0s
tests/error_predicate.hurl0.013s
tests/error_query_header_not_found.hurl0.002s
tests/error_query_invalid_json.hurl0.002s
tests/error_query_invalid_utf8.hurl0.002s
tests/error_template_variable_not_found.hurl0s
tests/error_template_variable_not_renderable.hurl0.002s
tests/error_timeout.hurl2.004s
tests/follow_redirect.hurl0.002s
tests/form_params.hurl0.004s
tests/headers.hurl0.011s
tests/hello.hurl0.003s
tests/multipart_form_data.hurl0.003s
tests/no_entry.hurl0s
tests/output.hurl0.003s
tests/patch.hurl0.002s
tests/post_base64.hurl0.002s
tests/post_file.hurl0.002s
tests/post_json.hurl0.015s
tests/post_multilines.hurl0.005s
tests/post_xml.hurl0.004s
tests/predicates-string.hurl0.005s
tests/put.hurl0.001s
tests/querystring_params.hurl0.007s
tests/redirect.hurl0.004s
tests/utf8.hurl0.002s
\ No newline at end of file +Hurl Report

Hurl Report

Sun, 11 Oct 2020 16:54:45 +0200
filenameduration
tests/assert_base64.hurl0.005s
tests/assert_header.hurl0.006s
tests/assert_json.hurl0.011s
tests/assert_match.hurl0.021s
tests/assert_regex.hurl0.008s
tests/assert_xpath.hurl0.006s
tests/bytes.hurl0.005s
tests/capture_and_assert.hurl0.006s
tests/captures.hurl0.012s
tests/cookies.hurl0.018s
tests/cookie_storage.hurl0.007s
tests/delete.hurl0.005s
tests/empty.hurl0s
tests/encoding.hurl0.007s
tests/error_assert_base64.hurl0.006s
tests/error_assert_file.hurl0.006s
tests/error_assert_header_not_found.hurl0.007s
tests/error_assert_header_value.hurl0.006s
tests/error_assert_http_version.hurl0.005s
tests/error_assert_invalid_predicate_type.hurl0.007s
tests/error_assert_match_utf8.hurl0.005s
tests/error_assert_query_cookie.hurl0.008s
tests/error_assert_query_invalid_regex.hurl0.006s
tests/error_assert_query_invalid_xpath.hurl0.006s
tests/error_assert_status.hurl0.006s
tests/error_assert_template_variable_not_found.hurl0.006s
tests/error_assert_value_error.hurl0.008s
tests/error_assert_variable.hurl0.008s
tests/error_file_read_access.hurl0s
tests/error_http_connection.hurl0.06s
tests/error_invalid_jsonpath.hurl0.005s
tests/error_invalid_url.hurl0s
tests/error_invalid_xml.hurl0.005s
tests/error_multipart_form_data.hurl0s
tests/error_predicate.hurl0.016s
tests/error_query_header_not_found.hurl0.006s
tests/error_query_invalid_json.hurl0.006s
tests/error_query_invalid_utf8.hurl0.006s
tests/error_template_variable_not_found.hurl0s
tests/error_template_variable_not_renderable.hurl0.006s
tests/error_timeout.hurl2.007s
tests/follow_redirect.hurl0.006s
tests/form_params.hurl0.008s
tests/headers.hurl0.014s
tests/hello.hurl0.007s
tests/multipart_form_data.hurl0.007s
tests/no_entry.hurl0s
tests/output.hurl0.007s
tests/patch.hurl0.006s
tests/post_base64.hurl0.005s
tests/post_file.hurl0.006s
tests/post_json.hurl0.017s
tests/post_multilines.hurl0.009s
tests/post_xml.hurl0.007s
tests/predicates-string.hurl0.008s
tests/put.hurl0.005s
tests/querystring_params.hurl0.011s
tests/redirect.hurl0.007s
tests/utf8.hurl0.006s
\ No newline at end of file diff --git a/integration/report/tests.json b/integration/report/tests.json index ee54cc326..849e0b10a 100644 --- a/integration/report/tests.json +++ b/integration/report/tests.json @@ -30,7 +30,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -43,11 +43,11 @@ {}, {} ], - "time": 2 + "time": 5 } ], "success": true, - "time": 2, + "time": 5, "cookies": [] }, { @@ -113,7 +113,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -135,11 +135,11 @@ {}, {} ], - "time": 2 + "time": 5 } ], "success": true, - "time": 3, + "time": 6, "cookies": [ { "domain": "localhost", @@ -201,7 +201,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -226,7 +226,7 @@ {}, {} ], - "time": 1 + "time": 5 }, { "request": { @@ -256,7 +256,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -303,7 +303,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -319,11 +319,11 @@ {}, {} ], - "time": 2 + "time": 1 } ], "success": true, - "time": 9, + "time": 11, "cookies": [] }, { @@ -357,7 +357,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -373,11 +373,11 @@ {}, {} ], - "time": 1 + "time": 5 } ], "success": true, - "time": 16, + "time": 21, "cookies": [] }, { @@ -411,7 +411,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -425,11 +425,11 @@ {}, {} ], - "time": 1 + "time": 5 } ], "success": true, - "time": 4, + "time": 8, "cookies": [] }, { @@ -463,7 +463,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -479,11 +479,11 @@ {}, {} ], - "time": 1 + "time": 5 } ], "success": true, - "time": 2, + "time": 6, "cookies": [] }, { @@ -517,7 +517,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -530,11 +530,11 @@ {}, {} ], - "time": 1 + "time": 5 } ], "success": true, - "time": 2, + "time": 5, "cookies": [] }, { @@ -568,7 +568,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -586,11 +586,11 @@ {}, {} ], - "time": 1 + "time": 5 } ], "success": true, - "time": 2, + "time": 6, "cookies": [] }, { @@ -632,7 +632,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -655,7 +655,7 @@ {}, {} ], - "time": 1 + "time": 5 }, { "request": { @@ -694,7 +694,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -736,7 +736,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -813,7 +813,7 @@ } ], "success": true, - "time": 8, + "time": 12, "cookies": [] }, { @@ -852,7 +852,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -864,7 +864,7 @@ }, {} ], - "time": 1 + "time": 5 }, { "request": { @@ -894,7 +894,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -946,7 +946,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -989,7 +989,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -1036,7 +1036,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -1066,7 +1066,7 @@ { "name": "cookie2", "value": "", - "expires": "Thu, 24-Sep-2020 19:02:07 GMT", + "expires": "Sun, 11-Oct-2020 14:54:42 GMT", "max_age": 0, "path": "/" } @@ -1078,7 +1078,7 @@ }, { "name": "Set-Cookie", - "value": "cookie2=; Expires=Thu, 24-Sep-2020 19:02:07 GMT; Max-Age=0; Path=/" + "value": "cookie2=; Expires=Sun, 11-Oct-2020 14:54:42 GMT; Max-Age=0; Path=/" }, { "name": "Content-Length", @@ -1090,7 +1090,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -1134,7 +1134,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -1214,7 +1214,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -1244,16 +1244,25 @@ } ], "success": true, - "time": 16, + "time": 18, "cookies": [ { - "domain": "#HttpOnly_.localhost", - "include_subdomain": "TRUE", + "domain": "localhost", + "include_subdomain": "FALSE", "path": "/", "https": "FALSE", + "expires": "1602428082", + "name": "cookie2", + "value": "" + }, + { + "domain": "#HttpOnly_localhost", + "include_subdomain": "FALSE", + "path": "/accounts", + "https": "TRUE", "expires": "1610576581", - "name": "HSID", - "value": "AYQEVnDKrdst" + "name": "LSID", + "value": "DQAAAKEaem_vYg" } ] }, @@ -1288,7 +1297,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -1300,7 +1309,7 @@ }, {} ], - "time": 1 + "time": 5 }, { "request": { @@ -1330,7 +1339,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -1346,7 +1355,7 @@ } ], "success": true, - "time": 3, + "time": 7, "cookies": [] }, { @@ -1380,7 +1389,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -1392,11 +1401,11 @@ }, {} ], - "time": 2 + "time": 5 } ], "success": true, - "time": 2, + "time": 5, "cookies": [] }, { @@ -1437,7 +1446,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -1451,7 +1460,7 @@ {}, {} ], - "time": 3 + "time": 5 }, { "request": { @@ -1481,7 +1490,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -1499,7 +1508,7 @@ } ], "success": true, - "time": 6, + "time": 7, "cookies": [] }, { @@ -1533,7 +1542,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -1546,11 +1555,11 @@ {}, {} ], - "time": 1 + "time": 5 } ], "success": false, - "time": 2, + "time": 6, "cookies": [] }, { @@ -1584,7 +1593,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -1597,11 +1606,11 @@ {}, {} ], - "time": 1 + "time": 5 } ], "success": false, - "time": 2, + "time": 6, "cookies": [] }, { @@ -1635,7 +1644,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -1648,11 +1657,11 @@ {}, {} ], - "time": 1 + "time": 7 } ], "success": false, - "time": 2, + "time": 7, "cookies": [] }, { @@ -1686,7 +1695,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -1699,11 +1708,11 @@ {}, {} ], - "time": 1 + "time": 5 } ], "success": false, - "time": 1, + "time": 6, "cookies": [] }, { @@ -1737,7 +1746,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -1749,11 +1758,11 @@ }, {} ], - "time": 1 + "time": 5 } ], "success": false, - "time": 1, + "time": 5, "cookies": [] }, { @@ -1787,7 +1796,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -1800,11 +1809,11 @@ {}, {} ], - "time": 1 + "time": 5 } ], "success": false, - "time": 2, + "time": 7, "cookies": [] }, { @@ -1838,7 +1847,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -1851,11 +1860,11 @@ {}, {} ], - "time": 1 + "time": 5 } ], "success": false, - "time": 2, + "time": 5, "cookies": [] }, { @@ -1909,7 +1918,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -1925,11 +1934,11 @@ {}, {} ], - "time": 1 + "time": 6 } ], "success": false, - "time": 4, + "time": 8, "cookies": [ { "domain": "localhost", @@ -1939,6 +1948,15 @@ "expires": "0", "name": "cookie1", "value": "value1" + }, + { + "domain": "localhost", + "include_subdomain": "FALSE", + "path": "/", + "https": "TRUE", + "expires": "0", + "name": "cookie2", + "value": "value2" } ] }, @@ -1973,7 +1991,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -1986,11 +2004,11 @@ {}, {} ], - "time": 1 + "time": 5 } ], "success": false, - "time": 2, + "time": 6, "cookies": [] }, { @@ -2024,7 +2042,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -2037,11 +2055,11 @@ {}, {} ], - "time": 1 + "time": 6 } ], "success": false, - "time": 2, + "time": 6, "cookies": [] }, { @@ -2075,7 +2093,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -2087,11 +2105,11 @@ }, {} ], - "time": 2 + "time": 5 } ], "success": false, - "time": 2, + "time": 6, "cookies": [] }, { @@ -2125,7 +2143,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -2138,11 +2156,11 @@ {}, {} ], - "time": 1 + "time": 5 } ], "success": false, - "time": 2, + "time": 6, "cookies": [] }, { @@ -2176,7 +2194,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -2191,11 +2209,11 @@ {}, {} ], - "time": 1 + "time": 5 } ], "success": false, - "time": 4, + "time": 8, "cookies": [] }, { @@ -2229,7 +2247,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -2258,11 +2276,11 @@ {}, {} ], - "time": 1 + "time": 5 } ], "success": false, - "time": 5, + "time": 8, "cookies": [] }, { @@ -2296,7 +2314,7 @@ } ], "success": false, - "time": 6, + "time": 60, "cookies": [] }, { @@ -2330,7 +2348,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -2343,11 +2361,11 @@ {}, {} ], - "time": 1 + "time": 5 } ], "success": false, - "time": 2, + "time": 5, "cookies": [] }, { @@ -2402,7 +2420,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -2415,11 +2433,11 @@ {}, {} ], - "time": 1 + "time": 5 } ], "success": false, - "time": 2, + "time": 5, "cookies": [] }, { @@ -2466,7 +2484,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -2489,11 +2507,11 @@ {}, {} ], - "time": 1 + "time": 5 } ], "success": false, - "time": 13, + "time": 16, "cookies": [] }, { @@ -2527,7 +2545,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -2540,11 +2558,11 @@ {}, {} ], - "time": 1 + "time": 5 } ], "success": false, - "time": 2, + "time": 6, "cookies": [] }, { @@ -2578,7 +2596,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -2591,11 +2609,11 @@ {}, {} ], - "time": 1 + "time": 5 } ], "success": false, - "time": 2, + "time": 6, "cookies": [] }, { @@ -2629,7 +2647,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -2642,11 +2660,11 @@ {}, {} ], - "time": 1 + "time": 5 } ], "success": false, - "time": 2, + "time": 6, "cookies": [] }, { @@ -2693,7 +2711,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:07 GMT" + "value": "Sun, 11 Oct 2020 14:54:42 GMT" } ] }, @@ -2714,7 +2732,7 @@ }, {} ], - "time": 1 + "time": 5 }, { "captures": [], @@ -2723,7 +2741,7 @@ } ], "success": false, - "time": 2, + "time": 6, "cookies": [] }, { @@ -2757,17 +2775,17 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:44 GMT" } ] }, "captures": [], "asserts": [], - "time": 2003 + "time": 2007 } ], "success": true, - "time": 2004, + "time": 2007, "cookies": [] }, { @@ -2805,7 +2823,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:44 GMT" } ] }, @@ -2818,11 +2836,11 @@ {}, {} ], - "time": 1 + "time": 5 } ], "success": false, - "time": 2, + "time": 6, "cookies": [] }, { @@ -2874,7 +2892,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:44 GMT" } ] }, @@ -2886,7 +2904,7 @@ }, {} ], - "time": 1 + "time": 5 }, { "request": { @@ -2921,7 +2939,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:44 GMT" } ] }, @@ -2937,7 +2955,7 @@ } ], "success": true, - "time": 4, + "time": 8, "cookies": [] }, { @@ -2971,7 +2989,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:44 GMT" } ] }, @@ -2983,7 +3001,7 @@ }, {} ], - "time": 2 + "time": 5 }, { "request": { @@ -3022,7 +3040,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:44 GMT" } ] }, @@ -3073,7 +3091,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:44 GMT" } ] }, @@ -3136,7 +3154,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:44 GMT" } ] }, @@ -3183,7 +3201,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:44 GMT" } ] }, @@ -3229,7 +3247,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:44 GMT" } ] }, @@ -3246,7 +3264,7 @@ } ], "success": true, - "time": 11, + "time": 14, "cookies": [] }, { @@ -3280,7 +3298,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:44 GMT" } ] }, @@ -3293,7 +3311,7 @@ {}, {} ], - "time": 1 + "time": 5 }, { "request": { @@ -3323,7 +3341,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:44 GMT" } ] }, @@ -3340,7 +3358,7 @@ } ], "success": true, - "time": 3, + "time": 7, "cookies": [] }, { @@ -3356,7 +3374,7 @@ "body": "" }, "response": { - "httpVersion": "HTTP/1.0", + "httpVersion": "HTTP/1.1", "status": 200, "cookies": [], "headers": [ @@ -3374,23 +3392,23 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:44 GMT" } ] }, "captures": [], "asserts": [ { - "actual": "1.0", - "expected": "1.0" + "actual": "1.1", + "expected": "*" }, {} ], - "time": 2 + "time": 6 } ], "success": true, - "time": 3, + "time": 7, "cookies": [] }, { @@ -3443,7 +3461,7 @@ }, {} ], - "time": 1 + "time": 5 }, { "request": { @@ -3489,7 +3507,7 @@ } ], "success": true, - "time": 3, + "time": 7, "cookies": [] }, { @@ -3540,7 +3558,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:44 GMT" } ] }, @@ -3554,11 +3572,11 @@ {}, {} ], - "time": 1 + "time": 5 } ], "success": true, - "time": 2, + "time": 6, "cookies": [] }, { @@ -3592,7 +3610,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:44 GMT" } ] }, @@ -3604,11 +3622,11 @@ }, {} ], - "time": 1 + "time": 5 } ], "success": true, - "time": 2, + "time": 5, "cookies": [] }, { @@ -3642,7 +3660,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:44 GMT" } ] }, @@ -3654,11 +3672,11 @@ }, {} ], - "time": 2 + "time": 5 } ], "success": true, - "time": 2, + "time": 6, "cookies": [] }, { @@ -3692,7 +3710,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:45 GMT" } ] }, @@ -3704,7 +3722,7 @@ }, {} ], - "time": 2 + "time": 5 }, { "request": { @@ -3734,7 +3752,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:45 GMT" } ] }, @@ -3776,7 +3794,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:45 GMT" } ] }, @@ -3818,7 +3836,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:45 GMT" } ] }, @@ -3860,7 +3878,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:45 GMT" } ] }, @@ -3902,7 +3920,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:45 GMT" } ] }, @@ -3944,7 +3962,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:45 GMT" } ] }, @@ -3991,7 +4009,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:45 GMT" } ] }, @@ -4001,7 +4019,7 @@ } ], "success": true, - "time": 15, + "time": 17, "cookies": [] }, { @@ -4035,7 +4053,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:45 GMT" } ] }, @@ -4047,7 +4065,7 @@ }, {} ], - "time": 1 + "time": 5 }, { "request": { @@ -4077,7 +4095,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:45 GMT" } ] }, @@ -4124,7 +4142,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:45 GMT" } ] }, @@ -4140,7 +4158,7 @@ } ], "success": true, - "time": 5, + "time": 9, "cookies": [] }, { @@ -4174,7 +4192,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:45 GMT" } ] }, @@ -4186,7 +4204,7 @@ }, {} ], - "time": 2 + "time": 5 }, { "request": { @@ -4216,7 +4234,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:45 GMT" } ] }, @@ -4232,7 +4250,7 @@ } ], "success": true, - "time": 4, + "time": 7, "cookies": [] }, { @@ -4266,7 +4284,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:45 GMT" } ] }, @@ -4282,7 +4300,7 @@ {}, {} ], - "time": 1 + "time": 5 }, { "request": { @@ -4312,7 +4330,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:45 GMT" } ] }, @@ -4330,7 +4348,7 @@ } ], "success": true, - "time": 5, + "time": 8, "cookies": [] }, { @@ -4364,7 +4382,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:45 GMT" } ] }, @@ -4376,11 +4394,11 @@ }, {} ], - "time": 1 + "time": 5 } ], "success": true, - "time": 1, + "time": 5, "cookies": [] }, { @@ -4431,7 +4449,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:45 GMT" } ] }, @@ -4443,7 +4461,7 @@ }, {} ], - "time": 1 + "time": 5 }, { "request": { @@ -4473,7 +4491,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:45 GMT" } ] }, @@ -4528,7 +4546,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:45 GMT" } ] }, @@ -4575,7 +4593,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:45 GMT" } ] }, @@ -4591,7 +4609,7 @@ } ], "success": true, - "time": 7, + "time": 11, "cookies": [] }, { @@ -4629,7 +4647,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:45 GMT" } ] }, @@ -4642,7 +4660,7 @@ {}, {} ], - "time": 2 + "time": 5 }, { "request": { @@ -4672,7 +4690,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:45 GMT" } ] }, @@ -4688,7 +4706,7 @@ } ], "success": true, - "time": 4, + "time": 7, "cookies": [] }, { @@ -4722,7 +4740,7 @@ }, { "name": "Date", - "value": "Thu, 24 Sep 2020 19:02:09 GMT" + "value": "Sun, 11 Oct 2020 14:54:45 GMT" } ] }, @@ -4736,11 +4754,11 @@ {}, {} ], - "time": 1 + "time": 5 } ], "success": true, - "time": 2, + "time": 6, "cookies": [] } ] \ No newline at end of file diff --git a/integration/tests/multipart_form_data.hurl b/integration/tests/multipart_form_data.hurl index 1ed700b4c..88dd22ece 100644 --- a/integration/tests/multipart_form_data.hurl +++ b/integration/tests/multipart_form_data.hurl @@ -6,7 +6,7 @@ upload1: file,hello.txt; upload2: file,hello.html; upload3: file,hello.txt; text/html -HTTP/1.0 200 +HTTP/* 200 diff --git a/src/core/ast.rs b/src/ast/core.rs similarity index 95% rename from src/core/ast.rs rename to src/ast/core.rs index 7bc5e7329..789b355d4 100644 --- a/src/core/ast.rs +++ b/src/ast/core.rs @@ -15,10 +15,12 @@ * limitations under the License. * */ +use super::json; use core::fmt; -use super::common::SourceInfo; -use super::json; +/// +/// Hurl AST +/// #[derive(Clone, Debug, PartialEq, Eq)] pub struct HurlFile { @@ -483,7 +485,7 @@ pub struct Number { // TBC: Issue with equality for f64 // represent your float only with integers -// must be easily compared to the core float value +// must be easily compared to the ast float value #[derive(Clone, Debug, PartialEq, Eq)] pub struct Float { pub int: i64, @@ -534,6 +536,38 @@ pub enum Bytes { }, } +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Pos { + pub line: usize, + pub column: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SourceInfo { + pub start: Pos, + pub end: Pos, +} + +impl SourceInfo { + pub fn init( + start_line: usize, + start_col: usize, + end_line: usize, + end_column: usize, + ) -> SourceInfo { + SourceInfo { + start: Pos { + line: start_line, + column: start_col, + }, + end: Pos { + line: end_line, + column: end_column, + }, + } + } +} + // // template // diff --git a/src/core/json.rs b/src/ast/json.rs similarity index 90% rename from src/core/json.rs rename to src/ast/json.rs index 8d2de9a11..642e26c30 100644 --- a/src/core/json.rs +++ b/src/ast/json.rs @@ -15,7 +15,14 @@ * limitations under the License. * */ -use super::ast::Template; +use super::core::Template; + +/// +/// This the AST for the JSON used within hurl +/// +/// It is a superset of the standard json spec. +/// Strings have been replaced by hurl template. +/// #[derive(Clone, Debug, PartialEq, Eq)] pub enum Value { @@ -65,11 +72,11 @@ pub struct ObjectElement { #[cfg(test)] pub mod tests { - use super::super::ast::{Expr, TemplateElement, Variable, Whitespace}; - use super::super::common::SourceInfo; + use super::super::core::SourceInfo; + use super::super::core::{Expr, TemplateElement, Variable, Whitespace}; use super::*; - pub fn person_value() -> Value { + pub fn json_person_value() -> Value { Value::Object { space0: "\n ".to_string(), elements: vec![ObjectElement { @@ -90,7 +97,7 @@ pub mod tests { } } - pub fn hello_world_value() -> Value { + pub fn json_hello_world_value() -> Value { // "hello\u0020{{name}}!" Value::String(Template { quotes: true, diff --git a/src/core/mod.rs b/src/ast/mod.rs similarity index 69% rename from src/core/mod.rs rename to src/ast/mod.rs index 67567d986..e5704e118 100644 --- a/src/core/mod.rs +++ b/src/ast/mod.rs @@ -15,17 +15,14 @@ * limitations under the License. * */ -pub mod ast; -pub mod common; -pub mod json; -//#[derive(Clone, Debug, PartialEq, Eq)] -//pub struct Pos { -// pub line: usize, -// pub column: usize, -//} -// -//#[derive(Clone, Debug, PartialEq, Eq)] -//pub struct SourceInfo { -// pub start: Pos, -// pub end: Pos, -//} + +pub use self::core::*; +pub use self::json::ListElement as JsonListElement; +pub use self::json::ObjectElement as JsonObjectElement; +pub use self::json::Value as JsonValue; + +#[cfg(test)] +pub use self::json::tests::*; + +mod core; +mod json; diff --git a/src/bin/hurl.rs b/src/bin/hurl.rs index 9aa070f47..3679f6a65 100644 --- a/src/bin/hurl.rs +++ b/src/bin/hurl.rs @@ -28,14 +28,11 @@ use chrono::{DateTime, Local}; use clap::{AppSettings, ArgMatches}; use hurl::cli; -use hurl::core::common::FormatError; -use hurl::format; use hurl::html; use hurl::http; use hurl::parser; use hurl::runner; -use hurl::runner::core::*; -use hurl::runner::log_deserialize; +use hurl::runner::{HurlResult, RunnerOptions}; use std::time::Duration; #[derive(Clone, Debug, PartialEq, Eq)] @@ -61,43 +58,49 @@ fn execute( current_dir: &Path, file_root: Option, cli_options: CLIOptions, - logger: format::logger::Logger, + log_verbose: &impl Fn(&str), + log_error_message: &impl Fn(bool, &str), ) -> HurlResult { + let lines: Vec = regex::Regex::new(r"\n|\r\n") + .unwrap() + .split(&contents) + .map(|l| l.to_string()) + .collect(); + let optional_filename = if filename == "" { + None + } else { + Some(filename.to_string()) + }; + let log_parser_error = + cli::make_logger_parser_error(lines.clone(), cli_options.color, optional_filename.clone()); + let log_runner_error = + cli::make_logger_runner_error(lines, cli_options.color, optional_filename); match parser::parse_hurl_file(contents.as_str()) { Err(e) => { - let error = hurl::format::error::Error { - source_info: e.source_info(), - description: e.description(), - fixme: e.fixme(), - lines: vec![], - filename: "".to_string(), - warning: false, - color: logger.color, - }; - logger.error(&error); + log_parser_error(&e, false); std::process::exit(2); } Ok(hurl_file) => { - logger.verbose(format!("fail fast: {}", cli_options.fail_fast).as_str()); - logger.verbose(format!("insecure: {}", cli_options.insecure).as_str()); - logger.verbose(format!("follow redirect: {}", cli_options.follow_location).as_str()); + log_verbose(format!("fail fast: {}", cli_options.fail_fast).as_str()); + log_verbose(format!("insecure: {}", cli_options.insecure).as_str()); + log_verbose(format!("follow redirect: {}", cli_options.follow_location).as_str()); if let Some(n) = cli_options.max_redirect { - logger.verbose(format!("max redirect: {}", n).as_str()); + log_verbose(format!("max redirect: {}", n).as_str()); } if let Some(proxy) = cli_options.proxy.clone() { - logger.verbose(format!("proxy: {}", proxy).as_str()); + log_verbose(format!("proxy: {}", proxy).as_str()); } if !cli_options.variables.is_empty() { - logger.verbose("variables:"); + log_verbose("variables:"); for (name, value) in cli_options.variables.clone() { - logger.verbose(format!(" {}={}", name, value).as_str()); + log_verbose(format!(" {}={}", name, value).as_str()); } } if let Some(to_entry) = cli_options.to_entry { if to_entry < hurl_file.entries.len() { - logger.verbose( + log_verbose( format!( "executing {}/{} entries", to_entry.to_string(), @@ -106,7 +109,7 @@ fn execute( .as_str(), ); } else { - logger.verbose("executing all entries"); + log_verbose("executing all entries"); } } @@ -145,19 +148,20 @@ fn execute( } Some(filename) => filename, }; - let options = RunnerOptions { fail_fast: cli_options.fail_fast, variables: cli_options.variables, to_entry: cli_options.to_entry, + context_dir, }; - runner::file::run( + runner::run_hurl_file( hurl_file, &mut client, filename.to_string(), - context_dir, options, - logger, + &log_verbose, + &log_error_message, + &log_runner_error, ) } } @@ -173,63 +177,57 @@ fn output_color(matches: ArgMatches) -> bool { } } -fn to_entry(matches: ArgMatches, logger: format::logger::Logger) -> Option { +fn to_entry(matches: ArgMatches) -> Result, cli::CLIError> { match matches.value_of("to_entry") { Some(value) => match value.parse() { - Ok(v) => Some(v), - Err(_) => { - logger.error_message( - "Invalid value for option --to-entry - must be a positive integer!".to_string(), - ); - std::process::exit(1); - } + Ok(v) => Ok(Some(v)), + Err(_) => Err(cli::CLIError { + message: "Invalid value for option --to-entry - must be a positive integer!" + .to_string(), + }), }, - None => None, + None => Ok(None), } } fn json_file( matches: ArgMatches, - logger: format::logger::Logger, -) -> (Vec, Option) { + log_verbose: impl Fn(&str), +) -> Result<(Vec, Option), cli::CLIError> { if let Some(filename) = matches.value_of("json") { let path = Path::new(filename); let results = if matches.is_present("append") && std::path::Path::new(&path).exists() { - logger.verbose(format!("Appending session to {}", path.display()).as_str()); + log_verbose(format!("Appending session to {}", path.display()).as_str()); let data = fs::read_to_string(path).unwrap(); let v: serde_json::Value = match serde_json::from_str(data.as_str()) { - Ok(v) => v, + Ok(val) => val, Err(_) => { - logger.error_message(format!( - "The file {} is not a valid json file", - path.display() - )); - std::process::exit(127); + return Err(cli::CLIError { + message: format!("The file {} is not a valid json file", path.display()), + }); } }; - match log_deserialize::parse_results(v) { + match runner::deserialize_results(v) { Err(msg) => { - logger - .error_message(format!("Existing Hurl json can not be parsed! - {}", msg)); - std::process::exit(127); + return Err(cli::CLIError { + message: format!("Existing Hurl json can not be parsed! - {}", msg), + }); } Ok(results) => results, } } else { - if matches.is_present("verbose") { - logger.error_message(format!("* Writing session to {}", path.display())); - } + log_verbose(format!("* Writing session to {}", path.display()).as_str()); vec![] }; - (results, Some(path.to_path_buf())) + Ok((results, Some(path.to_path_buf()))) } else { - (vec![], None) + Ok((vec![], None)) } } -fn html_report(matches: ArgMatches, logger: format::logger::Logger) -> Option { +fn html_report(matches: ArgMatches) -> Result, cli::CLIError> { if let Some(dir) = matches.value_of("html_report") { let path = Path::new(dir); if std::path::Path::new(&path).exists() { @@ -238,48 +236,51 @@ fn html_report(matches: ArgMatches, logger: format::logger::Logger) -> Option { - logger.error_message(format!("Html dir {} can not be created", path.display())); - std::process::exit(127) - } - Ok(_) => Some(path.to_path_buf()), + Err(_) => Err(cli::CLIError { + message: format!("Html dir {} can not be created", path.display()), + }), + Ok(_) => Ok(Some(path.to_path_buf())), } } } else { - None + Ok(None) } } -fn variables(matches: ArgMatches, logger: format::logger::Logger) -> HashMap { +fn variables(matches: ArgMatches) -> Result, cli::CLIError> { let mut variables = HashMap::new(); if matches.is_present("variable") { let input: Vec<_> = matches.values_of("variable").unwrap().collect(); for s in input { match s.find('=') { None => { - logger.error_message(format!("Missing variable value for {}!", s)); - std::process::exit(1); + return Err(cli::CLIError { + message: format!("Missing variable value for {}!", s), + }); } Some(index) => { let (name, value) = s.split_at(index); if variables.contains_key(name) { - std::process::exit(1); + return Err(cli::CLIError { + message: format!("Variable {} defined twice!", name), + }); } variables.insert(name.to_string(), value[1..].to_string()); } }; } } - variables + Ok(variables) } fn app() -> clap::App<'static, 'static> { @@ -437,25 +438,25 @@ fn app() -> clap::App<'static, 'static> { ) } -pub fn unwrap_or_exit(result: Result, logger: format::logger::Logger) -> T { +pub fn unwrap_or_exit( + log_error_message: &impl Fn(bool, &str), + result: Result, +) -> T { match result { Ok(v) => v, Err(e) => { - logger.error_message(e.message); + log_error_message(false, e.message.as_str()); std::process::exit(127); } } } -fn parse_options( - matches: ArgMatches, - logger: format::logger::Logger, -) -> Result { +fn parse_options(matches: ArgMatches) -> Result { let verbose = matches.is_present("verbose"); let color = output_color(matches.clone()); let fail_fast = !matches.is_present("fail_at_end"); - let variables = variables(matches.clone(), logger.clone()); - let to_entry = to_entry(matches.clone(), logger); + let variables = variables(matches.clone())?; + let to_entry = to_entry(matches.clone())?; let proxy = matches.value_of("proxy").map(|x| x.to_string()); let no_proxy = matches.value_of("proxy").map(|x| x.to_string()); let insecure = matches.is_present("insecure"); @@ -467,7 +468,7 @@ fn parse_options( Some(s) => match s.parse::() { Ok(x) => Some(x), Err(_) => { - return Err(cli::Error { + return Err(cli::CLIError { message: "max_redirs option can not be parsed".to_string(), }); } @@ -479,7 +480,7 @@ fn parse_options( Some(s) => match s.parse::() { Ok(n) => Duration::from_secs(n), Err(_) => { - return Err(cli::Error { + return Err(cli::CLIError { message: "max_time option can not be parsed".to_string(), }); } @@ -491,7 +492,7 @@ fn parse_options( Some(s) => match s.parse::() { Ok(n) => Duration::from_secs(n), Err(_) => { - return Err(cli::Error { + return Err(cli::CLIError { message: "connect-timeout option can not be parsed".to_string(), }); } @@ -515,7 +516,7 @@ fn parse_options( }) } -fn main() -> Result<(), cli::Error> { +fn main() { let app = app(); let matches = app.clone().get_matches(); @@ -540,32 +541,26 @@ fn main() -> Result<(), cli::Error> { Some(value) => Some(value.to_string()), _ => None, }; + let verbose = matches.is_present("verbose"); + let log_verbose = cli::make_logger_verbose(verbose); + let color = output_color(matches.clone()); + let log_error_message = cli::make_logger_error_message(color); + let cli_options = unwrap_or_exit(&log_error_message, parse_options(matches.clone())); - let logger = format::logger::Logger { - filename: None, - lines: vec![], - verbose: matches.is_present("verbose"), - color: output_color(matches.clone()), - }; - - let (mut hurl_results, json_file) = json_file(matches.clone(), logger.clone()); - let html_report = html_report(matches.clone(), logger.clone()); + let (mut hurl_results, json_file) = + unwrap_or_exit(&log_error_message, json_file(matches.clone(), &log_verbose)); + let html_report = unwrap_or_exit(&log_error_message, html_report(matches.clone())); let cookies_output_file = match matches.value_of("cookies_output_file") { None => None, Some(filename) => { let filename = unwrap_or_exit( - cli::options::cookies_output_file(filename.to_string(), filenames.len()), - logger.clone(), + &log_error_message, + cli::cookies_output_file(filename.to_string(), filenames.len()), ); Some(filename) } }; - let cli_options = unwrap_or_exit( - parse_options(matches.clone(), logger.clone()), - logger.clone(), - ); - for filename in filenames { let contents = if filename == "-" { let mut contents = String::new(); @@ -575,30 +570,22 @@ fn main() -> Result<(), cli::Error> { contents } else { if !Path::new(filename).exists() { - logger.error_message(format!("Input file {} does not exit!", filename)); + log_error_message( + false, + format!("Input file {} does not exit!", filename).as_str(), + ); std::process::exit(1); } fs::read_to_string(filename).expect("Something went wrong reading the file") }; - let lines: Vec = regex::Regex::new(r"\n|\r\n") - .unwrap() - .split(&contents) - .map(|l| l.to_string()) - .collect(); - let logger = format::logger::Logger { - filename: Some(filename.to_string()), - lines: lines.clone(), - verbose: cli_options.verbose, - color: cli_options.color, - }; - let hurl_result = execute( filename, contents, current_dir, file_root.clone(), cli_options.clone(), - logger.clone(), + &log_verbose, + &log_error_message, ); if hurl_result.errors().is_empty() { @@ -607,7 +594,7 @@ fn main() -> Result<(), cli::Error> { if let Some(entry_result) = hurl_result.entries.last() { if let Some(response) = entry_result.response.clone() { if matches.is_present("include") { - logger.info( + cli::log_info( format!( "HTTP/{} {}", response.version.to_string(), @@ -616,24 +603,28 @@ fn main() -> Result<(), cli::Error> { .as_str(), ); for header in response.headers.clone() { - logger.info(format!("{}: {}", header.name, header.value).as_str()); + cli::log_info(format!("{}: {}", header.name, header.value).as_str()); } - logger.info(""); + cli::log_info(""); } - write_output(response.body, matches.value_of("output"), logger.clone()); + unwrap_or_exit( + &log_error_message, + write_output(response.body, matches.value_of("output")), + ); } else { - logger.warning_message("no response has been received".to_string()); + cli::log_info("no response has been received"); } } else { - logger.warning_message(format!( - "warning: no entry have been executed {}", - if filename == "-" { - "".to_string() - } else { - format!("for file {}", filename) - } - )); + let source = if filename == "-" { + "".to_string() + } else { + format!("for file {}", filename).to_string() + }; + log_error_message( + true, + format!("no entry have been executed {}", source).as_str(), + ); }; } @@ -643,34 +634,38 @@ fn main() -> Result<(), cli::Error> { if let Some(file_path) = json_file { let mut file = match std::fs::File::create(&file_path) { Err(why) => { - logger.error_message(format!( - "Issue writing to {}: {:?}", - file_path.display(), - why - )); + log_error_message( + false, + format!("Issue writing to {}: {:?}", file_path.display(), why).as_str(), + ); std::process::exit(127) } Ok(file) => file, }; let serialized = serde_json::to_string_pretty(&hurl_results).unwrap(); if let Err(why) = file.write_all(serialized.as_bytes()) { - logger.error_message(format!( - "Issue writing to {}: {:?}", - file_path.display(), - why - )); + log_error_message( + false, + format!("Issue writing to {}: {:?}", file_path.display(), why).as_str(), + ); std::process::exit(127) } } if let Some(dir_path) = html_report { - logger.verbose(format!("Writing html report to {}", dir_path.display()).as_str()); - write_html_report(dir_path, hurl_results.clone(), logger.clone()); + log_verbose(format!("Writing html report to {}", dir_path.display()).as_str()); + unwrap_or_exit( + &log_error_message, + write_html_report(dir_path, hurl_results.clone()), + ); } if let Some(file_path) = cookies_output_file { - logger.verbose(format!("Writing cookies to {}", file_path.display()).as_str()); - write_cookies_file(file_path, hurl_results.clone(), logger); + log_verbose(format!("Writing cookies to {}", file_path.display()).as_str()); + unwrap_or_exit( + &log_error_message, + write_cookies_file(file_path, hurl_results.clone()), + ); } std::process::exit(exit_code(hurl_results)); @@ -680,7 +675,7 @@ fn exit_code(hurl_results: Vec) -> i32 { let mut count_errors_runner = 0; let mut count_errors_assert = 0; for hurl_result in hurl_results.clone() { - let runner_errors: Vec = hurl_result + let runner_errors: Vec = hurl_result .clone() .errors() .iter() @@ -704,7 +699,7 @@ fn exit_code(hurl_results: Vec) -> i32 { } } -fn write_output(bytes: Vec, filename: Option<&str>, logger: format::logger::Logger) { +fn write_output(bytes: Vec, filename: Option<&str>) -> Result<(), cli::CLIError> { match filename { None => { let stdout = io::stdout(); @@ -713,18 +708,21 @@ fn write_output(bytes: Vec, filename: Option<&str>, logger: format::logger:: handle .write_all(bytes.as_slice()) .expect("writing bytes to console"); + Ok(()) } Some(filename) => { let path = Path::new(filename); let mut file = match std::fs::File::create(&path) { Err(why) => { - logger.error_message(format!("Issue writing to {}: {:?}", path.display(), why)); - std::process::exit(127) + return Err(cli::CLIError { + message: format!("Issue writing to {}: {:?}", path.display(), why), + }); } Ok(file) => file, }; file.write_all(bytes.as_slice()) .expect("writing bytes to file"); + Ok(()) } } } @@ -732,16 +730,12 @@ fn write_output(bytes: Vec, filename: Option<&str>, logger: format::logger:: fn write_cookies_file( file_path: PathBuf, hurl_results: Vec, - logger: format::logger::Logger, -) { +) -> Result<(), cli::CLIError> { let mut file = match std::fs::File::create(&file_path) { Err(why) => { - logger.error_message(format!( - "Issue writing to {}: {:?}", - file_path.display(), - why - )); - std::process::exit(127) + return Err(cli::CLIError { + message: format!("Issue writing to {}: {:?}", file_path.display(), why), + }); } Ok(file) => file, }; @@ -752,8 +746,9 @@ fn write_cookies_file( .to_string(); match hurl_results.first() { None => { - logger.error_message("Issue fetching results".to_string()); - std::process::exit(127) + return Err(cli::CLIError { + message: "Issue fetching results".to_string(), + }); } Some(result) => { for cookie in result.cookies.clone() { @@ -764,20 +759,17 @@ fn write_cookies_file( } if let Err(why) = file.write_all(s.as_bytes()) { - logger.error_message(format!( - "Issue writing to {}: {:?}", - file_path.display(), - why - )); - std::process::exit(127) + return Err(cli::CLIError { + message: format!("Issue writing to {}: {:?}", file_path.display(), why), + }); } + Ok(()) } fn write_html_report( dir_path: PathBuf, hurl_results: Vec, - logger: format::logger::Logger, -) { +) -> Result<(), cli::CLIError> { //let now: DateTime = Utc::now(); let now: DateTime = Local::now(); let html = create_html_index(now.to_rfc2822(), hurl_results); @@ -786,65 +778,54 @@ fn write_html_report( let file_path = dir_path.join("index.html"); let mut file = match std::fs::File::create(&file_path) { Err(why) => { - logger.error_message(format!( - "Issue writing to {}: {:?}", - file_path.display(), - why - )); - std::process::exit(127) + return Err(cli::CLIError { + message: format!("Issue writing to {}: {:?}", file_path.display(), why), + }); } Ok(file) => file, }; if let Err(why) = file.write_all(s.as_bytes()) { - logger.error_message(format!( - "Issue writing to {}: {:?}", - file_path.display(), - why - )); - std::process::exit(127) + return Err(cli::CLIError { + message: format!("Issue writing to {}: {:?}", file_path.display(), why), + }); } let file_path = dir_path.join("report.css"); let mut file = match std::fs::File::create(&file_path) { Err(why) => { - logger.error_message(format!( - "Issue writing to {}: {:?}", - file_path.display(), - why - )); - std::process::exit(127) + return Err(cli::CLIError { + message: format!("Issue writing to {}: {:?}", file_path.display(), why), + }); } Ok(file) => file, }; if let Err(why) = file.write_all(include_bytes!("report.css")) { - logger.error_message(format!( - "Issue writing to {}: {:?}", - file_path.display(), - why - )); - std::process::exit(127) + return Err(cli::CLIError { + message: format!("Issue writing to {}: {:?}", file_path.display(), why), + }); } + Ok(()) } -fn create_html_index(now: String, hurl_results: Vec) -> html::ast::Html { - let head = html::ast::Head { +fn create_html_index(now: String, hurl_results: Vec) -> html::Html { + let head = html::Head { title: "Hurl Report".to_string(), stylesheet: Some("report.css".to_string()), }; - let body = html::ast::Body { + let body = html::Body { children: vec![ - html::ast::Element::NodeElement { + html::Element::NodeElement { name: "h2".to_string(), attributes: vec![], - children: vec![html::ast::Element::TextElement("Hurl Report".to_string())], + children: vec![html::Element::TextElement("Hurl Report".to_string())], }, - html::ast::Element::NodeElement { + html::Element::NodeElement { name: "div".to_string(), - attributes: vec![html::ast::Attribute::Class("date".to_string())], - children: vec![html::ast::Element::TextElement(now)], + attributes: vec![html::Attribute::Class("date".to_string())], + children: vec![html::Element::TextElement(now)], }, - html::ast::Element::NodeElement { + html::Element::NodeElement { name: "table".to_string(), attributes: vec![], children: vec![ @@ -854,64 +835,64 @@ fn create_html_index(now: String, hurl_results: Vec) -> html::ast::H }, ], }; - html::ast::Html { head, body } + html::Html { head, body } } -fn create_html_table_header() -> html::ast::Element { - html::ast::Element::NodeElement { +fn create_html_table_header() -> html::Element { + html::Element::NodeElement { name: "thead".to_string(), attributes: vec![], - children: vec![html::ast::Element::NodeElement { + children: vec![html::Element::NodeElement { name: "tr".to_string(), attributes: vec![], children: vec![ - html::ast::Element::NodeElement { + html::Element::NodeElement { name: "td".to_string(), attributes: vec![], - children: vec![html::ast::Element::TextElement("filename".to_string())], + children: vec![html::Element::TextElement("filename".to_string())], }, - html::ast::Element::NodeElement { + html::Element::NodeElement { name: "td".to_string(), attributes: vec![], - children: vec![html::ast::Element::TextElement("duration".to_string())], + children: vec![html::Element::TextElement("duration".to_string())], }, ], }], } } -fn create_html_table_body(hurl_results: Vec) -> html::ast::Element { +fn create_html_table_body(hurl_results: Vec) -> html::Element { let children = hurl_results .iter() .map(|result| create_html_result(result.clone())) .collect(); - html::ast::Element::NodeElement { + html::Element::NodeElement { name: "tbody".to_string(), attributes: vec![], children, } } -fn create_html_result(result: HurlResult) -> html::ast::Element { +fn create_html_result(result: HurlResult) -> html::Element { let status = if result.success { "success".to_string() } else { "failure".to_string() }; - html::ast::Element::NodeElement { + html::Element::NodeElement { name: "tr".to_string(), attributes: vec![], children: vec![ - html::ast::Element::NodeElement { + html::Element::NodeElement { name: "td".to_string(), - attributes: vec![html::ast::Attribute::Class(status)], - children: vec![html::ast::Element::TextElement(result.filename.clone())], + attributes: vec![html::Attribute::Class(status)], + children: vec![html::Element::TextElement(result.filename.clone())], }, - html::ast::Element::NodeElement { + html::Element::NodeElement { name: "td".to_string(), attributes: vec![], - children: vec![html::ast::Element::TextElement(format!( + children: vec![html::Element::TextElement(format!( "{}s", result.time_in_ms as f64 / 1000.0 ))], diff --git a/src/bin/hurlfmt.rs b/src/bin/hurlfmt.rs index 16691c77a..d9bb7aef5 100644 --- a/src/bin/hurlfmt.rs +++ b/src/bin/hurlfmt.rs @@ -25,10 +25,9 @@ use std::process; use atty::Stream; -use hurl::core::common::FormatError; -use hurl::format::html; -use hurl::format::text; -use hurl::linter::core::Lintable; +use hurl::cli; +use hurl::format; +use hurl::linter::Lintable; use hurl::parser; fn main() { @@ -123,6 +122,8 @@ fn main() { atty::is(Stream::Stdout) }; + let log_error_message = cli::make_logger_error_message(output_color); + let filename = match matches.value_of("INPUT") { None => "-", Some("-") => "-", @@ -140,7 +141,7 @@ fn main() { }; if matches.is_present("in_place") && filename == "-" { - eprintln!("You can not use inplace with standard input stream!"); + log_error_message(false, "You can not use inplace with standard input stream!"); std::process::exit(1); }; @@ -159,43 +160,31 @@ fn main() { .split(&contents) .collect(); - let lines = lines.iter().map(|s| (*s).to_string()).collect(); + let lines: Vec = lines.iter().map(|s| (*s).to_string()).collect(); + let optional_filename = if filename == "" { + None + } else { + Some(filename.to_string()) + }; + let log_parser_error = + cli::make_logger_parser_error(lines.clone(), output_color, optional_filename.clone()); + let log_linter_error = cli::make_logger_linter_error(lines, output_color, optional_filename); match parser::parse_hurl_file(contents.as_str()) { Err(e) => { - //eprintln!("Error {:?}", e); - - let error = hurl::format::error::Error { - source_info: e.source_info(), - description: e.description(), - fixme: e.fixme(), - lines, - filename: filename.to_string(), - warning: true, - color: output_color, - }; - eprintln!("{}", error.format()); + log_parser_error(&e, false); process::exit(2); } Ok(hurl_file) => { if matches.is_present("check") { for e in hurl_file.errors() { - let error = hurl::format::error::Error { - source_info: e.source_info(), - description: e.description(), - fixme: e.fixme(), - lines: lines.clone(), - filename: filename.to_string(), - warning: true, - color: output_color, - }; - eprintln!("{}", error.format()); + log_linter_error(&e, true); } std::process::exit(1); } else if matches.is_present("ast_output") { eprintln!("{:#?}", hurl_file); } else if matches.is_present("html_output") { let standalone = matches.is_present("standalone"); - println!("{}", html::format(hurl_file, standalone)); + println!("{}", format::format_html(hurl_file, standalone)); } else { let hurl_file = if matches.is_present("no_format") { hurl_file @@ -205,13 +194,13 @@ fn main() { if matches.is_present("in_place") { match fs::File::create(filename) { Ok(mut f) => { - let s = text::format(hurl_file, false); + let s = format::format_text(hurl_file, false); f.write_all(s.as_bytes()).unwrap(); } Err(_) => eprintln!("Error opening file {} in write mode", filename), }; } else { - print!("{}", text::format(hurl_file, output_color)); + print!("{}", format::format_text(hurl_file, output_color)); }; } } diff --git a/src/cli/error.rs b/src/cli/error.rs new file mode 100644 index 000000000..a4d990057 --- /dev/null +++ b/src/cli/error.rs @@ -0,0 +1,227 @@ +use crate::ast::SourceInfo; +use crate::linter; +use crate::linter::LinterError; +use crate::parser; +use crate::parser::ParseError; +use crate::runner; +use crate::runner::RunnerError; + +pub trait Error { + fn source_info(&self) -> SourceInfo; + fn description(&self) -> String; + fn fixme(&self) -> String; +} + +/// +/// Textual Output for parser errors +/// + +impl Error for parser::Error { + fn source_info(&self) -> SourceInfo { + SourceInfo { + start: self.pos.clone(), + end: self.pos.clone(), + } + } + + fn description(&self) -> String { + match self.clone().inner { + ParseError::Method { .. } => "Parsing Method".to_string(), + ParseError::Version { .. } => "Parsing Version".to_string(), + ParseError::Status { .. } => "Parsing Status".to_string(), + ParseError::Filename { .. } => "Parsing Filename".to_string(), + ParseError::Expecting { .. } => "Parsing literal".to_string(), + ParseError::Space { .. } => "Parsing space".to_string(), + ParseError::SectionName { .. } => "Parsing section name".to_string(), + ParseError::JsonpathExpr { .. } => "Parsing jsonpath expression".to_string(), + ParseError::XPathExpr { .. } => "Parsing xpath expression".to_string(), + ParseError::TemplateVariable { .. } => "Parsing template variable".to_string(), + ParseError::Json { .. } => "Parsing json".to_string(), + ParseError::Predicate { .. } => "Parsing predicate".to_string(), + ParseError::PredicateValue { .. } => "Parsing predicate value".to_string(), + ParseError::RegexExpr { .. } => "Parsing regex".to_string(), + ParseError::DuplicateSection { .. } => "Parsing section".to_string(), + ParseError::RequestSection { .. } => "Parsing section".to_string(), + ParseError::ResponseSection { .. } => "Parsing section".to_string(), + ParseError::EscapeChar { .. } => "Parsing escape character".to_string(), + ParseError::InvalidCookieAttribute { .. } => "Parsing cookie attribute".to_string(), + _ => format!("{:?}", self), + } + } + + fn fixme(&self) -> String { + match self.inner.clone() { + ParseError::Method { .. } => "Available HTTP Method GET, POST, ...".to_string(), + ParseError::Version { .. } => "The http version must be 1.0, 1.1, 2 or *".to_string(), + ParseError::Status { .. } => "The http status is not valid".to_string(), + ParseError::Filename { .. } => "expecting a filename".to_string(), + ParseError::Expecting { value } => format!("expecting '{}'", value), + ParseError::Space { .. } => "expecting a space".to_string(), + ParseError::SectionName { name } => format!("the section {} is not valid", name), + ParseError::JsonpathExpr { .. } => "expecting a jsonpath expression".to_string(), + ParseError::XPathExpr { .. } => "expecting a xpath expression".to_string(), + ParseError::TemplateVariable { .. } => "expecting a variable".to_string(), + ParseError::Json { .. } => "json error".to_string(), + ParseError::Predicate { .. } => "expecting a predicate".to_string(), + ParseError::PredicateValue { .. } => "invalid predicate value".to_string(), + ParseError::RegexExpr { .. } => "Invalid Regex expression".to_string(), + ParseError::DuplicateSection { .. } => "The section is already defined".to_string(), + ParseError::RequestSection { .. } => { + "This is not a valid section for a request".to_string() + } + ParseError::ResponseSection { .. } => { + "This is not a valid section for a response".to_string() + } + ParseError::EscapeChar { .. } => "The escaping sequence is not valid".to_string(), + ParseError::InvalidCookieAttribute { .. } => { + "The cookie attribute is not valid".to_string() + } + _ => format!("{:?}", self), + } + } +} + +/// +/// Textual Output for runner errors +/// +/// + +impl Error for runner::Error { + fn source_info(&self) -> SourceInfo { + self.clone().source_info + } + + fn description(&self) -> String { + match &self.inner { + RunnerError::InvalidURL(..) => "Invalid url".to_string(), + RunnerError::TemplateVariableNotDefined { .. } => "Undefined Variable".to_string(), + RunnerError::VariableNotDefined { .. } => "Undefined Variable".to_string(), + RunnerError::HttpConnection { .. } => "Http Connection".to_string(), + RunnerError::CouldNotResolveProxyName => "Http Connection".to_string(), + RunnerError::CouldNotResolveHost => "Http Connection".to_string(), + RunnerError::FailToConnect => "Http Connection".to_string(), + RunnerError::Timeout => "Http Connection".to_string(), + RunnerError::TooManyRedirect => "Http Connection".to_string(), + RunnerError::CouldNotParseResponse => "Http Connection".to_string(), + RunnerError::SSLCertificate => "Http Connection".to_string(), + RunnerError::PredicateValue { .. } => "Assert - Predicate Value Failed".to_string(), + RunnerError::InvalidRegex {} => "Invalid regex".to_string(), + RunnerError::FileReadAccess { .. } => "File ReadAccess".to_string(), + RunnerError::QueryInvalidXml { .. } => "Invalid XML".to_string(), + RunnerError::QueryInvalidXpathEval {} => "Invalid xpath expression".to_string(), + RunnerError::QueryHeaderNotFound {} => "Header not Found".to_string(), + RunnerError::QueryCookieNotFound {} => "Cookie not Found".to_string(), + RunnerError::AssertHeaderValueError { .. } => "Assert Header Value".to_string(), + RunnerError::AssertBodyValueError { .. } => "Assert Body Value".to_string(), + RunnerError::AssertVersion { .. } => "Assert Http Version".to_string(), + RunnerError::AssertStatus { .. } => "Assert Status".to_string(), + RunnerError::QueryInvalidJson { .. } => "Invalid Json".to_string(), + RunnerError::QueryInvalidJsonpathExpression { .. } => "Invalid jsonpath".to_string(), + RunnerError::PredicateType { .. } => "Assert - Inconsistent predicate type".to_string(), + RunnerError::SubqueryInvalidInput { .. } => "Subquery error".to_string(), + RunnerError::InvalidDecoding { .. } => "Invalid Decoding".to_string(), + RunnerError::InvalidCharset { .. } => "Invalid Charset".to_string(), + RunnerError::AssertFailure { .. } => "Assert Failure".to_string(), + RunnerError::UnrenderableVariable { .. } => "Unrenderable Variable".to_string(), + RunnerError::NoQueryResult { .. } => "No query result".to_string(), + } + } + + fn fixme(&self) -> String { + match &self.inner { + RunnerError::InvalidURL(url) => format!("Invalid url <{}>", url), + RunnerError::TemplateVariableNotDefined { name } => { + format!("You must set the variable {}", name) + } + RunnerError::HttpConnection { url, message } => { + format!("can not connect to {} ({})", url, message) + } + RunnerError::CouldNotResolveProxyName => "Could not resolve proxy name".to_string(), + RunnerError::CouldNotResolveHost => "Could not resolve host".to_string(), + RunnerError::FailToConnect => "Fail to connect".to_string(), + RunnerError::Timeout => "Timeout has been reached".to_string(), + RunnerError::TooManyRedirect => "Too many redirect".to_string(), + RunnerError::CouldNotParseResponse => "Could not parse response".to_string(), + RunnerError::SSLCertificate => "SSl Certificate".to_string(), + RunnerError::AssertVersion { actual, .. } => format!("actual value is <{}>", actual), + RunnerError::AssertStatus { actual, .. } => format!("actual value is <{}>", actual), + RunnerError::PredicateValue(value) => { + format!("actual value is <{}>", value.to_string()) + } + RunnerError::InvalidRegex {} => "Regex expression is not valid".to_string(), + RunnerError::FileReadAccess { value } => format!("File {} can not be read", value), + RunnerError::QueryInvalidXml { .. } => { + "The Http response is not a valid XML".to_string() + } + RunnerError::QueryHeaderNotFound {} => { + "This header has not been found in the response".to_string() + } + RunnerError::QueryCookieNotFound {} => { + "This cookie has not been found in the response".to_string() + } + RunnerError::QueryInvalidXpathEval {} => { + "The xpath expression is not valid".to_string() + } + RunnerError::AssertHeaderValueError { actual } => { + format!("actual value is <{}>", actual) + } + RunnerError::AssertBodyValueError { actual, .. } => { + format!("actual value is <{}>", actual) + } + RunnerError::QueryInvalidJson { .. } => { + "The http response is not a valid json".to_string() + } + RunnerError::QueryInvalidJsonpathExpression { value } => { + format!("the jsonpath expression '{}' is not valid", value) + } + RunnerError::PredicateType { .. } => { + "predicate type inconsistent with value return by query".to_string() + } + RunnerError::SubqueryInvalidInput => { + "Type from query result and subquery do not match".to_string() + } + RunnerError::InvalidDecoding { charset } => { + format!("The body can not be decoded with charset '{}'", charset) + } + RunnerError::InvalidCharset { charset } => { + format!("The charset '{}' is not valid", charset) + } + RunnerError::AssertFailure { + actual, expected, .. + } => format!("actual: {}\nexpected: {}", actual, expected), + RunnerError::VariableNotDefined { name } => { + format!("You must set the variable {}", name) + } + RunnerError::UnrenderableVariable { value } => { + format!("value {} can not be rendered", value) + } + RunnerError::NoQueryResult { .. } => "The query didn't return any result".to_string(), + } + } +} + +/// +/// Textual Output for linter errors +/// +/// +impl Error for linter::Error { + fn source_info(&self) -> SourceInfo { + self.clone().source_info + } + + fn description(&self) -> String { + match self.inner { + LinterError::UnneccessarySpace { .. } => "Unnecessary space".to_string(), + LinterError::UnneccessaryJsonEncoding {} => "Unnecessary json encoding".to_string(), + LinterError::OneSpace {} => "One space ".to_string(), + } + } + + fn fixme(&self) -> String { + match self.inner { + LinterError::UnneccessarySpace { .. } => "Remove space".to_string(), + LinterError::UnneccessaryJsonEncoding {} => "Use Simple String".to_string(), + LinterError::OneSpace {} => "Use only one space".to_string(), + } + } +} diff --git a/src/cli/logger.rs b/src/cli/logger.rs new file mode 100644 index 000000000..67ecaad19 --- /dev/null +++ b/src/cli/logger.rs @@ -0,0 +1,175 @@ +/* + * hurl (https://hurl.dev) + * Copyright (C) 2020 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::error::Error; +use crate::format::TerminalColor; +use crate::linter; +use crate::parser; +use crate::runner; + +pub fn make_logger_verbose(verbose: bool) -> impl Fn(&str) { + move |message| log_verbose(verbose, message) +} + +pub fn make_logger_error_message(color: bool) -> impl Fn(bool, &str) { + move |warning, message| log_error_message(color, warning, message) +} + +pub fn make_logger_parser_error( + lines: Vec, + color: bool, + filename: Option, +) -> impl Fn(&parser::Error, bool) { + move |error: &parser::Error, warning: bool| { + log_error(lines.clone(), color, filename.clone(), error, warning) + } +} + +pub fn make_logger_runner_error( + lines: Vec, + color: bool, + filename: Option, +) -> impl Fn(&runner::Error, bool) { + move |error: &runner::Error, warning: bool| { + log_error(lines.clone(), color, filename.clone(), error, warning) + } +} + +pub fn make_logger_linter_error( + lines: Vec, + color: bool, + filename: Option, +) -> impl Fn(&linter::Error, bool) { + move |error: &linter::Error, warning: bool| { + log_error(lines.clone(), color, filename.clone(), error, warning) + } +} + +pub fn log_info(message: &str) { + eprintln!("{}", message); +} + +fn log_error_message(color: bool, warning: bool, message: &str) { + let log_type = match (color, warning) { + (false, false) => "warning".to_string(), + (false, true) => "error".to_string(), + (true, false) => TerminalColor::Red.format("error".to_string()), + (true, true) => TerminalColor::Yellow.format("warning".to_string()), + }; + eprintln!("{}: {}", log_type, message); +} + +fn log_verbose(verbose: bool, message: &str) { + if verbose { + eprintln!("* {}", message); + } +} + +fn log_error( + lines: Vec, + color: bool, + filename: Option, + error: &dyn Error, + warning: bool, +) { + let line_number_size = if lines.len() < 100 { + 2 + } else if lines.len() < 1000 { + 3 + } else { + 4 + }; + + let error_type = if warning { + String::from("warning") + } else { + String::from("error") + }; + let error_type = if !color { + error_type + } else if warning { + TerminalColor::Yellow.format(error_type) + } else { + TerminalColor::Red.format(error_type) + }; + eprintln!("{}: {}", error_type, error.description()); + + if let Some(filename) = filename { + eprintln!( + "{}--> {}:{}:{}", + " ".repeat(line_number_size).as_str(), + filename, + error.source_info().start.line, + error.source_info().start.column, + ); + } + eprintln!("{} |", " ".repeat(line_number_size)); + + let line = lines.get(error.source_info().start.line - 1).unwrap(); + let line = str::replace(line, "\t", " "); // replace all your tabs with 4 characters + eprintln!( + "{line_number:>width$} |{line}", + line_number = error.source_info().start.line, + width = line_number_size, + line = if line.is_empty() { + line + } else { + format!(" {}", line) + } + ); + + // TODO: to clean/Refacto + // specific case for assert errors + if error.source_info().start.column == 0 { + let fix_me = &error.fixme(); + let fixme_lines: Vec<&str> = regex::Regex::new(r"\n|\r\n") + .unwrap() + .split(fix_me) + .collect(); + // edd an empty line at the end? + for line in fixme_lines { + eprintln!( + "{} | {}", + " ".repeat(line_number_size).as_str(), + fixme = line, + ); + } + } else { + let line = lines.get(error.source_info().start.line - 1).unwrap(); + let width = (error.source_info().end.column - error.source_info().start.column) as usize; + + let mut tab_shift = 0; + for (i, c) in line.chars().enumerate() { + if i >= error.source_info().start.column - 1 { + break; + }; + if c == '\t' { + tab_shift += 1; + } + } + eprintln!( + "{} | {}{} {fixme}", + " ".repeat(line_number_size).as_str(), + " ".repeat(error.source_info().start.column - 1 + tab_shift * 3), + "^".repeat(if width > 1 { width } else { 1 }), + fixme = error.fixme().as_str(), + ); + } + + eprintln!("{} |\n", " ".repeat(line_number_size)); +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 81c3878ca..7d5a2b11b 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -15,9 +15,14 @@ * limitations under the License. * */ -pub mod options; -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Error { - pub message: String, -} +pub use self::logger::{ + log_info, make_logger_error_message, make_logger_linter_error, make_logger_parser_error, + make_logger_runner_error, make_logger_verbose, +}; +pub use self::options::cookies_output_file; +pub use self::options::CLIError; + +mod error; +mod logger; +mod options; diff --git a/src/cli/options.rs b/src/cli/options.rs index ab11ebd17..8079a7825 100644 --- a/src/cli/options.rs +++ b/src/cli/options.rs @@ -16,11 +16,13 @@ * */ -use super::Error; +pub struct CLIError { + pub message: String, +} -pub fn cookies_output_file(filename: String, n: usize) -> Result { +pub fn cookies_output_file(filename: String, n: usize) -> Result { if n > 1 { - Err(Error { + Err(CLIError { message: "Only save cookies for a unique session".to_string(), }) } else { @@ -28,24 +30,3 @@ pub fn cookies_output_file(filename: String, n: usize) -> Result bool { - if color_present { - true - } else if no_color_present { - false - } else { - stdout - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_output_color() { - assert_eq!(output_color(true, false, true), true); - assert_eq!(output_color(false, false, true), true); - } -} diff --git a/src/core/common.rs b/src/core/common.rs deleted file mode 100644 index d7081601a..000000000 --- a/src/core/common.rs +++ /dev/null @@ -1,338 +0,0 @@ -/* - * hurl (https://hurl.dev) - * Copyright (C) 2020 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 std::fmt; - -use serde::ser::Serializer; -use serde::Serialize; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum DeprecatedValue { - Int(i32), - String(String), - List(usize), - Bool(bool), - Number(i32, u32), - // 9 decimal digits - ListInt(Vec), -} - -#[derive(Clone, Debug, PartialEq, Eq)] -//#[derive(Clone, Debug, PartialEq, PartialOrd)] -pub enum Value { - Unit, - Bool(bool), - Integer(i64), - - // can use simply Float(f64) - // the trait `std::cmp::Eq` is not implemented for `f64` - // integer/ decimals with 18 digits - Float(i64, u64), - // integer part, decimal part (9 digits) TODO Clarify your custom type - String(String), - List(Vec), - Object(Vec<(String, Value)>), - Nodeset(usize), - Bytes(Vec), - Null, -} - -impl fmt::Display for Value { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let value = match self { - Value::Integer(x) => x.to_string(), - Value::Bool(x) => x.to_string(), - Value::Float(int, dec) => format!("{}.{}", int, dec), - Value::String(x) => x.clone(), - Value::List(values) => { - let values: Vec = values.iter().map(|e| e.to_string()).collect(); - format!("[{}]", values.join(",")) - } - Value::Object(_) => "Object()".to_string(), - Value::Nodeset(x) => format!("Nodeset{:?}", x), - Value::Bytes(x) => format!("Bytes({:x?})", x), - Value::Null => "Null".to_string(), - Value::Unit => "Unit".to_string(), - }; - write!(f, "{}", value) - } -} - -impl Value { - pub fn _type(&self) -> String { - match self { - Value::Integer(_) => "integer".to_string(), - Value::Bool(_) => "boolean".to_string(), - Value::Float(_, _) => "float".to_string(), - Value::String(_) => "string".to_string(), - Value::List(_) => "list".to_string(), - Value::Object(_) => "object".to_string(), - Value::Nodeset(_) => "nodeset".to_string(), - Value::Bytes(_) => "bytes".to_string(), - Value::Null => "null".to_string(), - Value::Unit => "unit".to_string(), - } - } - - pub fn from_f64(value: f64) -> Value { - let integer = if value < 0.0 { - value.ceil() as i64 - } else { - value.floor() as i64 - }; - let decimal = (value.abs().fract() * 1_000_000_000_000_000_000.0).round() as u64; - Value::Float(integer, decimal) - } - - pub fn is_scalar(&self) -> bool { - match self { - Value::Nodeset(_) | Value::List(_) => false, - _ => true, - } - } - - pub fn from_json(value: &serde_json::Value) -> Value { - match value { - serde_json::Value::Null => Value::Null, - serde_json::Value::Bool(bool) => Value::Bool(*bool), - serde_json::Value::Number(n) => { - if n.is_f64() { - Value::from_f64(n.as_f64().unwrap()) - } else { - Value::Integer(n.as_i64().unwrap()) - } - } - serde_json::Value::String(s) => Value::String(s.to_string()), - serde_json::Value::Array(elements) => { - Value::List(elements.iter().map(|e| Value::from_json(e)).collect()) - } - serde_json::Value::Object(map) => { - let mut elements = vec![]; - for (key, value) in map { - elements.push((key.to_string(), Value::from_json(value))); - // - } - Value::Object(elements) - } - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Pos { - pub line: usize, - pub column: usize, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct SourceInfo { - pub start: Pos, - pub end: Pos, -} - -impl SourceInfo { - pub fn init( - start_line: usize, - start_col: usize, - end_line: usize, - end_column: usize, - ) -> SourceInfo { - SourceInfo { - start: Pos { - line: start_line, - column: start_col, - }, - end: Pos { - line: end_line, - column: end_column, - }, - } - } -} - -pub trait FormatError { - fn source_info(&self) -> SourceInfo; - fn description(&self) -> String; - fn fixme(&self) -> String; -} - -impl Serialize for Value { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match self { - Value::Bool(v) => serializer.serialize_bool(*v), - Value::Integer(v) => serializer.serialize_i64(*v), - Value::Float(i, d) => { - let value = *i as f64 + (*d as f64) / 1_000_000_000_000_000_000.0; - serializer.serialize_f64(value) - } - Value::String(s) => serializer.serialize_str(s), - Value::List(values) => serializer.collect_seq(values), - Value::Object(values) => serializer.collect_map(values.iter().map(|(k, v)| (k, v))), - Value::Nodeset(size) => { - let size = *size as i64; - serializer.collect_map(vec![ - ("type", serde_json::Value::String("nodeset".to_string())), - ("size", serde_json::Value::from(size)), - ]) - } - Value::Bytes(v) => { - let encoded = base64::encode(v); - serializer.serialize_str(&encoded) - } - Value::Null => serializer.serialize_none(), - Value::Unit => todo!("how to serialize that in json?"), - } - } -} - -impl Value { - pub fn to_json_value(&self) -> (String, serde_json::Value) { - match self.clone() { - Value::Bool(v) => ("bool".to_string(), serde_json::Value::Bool(v)), - Value::Integer(v) => ("integer".to_string(), serde_json::Value::from(v)), - Value::Float(i, d) => { - let value = i as f64 + (d as f64) / 1_000_000_000_000_000_000.0; - ("float".to_string(), serde_json::Value::from(value)) - } - Value::String(v) => ("string".to_string(), serde_json::Value::String(v)), - Value::List(_) => ("list".to_string(), serde_json::Value::Array(vec![])), - Value::Object(_) => todo!(), - Value::Nodeset(_) => todo!(), - Value::Bytes(_) => todo!(), - Value::Null => todo!(), - Value::Unit => todo!(), - } - } - - pub fn to_json(&self) -> serde_json::Value { - match self.clone() { - Value::Bool(v) => serde_json::Value::Bool(v), - Value::Integer(v) => serde_json::Value::from(v), - Value::Float(i, d) => { - let value = i as f64 + (d as f64) / 1_000_000_000_000_000_000.0; - serde_json::Value::from(value) - } - Value::String(v) => serde_json::Value::String(v), - Value::List(_) => serde_json::Value::Array(vec![]), - Value::Object(_) => todo!(), - Value::Nodeset(_) => todo!(), - Value::Bytes(_) => todo!(), - Value::Null => todo!(), - Value::Unit => todo!(), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_from_f64() { - assert_eq!(Value::from_f64(1.0), Value::Float(1, 0)); - assert_eq!(Value::from_f64(-1.0), Value::Float(-1, 0)); - assert_eq!( - Value::from_f64(1.1), - Value::Float(1, 100_000_000_000_000_096) - ); //TBC!! - assert_eq!( - Value::from_f64(-1.1), - Value::Float(-1, 100_000_000_000_000_096) - ); - assert_eq!( - Value::from_f64(1.5), - Value::Float(1, 500_000_000_000_000_000) - ); - } - - #[test] - fn test_from_json() { - assert_eq!( - Value::from_json(&serde_json::Value::String("hello".to_string())), - Value::String("hello".to_string()) - ); - assert_eq!( - Value::from_json(&serde_json::Value::Bool(true)), - Value::Bool(true) - ); - assert_eq!( - Value::from_json(&serde_json::Value::from(1)), - Value::Integer(1) - ); - assert_eq!( - Value::from_json(&serde_json::Value::from(1.5)), - Value::Float(1, 500_000_000_000_000_000) - ); - } - - #[test] - fn test_is_scalar() { - assert_eq!(Value::Integer(1).is_scalar(), true); - assert_eq!(Value::List(vec![]).is_scalar(), false); - } - - #[test] - fn test_serialize() { - assert_eq!(serde_json::to_string(&Value::Bool(true)).unwrap(), "true"); - assert_eq!( - serde_json::to_string(&Value::String("hello".to_string())).unwrap(), - "\"hello\"" - ); - assert_eq!(serde_json::to_string(&Value::Integer(1)).unwrap(), "1"); - assert_eq!( - serde_json::to_string(&Value::Float(1, 500_000_000_000_000_000)).unwrap(), - "1.5" - ); - assert_eq!( - serde_json::to_string(&Value::Float(1, 100_000_000_000_000_000)).unwrap(), - "1.1" - ); - assert_eq!( - serde_json::to_string(&Value::Float(1, 100_000_000_000_000_096)).unwrap(), - "1.1" - ); - assert_eq!( - serde_json::to_string(&Value::List(vec![ - Value::Integer(1), - Value::Integer(2), - Value::Integer(3) - ])) - .unwrap(), - "[1,2,3]" - ); - assert_eq!( - serde_json::to_string(&Value::Object(vec![( - "name".to_string(), - Value::String("Bob".to_string()) - )])) - .unwrap(), - r#"{"name":"Bob"}"# - ); - assert_eq!( - serde_json::to_string(&Value::Nodeset(4)).unwrap(), - r#"{"type":"nodeset","size":4}"# - ); - assert_eq!( - serde_json::to_string(&Value::Bytes(vec![65])).unwrap(), - r#""QQ==""# - ); - assert_eq!(serde_json::to_string(&Value::Null {}).unwrap(), "null"); - } -} diff --git a/src/format/error.rs b/src/format/error.rs deleted file mode 100644 index 87cd8a957..000000000 --- a/src/format/error.rs +++ /dev/null @@ -1,249 +0,0 @@ -/* - * hurl (https://hurl.dev) - * Copyright (C) 2020 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::core::common::SourceInfo; - -use super::color::TerminalColor; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Error { - //pub exit_code: usize, - pub source_info: SourceInfo, - pub description: String, - pub fixme: String, - pub lines: Vec, - pub filename: String, - pub warning: bool, - pub color: bool, -} - -impl Error { - pub fn format(self) -> String { - let mut s = "".to_string(); - let line_number_size = if self.lines.len() < 100 { - 2 - } else if self.lines.len() < 1000 { - 3 - } else { - 4 - }; - - let error_type = if self.warning { - String::from("warning") - } else { - String::from("error") - }; - let error_type = if !self.color { - error_type - } else if self.warning { - TerminalColor::Yellow.format(error_type) - } else { - TerminalColor::Red.format(error_type) - }; - s.push_str(format!("{}: {}\n", error_type, self.description).as_str()); - - if self.filename != "-" { - s.push_str( - format!( - "{}--> {}:{}:{}\n", - " ".repeat(line_number_size).as_str(), - self.filename, - self.source_info.start.line, - self.source_info.start.column, - ) - .as_str(), - ); - } - - s.push_str(format!("{} |\n", " ".repeat(line_number_size)).as_str()); - - let line = self.lines.get(self.source_info.start.line - 1).unwrap(); - let line = str::replace(line, "\t", " "); // replace all your tabs with 4 characters - s.push_str( - format!( - "{line_number:>width$} |{line}\n", - line_number = self.source_info.start.line, - width = line_number_size, - line = if line.is_empty() { - line - } else { - format!(" {}", line) - } - ) - .as_str(), - ); - - // TODO: to clean/Refacto - // specific case for assert errors - if self.source_info.start.column == 0 { - let fixme_lines: Vec<&str> = regex::Regex::new(r"\n|\r\n") - .unwrap() - .split(&self.fixme) - .collect(); - // edd an empty line at the end? - for line in fixme_lines { - s.push_str( - format!( - "{} | {}\n", - " ".repeat(line_number_size).as_str(), - fixme = line, - ) - .as_str(), - ); - } - } else { - let line = self.lines.get(self.source_info.start.line - 1).unwrap(); - let width = (self.source_info.end.column - self.source_info.start.column) as usize; - - let mut tab_shift = 0; - for (i, c) in line.chars().enumerate() { - if i >= self.source_info.start.column - 1 { - break; - }; - if c == '\t' { - tab_shift += 1; - } - } - s.push_str( - format!( - "{} | {}{} {fixme}\n", - " ".repeat(line_number_size).as_str(), - " ".repeat(self.source_info.start.column - 1 + tab_shift * 3), - "^".repeat(if width > 1 { width } else { 1 }), - fixme = self.fixme.as_str(), - ) - .as_str(), - ); - } - - s.push_str(format!("{} |\n", " ".repeat(line_number_size)).as_str()); - - s - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_basic() { - let filename = String::from("integration/hurl_error_lint/spaces.hurl"); - let lines = vec![String::from("GET\thttp://localhost:8000/hello")]; - let error = Error { - source_info: SourceInfo::init(1, 4, 1, 5), - description: String::from("One space"), - fixme: String::from("Use only one space"), - lines, - filename, - warning: true, - color: false, - }; - assert_eq!( - error.format(), - String::from( - r#"warning: One space - --> integration/hurl_error_lint/spaces.hurl:1:4 - | - 1 | GET http://localhost:8000/hello - | ^ Use only one space - | -"# - ) - ); - } - - #[test] - fn test_with_tabs() { - let filename = String::from("integration/hurl_error_lint/spaces.hurl"); - let lines = vec![String::from("GET\thttp://localhost:8000/hello ")]; - let error = Error { - source_info: SourceInfo::init(1, 32, 1, 32), - description: String::from("Unnecessary space"), - fixme: String::from("Remove space"), - lines, - filename, - warning: true, - color: false, - }; - assert_eq!( - error.format(), - concat!( - "warning: Unnecessary space\n", - " --> integration/hurl_error_lint/spaces.hurl:1:32\n", - " |\n", - " 1 | GET http://localhost:8000/hello \n", - " | ^ Remove space\n", - " |\n" - ) - ); - } - - #[test] - fn test_end_of_file() { - // todo: improve error location - - let filename = String::from("hurl_error_parser/json_unexpected_eof.hurl"); - let lines = vec![ - String::from("POST http://localhost:8000/data\n"), - String::from("{ \"name\":\n"), - String::from(""), - ]; - let error = Error { - source_info: SourceInfo::init(3, 1, 3, 1), - description: String::from("Parsing json"), - fixme: String::from("json error"), - lines, - filename, - warning: true, - color: false, - }; - assert_eq!( - error.format(), - String::from( - r#"warning: Parsing json - --> hurl_error_parser/json_unexpected_eof.hurl:3:1 - | - 3 | - | ^ json error - | -"# - ) - ); - } - - #[test] - fn test_assert_error() { - let filename = String::from("hurl_error_parser/json_unexpected_eof.hurl"); - let lines = vec![ - String::from("...\n"), - String::from("[Asserts]\n"), - String::from("jsonpath \"$.message\" startsWith \"hello\""), - ]; - let _error = Error { - source_info: SourceInfo::init(3, 0, 3, 0), - description: String::from("Assert Error"), - fixme: String::from("actual: string \nexpected: starts with string "), - lines, - filename, - warning: false, - color: false, - }; - - //assert_eq!(1,2); - } -} diff --git a/src/format/html.rs b/src/format/html.rs index 63e1c6854..b8d91e631 100644 --- a/src/format/html.rs +++ b/src/format/html.rs @@ -15,7 +15,7 @@ * limitations under the License. * */ -use super::super::core::ast::*; +use crate::ast::*; pub trait Htmlable { fn to_html(&self) -> String; diff --git a/src/format/logger.rs b/src/format/logger.rs deleted file mode 100644 index 817390cc8..000000000 --- a/src/format/logger.rs +++ /dev/null @@ -1,157 +0,0 @@ -/* - * hurl (https://hurl.dev) - * Copyright (C) 2020 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::color::TerminalColor; -use super::error::Error; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Logger { - pub filename: Option, - pub lines: Vec, - pub verbose: bool, - pub color: bool, -} - -impl Logger { - pub fn info(&self, s: &str) { - println!("{}", s); - } - - pub fn verbose(&self, s: &str) { - if self.verbose { - eprintln!("* {}", s); - } - } - - pub fn send(&self, s: String) { - if self.verbose { - eprintln!("> {}", s); - } - } - - pub fn receive(&self, s: String) { - if self.verbose { - eprintln!("< {}", s); - } - } - - pub fn error_message(&self, s: String) { - let error_type = if !self.color { - "error".to_string() - } else { - TerminalColor::Red.format("error".to_string()) - }; - eprintln!("{}: {}", error_type, s); - } - - pub fn warning_message(&self, s: String) { - let error_type = if !self.color { - "warning".to_string() - } else { - TerminalColor::Yellow.format("warning".to_string()) - }; - eprintln!("{}: {}", error_type, s); - } - - pub fn error(&self, err: &Error) { - let line_number_size = if self.lines.len() < 100 { - 2 - } else if self.lines.len() < 1000 { - 3 - } else { - 4 - }; - - let error_type = if err.warning { - String::from("warning") - } else { - String::from("error") - }; - let error_type = if !self.color { - error_type - } else if err.warning { - TerminalColor::Yellow.format(error_type) - } else { - TerminalColor::Red.format(error_type) - }; - eprintln!("{}: {}", error_type, err.description); - - if let Some(filename) = self.filename.clone() { - eprintln!( - "{}--> {}:{}:{}", - " ".repeat(line_number_size).as_str(), - filename, - err.source_info.start.line, - err.source_info.start.column, - ); - } - eprintln!("{} |", " ".repeat(line_number_size)); - - let line = self.lines.get(err.source_info.start.line - 1).unwrap(); - let line = str::replace(line, "\t", " "); // replace all your tabs with 4 characters - eprintln!( - "{line_number:>width$} |{line}", - line_number = err.source_info.start.line, - width = line_number_size, - line = if line.is_empty() { - line - } else { - format!(" {}", line) - } - ); - - // TODO: to clean/Refacto - // specific case for assert errors - if err.source_info.start.column == 0 { - let fixme_lines: Vec<&str> = regex::Regex::new(r"\n|\r\n") - .unwrap() - .split(&err.fixme) - .collect(); - // edd an empty line at the end? - for line in fixme_lines { - eprintln!( - "{} | {}", - " ".repeat(line_number_size).as_str(), - fixme = line, - ); - } - } else { - let line = self.lines.get(err.source_info.start.line - 1).unwrap(); - let width = (err.source_info.end.column - err.source_info.start.column) as usize; - - let mut tab_shift = 0; - for (i, c) in line.chars().enumerate() { - if i >= err.source_info.start.column - 1 { - break; - }; - if c == '\t' { - tab_shift += 1; - } - } - eprintln!( - "{} | {}{} {fixme}", - " ".repeat(line_number_size).as_str(), - " ".repeat(err.source_info.start.column - 1 + tab_shift * 3), - "^".repeat(if width > 1 { width } else { 1 }), - fixme = err.fixme.as_str(), - ); - } - - eprintln!("{} |\n", " ".repeat(line_number_size)); - } -} diff --git a/src/format/mod.rs b/src/format/mod.rs index aadb85ee4..9b7ae22a5 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -15,9 +15,13 @@ * limitations under the License. * */ -pub mod color; -pub mod error; -pub mod html; -pub mod logger; -pub mod text; -pub mod token; + +pub use self::color::TerminalColor; +pub use self::html::format as format_html; +pub use self::text::format as format_text; +pub use self::token::{Token, Tokenizable}; + +mod color; +mod html; +mod text; +mod token; diff --git a/src/format/text.rs b/src/format/text.rs index decac5411..4ddc78bb6 100644 --- a/src/format/text.rs +++ b/src/format/text.rs @@ -15,7 +15,8 @@ * limitations under the License. * */ -use super::super::core::ast::*; +use crate::ast::*; + use super::color::TerminalColor; use super::token::*; diff --git a/src/format/token.rs b/src/format/token.rs index 707e4443f..07d8b985d 100644 --- a/src/format/token.rs +++ b/src/format/token.rs @@ -16,8 +16,7 @@ * */ -use super::super::core::ast::*; -use super::super::core::json; +use crate::ast::*; #[derive(Clone, Debug, PartialEq, Eq)] pub enum Token { @@ -714,22 +713,22 @@ impl Tokenizable for Filename { } } -impl Tokenizable for json::Value { +impl Tokenizable for JsonValue { fn tokenize(&self) -> Vec { let mut tokens: Vec = vec![]; match self { - json::Value::String(s) => { + JsonValue::String(s) => { //tokens.push(Token::CodeDelimiter("\"".to_string())); tokens.append(&mut s.tokenize()); //tokens.push(Token::CodeDelimiter("\"".to_string())); } - json::Value::Number(value) => { + JsonValue::Number(value) => { tokens.push(Token::Number(value.clone())); } - json::Value::Boolean(value) => { + JsonValue::Boolean(value) => { tokens.push(Token::Number(value.to_string())); } - json::Value::List { space0, elements } => { + JsonValue::List { space0, elements } => { tokens.push(Token::CodeDelimiter("[".to_string())); tokens.push(Token::Whitespace(space0.clone())); for (i, element) in elements.iter().enumerate() { @@ -740,7 +739,7 @@ impl Tokenizable for json::Value { } tokens.push(Token::CodeDelimiter("]".to_string())); } - json::Value::Object { space0, elements } => { + JsonValue::Object { space0, elements } => { tokens.push(Token::CodeDelimiter("{".to_string())); tokens.push(Token::Whitespace(space0.clone())); for (i, element) in elements.iter().enumerate() { @@ -751,7 +750,7 @@ impl Tokenizable for json::Value { } tokens.push(Token::CodeDelimiter("}".to_string())); } - json::Value::Null {} => { + JsonValue::Null {} => { tokens.push(Token::Keyword("null".to_string())); } } @@ -759,7 +758,7 @@ impl Tokenizable for json::Value { } } -impl Tokenizable for json::ListElement { +impl Tokenizable for JsonListElement { fn tokenize(&self) -> Vec { let mut tokens: Vec = vec![]; tokens.push(Token::Whitespace(self.space0.clone())); @@ -769,7 +768,7 @@ impl Tokenizable for json::ListElement { } } -impl Tokenizable for json::ObjectElement { +impl Tokenizable for JsonObjectElement { fn tokenize(&self) -> Vec { let mut tokens: Vec = vec![]; tokens.push(Token::Whitespace(self.space0.clone())); diff --git a/src/html/mod.rs b/src/html/mod.rs index d0d29f92d..6c61c8d7c 100644 --- a/src/html/mod.rs +++ b/src/html/mod.rs @@ -15,5 +15,8 @@ * limitations under the License. * */ -pub mod ast; -pub mod render; + +pub use self::ast::{Attribute, Body, Element, Head, Html}; + +mod ast; +mod render; diff --git a/src/jsonpath/mod.rs b/src/jsonpath/mod.rs index 7301d64ee..a2f44e1ed 100644 --- a/src/jsonpath/mod.rs +++ b/src/jsonpath/mod.rs @@ -15,6 +15,9 @@ * limitations under the License. * */ -pub mod ast; -pub mod eval; -pub mod parser; + +pub use self::parser::parse; + +mod ast; +mod eval; +mod parser; diff --git a/src/jsonpath/parser/combinators.rs b/src/jsonpath/parser/combinators.rs index 1ddd6551d..fa200c26d 100644 --- a/src/jsonpath/parser/combinators.rs +++ b/src/jsonpath/parser/combinators.rs @@ -19,47 +19,6 @@ use super::error::*; use super::reader::Reader; use super::{ParseFunc, ParseResult}; -pub fn optional<'a, T>(f: ParseFunc<'a, T>, p: &mut Reader) -> ParseResult<'a, Option> { - let start = p.state.clone(); - match f(p) { - Ok(r) => Ok(Some(r)), - Err(e) => { - if e.recoverable { - p.state = start; - Ok(None) - } else { - Err(e) - } - } - } -} - -// make an error recoverable -// but does not reset cursor -pub fn recover<'a, T>(f: ParseFunc<'a, T>, p: &mut Reader) -> ParseResult<'a, T> { - // let start = p.state.clone(); - match f(p) { - Ok(r) => Ok(r), - Err(e) => Err(Error { - pos: e.pos, - recoverable: true, - inner: e.inner, - }), - } -} - -pub fn nonrecover<'a, T>(f: ParseFunc<'a, T>, p: &mut Reader) -> ParseResult<'a, T> { - //let start = p.state.clone(); - match f(p) { - Ok(r) => Ok(r), - Err(e) => Err(Error { - pos: e.pos, - recoverable: false, - inner: e.inner, - }), - } -} - pub fn zero_or_more<'a, T>(f: ParseFunc<'a, T>, p: &mut Reader) -> ParseResult<'a, Vec> { let _start = p.state.clone(); @@ -87,40 +46,6 @@ pub fn zero_or_more<'a, T>(f: ParseFunc<'a, T>, p: &mut Reader) -> ParseResult<' } } -pub fn one_or_more<'a, T>(f: ParseFunc<'a, T>, reader: &mut Reader) -> ParseResult<'a, Vec> { - let _initial_state = reader.state.clone(); - match f(reader) { - Ok(r) => { - let mut v = vec![r]; - loop { - let initial_state = reader.state.clone(); - match f(reader) { - Ok(r) => { - v.push(r); - } - Err(e) => { - if e.recoverable { - reader.state.pos = initial_state.pos; - reader.state.cursor = initial_state.cursor; - return Ok(v); - } else { - return Err(e); - }; - } - } - } - } - Err(Error { pos, inner, .. }) => { - // if zero occurence => should fail? - Err(Error { - pos, - recoverable: false, - inner, - }) - } - } -} - // return the last error when no default error is specified // tipically this should be recoverable pub fn choice<'a, T>(fs: Vec>, p: &mut Reader) -> ParseResult<'a, T> { @@ -144,22 +69,3 @@ pub fn choice<'a, T>(fs: Vec>, p: &mut Reader) -> ParseResult<' } } } - -pub fn peek(f: ParseFunc, p: Reader) -> ParseResult { - let start = p.state.clone(); - let mut p = p; - match f(&mut p) { - Ok(r) => { - p.state = start; - Ok(r) - } - Err(e) => { - p.state = start; - Err(Error { - pos: e.pos, - recoverable: false, - inner: e.inner, - }) - } - } -} diff --git a/src/jsonpath/parser/mod.rs b/src/jsonpath/parser/mod.rs index 7c150da3a..9ff06fda2 100644 --- a/src/jsonpath/parser/mod.rs +++ b/src/jsonpath/parser/mod.rs @@ -15,15 +15,10 @@ * limitations under the License. * */ + use error::Error; use reader::Reader; -pub mod combinators; -pub mod error; -pub mod parse; -pub mod primitives; -pub mod reader; - #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct Pos { pub line: usize, @@ -32,3 +27,11 @@ pub struct Pos { pub type ParseResult<'a, T> = std::result::Result; pub type ParseFunc<'a, T> = fn(&mut Reader) -> ParseResult<'a, T>; + +pub use self::parse::parse; + +mod combinators; +mod error; +mod parse; +mod primitives; +mod reader; diff --git a/src/lib.rs b/src/lib.rs index 1f42e4f1f..c342f09e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,8 +20,8 @@ #[macro_use] extern crate float_cmp; +pub mod ast; pub mod cli; -pub mod core; pub mod format; pub mod html; pub mod http; diff --git a/src/linter/core.rs b/src/linter/core.rs index 622a86e33..c4a5d34c9 100644 --- a/src/linter/core.rs +++ b/src/linter/core.rs @@ -15,7 +15,7 @@ * limitations under the License. * */ -use crate::core::common::{FormatError, SourceInfo}; +use crate::ast::SourceInfo; #[derive(Clone, Debug, PartialEq, Eq)] pub struct Error { @@ -30,28 +30,6 @@ pub enum LinterError { OneSpace {}, } -impl FormatError for Error { - fn source_info(&self) -> SourceInfo { - self.clone().source_info - } - - fn description(&self) -> String { - match self.inner { - LinterError::UnneccessarySpace { .. } => "Unnecessary space".to_string(), - LinterError::UnneccessaryJsonEncoding {} => "Unnecessary json encoding".to_string(), - LinterError::OneSpace {} => "One space ".to_string(), - } - } - - fn fixme(&self) -> String { - match self.inner { - LinterError::UnneccessarySpace { .. } => "Remove space".to_string(), - LinterError::UnneccessaryJsonEncoding {} => "Use Simple String".to_string(), - LinterError::OneSpace {} => "Use only one space".to_string(), - } - } -} - pub trait Lintable { fn errors(&self) -> Vec; fn lint(&self) -> T; diff --git a/src/linter/mod.rs b/src/linter/mod.rs index d700f45e4..b9037a313 100644 --- a/src/linter/mod.rs +++ b/src/linter/mod.rs @@ -15,5 +15,8 @@ * limitations under the License. * */ -pub mod core; -pub mod rules; + +pub use self::core::{Error, Lintable, LinterError}; + +mod core; +mod rules; diff --git a/src/linter/rules.rs b/src/linter/rules.rs index 57aba8a56..872ccfab5 100644 --- a/src/linter/rules.rs +++ b/src/linter/rules.rs @@ -15,8 +15,7 @@ * limitations under the License. * */ -use crate::core::ast::*; -use crate::core::common::SourceInfo; +use crate::ast::*; use super::core::{Error, Lintable, LinterError}; diff --git a/src/parser/bytes.rs b/src/parser/bytes.rs index 1f3271f5f..703939723 100644 --- a/src/parser/bytes.rs +++ b/src/parser/bytes.rs @@ -15,7 +15,7 @@ * limitations under the License. * */ -use crate::core::ast::*; +use crate::ast::*; use super::base64; use super::combinators::*; @@ -86,8 +86,6 @@ fn base64_bytes(reader: &mut Reader) -> ParseResult<'static, Bytes> { #[cfg(test)] mod tests { - use crate::core::common::{Pos, SourceInfo}; - use crate::core::json; use super::super::error::*; use super::*; @@ -98,22 +96,22 @@ mod tests { assert_eq!( bytes(&mut reader).unwrap(), Bytes::Json { - value: json::Value::List { + value: JsonValue::List { space0: "".to_string(), elements: vec![ - json::ListElement { + JsonListElement { space0: "".to_string(), - value: json::Value::Number("1".to_string()), + value: JsonValue::Number("1".to_string()), space1: "".to_string() }, - json::ListElement { + JsonListElement { space0: "".to_string(), - value: json::Value::Number("2".to_string()), + value: JsonValue::Number("2".to_string()), space1: "".to_string() }, - json::ListElement { + JsonListElement { space0: "".to_string(), - value: json::Value::Number("3".to_string()), + value: JsonValue::Number("3".to_string()), space1: "".to_string() }, ], @@ -126,7 +124,7 @@ mod tests { assert_eq!( bytes(&mut reader).unwrap(), Bytes::Json { - value: json::Value::Object { + value: JsonValue::Object { space0: " ".to_string(), elements: vec![], } @@ -138,7 +136,7 @@ mod tests { assert_eq!( bytes(&mut reader).unwrap(), Bytes::Json { - value: json::Value::Boolean(true) + value: JsonValue::Boolean(true) } ); assert_eq!(reader.state.cursor, 4); @@ -147,7 +145,7 @@ mod tests { assert_eq!( bytes(&mut reader).unwrap(), Bytes::Json { - value: json::Value::String(Template { + value: JsonValue::String(Template { quotes: true, elements: vec![], source_info: SourceInfo::init(1, 2, 1, 2), @@ -236,7 +234,7 @@ mod tests { assert_eq!( json_bytes(&mut reader).unwrap(), Bytes::Json { - value: json::Value::Number("100".to_string()) + value: JsonValue::Number("100".to_string()) } ); } diff --git a/src/parser/cookiepath.rs b/src/parser/cookiepath.rs index 8686f827d..516fae27b 100644 --- a/src/parser/cookiepath.rs +++ b/src/parser/cookiepath.rs @@ -1,4 +1,22 @@ -use crate::core::ast::*; +/* + * hurl (https://hurl.dev) + * Copyright (C) 2020 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::ast::*; use super::combinators::*; use super::error::*; @@ -64,7 +82,7 @@ fn cookiepath_attribute_name(reader: &mut Reader) -> ParseResult<'static, Cookie #[cfg(test)] mod tests { - use crate::core::common::{Pos, SourceInfo}; + use crate::ast::{Pos, SourceInfo}; use super::*; diff --git a/src/parser/error.rs b/src/parser/error.rs index 908f69eba..ac0ed627f 100644 --- a/src/parser/error.rs +++ b/src/parser/error.rs @@ -15,7 +15,7 @@ * limitations under the License. * */ -use crate::core::common::{FormatError, Pos, SourceInfo}; +use crate::ast::Pos; #[derive(Clone, Debug, PartialEq, Eq)] pub struct Error { @@ -58,68 +58,3 @@ pub enum ParseError { InvalidCookieAttribute, } - -impl FormatError for Error { - fn source_info(&self) -> SourceInfo { - SourceInfo { - start: self.pos.clone(), - end: self.pos.clone(), - } - } - - fn description(&self) -> String { - match self.clone().inner { - ParseError::Method { .. } => "Parsing Method".to_string(), - ParseError::Version { .. } => "Parsing Version".to_string(), - ParseError::Status { .. } => "Parsing Status".to_string(), - ParseError::Filename { .. } => "Parsing Filename".to_string(), - ParseError::Expecting { .. } => "Parsing literal".to_string(), - ParseError::Space { .. } => "Parsing space".to_string(), - ParseError::SectionName { .. } => "Parsing section name".to_string(), - ParseError::JsonpathExpr { .. } => "Parsing jsonpath expression".to_string(), - ParseError::XPathExpr { .. } => "Parsing xpath expression".to_string(), - ParseError::TemplateVariable { .. } => "Parsing template variable".to_string(), - ParseError::Json { .. } => "Parsing json".to_string(), - ParseError::Predicate { .. } => "Parsing predicate".to_string(), - ParseError::PredicateValue { .. } => "Parsing predicate value".to_string(), - ParseError::RegexExpr { .. } => "Parsing regex".to_string(), - ParseError::DuplicateSection { .. } => "Parsing section".to_string(), - ParseError::RequestSection { .. } => "Parsing section".to_string(), - ParseError::ResponseSection { .. } => "Parsing section".to_string(), - ParseError::EscapeChar { .. } => "Parsing escape character".to_string(), - ParseError::InvalidCookieAttribute { .. } => "Parsing cookie attribute".to_string(), - _ => format!("{:?}", self), - } - } - - fn fixme(&self) -> String { - match self.inner.clone() { - ParseError::Method { .. } => "Available HTTP Method GET, POST, ...".to_string(), - ParseError::Version { .. } => "The http version must be 1.0, 1.1, 2 or *".to_string(), - ParseError::Status { .. } => "The http status is not valid".to_string(), - ParseError::Filename { .. } => "expecting a filename".to_string(), - ParseError::Expecting { value } => format!("expecting '{}'", value), - ParseError::Space { .. } => "expecting a space".to_string(), - ParseError::SectionName { name } => format!("the section {} is not valid", name), - ParseError::JsonpathExpr { .. } => "expecting a jsonpath expression".to_string(), - ParseError::XPathExpr { .. } => "expecting a xpath expression".to_string(), - ParseError::TemplateVariable { .. } => "expecting a variable".to_string(), - ParseError::Json { .. } => "json error".to_string(), - ParseError::Predicate { .. } => "expecting a predicate".to_string(), - ParseError::PredicateValue { .. } => "invalid predicate value".to_string(), - ParseError::RegexExpr { .. } => "Invalid Regex expression".to_string(), - ParseError::DuplicateSection { .. } => "The section is already defined".to_string(), - ParseError::RequestSection { .. } => { - "This is not a valid section for a request".to_string() - } - ParseError::ResponseSection { .. } => { - "This is not a valid section for a response".to_string() - } - ParseError::EscapeChar { .. } => "The escaping sequence is not valid".to_string(), - ParseError::InvalidCookieAttribute { .. } => { - "The cookie attribute is not valid".to_string() - } - _ => format!("{:?}", self), - } - } -} diff --git a/src/parser/expr.rs b/src/parser/expr.rs index 8ee68c6f6..89163772b 100644 --- a/src/parser/expr.rs +++ b/src/parser/expr.rs @@ -15,8 +15,7 @@ * limitations under the License. * */ -use crate::core::ast::*; -use crate::core::common::SourceInfo; +use crate::ast::*; use super::error::*; use super::primitives::*; @@ -80,7 +79,7 @@ fn variable_name(reader: &mut Reader) -> ParseResult<'static, Variable> { #[cfg(test)] mod tests { - use crate::core::common::Pos; + use crate::ast::Pos; use super::*; diff --git a/src/parser/json.rs b/src/parser/json.rs index c5e0922b0..f8affd5a1 100644 --- a/src/parser/json.rs +++ b/src/parser/json.rs @@ -15,10 +15,7 @@ * limitations under the License. * */ -use crate::core::ast::Template; -use crate::core::common::Pos; -use crate::core::common::SourceInfo; -use crate::core::json; +use crate::ast::{JsonListElement, JsonObjectElement, JsonValue, Pos, SourceInfo, Template}; use super::combinators::*; use super::error; @@ -27,7 +24,7 @@ use super::reader::*; use super::template::*; use super::ParseResult; -pub fn parse(reader: &mut Reader) -> ParseResult<'static, json::Value> { +pub fn parse(reader: &mut Reader) -> ParseResult<'static, JsonValue> { choice( vec![ null_value, @@ -41,17 +38,17 @@ pub fn parse(reader: &mut Reader) -> ParseResult<'static, json::Value> { ) } -fn null_value(reader: &mut Reader) -> ParseResult<'static, json::Value> { +fn null_value(reader: &mut Reader) -> ParseResult<'static, JsonValue> { try_literal("null", reader)?; - Ok(json::Value::Null {}) + Ok(JsonValue::Null {}) } -fn boolean_value(reader: &mut Reader) -> ParseResult<'static, json::Value> { +fn boolean_value(reader: &mut Reader) -> ParseResult<'static, JsonValue> { let value = boolean(reader)?; - Ok(json::Value::Boolean(value)) + Ok(JsonValue::Boolean(value)) } -fn string_value(reader: &mut Reader) -> ParseResult<'static, json::Value> { +fn string_value(reader: &mut Reader) -> ParseResult<'static, JsonValue> { try_literal("\"", reader)?; let quotes = true; let mut chars = vec![]; @@ -80,7 +77,7 @@ fn string_value(reader: &mut Reader) -> ParseResult<'static, json::Value> { elements, source_info: SourceInfo { start, end }, }; - Ok(json::Value::String(template)) + Ok(JsonValue::String(template)) } fn any_char(reader: &mut Reader) -> ParseResult<'static, (char, String, Pos)> { @@ -164,7 +161,7 @@ fn hex_value(reader: &mut Reader) -> ParseResult<'static, u32> { Ok(value) } -fn number_value(reader: &mut Reader) -> ParseResult<'static, json::Value> { +fn number_value(reader: &mut Reader) -> ParseResult<'static, JsonValue> { let start = reader.state.pos.clone(); let sign = match try_literal("-", reader) { @@ -223,13 +220,13 @@ fn number_value(reader: &mut Reader) -> ParseResult<'static, json::Value> { "".to_string() }; - Ok(json::Value::Number(format!( + Ok(JsonValue::Number(format!( "{}{}{}{}", sign, integer, fraction, exponent ))) } -fn list_value(reader: &mut Reader) -> ParseResult<'static, json::Value> { +fn list_value(reader: &mut Reader) -> ParseResult<'static, JsonValue> { try_literal("[", reader)?; let space0 = whitespace(reader); let mut elements = vec![]; @@ -253,13 +250,13 @@ fn list_value(reader: &mut Reader) -> ParseResult<'static, json::Value> { } literal("]", reader)?; - Ok(json::Value::List { space0, elements }) + Ok(JsonValue::List { space0, elements }) } fn list_element( _type: Option, reader: &mut Reader, -) -> ParseResult<'static, json::ListElement> { +) -> ParseResult<'static, JsonListElement> { let save = reader.state.pos.clone(); let space0 = whitespace(reader); let pos = reader.state.pos.clone(); @@ -283,14 +280,14 @@ fn list_element( } } let space1 = whitespace(reader); - Ok(json::ListElement { + Ok(JsonListElement { space0, value, space1, }) } -fn object_value(reader: &mut Reader) -> ParseResult<'static, json::Value> { +fn object_value(reader: &mut Reader) -> ParseResult<'static, JsonValue> { try_literal("{", reader)?; let space0 = whitespace(reader); let mut elements = vec![]; @@ -315,10 +312,10 @@ fn object_value(reader: &mut Reader) -> ParseResult<'static, json::Value> { literal("}", reader)?; - Ok(json::Value::Object { space0, elements }) + Ok(JsonValue::Object { space0, elements }) } -fn object_element(reader: &mut Reader) -> ParseResult<'static, json::ObjectElement> { +fn object_element(reader: &mut Reader) -> ParseResult<'static, JsonObjectElement> { let space0 = whitespace(reader); literal("\"", reader)?; let name = key(reader)?; @@ -338,7 +335,7 @@ fn object_element(reader: &mut Reader) -> ParseResult<'static, json::ObjectEleme } }; let space3 = whitespace(reader); - Ok(json::ObjectElement { + Ok(JsonObjectElement { space0, name, space1, @@ -371,9 +368,8 @@ fn whitespace(reader: &mut Reader) -> String { #[cfg(test)] mod tests { - use crate::core::ast::TemplateElement; - use super::*; + use crate::ast::*; #[test] fn test_parse_error() { @@ -393,7 +389,7 @@ mod tests { #[test] fn test_null_value() { let mut reader = Reader::init("null"); - assert_eq!(null_value(&mut reader).unwrap(), json::Value::Null {}); + assert_eq!(null_value(&mut reader).unwrap(), JsonValue::Null {}); assert_eq!(reader.state.cursor, 4); let mut reader = Reader::init("true"); @@ -413,7 +409,7 @@ mod tests { let mut reader = Reader::init("true"); assert_eq!( boolean_value(&mut reader).unwrap(), - json::Value::Boolean(true) + JsonValue::Boolean(true) ); assert_eq!(reader.state.cursor, 4); @@ -434,7 +430,7 @@ mod tests { let mut reader = Reader::init("\"\""); assert_eq!( string_value(&mut reader).unwrap(), - json::Value::String(Template { + JsonValue::String(Template { quotes: true, elements: vec![], source_info: SourceInfo::init(1, 2, 1, 2), @@ -443,16 +439,13 @@ mod tests { assert_eq!(reader.state.cursor, 2); let mut reader = Reader::init("\"Hello\\u0020{{name}}!\""); - assert_eq!( - string_value(&mut reader).unwrap(), - json::tests::hello_world_value() - ); + assert_eq!(string_value(&mut reader).unwrap(), json_hello_world_value()); assert_eq!(reader.state.cursor, 22); let mut reader = Reader::init("\"{}\""); assert_eq!( string_value(&mut reader).unwrap(), - json::Value::String(Template { + JsonValue::String(Template { quotes: true, elements: vec![TemplateElement::String { value: "{}".to_string(), @@ -598,49 +591,49 @@ mod tests { let mut reader = Reader::init("100"); assert_eq!( number_value(&mut reader).unwrap(), - json::Value::Number("100".to_string()) + JsonValue::Number("100".to_string()) ); assert_eq!(reader.state.cursor, 3); let mut reader = Reader::init("1.333"); assert_eq!( number_value(&mut reader).unwrap(), - json::Value::Number("1.333".to_string()) + JsonValue::Number("1.333".to_string()) ); assert_eq!(reader.state.cursor, 5); let mut reader = Reader::init("-1"); assert_eq!( number_value(&mut reader).unwrap(), - json::Value::Number("-1".to_string()) + JsonValue::Number("-1".to_string()) ); assert_eq!(reader.state.cursor, 2); let mut reader = Reader::init("00"); assert_eq!( number_value(&mut reader).unwrap(), - json::Value::Number("0".to_string()) + JsonValue::Number("0".to_string()) ); assert_eq!(reader.state.cursor, 1); let mut reader = Reader::init("1e0"); assert_eq!( number_value(&mut reader).unwrap(), - json::Value::Number("1e0".to_string()) + JsonValue::Number("1e0".to_string()) ); assert_eq!(reader.state.cursor, 3); let mut reader = Reader::init("1e005"); assert_eq!( number_value(&mut reader).unwrap(), - json::Value::Number("1e005".to_string()) + JsonValue::Number("1e005".to_string()) ); assert_eq!(reader.state.cursor, 5); let mut reader = Reader::init("1e-005"); assert_eq!( number_value(&mut reader).unwrap(), - json::Value::Number("1e-005".to_string()) + JsonValue::Number("1e-005".to_string()) ); assert_eq!(reader.state.cursor, 6); } @@ -675,7 +668,7 @@ mod tests { let mut reader = Reader::init("[]"); assert_eq!( list_value(&mut reader).unwrap(), - json::Value::List { + JsonValue::List { space0: "".to_string(), elements: vec![] } @@ -685,7 +678,7 @@ mod tests { let mut reader = Reader::init("[ ]"); assert_eq!( list_value(&mut reader).unwrap(), - json::Value::List { + JsonValue::List { space0: " ".to_string(), elements: vec![] } @@ -695,11 +688,11 @@ mod tests { let mut reader = Reader::init("[true]"); assert_eq!( list_value(&mut reader).unwrap(), - json::Value::List { + JsonValue::List { space0: "".to_string(), - elements: vec![json::ListElement { + elements: vec![JsonListElement { space0: "".to_string(), - value: json::Value::Boolean(true), + value: JsonValue::Boolean(true), space1: "".to_string(), }], } @@ -743,9 +736,9 @@ mod tests { let mut reader = Reader::init("true"); assert_eq!( list_element(None, &mut reader).unwrap(), - json::ListElement { + JsonListElement { space0: "".to_string(), - value: json::Value::Boolean(true), + value: JsonValue::Boolean(true), space1: "".to_string(), } ); @@ -781,7 +774,7 @@ mod tests { let mut reader = Reader::init("{}"); assert_eq!( object_value(&mut reader).unwrap(), - json::Value::Object { + JsonValue::Object { space0: "".to_string(), elements: vec![] } @@ -791,7 +784,7 @@ mod tests { let mut reader = Reader::init("{ }"); assert_eq!( object_value(&mut reader).unwrap(), - json::Value::Object { + JsonValue::Object { space0: " ".to_string(), elements: vec![] } @@ -801,14 +794,14 @@ mod tests { let mut reader = Reader::init("{\n \"a\": true\n}"); assert_eq!( object_value(&mut reader).unwrap(), - json::Value::Object { + JsonValue::Object { space0: "\n ".to_string(), - elements: vec![json::ObjectElement { + elements: vec![JsonObjectElement { space0: "".to_string(), name: "a".to_string(), space1: "".to_string(), space2: " ".to_string(), - value: json::Value::Boolean(true), + value: JsonValue::Boolean(true), space3: "\n".to_string(), }], } @@ -841,12 +834,12 @@ mod tests { let mut reader = Reader::init("\"a\": true"); assert_eq!( object_element(&mut reader).unwrap(), - json::ObjectElement { + JsonObjectElement { space0: "".to_string(), name: "a".to_string(), space1: "".to_string(), space2: " ".to_string(), - value: json::Value::Boolean(true), + value: JsonValue::Boolean(true), space3: "".to_string(), } ); diff --git a/src/parser/mod.rs b/src/parser/mod.rs index b0c8fb822..7d080ad2e 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -15,25 +15,7 @@ * limitations under the License. * */ -use crate::core::ast::HurlFile; -use error::Error; - -mod base64; -mod bytes; -mod combinators; -mod cookiepath; -pub mod error; -mod expr; -pub mod json; -mod parsers; -mod predicate; -mod primitives; -mod query; -pub mod reader; -mod sections; -mod string; -mod template; -mod xml; +use crate::ast::HurlFile; pub type ParseResult<'a, T> = std::result::Result; pub type ParseFunc<'a, T> = fn(&mut reader::Reader) -> ParseResult<'a, T>; @@ -42,3 +24,25 @@ pub fn parse_hurl_file(s: &str) -> ParseResult<'static, HurlFile> { let mut reader = reader::Reader::init(s); parsers::hurl_file(&mut reader) } + +pub use self::error::{Error, ParseError}; +pub use self::json::parse as parse_json; +pub use self::reader::Reader; +pub use self::template::templatize; + +mod base64; +mod bytes; +mod combinators; +mod cookiepath; +mod error; +mod expr; +mod json; +mod parsers; +mod predicate; +mod primitives; +mod query; +mod reader; +mod sections; +mod string; +mod template; +mod xml; diff --git a/src/parser/parsers.rs b/src/parser/parsers.rs index 0710a408e..d4101c8c5 100644 --- a/src/parser/parsers.rs +++ b/src/parser/parsers.rs @@ -15,8 +15,7 @@ * limitations under the License. * */ -use crate::core::ast::*; -use crate::core::common::SourceInfo; +use crate::ast::*; use super::bytes::*; use super::combinators::*; @@ -300,9 +299,6 @@ fn body(reader: &mut Reader) -> ParseResult<'static, Body> { #[cfg(test)] mod tests { - use crate::core::common::Pos; - use crate::core::json; - use super::*; #[test] @@ -499,22 +495,22 @@ mod tests { assert_eq!( r.body.unwrap().value, Bytes::Json { - value: json::Value::List { + value: JsonValue::List { space0: "".to_string(), elements: vec![ - json::ListElement { + JsonListElement { space0: "".to_string(), - value: json::Value::Number("1".to_string()), + value: JsonValue::Number("1".to_string()), space1: "".to_string(), }, - json::ListElement { + JsonListElement { space0: "".to_string(), - value: json::Value::Number("2".to_string()), + value: JsonValue::Number("2".to_string()), space1: "".to_string(), }, - json::ListElement { + JsonListElement { space0: "".to_string(), - value: json::Value::Number("3".to_string()), + value: JsonValue::Number("3".to_string()), space1: "".to_string(), }, ], @@ -528,7 +524,7 @@ mod tests { assert_eq!( r.body.unwrap().value, Bytes::Json { - value: json::Value::String(Template { + value: JsonValue::String(Template { quotes: true, elements: vec![TemplateElement::String { value: "Hello".to_string(), @@ -545,7 +541,7 @@ mod tests { assert_eq!( r.body.unwrap().value, Bytes::Json { - value: json::Value::Number("100".to_string()) + value: JsonValue::Number("100".to_string()) } ); } @@ -737,22 +733,22 @@ mod tests { assert_eq!( b.value, Bytes::Json { - value: json::Value::List { + value: JsonValue::List { space0: "".to_string(), elements: vec![ - json::ListElement { + JsonListElement { space0: "".to_string(), - value: json::Value::Number("1".to_string()), + value: JsonValue::Number("1".to_string()), space1: "".to_string(), }, - json::ListElement { + JsonListElement { space0: "".to_string(), - value: json::Value::Number("2".to_string()), + value: JsonValue::Number("2".to_string()), space1: "".to_string(), }, - json::ListElement { + JsonListElement { space0: "".to_string(), - value: json::Value::Number("3".to_string()), + value: JsonValue::Number("3".to_string()), space1: "".to_string(), }, ], @@ -767,7 +763,7 @@ mod tests { assert_eq!( b.value, Bytes::Json { - value: json::Value::Object { + value: JsonValue::Object { space0: "".to_string(), elements: vec![], } @@ -781,7 +777,7 @@ mod tests { assert_eq!( b.value, Bytes::Json { - value: json::Value::Object { + value: JsonValue::Object { space0: "".to_string(), elements: vec![], } diff --git a/src/parser/predicate.rs b/src/parser/predicate.rs index 982b6ffe0..8d03228b3 100644 --- a/src/parser/predicate.rs +++ b/src/parser/predicate.rs @@ -1,5 +1,22 @@ -use crate::core::ast::*; -use crate::core::common::SourceInfo; +/* + * hurl (https://hurl.dev) + * Copyright (C) 2020 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::ast::*; use super::combinators::*; use super::error::*; @@ -210,7 +227,7 @@ fn predicate_value(reader: &mut Reader) -> ParseResult<'static, PredicateValue> #[cfg(test)] mod tests { - use crate::core::common::Pos; + use crate::ast::Pos; use super::*; diff --git a/src/parser/primitives.rs b/src/parser/primitives.rs index 700e2cdd4..90dc51a67 100644 --- a/src/parser/primitives.rs +++ b/src/parser/primitives.rs @@ -15,8 +15,7 @@ * limitations under the License. * */ -use crate::core::ast::*; -use crate::core::common::SourceInfo; +use crate::ast::*; use super::combinators::*; use super::error::*; @@ -519,7 +518,7 @@ pub fn hex_digit(reader: &mut Reader) -> ParseResult<'static, u32> { #[cfg(test)] mod tests { - use crate::core::common::Pos; + use crate::ast::Pos; use super::*; diff --git a/src/parser/query.rs b/src/parser/query.rs index 6c123411e..6a094b58a 100644 --- a/src/parser/query.rs +++ b/src/parser/query.rs @@ -1,6 +1,21 @@ -use crate::core::ast::*; -use crate::core::common::Pos; -use crate::core::common::SourceInfo; +/* + * hurl (https://hurl.dev) + * Copyright (C) 2020 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::ast::*; use super::combinators::*; use super::cookiepath::cookiepath; diff --git a/src/parser/reader.rs b/src/parser/reader.rs index 18263065d..63852c49f 100644 --- a/src/parser/reader.rs +++ b/src/parser/reader.rs @@ -15,7 +15,7 @@ * limitations under the License. * */ -use crate::core::common::Pos; +use crate::ast::Pos; #[derive(Clone, Debug, PartialEq, Eq)] pub struct Reader { diff --git a/src/parser/sections.rs b/src/parser/sections.rs index 06bb50444..97fcad2d7 100644 --- a/src/parser/sections.rs +++ b/src/parser/sections.rs @@ -15,9 +15,7 @@ * limitations under the License. * */ -use crate::core::ast::*; -use crate::core::common::Pos; -use crate::core::common::SourceInfo; +use crate::ast::*; use super::combinators::*; use super::error::*; @@ -368,7 +366,7 @@ fn assert(reader: &mut Reader) -> ParseResult<'static, Assert> { #[cfg(test)] mod tests { use super::*; - use crate::core::common::Pos; + use crate::ast::Pos; #[test] fn test_section_name() { diff --git a/src/parser/string.rs b/src/parser/string.rs index 26c36b725..14f78018e 100644 --- a/src/parser/string.rs +++ b/src/parser/string.rs @@ -15,9 +15,7 @@ * limitations under the License. * */ -use crate::core::ast::*; -use crate::core::common::Pos; -use crate::core::common::SourceInfo; +use crate::ast::*; use super::combinators::*; use super::error::*; diff --git a/src/parser/template.rs b/src/parser/template.rs index 60239257f..2c06d78f6 100644 --- a/src/parser/template.rs +++ b/src/parser/template.rs @@ -16,9 +16,7 @@ * */ -use crate::core::ast::TemplateElement; -use crate::core::common::Pos; -use crate::core::common::SourceInfo; +use crate::ast::{Pos, SourceInfo, TemplateElement}; use super::error; use super::expr; @@ -132,8 +130,7 @@ pub fn templatize(encoded_string: EncodedString) -> ParseResult<'static, Vec bool { @@ -149,7 +149,7 @@ impl Assert { #[cfg(test)] pub mod tests { - use crate::core::common::SourceInfo; + use crate::ast::SourceInfo; use super::super::query; use super::*; diff --git a/src/runner/body.rs b/src/runner/body.rs index c050d9e61..b59ec3eb1 100644 --- a/src/runner/body.rs +++ b/src/runner/body.rs @@ -21,10 +21,10 @@ use std::fs::File; use std::io::prelude::*; use std::path::Path; -use crate::core::common::Value; +use crate::ast::*; -use super::super::core::ast::*; use super::core::{Error, RunnerError}; +use super::value::Value; impl Body { pub fn eval( @@ -87,7 +87,7 @@ impl Bytes { #[cfg(test)] mod tests { - use crate::core::common::SourceInfo; + use crate::ast::SourceInfo; use super::*; diff --git a/src/runner/capture.rs b/src/runner/capture.rs index 3414671a8..66d8d127b 100644 --- a/src/runner/capture.rs +++ b/src/runner/capture.rs @@ -15,16 +15,15 @@ * limitations under the License. * */ +use regex::Regex; use std::collections::HashMap; -use regex::Regex; - -use crate::core::common::Value; +use crate::ast::*; use crate::http; -use super::super::core::ast::*; use super::core::RunnerError; use super::core::{CaptureResult, Error}; +use super::value::Value; impl Capture { pub fn eval( @@ -101,7 +100,7 @@ impl Subquery { #[cfg(test)] pub mod tests { - use crate::core::common::{Pos, SourceInfo}; + use crate::ast::{Pos, SourceInfo}; use super::*; diff --git a/src/runner/core.rs b/src/runner/core.rs index 1956b68d8..4da224f05 100644 --- a/src/runner/core.rs +++ b/src/runner/core.rs @@ -17,14 +17,17 @@ */ use std::collections::HashMap; -use crate::core::common::{FormatError, SourceInfo, Value}; +use crate::ast::SourceInfo; use crate::http; +use super::value::Value; + #[derive(Clone, Debug, PartialEq, Eq)] pub struct RunnerOptions { pub fail_fast: bool, pub variables: HashMap, pub to_entry: Option, + pub context_dir: String, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -179,118 +182,4 @@ pub enum RunnerError { }, } -impl FormatError for Error { - fn source_info(&self) -> SourceInfo { - self.clone().source_info - } - - fn description(&self) -> String { - match &self.inner { - RunnerError::InvalidURL(..) => "Invalid url".to_string(), - RunnerError::TemplateVariableNotDefined { .. } => "Undefined Variable".to_string(), - RunnerError::VariableNotDefined { .. } => "Undefined Variable".to_string(), - RunnerError::HttpConnection { .. } => "Http Connection".to_string(), - RunnerError::CouldNotResolveProxyName => "Http Connection".to_string(), - RunnerError::CouldNotResolveHost => "Http Connection".to_string(), - RunnerError::FailToConnect => "Http Connection".to_string(), - RunnerError::Timeout => "Http Connection".to_string(), - RunnerError::TooManyRedirect => "Http Connection".to_string(), - RunnerError::CouldNotParseResponse => "Http Connection".to_string(), - RunnerError::SSLCertificate => "Http Connection".to_string(), - RunnerError::PredicateValue { .. } => "Assert - Predicate Value Failed".to_string(), - RunnerError::InvalidRegex {} => "Invalid regex".to_string(), - RunnerError::FileReadAccess { .. } => "File ReadAccess".to_string(), - RunnerError::QueryInvalidXml { .. } => "Invalid XML".to_string(), - RunnerError::QueryInvalidXpathEval {} => "Invalid xpath expression".to_string(), - RunnerError::QueryHeaderNotFound {} => "Header not Found".to_string(), - RunnerError::QueryCookieNotFound {} => "Cookie not Found".to_string(), - RunnerError::AssertHeaderValueError { .. } => "Assert Header Value".to_string(), - RunnerError::AssertBodyValueError { .. } => "Assert Body Value".to_string(), - RunnerError::AssertVersion { .. } => "Assert Http Version".to_string(), - RunnerError::AssertStatus { .. } => "Assert Status".to_string(), - RunnerError::QueryInvalidJson { .. } => "Invalid Json".to_string(), - RunnerError::QueryInvalidJsonpathExpression { .. } => "Invalid jsonpath".to_string(), - RunnerError::PredicateType { .. } => "Assert - Inconsistent predicate type".to_string(), - RunnerError::SubqueryInvalidInput { .. } => "Subquery error".to_string(), - RunnerError::InvalidDecoding { .. } => "Invalid Decoding".to_string(), - RunnerError::InvalidCharset { .. } => "Invalid Charset".to_string(), - RunnerError::AssertFailure { .. } => "Assert Failure".to_string(), - RunnerError::UnrenderableVariable { .. } => "Unrenderable Variable".to_string(), - RunnerError::NoQueryResult { .. } => "No query result".to_string(), - } - } - - fn fixme(&self) -> String { - match &self.inner { - RunnerError::InvalidURL(url) => format!("Invalid url <{}>", url), - RunnerError::TemplateVariableNotDefined { name } => { - format!("You must set the variable {}", name) - } - RunnerError::HttpConnection { url, message } => { - format!("can not connect to {} ({})", url, message) - } - RunnerError::CouldNotResolveProxyName => "Could not resolve proxy name".to_string(), - RunnerError::CouldNotResolveHost => "Could not resolve host".to_string(), - RunnerError::FailToConnect => "Fail to connect".to_string(), - RunnerError::Timeout => "Timeout has been reached".to_string(), - RunnerError::TooManyRedirect => "Too many redirect".to_string(), - RunnerError::CouldNotParseResponse => "Could not parse response".to_string(), - RunnerError::SSLCertificate => "SSl Certificate".to_string(), - RunnerError::AssertVersion { actual, .. } => format!("actual value is <{}>", actual), - RunnerError::AssertStatus { actual, .. } => format!("actual value is <{}>", actual), - RunnerError::PredicateValue(value) => { - format!("actual value is <{}>", value.to_string()) - } - RunnerError::InvalidRegex {} => "Regex expression is not valid".to_string(), - RunnerError::FileReadAccess { value } => format!("File {} can not be read", value), - RunnerError::QueryInvalidXml { .. } => { - "The Http response is not a valid XML".to_string() - } - RunnerError::QueryHeaderNotFound {} => { - "This header has not been found in the response".to_string() - } - RunnerError::QueryCookieNotFound {} => { - "This cookie has not been found in the response".to_string() - } - RunnerError::QueryInvalidXpathEval {} => { - "The xpath expression is not valid".to_string() - } - RunnerError::AssertHeaderValueError { actual } => { - format!("actual value is <{}>", actual) - } - RunnerError::AssertBodyValueError { actual, .. } => { - format!("actual value is <{}>", actual) - } - RunnerError::QueryInvalidJson { .. } => { - "The http response is not a valid json".to_string() - } - RunnerError::QueryInvalidJsonpathExpression { value } => { - format!("the jsonpath expression '{}' is not valid", value) - } - RunnerError::PredicateType { .. } => { - "predicate type inconsistent with value return by query".to_string() - } - RunnerError::SubqueryInvalidInput => { - "Type from query result and subquery do not match".to_string() - } - RunnerError::InvalidDecoding { charset } => { - format!("The body can not be decoded with charset '{}'", charset) - } - RunnerError::InvalidCharset { charset } => { - format!("The charset '{}' is not valid", charset) - } - RunnerError::AssertFailure { - actual, expected, .. - } => format!("actual: {}\nexpected: {}", actual, expected), - RunnerError::VariableNotDefined { name } => { - format!("You must set the variable {}", name) - } - RunnerError::UnrenderableVariable { value } => { - format!("value {} can not be rendered", value) - } - RunnerError::NoQueryResult { .. } => "The query didn't return any result".to_string(), - } - } -} - // endregion diff --git a/src/runner/entry.rs b/src/runner/entry.rs index 3366c3c50..1f7fa7829 100644 --- a/src/runner/entry.rs +++ b/src/runner/entry.rs @@ -18,15 +18,13 @@ use std::collections::HashMap; use std::time::Instant; -use crate::core::ast::*; -use crate::core::common::SourceInfo; -use crate::core::common::Value; +use crate::ast::*; use crate::http; +use crate::http::HttpError; use super::core::*; use super::core::{Error, RunnerError}; -use crate::format::logger::Logger; -use crate::http::HttpError; +use super::value::Value; /// Run an entry with the hurl http client /// @@ -52,7 +50,8 @@ pub fn run( entry_index: usize, variables: &mut HashMap, context_dir: String, - logger: &Logger, + log_verbose: &impl Fn(&str), + log_error_message: &impl Fn(bool, &str), ) -> EntryResult { let http_request = match entry.clone().request.eval(variables, context_dir.clone()) { Ok(r) => r, @@ -63,15 +62,13 @@ pub fn run( captures: vec![], asserts: vec![], errors: vec![error], - time_in_ms: 0, }; } }; - logger - .verbose("------------------------------------------------------------------------------"); - logger.verbose(format!("executing entry {}", entry_index + 1).as_str()); + log_verbose("------------------------------------------------------------------------------"); + log_verbose(format!("executing entry {}", entry_index + 1).as_str()); // // Experimental features @@ -82,20 +79,23 @@ pub fn run( if let Ok(cookie) = http::Cookie::from_str(s.as_str()) { http_client.add_cookie(cookie); } else { - logger.verbose(format!("cookie string can not be parsed: '{}'", s).as_str()); + log_error_message( + true, + format!("cookie string can not be parsed: '{}'", s).as_str(), + ); } } if entry.request.clear_cookie_storage() { http_client.clear_cookie_storage(); } - logger.verbose(""); - logger.verbose("Cookie store:"); + log_verbose(""); + log_verbose("Cookie store:"); for cookie in http_client.get_cookie_storage() { - logger.verbose(cookie.to_string().as_str()); + log_verbose(cookie.to_string().as_str()); } - logger.verbose(""); - log_request(logger, &http_request); + log_verbose(""); + log_request(log_verbose, &http_request); let start = Instant::now(); let http_response = match http_client.execute(&http_request, 0) { @@ -134,7 +134,7 @@ pub fn run( }; let time_in_ms = start.elapsed().as_millis(); - logger.verbose(format!("Response Time: {}ms", time_in_ms).as_str()); + log_verbose(format!("Response Time: {}ms", time_in_ms).as_str()); let captures = match entry.response.clone() { None => vec![], @@ -177,13 +177,13 @@ pub fn run( .collect(); if !captures.is_empty() { - logger.verbose("Captures"); + log_verbose("Captures"); for capture in captures.clone() { - logger.verbose(format!("{}: {}", capture.name, capture.value).as_str()); + log_verbose(format!("{}: {}", capture.name, capture.value).as_str()); } } - logger.verbose(""); + log_verbose(""); EntryResult { request: Some(http_request), @@ -195,39 +195,39 @@ pub fn run( } } -pub fn log_request(logger: &Logger, request: &http::Request) { - logger.verbose("Request"); - logger.verbose(format!("{} {}", request.method, request.url).as_str()); +pub fn log_request(log_verbose: impl Fn(&str), request: &http::Request) { + log_verbose("Request"); + log_verbose(format!("{} {}", request.method, request.url).as_str()); for header in request.headers.clone() { - logger.verbose(header.to_string().as_str()); + log_verbose(header.to_string().as_str()); } if !request.querystring.is_empty() { - logger.verbose("[QueryStringParams]"); + log_verbose("[QueryStringParams]"); for param in request.querystring.clone() { - logger.verbose(param.to_string().as_str()); + log_verbose(param.to_string().as_str()); } } if !request.form.is_empty() { - logger.verbose("[FormParams]"); + log_verbose("[FormParams]"); for param in request.form.clone() { - logger.verbose(param.to_string().as_str()); + log_verbose(param.to_string().as_str()); } } if !request.multipart.is_empty() { - logger.verbose("[MultipartFormData]"); + log_verbose("[MultipartFormData]"); for param in request.multipart.clone() { - logger.verbose(param.to_string().as_str()); + log_verbose(param.to_string().as_str()); } } if !request.cookies.is_empty() { - logger.verbose("[Cookies]"); + log_verbose("[Cookies]"); for cookie in request.cookies.clone() { - logger.verbose(cookie.to_string().as_str()); + log_verbose(cookie.to_string().as_str()); } } if let Some(s) = request.content_type.clone() { - logger.verbose(""); - logger.verbose(format!("implicit content-type={}", s).as_str()); + log_verbose(""); + log_verbose(format!("implicit content-type={}", s).as_str()); } - logger.verbose(""); + log_verbose(""); } diff --git a/src/runner/expr.rs b/src/runner/expr.rs index 9c3b6e2f0..5197e110f 100644 --- a/src/runner/expr.rs +++ b/src/runner/expr.rs @@ -17,10 +17,10 @@ */ use std::collections::HashMap; -use crate::core::ast::Expr; -use crate::core::common::Value; +use crate::ast::Expr; use super::core::{Error, RunnerError}; +use super::value::Value; impl Expr { pub fn eval(self, variables: &HashMap) -> Result { diff --git a/src/runner/http_response.rs b/src/runner/http_response.rs index 069e0ce46..e0ba3d8b9 100644 --- a/src/runner/http_response.rs +++ b/src/runner/http_response.rs @@ -16,10 +16,12 @@ * */ +use encoding::{DecoderTrap, EncodingRef}; + +use crate::http::Response; + use super::cookie::ResponseCookie; use super::core::RunnerError; -use crate::http::Response; -use encoding::{DecoderTrap, EncodingRef}; impl Response { pub fn cookies(&self) -> Vec { diff --git a/src/runner/file.rs b/src/runner/hurl_file.rs similarity index 71% rename from src/runner/file.rs rename to src/runner/hurl_file.rs index 6cb143557..6fd1dd084 100644 --- a/src/runner/file.rs +++ b/src/runner/hurl_file.rs @@ -18,14 +18,12 @@ use std::collections::HashMap; use std::time::Instant; -use crate::core::ast::*; -use crate::core::common::Value; +use crate::ast::*; use crate::http; -use super::super::format; use super::core::*; use super::entry; -use crate::core::common::FormatError; +use super::value::Value; /// Run a Hurl file with the hurl http client /// @@ -33,8 +31,8 @@ use crate::core::common::FormatError; /// /// ``` /// use hurl::http; +/// use hurl::parser; /// use hurl::runner; -/// use hurl::format; /// /// // Parse Hurl file /// let filename = "sample.hurl".to_string(); @@ -42,7 +40,15 @@ use crate::core::common::FormatError; /// GET http://localhost:8000/hello /// HTTP/1.0 200 /// "#; -/// let hurl_file = hurl::parser::parse_hurl_file(s).unwrap(); +/// let hurl_file = parser::parse_hurl_file(s).unwrap(); +/// +/// // create loggers (function pointer or closure) +/// fn log_verbose(message: &str) { eprintln!("* {}", message); } +/// fn log_error_message(_warning:bool, message: &str) { eprintln!("{}", message); } +/// fn log_error(error: &runner::Error, _warning: bool) { eprintln!("* {:#?}", error); } +/// let log_verbose: fn(&str) = log_verbose; +/// let log_error_message: fn(bool, &str) = log_error_message; +/// let log_error: fn(&runner::Error, bool) = log_error; /// /// // Create an http client /// let options = http::ClientOptions { @@ -60,42 +66,35 @@ use crate::core::common::FormatError; /// /// // Define runner options /// let variables = std::collections::HashMap::new(); -/// let options = runner::core::RunnerOptions { +/// let options = runner::RunnerOptions { /// fail_fast: false, /// variables, /// to_entry: None, +/// context_dir: "current_dir".to_string(), /// }; /// -/// // create a logger -/// // It needs the text input as lines for reporting errors -/// let lines = regex::Regex::new(r"\n|\r\n").unwrap().split(&s).map(|l| l.to_string()).collect(); -/// let logger = format::logger::Logger { -/// filename: Some(filename.clone()), -/// lines, -/// verbose: false, -/// color: false -/// }; -/// /// // Run the hurl file -/// let context_dir = "current_dir".to_string(); -/// let hurl_results = runner::file::run( +/// let hurl_results = runner::run_hurl_file( /// hurl_file, /// &mut client, /// filename, -/// context_dir, /// options, -/// logger +/// &log_verbose, +/// &log_error_message, +/// &log_error, /// ); /// assert!(hurl_results.success); /// /// ``` +/// pub fn run( hurl_file: HurlFile, http_client: &mut http::Client, filename: String, - context_dir: String, options: RunnerOptions, - logger: format::logger::Logger, + log_verbose: &impl Fn(&str), + log_error_message: &impl Fn(bool, &str), + log_error: &impl Fn(&Error, bool), ) -> HurlResult { let mut entries = vec![]; let mut variables = HashMap::default(); @@ -124,23 +123,14 @@ pub fn run( http_client, entry_index, &mut variables, - context_dir.clone(), - &logger, + options.context_dir.clone(), + &log_verbose, + &log_error_message, ); entries.push(entry_result.clone()); for e in entry_result.errors.clone() { - let error = format::error::Error { - source_info: e.clone().source_info, - description: e.clone().description(), - fixme: e.fixme(), - lines: vec![], - filename: "".to_string(), - warning: false, - color: false, - }; - logger.clone().error(&error); + log_error(&e, false); } - if options.fail_fast && !entry_result.errors.is_empty() { break; } diff --git a/src/runner/json.rs b/src/runner/json.rs index bf9b7261a..acdd964a3 100644 --- a/src/runner/json.rs +++ b/src/runner/json.rs @@ -17,22 +17,22 @@ */ use std::collections::HashMap; -use crate::core::common::Value; -use crate::core::json; +use crate::ast::{JsonListElement, JsonObjectElement, JsonValue}; use super::core::Error; +use super::value::Value; -impl json::Value { +impl JsonValue { pub fn eval(self, variables: &HashMap) -> Result { match self { - json::Value::Null {} => Ok("null".to_string()), - json::Value::Number(s) => Ok(s), - json::Value::String(template) => { + JsonValue::Null {} => Ok("null".to_string()), + JsonValue::Number(s) => Ok(s), + JsonValue::String(template) => { let s = template.eval(variables)?; Ok(format!("\"{}\"", s)) } - json::Value::Boolean(v) => Ok(v.to_string()), - json::Value::List { space0, elements } => { + JsonValue::Boolean(v) => Ok(v.to_string()), + JsonValue::List { space0, elements } => { let mut elems_string = vec![]; for element in elements { let s = element.eval(variables)?; @@ -40,7 +40,7 @@ impl json::Value { } Ok(format!("[{}{}]", space0, elems_string.join(","))) } - json::Value::Object { space0, elements } => { + JsonValue::Object { space0, elements } => { let mut elems_string = vec![]; for element in elements { let s = element.eval(variables)?; @@ -52,14 +52,14 @@ impl json::Value { } } -impl json::ListElement { +impl JsonListElement { pub fn eval(self, variables: &HashMap) -> Result { let s = self.value.eval(variables)?; Ok(format!("{}{}{}", self.space0, s, self.space1)) } } -impl json::ObjectElement { +impl JsonObjectElement { pub fn eval(self, variables: &HashMap) -> Result { let value = self.value.eval(variables)?; Ok(format!( @@ -71,32 +71,30 @@ impl json::ObjectElement { #[cfg(test)] mod tests { - use crate::core::ast::{Template, TemplateElement}; - use crate::core::common::SourceInfo; - use super::super::core::RunnerError; use super::*; + use crate::ast::*; #[test] fn test_scalar_value() { let mut variables = HashMap::new(); variables.insert("name".to_string(), Value::String("Bob".to_string())); assert_eq!( - json::Value::Null {}.eval(&variables).unwrap(), + JsonValue::Null {}.eval(&variables).unwrap(), "null".to_string() ); assert_eq!( - json::Value::Number("3.14".to_string()) + JsonValue::Number("3.14".to_string()) .eval(&variables) .unwrap(), "3.14".to_string() ); assert_eq!( - json::Value::Boolean(false).eval(&variables).unwrap(), + JsonValue::Boolean(false).eval(&variables).unwrap(), "false".to_string() ); assert_eq!( - json::tests::hello_world_value().eval(&variables).unwrap(), + json_hello_world_value().eval(&variables).unwrap(), "\"Hello Bob!\"".to_string() ); } @@ -104,10 +102,7 @@ mod tests { #[test] fn test_error() { let variables = HashMap::new(); - let error = json::tests::hello_world_value() - .eval(&variables) - .err() - .unwrap(); + let error = json_hello_world_value().eval(&variables).err().unwrap(); assert_eq!(error.source_info, SourceInfo::init(1, 15, 1, 19)); assert_eq!( error.inner, @@ -122,7 +117,7 @@ mod tests { let mut variables = HashMap::new(); variables.insert("name".to_string(), Value::String("Bob".to_string())); assert_eq!( - json::Value::List { + JsonValue::List { space0: "".to_string(), elements: vec![], } @@ -132,22 +127,22 @@ mod tests { ); assert_eq!( - json::Value::List { + JsonValue::List { space0: "".to_string(), elements: vec![ - json::ListElement { + JsonListElement { space0: "".to_string(), - value: json::Value::Number("1".to_string()), + value: JsonValue::Number("1".to_string()), space1: "".to_string() }, - json::ListElement { + JsonListElement { space0: " ".to_string(), - value: json::Value::Number("-2".to_string()), + value: JsonValue::Number("-2".to_string()), space1: "".to_string() }, - json::ListElement { + JsonListElement { space0: " ".to_string(), - value: json::Value::Number("3.0".to_string()), + value: JsonValue::Number("3.0".to_string()), space1: "".to_string() }, ], @@ -166,17 +161,17 @@ mod tests { source_info: SourceInfo::init(0, 0, 0, 0), }; assert_eq!( - json::Value::List { + JsonValue::List { space0: "".to_string(), elements: vec![ - json::ListElement { + JsonListElement { space0: "".to_string(), - value: json::Value::String(template), + value: JsonValue::String(template), space1: "".to_string() }, - json::ListElement { + JsonListElement { space0: " ".to_string(), - value: json::tests::hello_world_value(), + value: json_hello_world_value(), space1: "".to_string() }, ], @@ -191,7 +186,7 @@ mod tests { fn test_object_value() { let variables = HashMap::new(); assert_eq!( - json::Value::Object { + JsonValue::Object { space0: "".to_string(), elements: vec![] } @@ -200,7 +195,7 @@ mod tests { "{}".to_string() ); assert_eq!( - json::tests::person_value().eval(&variables).unwrap(), + json_person_value().eval(&variables).unwrap(), r#"{ "firstName": "John" }"# diff --git a/src/runner/log_deserialize.rs b/src/runner/log_deserialize.rs index 3ae3389a1..8f1ac3dbd 100644 --- a/src/runner/log_deserialize.rs +++ b/src/runner/log_deserialize.rs @@ -16,9 +16,10 @@ * */ +use crate::http::*; + use super::cookie::*; use super::core::*; -use crate::http::*; type ParseError = String; @@ -281,6 +282,7 @@ pub fn parse_request_cookie(value: serde_json::Value) -> Result Result { if let serde_json::Value::Object(map) = value { let name = match map.get("name") { @@ -373,6 +375,7 @@ fn parse_version(s: String) -> Result { #[cfg(test)] mod tests { use super::*; + use crate::runner::value::Value; #[test] fn test_parse_request() { @@ -514,4 +517,24 @@ mod tests { Version::Http10 ); } + + #[test] + fn test_parse_value() { + assert_eq!( + Value::from_json(&serde_json::Value::String("hello".to_string())), + Value::String("hello".to_string()) + ); + assert_eq!( + Value::from_json(&serde_json::Value::Bool(true)), + Value::Bool(true) + ); + assert_eq!( + Value::from_json(&serde_json::Value::from(1)), + Value::Integer(1) + ); + assert_eq!( + Value::from_json(&serde_json::Value::from(1.5)), + Value::Float(1, 500_000_000_000_000_000) + ); + } } diff --git a/src/runner/log_serialize.rs b/src/runner/log_serialize.rs index ef85313d1..9cc2cea31 100644 --- a/src/runner/log_serialize.rs +++ b/src/runner/log_serialize.rs @@ -23,6 +23,7 @@ use crate::http::*; use super::cookie::*; use super::core::*; +use super::value::Value; impl Serialize for HurlResult { fn serialize(&self, serializer: S) -> Result @@ -225,3 +226,35 @@ impl Serialize for Cookie { state.end() } } + +impl Serialize for Value { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + Value::Bool(v) => serializer.serialize_bool(*v), + Value::Integer(v) => serializer.serialize_i64(*v), + Value::Float(i, d) => { + let value = *i as f64 + (*d as f64) / 1_000_000_000_000_000_000.0; + serializer.serialize_f64(value) + } + Value::String(s) => serializer.serialize_str(s), + Value::List(values) => serializer.collect_seq(values), + Value::Object(values) => serializer.collect_map(values.iter().map(|(k, v)| (k, v))), + Value::Nodeset(size) => { + let size = *size as i64; + serializer.collect_map(vec![ + ("type", serde_json::Value::String("nodeset".to_string())), + ("size", serde_json::Value::from(size)), + ]) + } + Value::Bytes(v) => { + let encoded = base64::encode(v); + serializer.serialize_str(&encoded) + } + Value::Null => serializer.serialize_none(), + Value::Unit => todo!("how to serialize that in json?"), + } + } +} diff --git a/src/runner/mod.rs b/src/runner/mod.rs index e13106a9c..e33f7e2e3 100644 --- a/src/runner/mod.rs +++ b/src/runner/mod.rs @@ -23,22 +23,27 @@ //! //! +pub use self::core::{Error, HurlResult, RunnerError, RunnerOptions}; +pub use self::hurl_file::run as run_hurl_file; +pub use self::log_deserialize::parse_results as deserialize_results; + mod assert; mod body; mod capture; mod cookie; -pub mod core; +mod core; mod entry; mod expr; -pub mod file; mod http_response; +mod hurl_file; mod json; -pub mod log_deserialize; -pub mod log_serialize; +mod log_deserialize; +mod log_serialize; mod multipart; mod predicate; mod query; -pub mod request; +mod request; mod response; mod template; +mod value; mod xpath; diff --git a/src/runner/multipart.rs b/src/runner/multipart.rs index 7021b0e18..de5134cdd 100644 --- a/src/runner/multipart.rs +++ b/src/runner/multipart.rs @@ -25,11 +25,11 @@ use std::io::prelude::*; use std::io::Read; use std::path::Path; -use crate::core::ast::*; -use crate::core::common::Value; +use crate::ast::*; use crate::http; use super::core::{Error, RunnerError}; +use super::value::Value; impl MultipartParam { pub fn eval( @@ -140,7 +140,7 @@ impl FileValue { #[cfg(test)] mod tests { - use crate::core::common::SourceInfo; + use crate::ast::SourceInfo; use super::*; diff --git a/src/runner/predicate.rs b/src/runner/predicate.rs index 2b3400b00..9ce5877ae 100644 --- a/src/runner/predicate.rs +++ b/src/runner/predicate.rs @@ -15,41 +15,14 @@ * limitations under the License. * */ +use regex::Regex; use std::collections::HashMap; -use regex::Regex; +use crate::ast::*; -use crate::core::common::Value; -use crate::core::common::{Pos, SourceInfo}; - -use super::super::core::ast::*; use super::core::*; use super::core::{Error, RunnerError}; - -// equals 10 function return () -// not equals 10 -// countEquals 3 return () => ok PredicateExpectedError -// not countEquals nok - -// PredicateValue => Recoverable with a not -// PredicateType - -// xpath boolean(//user) equals 10 -// ^^^^^^^^^^ Type does not matched with value return by query (generic message for the time-being -// xpath boolean(//user) not equals 10 -// ^^^^^^^^^^^^^ Type does not matched with value return by query -// xpath cont(//user) equals 10 -// ^^^^^^^^^^^^^ actual value is 9 -// xpath cont(//user) greaterThan 10 -// ^^^^^^^^^^^^^^ actual value is 9 - -// Predicate -// 2 evals - -// 1) eval template -// 2) eval predicate - -// equals template becomes and equals string +use super::value::Value; impl Predicate { pub fn eval(self, variables: &HashMap, value: Option) -> PredicateResult { diff --git a/src/runner/query.rs b/src/runner/query.rs index cb93c274a..95926f450 100644 --- a/src/runner/query.rs +++ b/src/runner/query.rs @@ -16,17 +16,16 @@ * */ +use regex::Regex; use std::collections::HashMap; -use regex::Regex; - -use crate::core::common::Value; +use crate::ast::*; use crate::http; use crate::jsonpath; -use super::super::core::ast::*; use super::cookie; use super::core::{Error, RunnerError}; +use super::value::Value; use super::xpath; pub type QueryResult = Result, Error>; @@ -142,7 +141,7 @@ impl Query { // } // }; // Using your own json implem - let query = match jsonpath::parser::parse::parse(value.as_str()) { + let query = match jsonpath::parse(value.as_str()) { Ok(q) => q, Err(_) => { return Err(Error { @@ -249,10 +248,38 @@ impl CookieAttributeName { } } +impl Value { + pub fn from_json(value: &serde_json::Value) -> Value { + match value { + serde_json::Value::Null => Value::Null, + serde_json::Value::Bool(bool) => Value::Bool(*bool), + serde_json::Value::Number(n) => { + if n.is_f64() { + Value::from_f64(n.as_f64().unwrap()) + } else { + Value::Integer(n.as_i64().unwrap()) + } + } + serde_json::Value::String(s) => Value::String(s.to_string()), + serde_json::Value::Array(elements) => { + Value::List(elements.iter().map(|e| Value::from_json(e)).collect()) + } + serde_json::Value::Object(map) => { + let mut elements = vec![]; + for (key, value) in map { + elements.push((key.to_string(), Value::from_json(value))); + // + } + Value::Object(elements) + } + } + } +} + #[cfg(test)] pub mod tests { use super::*; - use crate::core::common::{Pos, SourceInfo}; + use crate::ast::{Pos, SourceInfo}; pub fn xpath_invalid_query() -> Query { // xpath ??? diff --git a/src/runner/request.rs b/src/runner/request.rs index aae6a4348..67bf97726 100644 --- a/src/runner/request.rs +++ b/src/runner/request.rs @@ -23,11 +23,11 @@ use std::collections::HashMap; #[allow(unused)] use std::io::prelude::*; -use crate::core::ast::*; -use crate::core::common::Value; +use crate::ast::*; use crate::http; use super::core::Error; +use super::value::Value; impl Request { pub fn eval( @@ -61,7 +61,7 @@ impl Request { form.push(http::Param { name, value }); } // if !self.clone().form_params().is_empty() { - // headers.push(http::core::Header { + // headers.push(http::ast::Header { // name: String::from("Content-Type"), // value: String::from("application/x-www-form-urlencoded"), // }); @@ -105,7 +105,7 @@ impl Request { // if self.content_type().is_none() { // if let Some(body) = self.body { // if let Bytes::Json { .. } = body.value { - // headers.push(http::core::Header { + // headers.push(http::ast::Header { // name: String::from("Content-Type"), // value: String::from("application/json"), // }); @@ -208,7 +208,7 @@ impl Method { #[cfg(test)] mod tests { - use crate::core::common::SourceInfo; + use crate::ast::SourceInfo; use super::super::core::RunnerError; use super::*; diff --git a/src/runner/response.rs b/src/runner/response.rs index bbd2ca189..3f8dac95d 100644 --- a/src/runner/response.rs +++ b/src/runner/response.rs @@ -17,14 +17,11 @@ */ use std::collections::HashMap; -use crate::core::common::Value; -use crate::core::common::{Pos, SourceInfo}; +use crate::ast::*; use crate::http; -use crate::runner::core::RunnerError; -use super::super::core::ast::*; -use super::core::Error; use super::core::*; +use super::value::Value; impl Response { pub fn eval_asserts( diff --git a/src/runner/template.rs b/src/runner/template.rs index 67a27ea44..2685b6c4d 100644 --- a/src/runner/template.rs +++ b/src/runner/template.rs @@ -17,10 +17,10 @@ */ use std::collections::HashMap; -use crate::core::ast::*; -use crate::core::common::Value; +use crate::ast::*; use super::core::{Error, RunnerError}; +use super::value::Value; impl Template { pub fn eval(self, variables: &HashMap) -> Result { @@ -80,7 +80,7 @@ impl Value { #[cfg(test)] mod tests { - use crate::core::common::SourceInfo; + use crate::ast::SourceInfo; use super::*; diff --git a/src/runner/value.rs b/src/runner/value.rs new file mode 100644 index 000000000..d6033140d --- /dev/null +++ b/src/runner/value.rs @@ -0,0 +1,126 @@ +/* + * hurl (https://hurl.dev) + * Copyright (C) 2020 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 std::fmt; + +/// +/// Type system used in hurl +/// Values are used by queries, captures, asserts and predicates +/// + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Value { + Unit, + Bool(bool), + Integer(i64), + + // can not use simply Float(f64) + // the trait `std::cmp::Eq` is not implemented for `f64` + // integer part, decimal part (9 digits) TODO Clarify your custom type + Float(i64, u64), + + String(String), + List(Vec), + Object(Vec<(String, Value)>), + Nodeset(usize), + Bytes(Vec), + Null, +} + +impl fmt::Display for Value { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let value = match self { + Value::Integer(x) => x.to_string(), + Value::Bool(x) => x.to_string(), + Value::Float(int, dec) => format!("{}.{}", int, dec), + Value::String(x) => x.clone(), + Value::List(values) => { + let values: Vec = values.iter().map(|e| e.to_string()).collect(); + format!("[{}]", values.join(",")) + } + Value::Object(_) => "Object()".to_string(), + Value::Nodeset(x) => format!("Nodeset{:?}", x), + Value::Bytes(x) => format!("Bytes({:x?})", x), + Value::Null => "Null".to_string(), + Value::Unit => "Unit".to_string(), + }; + write!(f, "{}", value) + } +} + +impl Value { + pub fn _type(&self) -> String { + match self { + Value::Integer(_) => "integer".to_string(), + Value::Bool(_) => "boolean".to_string(), + Value::Float(_, _) => "float".to_string(), + Value::String(_) => "string".to_string(), + Value::List(_) => "list".to_string(), + Value::Object(_) => "object".to_string(), + Value::Nodeset(_) => "nodeset".to_string(), + Value::Bytes(_) => "bytes".to_string(), + Value::Null => "null".to_string(), + Value::Unit => "unit".to_string(), + } + } + + pub fn from_f64(value: f64) -> Value { + let integer = if value < 0.0 { + value.ceil() as i64 + } else { + value.floor() as i64 + }; + let decimal = (value.abs().fract() * 1_000_000_000_000_000_000.0).round() as u64; + Value::Float(integer, decimal) + } + + pub fn is_scalar(&self) -> bool { + match self { + Value::Nodeset(_) | Value::List(_) => false, + _ => true, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_from_f64() { + assert_eq!(Value::from_f64(1.0), Value::Float(1, 0)); + assert_eq!(Value::from_f64(-1.0), Value::Float(-1, 0)); + assert_eq!( + Value::from_f64(1.1), + Value::Float(1, 100_000_000_000_000_096) + ); //TBC!! + assert_eq!( + Value::from_f64(-1.1), + Value::Float(-1, 100_000_000_000_000_096) + ); + assert_eq!( + Value::from_f64(1.5), + Value::Float(1, 500_000_000_000_000_000) + ); + } + + #[test] + fn test_is_scalar() { + assert_eq!(Value::Integer(1).is_scalar(), true); + assert_eq!(Value::List(vec![]).is_scalar(), false); + } +} diff --git a/src/runner/xpath.rs b/src/runner/xpath.rs index ca737e140..b65069cc3 100644 --- a/src/runner/xpath.rs +++ b/src/runner/xpath.rs @@ -15,12 +15,13 @@ * limitations under the License. * */ + // unique entry point to libxml extern crate libxml; use std::ffi::CStr; -use super::super::core::common::Value; +use super::value::Value; #[derive(Clone, Debug, PartialEq, Eq)] pub enum XpathError { diff --git a/tests/json.rs b/tests/json.rs index d650ea3bc..56d878b5b 100644 --- a/tests/json.rs +++ b/tests/json.rs @@ -23,10 +23,9 @@ use std::fs; use proptest::prelude::prop::test_runner::TestRunner; use proptest::prelude::*; -use hurl::core::ast::{Expr, Template, TemplateElement, Variable, Whitespace}; -use hurl::core::common::SourceInfo; -use hurl::core::json; -use hurl::format::token::*; +use hurl::ast::*; +use hurl::format::{Token, Tokenizable}; +use hurl::parser::{parse_json, Reader}; fn whitespace() -> BoxedStrategy { prop_oneof![ @@ -39,37 +38,37 @@ fn whitespace() -> BoxedStrategy { // region strategy scalar/leaves -fn value_number() -> BoxedStrategy { +fn value_number() -> BoxedStrategy { prop_oneof![ - Just(json::Value::Number("0".to_string())), - Just(json::Value::Number("1".to_string())), - Just(json::Value::Number("1.33".to_string())), - Just(json::Value::Number("-100".to_string())) + Just(JsonValue::Number("0".to_string())), + Just(JsonValue::Number("1".to_string())), + Just(JsonValue::Number("1.33".to_string())), + Just(JsonValue::Number("-100".to_string())) ] .boxed() } -fn value_boolean() -> BoxedStrategy { +fn value_boolean() -> BoxedStrategy { prop_oneof![ - Just(json::Value::Boolean(true)), - Just(json::Value::Boolean(false)), + Just(JsonValue::Boolean(true)), + Just(JsonValue::Boolean(false)), ] .boxed() } -fn value_string() -> BoxedStrategy { +fn value_string() -> BoxedStrategy { let source_info = SourceInfo::init(0, 0, 0, 0); let variable = Variable { name: "name".to_string(), source_info: source_info.clone(), }; prop_oneof![ - Just(json::Value::String(Template { + Just(JsonValue::String(Template { elements: vec![], quotes: true, source_info: source_info.clone() })), - Just(json::Value::String(Template { + Just(JsonValue::String(Template { elements: vec![TemplateElement::String { encoded: "Hello".to_string(), value: "Hello".to_string(), @@ -77,7 +76,7 @@ fn value_string() -> BoxedStrategy { quotes: true, source_info: source_info.clone() })), - Just(json::Value::String(Template { + Just(JsonValue::String(Template { elements: vec![ TemplateElement::String { encoded: "Hello\\u0020 ".to_string(), @@ -106,7 +105,7 @@ fn value_string() -> BoxedStrategy { // region strategy value -fn value() -> BoxedStrategy { +fn value() -> BoxedStrategy { let leaf = prop_oneof![value_boolean(), value_string(), value_number(),]; leaf.prop_recursive( 8, // 8 levels deep @@ -115,14 +114,14 @@ fn value() -> BoxedStrategy { |value| { prop_oneof![ // Lists - (whitespace()).prop_map(|space0| json::Value::List { + (whitespace()).prop_map(|space0| JsonValue::List { space0, elements: vec![] }), (whitespace(), whitespace(), value.clone()).prop_map(|(space0, space1, value)| { - json::Value::List { + JsonValue::List { space0, - elements: vec![json::ListElement { + elements: vec![JsonListElement { space0: "".to_string(), value, space1, @@ -138,15 +137,15 @@ fn value() -> BoxedStrategy { value_number() ) .prop_map( - |(space00, space01, value0, space10, space11, value1)| json::Value::List { + |(space00, space01, value0, space10, space11, value1)| JsonValue::List { space0: space00, elements: vec![ - json::ListElement { + JsonListElement { space0: "".to_string(), value: value0, space1: space01 }, - json::ListElement { + JsonListElement { space0: space10, value: value1, space1: space11 @@ -163,15 +162,15 @@ fn value() -> BoxedStrategy { value_boolean() ) .prop_map( - |(space00, space01, value0, space10, space11, value1)| json::Value::List { + |(space00, space01, value0, space10, space11, value1)| JsonValue::List { space0: space00, elements: vec![ - json::ListElement { + JsonListElement { space0: "".to_string(), value: value0, space1: space01 }, - json::ListElement { + JsonListElement { space0: space10, value: value1, space1: space11 @@ -188,15 +187,15 @@ fn value() -> BoxedStrategy { value_string() ) .prop_map( - |(space00, space01, value0, space10, space11, value1)| json::Value::List { + |(space00, space01, value0, space10, space11, value1)| JsonValue::List { space0: space00, elements: vec![ - json::ListElement { + JsonListElement { space0: "".to_string(), value: value0, space1: space01 }, - json::ListElement { + JsonListElement { space0: space10, value: value1, space1: space11 @@ -205,7 +204,7 @@ fn value() -> BoxedStrategy { } ), // Object - (whitespace()).prop_map(|space0| json::Value::Object { + (whitespace()).prop_map(|space0| JsonValue::Object { space0, elements: vec![] }), @@ -217,9 +216,9 @@ fn value() -> BoxedStrategy { whitespace() ) .prop_map(|(space0, space1, space2, value, space3)| { - json::Value::Object { + JsonValue::Object { space0, - elements: vec![json::ObjectElement { + elements: vec![JsonObjectElement { space0: "".to_string(), name: "key1".to_string(), space1, @@ -253,7 +252,7 @@ fn format_token(token: Token) -> String { } } -fn format_value(value: json::Value) -> String { +fn format_value(value: JsonValue) -> String { let tokens = value.tokenize(); //eprintln!("{:?}", tokens); tokens @@ -272,8 +271,8 @@ fn test_echo() { //eprintln!("value={:#?}", value); let s = format_value(value); eprintln!("s={}", s); - let mut reader = hurl::parser::reader::Reader::init(s.as_str()); - let parsed_value = hurl::parser::json::parse(&mut reader).unwrap(); + let mut reader = Reader::init(s.as_str()); + let parsed_value = parse_json(&mut reader).unwrap(); assert_eq!(format_value(parsed_value), s); Ok(()) @@ -290,8 +289,8 @@ fn test_parse_files() { let path = p.unwrap().path(); println!("parsing json file {}", path.display()); let s = fs::read_to_string(path).expect("Something went wrong reading the file"); - let mut reader = hurl::parser::reader::Reader::init(s.as_str()); - let parsed_value = hurl::parser::json::parse(&mut reader).unwrap(); + let mut reader = Reader::init(s.as_str()); + let parsed_value = parse_json(&mut reader).unwrap(); assert_eq!(format_value(parsed_value), s); } diff --git a/tests/jsonpath.rs b/tests/jsonpath.rs index 9a12063f9..6c9f363fb 100644 --- a/tests/jsonpath.rs +++ b/tests/jsonpath.rs @@ -25,7 +25,7 @@ use serde_json::json; use hurl::jsonpath; fn test_ok(s: &str, value: serde_json::Value) -> Vec { - return match jsonpath::parser::parse::parse(s) { + return match jsonpath::parse(s) { Ok(expr) => expr.eval(value), Err(e) => panic!("{:?}", e), }; diff --git a/tests/runner.rs b/tests/runner.rs index bf8845a92..e0cb8ae9d 100644 --- a/tests/runner.rs +++ b/tests/runner.rs @@ -17,15 +17,22 @@ */ extern crate hurl; -use hurl::core::ast; -use hurl::core::ast::{EncodedString, Template, TemplateElement}; -use hurl::core::common::{Pos, SourceInfo}; -use hurl::format; +use hurl::ast::*; use hurl::http; use hurl::runner; -use hurl::runner::core::RunnerOptions; +use hurl::runner::RunnerOptions; use std::collections::HashMap; +pub fn log_verbose(message: &str) { + eprintln!("* {}", message); +} +pub fn log_error_message(_warning: bool, message: &str) { + eprintln!("{}", message); +} +pub fn log_runner_error(error: &runner::Error, _warning: bool) { + eprintln!("* {:#?}", error); +} + // can be used for debugging #[test] fn test_hurl_file() { @@ -54,52 +61,51 @@ fn test_hurl_file() { .collect(); // edd an empty line at the end? lines.push(""); - let lines: Vec = lines.iter().map(|s| s.to_string()).collect(); let options = RunnerOptions { fail_fast: false, variables, to_entry: None, - }; - let logger = format::logger::Logger { - filename: Some(filename.to_string()), - lines: lines, - verbose: false, - color: false, + context_dir: "current_dir".to_string(), }; - let _hurl_log = runner::file::run( + let log_verbose: fn(&str) = log_verbose; + let log_error_message: fn(bool, &str) = log_error_message; + let log_runner_error: fn(&runner::Error, bool) = log_runner_error; + + let _hurl_log = runner::run_hurl_file( hurl_file, &mut client, //&mut variables, filename.to_string(), - "current_dir".to_string(), options, - logger, + &log_verbose, + &log_error_message, + &log_runner_error, ); // assert_eq!(1,2) } #[cfg(test)] -fn hello_request() -> ast::Request { +fn hello_request() -> Request { // GET http://localhost;8000/hello let source_info = SourceInfo { start: Pos { line: 1, column: 1 }, end: Pos { line: 1, column: 1 }, }; - let whitespace = ast::Whitespace { + let whitespace = Whitespace { value: "".to_string(), source_info: source_info.clone(), }; - let line_terminator = ast::LineTerminator { + let line_terminator = LineTerminator { space0: whitespace.clone(), comment: None, newline: whitespace.clone(), }; - ast::Request { + Request { line_terminators: vec![], space0: whitespace.clone(), - method: ast::Method::Get, + method: Method::Get, space1: whitespace.clone(), url: Template { quotes: false, @@ -109,12 +115,12 @@ fn hello_request() -> ast::Request { }], source_info: source_info.clone(), }, - line_terminator0: ast::LineTerminator { + line_terminator0: LineTerminator { space0: whitespace.clone(), comment: None, newline: whitespace.clone(), }, - headers: vec![ast::KeyValue { + headers: vec![KeyValue { line_terminators: vec![], space0: whitespace.clone(), key: EncodedString { @@ -159,27 +165,27 @@ fn test_hello() { start: Pos { line: 1, column: 1 }, end: Pos { line: 1, column: 1 }, }; - let whitespace = ast::Whitespace { + let whitespace = Whitespace { value: "".to_string(), source_info: source_info.clone(), }; let request = hello_request(); - let hurl_file = ast::HurlFile { - entries: vec![ast::Entry { + let hurl_file = HurlFile { + entries: vec![Entry { request, - response: Some(ast::Response { + response: Some(Response { line_terminators: vec![], - version: ast::Version { - value: ast::VersionValue::Version11, + version: Version { + value: VersionValue::Version11, source_info: source_info.clone(), }, space0: whitespace.clone(), - status: ast::Status { + status: Status { value: 200, source_info: source_info.clone(), }, space1: whitespace.clone(), - line_terminator0: ast::LineTerminator { + line_terminator0: LineTerminator { space0: whitespace.clone(), comment: None, newline: whitespace.clone(), @@ -192,26 +198,24 @@ fn test_hello() { }], line_terminators: vec![], }; - let lines = vec![String::from("line1")]; let variables = HashMap::new(); let options = RunnerOptions { fail_fast: true, variables, to_entry: None, + context_dir: "current_dir".to_string(), }; - let logger = format::logger::Logger { - filename: None, - lines, - verbose: false, - color: false, - }; - let _hurl_log = runner::file::run( + let log_verbose: fn(&str) = log_verbose; + let log_error_message: fn(bool, &str) = log_error_message; + let log_runner_error: fn(&runner::Error, bool) = log_runner_error; + let _hurl_log = runner::run_hurl_file( hurl_file, &mut client, String::from("filename"), - "current_dir".to_string(), options, - logger, + &log_verbose, + &log_error_message, + &log_runner_error, ); //assert_eq!(hurl_log.entries.len(), 1); //assert_eq!(hurl_log.entries.get(0).unwrap().response.status, 200); diff --git a/tests/server/mod.rs b/tests/server/mod.rs index 0047fb986..42128721f 100644 --- a/tests/server/mod.rs +++ b/tests/server/mod.rs @@ -1,4 +1,20 @@ -#![allow(dead_code)] +/* + * hurl (https://hurl.dev) + * Copyright (C) 2020 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 std::collections::HashSet; use std::io::prelude::*;