From 7ae81bc3394eba760aaa3185e6a20c988d529846 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Amiel Date: Wed, 5 Nov 2025 17:47:49 +0100 Subject: [PATCH] Implements isObject predicate. --- .../hurl/tests_failed/predicate/predicate.err | 78 +++++++++++-------- .../tests_failed/predicate/predicate.hurl | 1 + .../hurl/tests_ok/assert/assert_header.hurl | 2 + .../tests_ok/assert/assert_header.out.pattern | 2 +- .../hurl/tests_ok/assert/assert_json.hurl | 3 + .../hurlfmt/tests_export/predicate.html | 1 + .../hurlfmt/tests_export/predicate.hurl | 1 + .../hurlfmt/tests_export/predicate.json | 2 +- .../hurlfmt/tests_export/predicate.lint.hurl | 1 + packages/hurl/src/runner/predicate.rs | 14 +++- packages/hurl/src/runner/value_impl.rs | 5 ++ packages/hurl_core/src/ast/section.rs | 2 + packages/hurl_core/src/ast/visit.rs | 1 + packages/hurl_core/src/parser/predicate.rs | 6 ++ packages/hurlfmt/src/format/json.rs | 1 + packages/hurlfmt/src/linter/rewrite.rs | 1 + 16 files changed, 84 insertions(+), 37 deletions(-) diff --git a/integration/hurl/tests_failed/predicate/predicate.err b/integration/hurl/tests_failed/predicate/predicate.err index 5b8a08fe7c..905b594d92 100644 --- a/integration/hurl/tests_failed/predicate/predicate.err +++ b/integration/hurl/tests_failed/predicate/predicate.err @@ -469,9 +469,9 @@ error: Assert failure | | GET http://localhost:8000/predicate/error/type | ... -50 | jsonpath "$.not-exist" isDate +50 | jsonpath "$.not-exist" isObject | actual: none - | expected: date + | expected: object | error: Assert failure @@ -479,9 +479,9 @@ error: Assert failure | | GET http://localhost:8000/predicate/error/type | ... -51 | jsonpath "$.not-exist" exists +51 | jsonpath "$.not-exist" isDate | actual: none - | expected: something + | expected: date | error: Assert failure @@ -489,9 +489,9 @@ error: Assert failure | | GET http://localhost:8000/predicate/error/type | ... -52 | jsonpath "$.not-exist" isEmpty +52 | jsonpath "$.not-exist" exists | actual: none - | expected: empty + | expected: something | error: Assert failure @@ -499,9 +499,9 @@ error: Assert failure | | GET http://localhost:8000/predicate/error/type | ... -53 | jsonpath "$.not-exist" isIpv4 +53 | jsonpath "$.not-exist" isEmpty | actual: none - | expected: ipv4 + | expected: empty | error: Assert failure @@ -509,9 +509,9 @@ error: Assert failure | | GET http://localhost:8000/predicate/error/type | ... -54 | jsonpath "$.not-exist" isIpv6 +54 | jsonpath "$.not-exist" isIpv4 | actual: none - | expected: ipv6 + | expected: ipv4 | error: Assert failure @@ -519,9 +519,9 @@ error: Assert failure | | GET http://localhost:8000/predicate/error/type | ... -55 | jsonpath "$.not-exist" isUuid +55 | jsonpath "$.not-exist" isIpv6 | actual: none - | expected: uuid + | expected: ipv6 | error: Assert failure @@ -529,9 +529,9 @@ error: Assert failure | | GET http://localhost:8000/predicate/error/type | ... -56 | jsonpath "$.not_a_date" isIsoDate - | actual: 2018 - | expected: string with format YYYY-MM-DDTHH:mm:ss.sssZ +56 | jsonpath "$.not-exist" isUuid + | actual: none + | expected: uuid | error: Assert failure @@ -539,9 +539,9 @@ error: Assert failure | | GET http://localhost:8000/predicate/error/type | ... -57 | jsonpath "$.is_a_date" not isIsoDate - | actual: 2018-12-10T13:45:00.000Z - | expected: not string with format YYYY-MM-DDTHH:mm:ss.sssZ +57 | jsonpath "$.not_a_date" isIsoDate + | actual: 2018 + | expected: string with format YYYY-MM-DDTHH:mm:ss.sssZ | error: Assert failure @@ -549,9 +549,9 @@ error: Assert failure | | GET http://localhost:8000/predicate/error/type | ... -58 | jsonpath "$.not_a_date" isNumber - | actual: string <2018> - | expected: number +58 | jsonpath "$.is_a_date" not isIsoDate + | actual: 2018-12-10T13:45:00.000Z + | expected: not string with format YYYY-MM-DDTHH:mm:ss.sssZ | error: Assert failure @@ -559,9 +559,9 @@ error: Assert failure | | GET http://localhost:8000/predicate/error/type | ... -59 | jsonpath "$.is_a_date" isIpv4 - | actual: 2018-12-10T13:45:00.000Z - | expected: string in IPv4 format +59 | jsonpath "$.not_a_date" isNumber + | actual: string <2018> + | expected: number | error: Assert failure @@ -569,9 +569,9 @@ error: Assert failure | | GET http://localhost:8000/predicate/error/type | ... -60 | jsonpath "$.is_a_date" isIpv6 +60 | jsonpath "$.is_a_date" isIpv4 | actual: 2018-12-10T13:45:00.000Z - | expected: string in IPv6 format + | expected: string in IPv4 format | error: Assert failure @@ -579,8 +579,8 @@ error: Assert failure | | GET http://localhost:8000/predicate/error/type | ... -61 | jsonpath "$.ipv4" isIpv6 - | actual: 127.0.0.1 +61 | jsonpath "$.is_a_date" isIpv6 + | actual: 2018-12-10T13:45:00.000Z | expected: string in IPv6 format | @@ -589,9 +589,9 @@ error: Assert failure | | GET http://localhost:8000/predicate/error/type | ... -62 | jsonpath "$.ipv4" not isIpv4 +62 | jsonpath "$.ipv4" isIpv6 | actual: 127.0.0.1 - | expected: not string in IPv4 format + | expected: string in IPv6 format | error: Assert failure @@ -599,9 +599,9 @@ error: Assert failure | | GET http://localhost:8000/predicate/error/type | ... -63 | jsonpath "$.ipv6" isIpv4 - | actual: 2001:db8::1 - | expected: string in IPv4 format +63 | jsonpath "$.ipv4" not isIpv4 + | actual: 127.0.0.1 + | expected: not string in IPv4 format | error: Assert failure @@ -609,7 +609,17 @@ error: Assert failure | | GET http://localhost:8000/predicate/error/type | ... -64 | jsonpath "$.ipv6" not isIpv6 +64 | jsonpath "$.ipv6" isIpv4 + | actual: 2001:db8::1 + | expected: string in IPv4 format + | + +error: Assert failure + --> tests_failed/predicate/predicate.hurl:65:0 + | + | GET http://localhost:8000/predicate/error/type + | ... +65 | jsonpath "$.ipv6" not isIpv6 | actual: 2001:db8::1 | expected: not string in IPv6 format | diff --git a/integration/hurl/tests_failed/predicate/predicate.hurl b/integration/hurl/tests_failed/predicate/predicate.hurl index 1317645621..2b4f4e3594 100644 --- a/integration/hurl/tests_failed/predicate/predicate.hurl +++ b/integration/hurl/tests_failed/predicate/predicate.hurl @@ -47,6 +47,7 @@ jsonpath "$.not-exist" isBoolean jsonpath "$.not-exist" isString jsonpath "$.not-exist" isCollection jsonpath "$.not-exist" isList +jsonpath "$.not-exist" isObject jsonpath "$.not-exist" isDate jsonpath "$.not-exist" exists jsonpath "$.not-exist" isEmpty diff --git a/integration/hurl/tests_ok/assert/assert_header.hurl b/integration/hurl/tests_ok/assert/assert_header.hurl index bb863e00f2..9e89eaaf21 100644 --- a/integration/hurl/tests_ok/assert/assert_header.hurl +++ b/integration/hurl/tests_ok/assert/assert_header.hurl @@ -18,6 +18,7 @@ header "Set-Cookie" contains "cookie1=value1; Path=/" header "Set-Cookie" not contains "cookie4=value4; Path=/" header "X-Fruit" isList header "X-Fruit" isCollection +header "X-Fruit" not isObject header "x-fruit" count == 4 header "X-Fruit" nth 0 == "Banana" header "x-fruit" nth 1 == "Lemon" @@ -38,6 +39,7 @@ header "X-Fruit" isList header "X-Fruit" isCollection variable "fruits" isList variable "fruits" isCollection +variable "fruits" not isObject variable "fruits" count == 4 variable "fruits" nth 0 == "Banana" variable "fruits" nth 3 == "Strawberry" diff --git a/integration/hurl/tests_ok/assert/assert_header.out.pattern b/integration/hurl/tests_ok/assert/assert_header.out.pattern index 7ffe8dc315..b175a5f5ba 100644 --- a/integration/hurl/tests_ok/assert/assert_header.out.pattern +++ b/integration/hurl/tests_ok/assert/assert_header.out.pattern @@ -1 +1 @@ -{"cookies":[{"domain":"localhost","expires":"<<<\d+>>>","https":"FALSE","include_subdomain":"FALSE","name":"cookie1","path":"/","value":"value1"},{"domain":"localhost","expires":"<<<\d+>>>","https":"FALSE","include_subdomain":"FALSE","name":"cookie2","path":"/","value":"value2"},{"domain":"localhost","expires":"<<<\d+>>>","https":"FALSE","include_subdomain":"FALSE","name":"cookie3","path":"/","value":"value3"}],"entries":[{"asserts":[{"line":2,"success":true},{"line":2,"success":true},{"line":3,"success":true},{"line":4,"success":true},{"line":5,"success":true},{"line":7,"success":true},{"line":8,"success":true},{"line":9,"success":true},{"line":10,"success":true},{"line":11,"success":true},{"line":12,"success":true},{"line":13,"success":true},{"line":14,"success":true},{"line":15,"success":true},{"line":16,"success":true},{"line":17,"success":true},{"line":18,"success":true},{"line":19,"success":true},{"line":20,"success":true},{"line":21,"success":true},{"line":22,"success":true},{"line":23,"success":true},{"line":24,"success":true},{"line":25,"success":true},{"line":26,"success":true},{"line":27,"success":true}],"calls":[{"request":{"cookies":[],"headers":[{"name":"Host","value":"localhost:8000"},{"name":"Accept","value":"*/*"},{"name":"User-Agent","value":"hurl/<<<.*?>>>"}],"method":"GET","query_string":[],"url":"http://localhost:8000/assert-header"},"response":{"cookies":[{"name":"cookie1","path":"/","value":"value1"},{"name":"cookie2","path":"/","value":"value2"},{"name":"cookie3","path":"/","value":"value3"}],"headers":[{"name":"Content-Length","value":"0"},{"name":"Content-Type","value":"text/html; charset=utf-8"},{"name":"Date","value":"<<<.*?>>>"},{"name":"Etag","value":"\"33a64df551425fcc55e4d42a148795d9f25f89d4\""},{"name":"Expires","value":"<<<.*?>>>"},{"name":"Header1","value":"value1"},{"name":"Server","value":"Flask Server"},{"name":"Set-Cookie","value":"cookie1=value1; Path=/"},{"name":"Set-Cookie","value":"cookie2=value2; Path=/"},{"name":"Set-Cookie","value":"cookie3=value3; Path=/"},{"name":"Via","value":"waitress"},{"name":"X-Fruit","value":"Banana"},{"name":"X-Fruit","value":"Lemon"},{"name":"X-Fruit","value":"Grape"},{"name":"X-Fruit","value":"Strawberry"}],"http_version":"HTTP/1.1","status":200},"timings":{"app_connect":<<<\d+>>>,"begin_call":"<<<.*?>>>","connect":<<<\d+>>>,"end_call":"<<<.*?>>>","name_lookup":<<<\d+>>>,"pre_transfer":<<<\d+>>>,"start_transfer":<<<\d+>>>,"total":<<<\d+>>>}}],"captures":[],"curl_cmd":"curl 'http://localhost:8000/assert-header'","index":1,"line":1,"time":<<<\d+>>>},{"asserts":[{"line":33,"success":true},{"line":33,"success":true},{"line":37,"success":true},{"line":38,"success":true},{"line":39,"success":true},{"line":40,"success":true},{"line":41,"success":true},{"line":42,"success":true},{"line":43,"success":true}],"calls":[{"request":{"cookies":[{"name":"cookie3","value":"value3"},{"name":"cookie2","value":"value2"},{"name":"cookie1","value":"value1"}],"headers":[{"name":"Host","value":"localhost:8000"},{"name":"Accept","value":"*/*"},{"name":"Cookie","value":"cookie3=value3; cookie2=value2; cookie1=value1"},{"name":"User-Agent","value":"hurl/<<<.*?>>>"}],"method":"GET","query_string":[],"url":"http://localhost:8000/assert-header"},"response":{"cookies":[{"name":"cookie1","path":"/","value":"value1"},{"name":"cookie2","path":"/","value":"value2"},{"name":"cookie3","path":"/","value":"value3"}],"headers":[{"name":"Content-Length","value":"0"},{"name":"Content-Type","value":"text/html; charset=utf-8"},{"name":"Date","value":"<<<.*?>>>"},{"name":"Etag","value":"\"33a64df551425fcc55e4d42a148795d9f25f89d4\""},{"name":"Expires","value":"<<<.*?>>>"},{"name":"Header1","value":"value1"},{"name":"Server","value":"Flask Server"},{"name":"Set-Cookie","value":"cookie1=value1; Path=/"},{"name":"Set-Cookie","value":"cookie2=value2; Path=/"},{"name":"Set-Cookie","value":"cookie3=value3; Path=/"},{"name":"Via","value":"waitress"},{"name":"X-Fruit","value":"Banana"},{"name":"X-Fruit","value":"Lemon"},{"name":"X-Fruit","value":"Grape"},{"name":"X-Fruit","value":"Strawberry"}],"http_version":"HTTP/1.1","status":200},"timings":{"app_connect":<<<\d+>>>,"begin_call":"<<<.*?>>>","connect":<<<\d+>>>,"end_call":"<<<.*?>>>","name_lookup":<<<\d+>>>,"pre_transfer":<<<\d+>>>,"start_transfer":<<<\d+>>>,"total":<<<\d+>>>}}],"captures":[{"name":"fruits","value":["Banana","Lemon","Grape","Strawberry"]}],"curl_cmd":"curl --cookie 'cookie1=value1; cookie2=value2; cookie3=value3' 'http://localhost:8000/assert-header'","index":2,"line":32,"time":<<<\d+>>>},{"asserts":[{"line":49,"success":true},{"line":49,"success":true},{"line":51,"success":true},{"line":52,"success":true}],"calls":[{"request":{"cookies":[{"name":"cookie3","value":"value3"},{"name":"cookie2","value":"value2"},{"name":"cookie1","value":"value1"}],"headers":[{"name":"Host","value":"localhost:8000"},{"name":"Accept","value":"*/*"},{"name":"Cookie","value":"cookie3=value3; cookie2=value2; cookie1=value1"},{"name":"User-Agent","value":"hurl/<<<.*?>>>"}],"method":"GET","query_string":[],"url":"http://localhost:8000/assert-header-location-http"},"response":{"cookies":[],"headers":[{"name":"Content-Length","value":"229"},{"name":"Content-Type","value":"text/html; charset=utf-8"},{"name":"Date","value":"<<<.*?>>>"},{"name":"Location","value":"http://localhost:8000"},{"name":"Server","value":"Flask Server"},{"name":"Via","value":"waitress"}],"http_version":"HTTP/1.1","status":302},"timings":{"app_connect":<<<\d+>>>,"begin_call":"<<<.*?>>>","connect":<<<\d+>>>,"end_call":"<<<.*?>>>","name_lookup":<<<\d+>>>,"pre_transfer":<<<\d+>>>,"start_transfer":<<<\d+>>>,"total":<<<\d+>>>}}],"captures":[],"curl_cmd":"curl --cookie 'cookie1=value1; cookie2=value2; cookie3=value3' 'http://localhost:8000/assert-header-location-http'","index":3,"line":48,"time":<<<\d+>>>},{"asserts":[{"line":56,"success":true},{"line":56,"success":true},{"line":58,"success":true},{"line":59,"success":true}],"calls":[{"request":{"cookies":[{"name":"cookie3","value":"value3"},{"name":"cookie2","value":"value2"},{"name":"cookie1","value":"value1"}],"headers":[{"name":"Host","value":"localhost:8000"},{"name":"Accept","value":"*/*"},{"name":"Cookie","value":"cookie3=value3; cookie2=value2; cookie1=value1"},{"name":"User-Agent","value":"hurl/<<<.*?>>>"}],"method":"GET","query_string":[],"url":"http://localhost:8000/assert-header-location-custom-scheme"},"response":{"cookies":[],"headers":[{"name":"Content-Length","value":"265"},{"name":"Content-Type","value":"text/html; charset=utf-8"},{"name":"Date","value":"<<<.*?>>>"},{"name":"Location","value":"market://details?id=com.example.package"},{"name":"Server","value":"Flask Server"},{"name":"Via","value":"waitress"}],"http_version":"HTTP/1.1","status":302},"timings":{"app_connect":<<<\d+>>>,"begin_call":"<<<.*?>>>","connect":<<<\d+>>>,"end_call":"<<<.*?>>>","name_lookup":<<<\d+>>>,"pre_transfer":<<<\d+>>>,"start_transfer":<<<\d+>>>,"total":<<<\d+>>>}}],"captures":[],"curl_cmd":"curl --cookie 'cookie1=value1; cookie2=value2; cookie3=value3' 'http://localhost:8000/assert-header-location-custom-scheme'","index":4,"line":55,"time":<<<\d+>>>},{"asserts":[{"line":63,"success":true},{"line":63,"success":true},{"line":65,"success":true},{"line":66,"success":true}],"calls":[{"request":{"cookies":[{"name":"cookie3","value":"value3"},{"name":"cookie2","value":"value2"},{"name":"cookie1","value":"value1"}],"headers":[{"name":"Host","value":"localhost:8000"},{"name":"Accept","value":"*/*"},{"name":"Cookie","value":"cookie3=value3; cookie2=value2; cookie1=value1"},{"name":"User-Agent","value":"hurl/<<<.*?>>>"}],"method":"GET","query_string":[],"url":"http://localhost:8000/assert-header-location-xxx"},"response":{"cookies":[],"headers":[{"name":"Content-Length","value":"193"},{"name":"Content-Type","value":"text/html; charset=utf-8"},{"name":"Date","value":"<<<.*?>>>"},{"name":"Location","value":"xxx"},{"name":"Server","value":"Flask Server"},{"name":"Via","value":"waitress"}],"http_version":"HTTP/1.1","status":302},"timings":{"app_connect":<<<\d+>>>,"begin_call":"<<<.*?>>>","connect":<<<\d+>>>,"end_call":"<<<.*?>>>","name_lookup":<<<\d+>>>,"pre_transfer":<<<\d+>>>,"start_transfer":<<<\d+>>>,"total":<<<\d+>>>}}],"captures":[],"curl_cmd":"curl --cookie 'cookie1=value1; cookie2=value2; cookie3=value3' 'http://localhost:8000/assert-header-location-xxx'","index":5,"line":62,"time":<<<\d+>>>}],"filename":"tests_ok/assert/assert_header.hurl","success":true,"time":<<<\d+>>>} +{"cookies":[{"domain":"localhost","expires":"<<<\d+>>>","https":"FALSE","include_subdomain":"FALSE","name":"cookie1","path":"/","value":"value1"},{"domain":"localhost","expires":"<<<\d+>>>","https":"FALSE","include_subdomain":"FALSE","name":"cookie2","path":"/","value":"value2"},{"domain":"localhost","expires":"<<<\d+>>>","https":"FALSE","include_subdomain":"FALSE","name":"cookie3","path":"/","value":"value3"}],"entries":[{"asserts":[{"line":2,"success":true},{"line":2,"success":true},{"line":3,"success":true},{"line":4,"success":true},{"line":5,"success":true},{"line":7,"success":true},{"line":8,"success":true},{"line":9,"success":true},{"line":10,"success":true},{"line":11,"success":true},{"line":12,"success":true},{"line":13,"success":true},{"line":14,"success":true},{"line":15,"success":true},{"line":16,"success":true},{"line":17,"success":true},{"line":18,"success":true},{"line":19,"success":true},{"line":20,"success":true},{"line":21,"success":true},{"line":22,"success":true},{"line":23,"success":true},{"line":24,"success":true},{"line":25,"success":true},{"line":26,"success":true},{"line":27,"success":true},{"line":28,"success":true}],"calls":[{"request":{"cookies":[],"headers":[{"name":"Host","value":"localhost:8000"},{"name":"Accept","value":"*/*"},{"name":"User-Agent","value":"hurl/<<<.*?>>>"}],"method":"GET","query_string":[],"url":"http://localhost:8000/assert-header"},"response":{"cookies":[{"name":"cookie1","path":"/","value":"value1"},{"name":"cookie2","path":"/","value":"value2"},{"name":"cookie3","path":"/","value":"value3"}],"headers":[{"name":"Content-Length","value":"0"},{"name":"Content-Type","value":"text/html; charset=utf-8"},{"name":"Date","value":"<<<.*?>>>"},{"name":"Etag","value":"\"33a64df551425fcc55e4d42a148795d9f25f89d4\""},{"name":"Expires","value":"<<<.*?>>>"},{"name":"Header1","value":"value1"},{"name":"Server","value":"Flask Server"},{"name":"Set-Cookie","value":"cookie1=value1; Path=/"},{"name":"Set-Cookie","value":"cookie2=value2; Path=/"},{"name":"Set-Cookie","value":"cookie3=value3; Path=/"},{"name":"Via","value":"waitress"},{"name":"X-Fruit","value":"Banana"},{"name":"X-Fruit","value":"Lemon"},{"name":"X-Fruit","value":"Grape"},{"name":"X-Fruit","value":"Strawberry"}],"http_version":"HTTP/1.1","status":200},"timings":{"app_connect":<<<\d+>>>,"begin_call":"<<<.*?>>>","connect":<<<\d+>>>,"end_call":"<<<.*?>>>","name_lookup":<<<\d+>>>,"pre_transfer":<<<\d+>>>,"start_transfer":<<<\d+>>>,"total":<<<\d+>>>}}],"captures":[],"curl_cmd":"curl 'http://localhost:8000/assert-header'","index":1,"line":1,"time":<<<\d+>>>},{"asserts":[{"line":34,"success":true},{"line":34,"success":true},{"line":38,"success":true},{"line":39,"success":true},{"line":40,"success":true},{"line":41,"success":true},{"line":42,"success":true},{"line":43,"success":true},{"line":44,"success":true},{"line":45,"success":true}],"calls":[{"request":{"cookies":[{"name":"cookie3","value":"value3"},{"name":"cookie2","value":"value2"},{"name":"cookie1","value":"value1"}],"headers":[{"name":"Host","value":"localhost:8000"},{"name":"Accept","value":"*/*"},{"name":"Cookie","value":"cookie3=value3; cookie2=value2; cookie1=value1"},{"name":"User-Agent","value":"hurl/<<<.*?>>>"}],"method":"GET","query_string":[],"url":"http://localhost:8000/assert-header"},"response":{"cookies":[{"name":"cookie1","path":"/","value":"value1"},{"name":"cookie2","path":"/","value":"value2"},{"name":"cookie3","path":"/","value":"value3"}],"headers":[{"name":"Content-Length","value":"0"},{"name":"Content-Type","value":"text/html; charset=utf-8"},{"name":"Date","value":"<<<.*?>>>"},{"name":"Etag","value":"\"33a64df551425fcc55e4d42a148795d9f25f89d4\""},{"name":"Expires","value":"<<<.*?>>>"},{"name":"Header1","value":"value1"},{"name":"Server","value":"Flask Server"},{"name":"Set-Cookie","value":"cookie1=value1; Path=/"},{"name":"Set-Cookie","value":"cookie2=value2; Path=/"},{"name":"Set-Cookie","value":"cookie3=value3; Path=/"},{"name":"Via","value":"waitress"},{"name":"X-Fruit","value":"Banana"},{"name":"X-Fruit","value":"Lemon"},{"name":"X-Fruit","value":"Grape"},{"name":"X-Fruit","value":"Strawberry"}],"http_version":"HTTP/1.1","status":200},"timings":{"app_connect":<<<\d+>>>,"begin_call":"<<<.*?>>>","connect":<<<\d+>>>,"end_call":"<<<.*?>>>","name_lookup":<<<\d+>>>,"pre_transfer":<<<\d+>>>,"start_transfer":<<<\d+>>>,"total":<<<\d+>>>}}],"captures":[{"name":"fruits","value":["Banana","Lemon","Grape","Strawberry"]}],"curl_cmd":"curl --cookie 'cookie1=value1; cookie2=value2; cookie3=value3' 'http://localhost:8000/assert-header'","index":2,"line":33,"time":<<<\d+>>>},{"asserts":[{"line":51,"success":true},{"line":51,"success":true},{"line":53,"success":true},{"line":54,"success":true}],"calls":[{"request":{"cookies":[{"name":"cookie3","value":"value3"},{"name":"cookie2","value":"value2"},{"name":"cookie1","value":"value1"}],"headers":[{"name":"Host","value":"localhost:8000"},{"name":"Accept","value":"*/*"},{"name":"Cookie","value":"cookie3=value3; cookie2=value2; cookie1=value1"},{"name":"User-Agent","value":"hurl/<<<.*?>>>"}],"method":"GET","query_string":[],"url":"http://localhost:8000/assert-header-location-http"},"response":{"cookies":[],"headers":[{"name":"Content-Length","value":"229"},{"name":"Content-Type","value":"text/html; charset=utf-8"},{"name":"Date","value":"<<<.*?>>>"},{"name":"Location","value":"http://localhost:8000"},{"name":"Server","value":"Flask Server"},{"name":"Via","value":"waitress"}],"http_version":"HTTP/1.1","status":302},"timings":{"app_connect":<<<\d+>>>,"begin_call":"<<<.*?>>>","connect":<<<\d+>>>,"end_call":"<<<.*?>>>","name_lookup":<<<\d+>>>,"pre_transfer":<<<\d+>>>,"start_transfer":<<<\d+>>>,"total":<<<\d+>>>}}],"captures":[],"curl_cmd":"curl --cookie 'cookie1=value1; cookie2=value2; cookie3=value3' 'http://localhost:8000/assert-header-location-http'","index":3,"line":50,"time":<<<\d+>>>},{"asserts":[{"line":58,"success":true},{"line":58,"success":true},{"line":60,"success":true},{"line":61,"success":true}],"calls":[{"request":{"cookies":[{"name":"cookie3","value":"value3"},{"name":"cookie2","value":"value2"},{"name":"cookie1","value":"value1"}],"headers":[{"name":"Host","value":"localhost:8000"},{"name":"Accept","value":"*/*"},{"name":"Cookie","value":"cookie3=value3; cookie2=value2; cookie1=value1"},{"name":"User-Agent","value":"hurl/<<<.*?>>>"}],"method":"GET","query_string":[],"url":"http://localhost:8000/assert-header-location-custom-scheme"},"response":{"cookies":[],"headers":[{"name":"Content-Length","value":"265"},{"name":"Content-Type","value":"text/html; charset=utf-8"},{"name":"Date","value":"<<<.*?>>>"},{"name":"Location","value":"market://details?id=com.example.package"},{"name":"Server","value":"Flask Server"},{"name":"Via","value":"waitress"}],"http_version":"HTTP/1.1","status":302},"timings":{"app_connect":<<<\d+>>>,"begin_call":"<<<.*?>>>","connect":<<<\d+>>>,"end_call":"<<<.*?>>>","name_lookup":<<<\d+>>>,"pre_transfer":<<<\d+>>>,"start_transfer":<<<\d+>>>,"total":<<<\d+>>>}}],"captures":[],"curl_cmd":"curl --cookie 'cookie1=value1; cookie2=value2; cookie3=value3' 'http://localhost:8000/assert-header-location-custom-scheme'","index":4,"line":57,"time":<<<\d+>>>},{"asserts":[{"line":65,"success":true},{"line":65,"success":true},{"line":67,"success":true},{"line":68,"success":true}],"calls":[{"request":{"cookies":[{"name":"cookie3","value":"value3"},{"name":"cookie2","value":"value2"},{"name":"cookie1","value":"value1"}],"headers":[{"name":"Host","value":"localhost:8000"},{"name":"Accept","value":"*/*"},{"name":"Cookie","value":"cookie3=value3; cookie2=value2; cookie1=value1"},{"name":"User-Agent","value":"hurl/<<<.*?>>>"}],"method":"GET","query_string":[],"url":"http://localhost:8000/assert-header-location-xxx"},"response":{"cookies":[],"headers":[{"name":"Content-Length","value":"193"},{"name":"Content-Type","value":"text/html; charset=utf-8"},{"name":"Date","value":"<<<.*?>>>"},{"name":"Location","value":"xxx"},{"name":"Server","value":"Flask Server"},{"name":"Via","value":"waitress"}],"http_version":"HTTP/1.1","status":302},"timings":{"app_connect":<<<\d+>>>,"begin_call":"<<<.*?>>>","connect":<<<\d+>>>,"end_call":"<<<.*?>>>","name_lookup":<<<\d+>>>,"pre_transfer":<<<\d+>>>,"start_transfer":<<<\d+>>>,"total":<<<\d+>>>}}],"captures":[],"curl_cmd":"curl --cookie 'cookie1=value1; cookie2=value2; cookie3=value3' 'http://localhost:8000/assert-header-location-xxx'","index":5,"line":64,"time":<<<\d+>>>}],"filename":"tests_ok/assert/assert_header.hurl","success":true,"time":<<<\d+>>>} diff --git a/integration/hurl/tests_ok/assert/assert_json.hurl b/integration/hurl/tests_ok/assert/assert_json.hurl index a918767089..d8c4dcdd6d 100644 --- a/integration/hurl/tests_ok/assert/assert_json.hurl +++ b/integration/hurl/tests_ok/assert/assert_json.hurl @@ -29,9 +29,11 @@ jsonpath "$.success" isBoolean jsonpath "$.errors" count == 2 jsonpath "$.errors" isList jsonpath "$.errors" isCollection +jsonpath "$.errors" not isObject jsonpath "$.failures" count == 1 jsonpath "$.failures" isList jsonpath "$.failures" isCollection +jsonpath "$.failures" not isObject jsonpath "$.warnings" count == 0 jsonpath "$.warnings" isEmpty jsonpath "$.message" == "Bob says \"Hello\"" @@ -42,6 +44,7 @@ jsonpath "$.warnings" exists jsonpath "$.errors[0]" exists jsonpath "$.errors[0]" not isList jsonpath "$.errors[0]" isCollection +jsonpath "$.errors[0]" isObject jsonpath "$.errors[0].id" == "error1" jsonpath "$.errors[0].id" isString jsonpath "$.errors[0]['id']" == "error1" diff --git a/integration/hurlfmt/tests_export/predicate.html b/integration/hurlfmt/tests_export/predicate.html index 3d1837e928..758aed1ccf 100644 --- a/integration/hurlfmt/tests_export/predicate.html +++ b/integration/hurlfmt/tests_export/predicate.html @@ -18,6 +18,7 @@ jsonpath "$.succeeded" isBoolean # isBoolean jsonpath "$.books" isList # isList jsonpath "$.books" isCollection # isCollection +jsonpath "$.books" isObject # isObject certificate "Expire-Date" isDate # isDate jsonpath "$.publication_date" isIsoDate # isIsoDate jsonpath "$.movies" isEmpty # isEmpty diff --git a/integration/hurlfmt/tests_export/predicate.hurl b/integration/hurlfmt/tests_export/predicate.hurl index aaa8f75beb..2be61f259a 100644 --- a/integration/hurlfmt/tests_export/predicate.hurl +++ b/integration/hurlfmt/tests_export/predicate.hurl @@ -18,6 +18,7 @@ jsonpath "$.nooks" contains "Dune" # contains jsonpath "$.succeeded" isBoolean # isBoolean jsonpath "$.books" isList # isList jsonpath "$.books" isCollection # isCollection +jsonpath "$.books" isObject # isObject certificate "Expire-Date" isDate # isDate jsonpath "$.publication_date" isIsoDate # isIsoDate jsonpath "$.movies" isEmpty # isEmpty diff --git a/integration/hurlfmt/tests_export/predicate.json b/integration/hurlfmt/tests_export/predicate.json index 0487e17c3a..58b264f25c 100644 --- a/integration/hurlfmt/tests_export/predicate.json +++ b/integration/hurlfmt/tests_export/predicate.json @@ -1 +1 @@ -{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/dummy"},"response":{"status":200,"asserts":[{"query":{"type":"jsonpath","expr":"$.book"},"predicate":{"not":true,"type":"==","value":"Dune"}},{"query":{"type":"jsonpath","expr":"$.book"},"predicate":{"type":"==","value":"Dune"}},{"query":{"type":"jsonpath","expr":"$.color"},"predicate":{"type":"!=","value":"red"}},{"query":{"type":"jsonpath","expr":"$.year"},"predicate":{"type":">","value":1978}},{"query":{"type":"jsonpath","expr":"$.year"},"predicate":{"type":">=","value":1978}},{"query":{"type":"jsonpath","expr":"$.year"},"predicate":{"type":"<","value":1978}},{"query":{"type":"jsonpath","expr":"$.year"},"predicate":{"type":"<=","value":1978}},{"query":{"type":"jsonpath","expr":"$.movie"},"predicate":{"type":"contains","value":"Empire"}},{"query":{"type":"bytes"},"predicate":{"type":"contains","value":"vu8=","encoding":"base64"}},{"query":{"type":"jsonpath","expr":"$.movie"},"predicate":{"type":"endsWith","value":"Back"}},{"query":{"type":"bytes"},"predicate":{"type":"endsWith","value":"qxI0Vg==","encoding":"base64"}},{"query":{"type":"jsonpath","expr":"$.book"},"predicate":{"type":"exists"}},{"query":{"type":"jsonpath","expr":"$.nooks"},"predicate":{"type":"includes","value":"Dune"}},{"query":{"type":"jsonpath","expr":"$.nooks"},"predicate":{"type":"contains","value":"Dune"}},{"query":{"type":"jsonpath","expr":"$.succeeded"},"predicate":{"type":"isBoolean"}},{"query":{"type":"jsonpath","expr":"$.books"},"predicate":{"type":"isList"}},{"query":{"type":"jsonpath","expr":"$.books"},"predicate":{"type":"isCollection"}},{"query":{"type":"certificate","expr":"Expire-Date"},"predicate":{"type":"isDate"}},{"query":{"type":"jsonpath","expr":"$.publication_date"},"predicate":{"type":"isIsoDate"}},{"query":{"type":"jsonpath","expr":"$.movies"},"predicate":{"type":"isEmpty"}},{"query":{"type":"jsonpath","expr":"$.height"},"predicate":{"type":"isFloat"}},{"query":{"type":"jsonpath","expr":"$.count"},"predicate":{"type":"isInteger"}},{"query":{"type":"jsonpath","expr":"$.name"},"predicate":{"type":"isString"}},{"query":{"type":"jsonpath","expr":"$.release"},"predicate":{"type":"matches","value":"\\d{4}"}},{"query":{"type":"jsonpath","expr":"$.release"},"predicate":{"type":"matches","value":"\\d{4}","encoding":"regex"}},{"query":{"type":"jsonpath","expr":"$.movie"},"predicate":{"type":"startsWith","value":"The"}},{"query":{"type":"bytes"},"predicate":{"type":"startsWith","value":"77u/","encoding":"base64"}},{"query":{"type":"jsonpath","expr":"$.count"},"predicate":{"type":"isNumber"}},{"query":{"type":"ip"},"predicate":{"type":"isIpv6"}},{"query":{"type":"ip"},"predicate":{"type":"isIpv4"}},{"query":{"type":"jsonpath","expr":"$.uuid"},"predicate":{"type":"isUuid"}}]}}]} +{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/dummy"},"response":{"status":200,"asserts":[{"query":{"type":"jsonpath","expr":"$.book"},"predicate":{"not":true,"type":"==","value":"Dune"}},{"query":{"type":"jsonpath","expr":"$.book"},"predicate":{"type":"==","value":"Dune"}},{"query":{"type":"jsonpath","expr":"$.color"},"predicate":{"type":"!=","value":"red"}},{"query":{"type":"jsonpath","expr":"$.year"},"predicate":{"type":">","value":1978}},{"query":{"type":"jsonpath","expr":"$.year"},"predicate":{"type":">=","value":1978}},{"query":{"type":"jsonpath","expr":"$.year"},"predicate":{"type":"<","value":1978}},{"query":{"type":"jsonpath","expr":"$.year"},"predicate":{"type":"<=","value":1978}},{"query":{"type":"jsonpath","expr":"$.movie"},"predicate":{"type":"contains","value":"Empire"}},{"query":{"type":"bytes"},"predicate":{"type":"contains","value":"vu8=","encoding":"base64"}},{"query":{"type":"jsonpath","expr":"$.movie"},"predicate":{"type":"endsWith","value":"Back"}},{"query":{"type":"bytes"},"predicate":{"type":"endsWith","value":"qxI0Vg==","encoding":"base64"}},{"query":{"type":"jsonpath","expr":"$.book"},"predicate":{"type":"exists"}},{"query":{"type":"jsonpath","expr":"$.nooks"},"predicate":{"type":"includes","value":"Dune"}},{"query":{"type":"jsonpath","expr":"$.nooks"},"predicate":{"type":"contains","value":"Dune"}},{"query":{"type":"jsonpath","expr":"$.succeeded"},"predicate":{"type":"isBoolean"}},{"query":{"type":"jsonpath","expr":"$.books"},"predicate":{"type":"isList"}},{"query":{"type":"jsonpath","expr":"$.books"},"predicate":{"type":"isCollection"}},{"query":{"type":"jsonpath","expr":"$.books"},"predicate":{"type":"isObject"}},{"query":{"type":"certificate","expr":"Expire-Date"},"predicate":{"type":"isDate"}},{"query":{"type":"jsonpath","expr":"$.publication_date"},"predicate":{"type":"isIsoDate"}},{"query":{"type":"jsonpath","expr":"$.movies"},"predicate":{"type":"isEmpty"}},{"query":{"type":"jsonpath","expr":"$.height"},"predicate":{"type":"isFloat"}},{"query":{"type":"jsonpath","expr":"$.count"},"predicate":{"type":"isInteger"}},{"query":{"type":"jsonpath","expr":"$.name"},"predicate":{"type":"isString"}},{"query":{"type":"jsonpath","expr":"$.release"},"predicate":{"type":"matches","value":"\\d{4}"}},{"query":{"type":"jsonpath","expr":"$.release"},"predicate":{"type":"matches","value":"\\d{4}","encoding":"regex"}},{"query":{"type":"jsonpath","expr":"$.movie"},"predicate":{"type":"startsWith","value":"The"}},{"query":{"type":"bytes"},"predicate":{"type":"startsWith","value":"77u/","encoding":"base64"}},{"query":{"type":"jsonpath","expr":"$.count"},"predicate":{"type":"isNumber"}},{"query":{"type":"ip"},"predicate":{"type":"isIpv6"}},{"query":{"type":"ip"},"predicate":{"type":"isIpv4"}},{"query":{"type":"jsonpath","expr":"$.uuid"},"predicate":{"type":"isUuid"}}]}}]} diff --git a/integration/hurlfmt/tests_export/predicate.lint.hurl b/integration/hurlfmt/tests_export/predicate.lint.hurl index aaa8f75beb..2be61f259a 100644 --- a/integration/hurlfmt/tests_export/predicate.lint.hurl +++ b/integration/hurlfmt/tests_export/predicate.lint.hurl @@ -18,6 +18,7 @@ jsonpath "$.nooks" contains "Dune" # contains jsonpath "$.succeeded" isBoolean # isBoolean jsonpath "$.books" isList # isList jsonpath "$.books" isCollection # isCollection +jsonpath "$.books" isObject # isObject certificate "Expire-Date" isDate # isDate jsonpath "$.publication_date" isIsoDate # isIsoDate jsonpath "$.movies" isEmpty # isEmpty diff --git a/packages/hurl/src/runner/predicate.rs b/packages/hurl/src/runner/predicate.rs index 12b3a921d0..aaddd18f21 100644 --- a/packages/hurl/src/runner/predicate.rs +++ b/packages/hurl/src/runner/predicate.rs @@ -200,8 +200,9 @@ fn expected_no_value( PredicateFuncValue::IsIpv4 => Ok("ipv4".to_string()), PredicateFuncValue::IsIpv6 => Ok("ipv6".to_string()), PredicateFuncValue::IsIsoDate => Ok("date".to_string()), - PredicateFuncValue::IsNumber => Ok("number".to_string()), PredicateFuncValue::IsList => Ok("list".to_string()), + PredicateFuncValue::IsNumber => Ok("number".to_string()), + PredicateFuncValue::IsObject => Ok("object".to_string()), PredicateFuncValue::IsString => Ok("string".to_string()), PredicateFuncValue::IsUuid => Ok("uuid".to_string()), } @@ -281,6 +282,7 @@ fn eval_predicate_func( PredicateFuncValue::IsIsoDate => eval_is_iso_date(value), PredicateFuncValue::IsList => eval_is_list(value), PredicateFuncValue::IsNumber => eval_is_number(value), + PredicateFuncValue::IsObject => eval_is_object(value), PredicateFuncValue::IsString => eval_is_string(value), PredicateFuncValue::IsUuid => eval_is_uuid(value), } @@ -537,6 +539,16 @@ fn eval_is_list(actual: &Value) -> Result { }) } +/// Evaluates if an `actual` value is an object. +fn eval_is_object(actual: &Value) -> Result { + Ok(PredicateResult { + success: actual.is_object(), + actual: actual.repr(), + expected: "object".to_string(), + type_mismatch: false, + }) +} + /// Evaluates if an `actual` value is a date. fn eval_is_date(actual: &Value) -> Result { Ok(PredicateResult { diff --git a/packages/hurl/src/runner/value_impl.rs b/packages/hurl/src/runner/value_impl.rs index 5c5c62ddfd..02fe8c99fc 100644 --- a/packages/hurl/src/runner/value_impl.rs +++ b/packages/hurl/src/runner/value_impl.rs @@ -112,6 +112,11 @@ impl Value { self.kind() == ValueKind::Bytes || self.kind() == ValueKind::List } + /// Returns `true` the value is an object, otherwise `false`. + pub fn is_object(&self) -> bool { + self.kind() == ValueKind::Nodeset || self.kind() == ValueKind::Object + } + /// Returns `true` the value is a collection, otherwise `false`. pub fn is_collection(&self) -> bool { self.kind() == ValueKind::Bytes diff --git a/packages/hurl_core/src/ast/section.rs b/packages/hurl_core/src/ast/section.rs index b126e78baf..eb6c73f698 100644 --- a/packages/hurl_core/src/ast/section.rs +++ b/packages/hurl_core/src/ast/section.rs @@ -414,6 +414,7 @@ pub enum PredicateFuncValue { IsIsoDate, IsList, IsNumber, + IsObject, IsString, IsUuid, } @@ -445,6 +446,7 @@ impl PredicateFuncValue { PredicateFuncValue::IsIsoDate => "isIsoDate", PredicateFuncValue::IsList => "isList", PredicateFuncValue::IsNumber => "isNumber", + PredicateFuncValue::IsObject => "isObject", PredicateFuncValue::IsString => "isString", PredicateFuncValue::IsUuid => "isUuid", } diff --git a/packages/hurl_core/src/ast/visit.rs b/packages/hurl_core/src/ast/visit.rs index f2cce3c1f6..e5154349ee 100644 --- a/packages/hurl_core/src/ast/visit.rs +++ b/packages/hurl_core/src/ast/visit.rs @@ -695,6 +695,7 @@ pub fn walk_predicate(visitor: &mut V, pred: &Predicate) { | PredicateFuncValue::IsIsoDate | PredicateFuncValue::IsList | PredicateFuncValue::IsNumber + | PredicateFuncValue::IsObject | PredicateFuncValue::IsString | PredicateFuncValue::IsUuid => {} } diff --git a/packages/hurl_core/src/parser/predicate.rs b/packages/hurl_core/src/parser/predicate.rs index 238dbcd666..7ca6f05f54 100644 --- a/packages/hurl_core/src/parser/predicate.rs +++ b/packages/hurl_core/src/parser/predicate.rs @@ -88,6 +88,7 @@ fn predicate_func_value(reader: &mut Reader) -> ParseResult string_predicate, collection_predicate, is_list_predicate, + is_object_predicate, date_predicate, iso_date_predicate, exist_predicate, @@ -330,6 +331,11 @@ fn is_list_predicate(reader: &mut Reader) -> ParseResult { Ok(PredicateFuncValue::IsList) } +fn is_object_predicate(reader: &mut Reader) -> ParseResult { + try_literal("isObject", reader)?; + Ok(PredicateFuncValue::IsObject) +} + #[cfg(test)] mod tests { use super::*; diff --git a/packages/hurlfmt/src/format/json.rs b/packages/hurlfmt/src/format/json.rs index 86b997c156..426784f4df 100644 --- a/packages/hurlfmt/src/format/json.rs +++ b/packages/hurlfmt/src/format/json.rs @@ -571,6 +571,7 @@ impl ToJson for Predicate { | PredicateFuncValue::IsIsoDate | PredicateFuncValue::IsList | PredicateFuncValue::IsNumber + | PredicateFuncValue::IsObject | PredicateFuncValue::IsString | PredicateFuncValue::IsUuid => {} } diff --git a/packages/hurlfmt/src/linter/rewrite.rs b/packages/hurlfmt/src/linter/rewrite.rs index cff6696834..84b8fb6ae2 100644 --- a/packages/hurlfmt/src/linter/rewrite.rs +++ b/packages/hurlfmt/src/linter/rewrite.rs @@ -632,6 +632,7 @@ impl Lint for PredicateFuncValue { | PredicateFuncValue::IsIsoDate | PredicateFuncValue::IsList | PredicateFuncValue::IsNumber + | PredicateFuncValue::IsObject | PredicateFuncValue::IsString | PredicateFuncValue::IsUuid => {} }