diff --git a/integration/tests_failed/error_format_long.err.pattern b/integration/tests_failed/error_format_long.err.pattern index 01075807b6c..79b8e9f757c 100644 --- a/integration/tests_failed/error_format_long.err.pattern +++ b/integration/tests_failed/error_format_long.err.pattern @@ -31,3 +31,39 @@ error: Assert failure | expected: int <2> | +HTTP/1.1 200 +Server: Werkzeug/~~~ Python/~~~ +Date: ~~~ +Content-Type: application/json +Content-Length: 115 +Server: Flask Server +Connection: close + +{"books": [{"name": "Dune", "author": "Franck Herbert"}, {"name": "Les Mis\u00e9rables", "author": "Victor Hugo"}]} + +error: Assert failure + --> tests_failed/error_format_long.hurl:14:0 + | +14 | jsonpath "$.books" count == 12 + | actual: int <2> + | expected: int <12> + | + +HTTP/1.1 200 +Server: Werkzeug/~~~ Python/~~~ +Date: ~~~ +Content-Type: application/problem+json +Content-Length: 258 +Server: Flask Server +Connection: close + +{"type": "https://example.com/probs/out-of-credit", "title": "You do not have enough credit.", "detail": "Your current balance is 30, but that costs 50.", "instance": "/account/12345/msgs/abc", "balance": 30, "accounts": ["/account/12345", "/account/67890"]} + +error: Assert failure + --> tests_failed/error_format_long.hurl:21:0 + | +21 | jsonpath "$.title" == "You have enough credit." + | actual: string + | expected: string + | + diff --git a/integration/tests_failed/error_format_long.html b/integration/tests_failed/error_format_long.html index 044501e4579..f2c09ae270a 100644 --- a/integration/tests_failed/error_format_long.html +++ b/integration/tests_failed/error_format_long.html @@ -1,8 +1,27 @@ -
GET http://localhost:8000/error-assert-xpath
+
# Using --error-format long, the response body
+# is logged when there are errors. If the response is a kind of
+# text (like text/html or application/json), the body is logged as text,
+# otherwise as a binary.
+GET http://localhost:8000/error-format-long/html
 HTTP 200
 Content-Type: text/html
 [Asserts]
 xpath "string(//head/title)" == "Welcome!"
 xpath "//foo" isEmpty
 xpath "//title" count == 2
+
+
+GET http://localhost:8000/error-format-long/json
+HTTP 200
+Content-Type: application/json
+[Asserts]
+jsonpath "$.books" count == 12
+
+
+# RFC-7807 application/problem+json is also supported
+GET http://localhost:8000/error-format-long/rfc-7807
+HTTP 200
+Content-Type: application/problem+json
+[Asserts]
+jsonpath "$.title" == "You have enough credit."
 
diff --git a/integration/tests_failed/error_format_long.hurl b/integration/tests_failed/error_format_long.hurl index 03cd7261102..659a43d79d0 100644 --- a/integration/tests_failed/error_format_long.hurl +++ b/integration/tests_failed/error_format_long.hurl @@ -1,7 +1,26 @@ -GET http://localhost:8000/error-assert-xpath +# Using --error-format long, the response body +# is logged when there are errors. If the response is a kind of +# text (like text/html or application/json), the body is logged as text, +# otherwise as a binary. +GET http://localhost:8000/error-format-long/html HTTP 200 Content-Type: text/html [Asserts] xpath "string(//head/title)" == "Welcome!" xpath "//foo" isEmpty xpath "//title" count == 2 + + +GET http://localhost:8000/error-format-long/json +HTTP 200 +Content-Type: application/json +[Asserts] +jsonpath "$.books" count == 12 + + +# RFC-7807 application/problem+json is also supported +GET http://localhost:8000/error-format-long/rfc-7807 +HTTP 200 +Content-Type: application/problem+json +[Asserts] +jsonpath "$.title" == "You have enough credit." diff --git a/integration/tests_failed/error_format_long.json b/integration/tests_failed/error_format_long.json index dc9b256d23a..87a1cea1e8e 100644 --- a/integration/tests_failed/error_format_long.json +++ b/integration/tests_failed/error_format_long.json @@ -1 +1 @@ -{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/error-assert-xpath"},"response":{"status":200,"headers":[{"name":"Content-Type","value":"text/html"}],"asserts":[{"query":{"type":"xpath","expr":"string(//head/title)"},"predicate":{"type":"equal","value":"Welcome!"}},{"query":{"type":"xpath","expr":"//foo"},"predicate":{"type":"isEmpty"}},{"query":{"type":"xpath","expr":"//title"},"filters":[{"type":"count"}],"predicate":{"type":"equal","value":2}}]}}]} +{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/error-format-long/html"},"response":{"status":200,"headers":[{"name":"Content-Type","value":"text/html"}],"asserts":[{"query":{"type":"xpath","expr":"string(//head/title)"},"predicate":{"type":"equal","value":"Welcome!"}},{"query":{"type":"xpath","expr":"//foo"},"predicate":{"type":"isEmpty"}},{"query":{"type":"xpath","expr":"//title"},"filters":[{"type":"count"}],"predicate":{"type":"equal","value":2}}]}},{"request":{"method":"GET","url":"http://localhost:8000/error-format-long/json"},"response":{"status":200,"headers":[{"name":"Content-Type","value":"application/json"}],"asserts":[{"query":{"type":"jsonpath","expr":"$.books"},"filters":[{"type":"count"}],"predicate":{"type":"equal","value":12}}]}},{"request":{"method":"GET","url":"http://localhost:8000/error-format-long/rfc-7807"},"response":{"status":200,"headers":[{"name":"Content-Type","value":"application/problem+json"}],"asserts":[{"query":{"type":"jsonpath","expr":"$.title"},"predicate":{"type":"equal","value":"You have enough credit."}}]}}]} diff --git a/integration/tests_failed/error_format_long.ps1 b/integration/tests_failed/error_format_long.ps1 index 19e0b44bfe5..ab772e550bc 100755 --- a/integration/tests_failed/error_format_long.ps1 +++ b/integration/tests_failed/error_format_long.ps1 @@ -1,3 +1,3 @@ Set-StrictMode -Version latest $ErrorActionPreference = 'Stop' -hurl --error-format long tests_failed/error_format_long.hurl +hurl --error-format long --fail-at-end tests_failed/error_format_long.hurl diff --git a/integration/tests_failed/error_format_long.py b/integration/tests_failed/error_format_long.py new file mode 100644 index 00000000000..beef62a1e17 --- /dev/null +++ b/integration/tests_failed/error_format_long.py @@ -0,0 +1,39 @@ +import json + +from app import app +from flask import Response + + +@app.route("/error-format-long/html") +def error_format_html(): + return "Test" + + +@app.route("/error-format-long/json") +def error_format_json(): + data = { + "books": [ + { + "name": "Dune", + "author": "Franck Herbert", + }, + { + "name": "Les Misérables", + "author": "Victor Hugo", + }, + ] + } + return Response(json.dumps(data), mimetype="application/json") + + +@app.route("/error-format-long/rfc-7807") +def error_format_problem_json(): + data = { + "type": "https://example.com/probs/out-of-credit", + "title": "You do not have enough credit.", + "detail": "Your current balance is 30, but that costs 50.", + "instance": "/account/12345/msgs/abc", + "balance": 30, + "accounts": ["/account/12345", "/account/67890"], + } + return Response(json.dumps(data), mimetype="application/problem+json") diff --git a/integration/tests_failed/error_format_long.sh b/integration/tests_failed/error_format_long.sh index 5f42655d517..6b3f96181f7 100755 --- a/integration/tests_failed/error_format_long.sh +++ b/integration/tests_failed/error_format_long.sh @@ -1,3 +1,3 @@ #!/bin/bash set -Eeuo pipefail -hurl --error-format long tests_failed/error_format_long.hurl \ No newline at end of file +hurl --error-format long --fail-at-end tests_failed/error_format_long.hurl diff --git a/packages/hurl/src/http/mimetype.rs b/packages/hurl/src/http/mimetype.rs index f2dba369b17..c81e05f829a 100644 --- a/packages/hurl/src/http/mimetype.rs +++ b/packages/hurl/src/http/mimetype.rs @@ -18,10 +18,15 @@ /// Returns true if binary data with this `content_type` can be decoded as text. pub fn is_kind_of_text(content_type: &str) -> bool { - content_type.contains("text/") - || content_type.contains("application/json") - || content_type.contains("application/xml") - || content_type.contains("application/x-www-form-urlencoded") + let content_types = [ + "text/", + "application/json", + "application/problem+json", // See https://datatracker.ietf.org/doc/html/rfc7807 + "application/xml", + "application/x-www-form-urlencoded", + ]; + + content_types.iter().any(|c| content_type.contains(c)) } /// Returns true if this `content_type` is HTML.