From 17d8dc577e79e9aefcbba177425673accf967a95 Mon Sep 17 00:00:00 2001 From: theoforger Date: Thu, 19 Dec 2024 16:44:11 -0500 Subject: [PATCH] Add header option per request * Remove `cli-only` * Add `header` in the `options` field * Add integration test for header option --- README.md | 2 +- docs/grammar.md | 42 ++++++++++--------- docs/manual.md | 2 +- docs/manual/hurl.1 | 4 +- docs/manual/hurl.md | 2 - docs/manual/hurlfmt.1 | 2 +- docs/spec/grammar/hurl.grammar | 3 ++ docs/spec/options/hurl/header.option | 1 - .../tests_error_parser/invalid_option.err | 2 +- integration/hurl/tests_ok/header_option.hurl | 7 ++++ integration/hurl/tests_ok/header_option.ps1 | 4 ++ integration/hurl/tests_ok/header_option.py | 12 ++++++ integration/hurl/tests_ok/header_option.sh | 4 ++ integration/hurlfmt/tests_export/options.html | 2 + integration/hurlfmt/tests_export/options.hurl | 2 + integration/hurlfmt/tests_export/options.json | 2 +- .../hurlfmt/tests_export/options.lint.hurl | 2 + packages/hurl/README.md | 2 +- packages/hurl/src/runner/options.rs | 4 ++ packages/hurl_core/src/ast/core.rs | 3 ++ packages/hurl_core/src/format/html.rs | 1 + packages/hurl_core/src/parser/error.rs | 1 + packages/hurl_core/src/parser/option.rs | 6 +++ packages/hurlfmt/src/format/json.rs | 1 + packages/hurlfmt/src/format/token.rs | 1 + 25 files changed, 82 insertions(+), 32 deletions(-) create mode 100644 integration/hurl/tests_ok/header_option.hurl create mode 100644 integration/hurl/tests_ok/header_option.ps1 create mode 100644 integration/hurl/tests_ok/header_option.py create mode 100755 integration/hurl/tests_ok/header_option.sh diff --git a/README.md b/README.md index 09c30f5fd8e..db92f6643b2 100644 --- a/README.md +++ b/README.md @@ -1262,7 +1262,7 @@ will follow a redirection only for the second entry. | --file-root <DIR> | Set root directory to import files in Hurl. This is used for files in multipart form data, request body and response output.
When it is not explicitly defined, files are relative to the Hurl file's directory.

This is a cli-only option.
| | --from-entry <ENTRY_NUMBER> | Execute Hurl file from ENTRY_NUMBER (starting at 1).

This is a cli-only option.
| | --glob <GLOB> | Specify input files that match the given glob pattern.

Multiple glob flags may be used. This flag supports common Unix glob patterns like *, ? and [].
However, to avoid your shell accidentally expanding glob patterns before Hurl handles them, you must use single quotes or double quotes around each pattern.

This is a cli-only option.
| -| -H, --header <HEADER> | Add an extra header to include in information sent. Can be used several times in a command

Do not add newlines or carriage returns

This is a cli-only option.
| +| -H, --header <HEADER> | Add an extra header to include in information sent. Can be used several times in a command

Do not add newlines or carriage returns
| | -0, --http1.0 | Tells Hurl to use HTTP version 1.0 instead of using its internally preferred HTTP version.
| | --http1.1 | Tells Hurl to use HTTP version 1.1.
| | --http2 | Tells Hurl to use HTTP version 2.
For HTTPS, this means Hurl negotiates HTTP/2 in the TLS handshake. Hurl does this by default.
For HTTP, this means Hurl attempts to upgrade the request to HTTP/2 using the Upgrade: request header.
| diff --git a/docs/grammar.md b/docs/grammar.md index 3edc850025f..362cee5b531 100644 --- a/docs/grammar.md +++ b/docs/grammar.md @@ -77,7 +77,7 @@ Short description:
assert(used by asserts-section)
option(used by options-section)
+(aws-sigv4-option|ca-certificate-option|client-certificate-option|client-key-option|compressed-option|connect-to-option|connect-timeout-option|delay-option|follow-redirect-option|follow-redirect-trusted-option|header-option|http10-option|http11-option|http2-option|http3-option|insecure-option|ipv4-option|ipv6-option|limit-rate-option|max-redirs-option|netrc-option|netrc-file-option|netrc-optional-option|output-option|path-as-is-option|proxy-option|repeat-option|resolve-option|retry-option|retry-interval-option|skip-option|unix-socket-option|user-option|variable-option|verbose-option|very-verbose-option)
aws-sigv4-option(used by option)
aws-sigv4 : value-string lt
ca-certificate-option(used by option)
cacert : filename lt
client-certificate-option(used by option)
@@ -88,6 +88,7 @@ Short description:
delay-option(used by option)
delay : duration-option lt
follow-redirect-option(used by option)
location : boolean-option lt
follow-redirect-trusted-option(used by option)
location-trusted : boolean-option lt
+
header-option(used by option)
header : value-string lt
http10-option(used by option)
http1.0 : boolean-option lt
http11-option(used by option)
http1.1 : boolean-option lt
http2-option(used by option)
http2 : boolean-option lt
@@ -114,9 +115,9 @@ Short description:
verbose-option(used by option)
verbose : boolean-option lt
very-verbose-option(used by option)
very-verbose : boolean-option lt
variable-definition(used by variable-option)
-
-
-
+
+
+
duration-unit(used by duration-option)
ms|s|m
variable-value(used by variable-definition)
 null
|boolean
@@ -202,7 +203,7 @@ Short description: |oneline-file
|oneline-hex
|quoted-string
-|template
+|placeholder

Bytes

bytes(used by body)
 json-value
|xml
|multiline-string
@@ -214,24 +215,24 @@ Short description:
base64, [A-Z0-9+-= \n]+ ;
oneline-file(used by predicate-valuebytes)
file, filename ;
-

Strings

quoted-string-text(used by quoted-string-content)
~["\\]+
quoted-string-escaped-char(used by quoted-string-content)
\ ("|\|\b|\f|\n|\r|\t|\u unicode-char)
- +
key-string-content(used by key-string)
key-string-text(used by key-string-content)
(alphanum|_|-|.|[|]|@|$)+
key-string-escaped-char(used by key-string-content)
\ (#|:|\|\b|\f|\n|\r|\t|\u unicode-char)
- +
value-string-text(used by value-string-content)
~[#\n\\]+
value-string-escaped-char(used by value-string-content)
\ (#|\|\b|\f|\n|\r|\t|\u unicode-char)
-
oneline-string(used by predicate-valuebytes)
+
oneline-string(used by predicate-valuebytes)
oneline-string-text(used by oneline-string-content)
~[#\n\\] ~`
oneline-string-escaped-char(used by oneline-string-content)
\ (`|#|\|b|f|u unicode-char)
multiline-string-type(used by multiline-string)
 base64
|hex
@@ -243,16 +244,16 @@ Short description:
multiline-string-text(used by multiline-string-content)
~[\\]+ ~```
multiline-string-escaped-char(used by multiline-string-content)
\ (\|b|f|n|r|t|`|u unicode-char)
- +
filename-content(used by filename)
filename-text(used by filename-content)
~[#;{} \n\\]+
filename-escaped-char(used by filename-content)
\ (\|b|f|n|r|t|#|;| |{|}|u unicode-char)
- +
filename-password-text(used by filename-password-content)
~[#;{} \n\\]+
filename-password-escaped-char(used by filename-password-content)
\ (\|b|f|n|r|t|#|;| |{|}|:|u unicode-char)
-

JSON

json-value(used by bytesjson-key-valuejson-array)

JSON

json-value(used by bytesjson-key-valuejson-array)
 placeholder
|json-object
|json-array
|json-string
@@ -262,13 +263,14 @@ Short description:
json-object(used by json-value)
json-key-value(used by json-object)
json-array(used by json-value)
[ json-value (, json-value)* ]
-
json-string(used by json-valuejson-key-value)
+
json-string-text(used by json-string-content)
~["\\]
json-string-escaped-char(used by json-string-content)
\ ("|\|b|f|n|r|t|u hexdigit hexdigit hexdigit hexdigit)
-
json-number(used by json-value)
-

Template / Expression

- +
json-number(used by json-value)
+
json-integer(used by json-number)
0|[1-9] digit*
+

Function

function(used by expr)
 env-function
|now-function
@@ -315,15 +317,15 @@ Short description:

Lexical Grammar

true|false
alphanum(used by key-string-text)
[A-Za-z0-9]
- + -
digit(used by integerfractionexponent)
[0-9]
+
digit(used by json-integerintegerfractionexponent)
[0-9]
[0-9A-Fa-f]
fraction(used by json-numberfloat)
. digit+
exponent(used by json-number)
(e|E) (+|-)? digit+
- +
comment(used by lt)
# ~[\n]*
regex-content(used by regex)
diff --git a/docs/manual.md b/docs/manual.md index a51610ab1f2..cf30065bd67 100644 --- a/docs/manual.md +++ b/docs/manual.md @@ -165,7 +165,7 @@ will follow a redirection only for the second entry. | --file-root <DIR> | Set root directory to import files in Hurl. This is used for files in multipart form data, request body and response output.
When it is not explicitly defined, files are relative to the Hurl file's directory.

This is a cli-only option.
| | --from-entry <ENTRY_NUMBER> | Execute Hurl file from ENTRY_NUMBER (starting at 1).

This is a cli-only option.
| | --glob <GLOB> | Specify input files that match the given glob pattern.

Multiple glob flags may be used. This flag supports common Unix glob patterns like *, ? and [].
However, to avoid your shell accidentally expanding glob patterns before Hurl handles them, you must use single quotes or double quotes around each pattern.

This is a cli-only option.
| -| -H, --header <HEADER> | Add an extra header to include in information sent. Can be used several times in a command

Do not add newlines or carriage returns

This is a cli-only option.
| +| -H, --header <HEADER> | Add an extra header to include in information sent. Can be used several times in a command

Do not add newlines or carriage returns
| | -0, --http1.0 | Tells Hurl to use HTTP version 1.0 instead of using its internally preferred HTTP version.
| | --http1.1 | Tells Hurl to use HTTP version 1.1.
| | --http2 | Tells Hurl to use HTTP version 2.
For HTTPS, this means Hurl negotiates HTTP/2 in the TLS handshake. Hurl does this by default.
For HTTP, this means Hurl attempts to upgrade the request to HTTP/2 using the Upgrade: request header.
| diff --git a/docs/manual/hurl.1 b/docs/manual/hurl.1 index 820513e17ee..7e8c166b928 100644 --- a/docs/manual/hurl.1 +++ b/docs/manual/hurl.1 @@ -1,4 +1,4 @@ -.TH hurl 1 "16 Dec 2024" "hurl 6.1.0-SNAPSHOT" " Hurl Manual" +.TH hurl 1 "19 Dec 2024" "hurl 6.1.0-SNAPSHOT" " Hurl Manual" .SH NAME hurl - run and test HTTP requests. @@ -243,8 +243,6 @@ Add an extra header to include in information sent. Can be used several times in Do not add newlines or carriage returns -This is a cli-only option. - .IP "-0, --http1.0 " Tells Hurl to use HTTP version 1.0 instead of using its internally preferred HTTP version. diff --git a/docs/manual/hurl.md b/docs/manual/hurl.md index 2a8c1012f27..14b6027b3d1 100644 --- a/docs/manual/hurl.md +++ b/docs/manual/hurl.md @@ -262,8 +262,6 @@ Add an extra header to include in information sent. Can be used several times in Do not add newlines or carriage returns -This is a cli-only option. - ### -0, --http1.0 {#http10} Tells Hurl to use HTTP version 1.0 instead of using its internally preferred HTTP version. diff --git a/docs/manual/hurlfmt.1 b/docs/manual/hurlfmt.1 index fbea51717e9..5b82db2f435 100644 --- a/docs/manual/hurlfmt.1 +++ b/docs/manual/hurlfmt.1 @@ -1,4 +1,4 @@ -.TH hurl 1 "16 Dec 2024" "hurl 6.1.0-SNAPSHOT" " Hurl Manual" +.TH hurl 1 "19 Dec 2024" "hurl 6.1.0-SNAPSHOT" " Hurl Manual" .SH NAME hurlfmt - format Hurl files diff --git a/docs/spec/grammar/hurl.grammar b/docs/spec/grammar/hurl.grammar index ddb0d2c4517..e03f502cd1a 100644 --- a/docs/spec/grammar/hurl.grammar +++ b/docs/spec/grammar/hurl.grammar @@ -126,6 +126,7 @@ option: | delay-option | follow-redirect-option | follow-redirect-trusted-option + | header-option | http10-option | http11-option | http2-option @@ -173,6 +174,8 @@ follow-redirect-option: "location" ":" boolean-option lt follow-redirect-trusted-option: "location-trusted" ":" boolean-option lt +header-option: "header" ":" value-string lt + http10-option: "http1.0" ":" boolean-option lt http11-option: "http1.1" ":" boolean-option lt diff --git a/docs/spec/options/hurl/header.option b/docs/spec/options/hurl/header.option index 2a1038ee29d..93c732c064d 100644 --- a/docs/spec/options/hurl/header.option +++ b/docs/spec/options/hurl/header.option @@ -5,7 +5,6 @@ value: HEADER help: Pass custom header(s) to server help_heading: HTTP options multi: append -cli_only: true --- Add an extra header to include in information sent. Can be used several times in a command diff --git a/integration/hurl/tests_error_parser/invalid_option.err b/integration/hurl/tests_error_parser/invalid_option.err index 7563577ab7a..bd2c17cfec1 100644 --- a/integration/hurl/tests_error_parser/invalid_option.err +++ b/integration/hurl/tests_error_parser/invalid_option.err @@ -2,6 +2,6 @@ error: Parsing option --> tests_error_parser/invalid_option.hurl:3:1 | 3 | foo: true - | ^ the option name is not valid. Valid values are aws-sigv4, cacert, cert, compressed, connect-to, delay, insecure, http1.0, http1.1, http2, http3, ipv4, ipv6, key, location, max-redirs, output, path-as-is, proxy, resolve, retry, retry-interval, skip, unix-socket, variable, verbose, very-verbose + | ^ the option name is not valid. Valid values are aws-sigv4, cacert, cert, compressed, connect-to, delay, insecure, header, http1.0, http1.1, http2, http3, ipv4, ipv6, key, location, max-redirs, output, path-as-is, proxy, resolve, retry, retry-interval, skip, unix-socket, variable, verbose, very-verbose | diff --git a/integration/hurl/tests_ok/header_option.hurl b/integration/hurl/tests_ok/header_option.hurl new file mode 100644 index 00000000000..f1b95fbad75 --- /dev/null +++ b/integration/hurl/tests_ok/header_option.hurl @@ -0,0 +1,7 @@ +GET http://localhost:8000/header-option +test: from-header-syntax +[Options] +header: test: from-option-1 +header: test: from-option-2 +header: another-test: from-option +HTTP 200 diff --git a/integration/hurl/tests_ok/header_option.ps1 b/integration/hurl/tests_ok/header_option.ps1 new file mode 100644 index 00000000000..afbfb4719ad --- /dev/null +++ b/integration/hurl/tests_ok/header_option.ps1 @@ -0,0 +1,4 @@ +Set-StrictMode -Version latest +$ErrorActionPreference = 'Stop' + +hurl tests_ok/header_option.hurl --header 'test: from-cli' diff --git a/integration/hurl/tests_ok/header_option.py b/integration/hurl/tests_ok/header_option.py new file mode 100644 index 00000000000..411b257029f --- /dev/null +++ b/integration/hurl/tests_ok/header_option.py @@ -0,0 +1,12 @@ +from app import app +from flask import request + + +@app.route("/header-option") +def header_option(): + assert ( + request.headers.get("test") + == "from-header-syntax,from-cli,from-option-1,from-option-2" + ) + assert request.headers.get("another-test") == "from-option" + return "" diff --git a/integration/hurl/tests_ok/header_option.sh b/integration/hurl/tests_ok/header_option.sh new file mode 100755 index 00000000000..e82e3987c18 --- /dev/null +++ b/integration/hurl/tests_ok/header_option.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -Eeuo pipefail + +hurl tests_ok/header_option.hurl --header 'test: from-cli' diff --git a/integration/hurlfmt/tests_export/options.html b/integration/hurlfmt/tests_export/options.html index 2bb8fc88fe7..771a1fafbae 100644 --- a/integration/hurlfmt/tests_export/options.html +++ b/integration/hurlfmt/tests_export/options.html @@ -13,6 +13,7 @@ delay: 1s location: false location-trusted: false +header: key: value http1.0: false http1.1: false http2: false @@ -61,6 +62,7 @@ delay: {{delay}} location: {{location}} location-trusted: {{location-trusted}} +header: {{header}} http1.0: {{http10}} http1.1: {{http11}} http2: {{http2}} diff --git a/integration/hurlfmt/tests_export/options.hurl b/integration/hurlfmt/tests_export/options.hurl index be466651d43..a7a146bbb70 100644 --- a/integration/hurlfmt/tests_export/options.hurl +++ b/integration/hurlfmt/tests_export/options.hurl @@ -13,6 +13,7 @@ delay: 1000ms delay: 1s location: false location-trusted: false +header: key: value http1.0: false http1.1: false http2: false @@ -61,6 +62,7 @@ connect-timeout: {{connect-timeout}} delay: {{delay}} location: {{location}} location-trusted: {{location-trusted}} +header: {{header}} http1.0: {{http10}} http1.1: {{http11}} http2: {{http2}} diff --git a/integration/hurlfmt/tests_export/options.json b/integration/hurlfmt/tests_export/options.json index 42c30d9d756..9b571f9cfba 100644 --- a/integration/hurlfmt/tests_export/options.json +++ b/integration/hurlfmt/tests_export/options.json @@ -1 +1 @@ -{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/hello","options":[{"name":"aws-sigv4","value":"aws:amz:eu-central-1:sts"},{"name":"cacert","value":"cacertfile"},{"name":"cert","value":"certfile"},{"name":"cert","value":"certfile:qU114@q,[\"NO"},{"name":"key","value":"keyfile"},{"name":"compressed","value":false},{"name":"connect-to","value":"example.com:443:example.net:8443"},{"value":60,"unit":"s","name":"connect-timeout"},{"name":"delay","value":1000},{"value":1000,"unit":"ms","name":"delay"},{"value":1,"unit":"s","name":"delay"},{"name":"location","value":false},{"name":"location-trusted","value":false},{"name":"http1.0","value":false},{"name":"http1.1","value":false},{"name":"http2","value":false},{"name":"http3","value":false},{"name":"insecure","value":false},{"name":"ipv4","value":false},{"name":"ipv6","value":false},{"name":"limit-rate","value":1000},{"name":"max-redirs","value":10},{"name":"netrc","value":false},{"name":"netrc-file","value":"netrcfile"},{"name":"netrc-optional","value":false},{"name":"output","value":"output.txt"},{"name":"path-as-is","value":false},{"name":"proxy","value":"http://proxy.example"},{"name":"repeat","value":-1},{"name":"repeat","value":5},{"name":"resolve","value":"example.com:443:127.0.0.1"},{"name":"retry","value":0},{"name":"retry","value":-1},{"name":"retry","value":4},{"name":"retry-interval","value":1000},{"value":1000,"unit":"ms","name":"retry-interval"},{"value":1,"unit":"s","name":"retry-interval"},{"name":"skip","value":false},{"name":"unix-socket","value":"build/unix_socket.sock"},{"name":"user","value":"bob:secret"},{"name":"variable","value":"user=null"},{"name":"variable","value":"status=true"},{"name":"variable","value":"count=2"},{"name":"variable","value":"score=7.7"},{"name":"variable","value":"name=Bob"},{"name":"variable","value":"name=Bob"},{"name":"verbose","value":false},{"name":"very-verbose","value":false}]}},{"request":{"method":"GET","url":"http://localhost:8000/hello","options":[{"name":"aws-sigv4","value":"{{aws-sigv4}}"},{"name":"cacert","value":"{{cacert}}"},{"name":"cert","value":"{{cert}}"},{"name":"key","value":"{{key}}"},{"name":"compressed","value":"{{compressed}}"},{"name":"connect-to","value":"{{connect-to}}"},{"name":"connect-timeout","value":"{{connect-timeout}}"},{"name":"delay","value":"{{delay}}"},{"name":"location","value":"{{location}}"},{"name":"location-trusted","value":"{{location-trusted}}"},{"name":"http1.0","value":"{{http10}}"},{"name":"http1.1","value":"{{http11}}"},{"name":"http2","value":"{{http2}}"},{"name":"http3","value":"{{http3}}"},{"name":"insecure","value":"{{insecure}}"},{"name":"ipv4","value":"{{ipv4}}"},{"name":"ipv6","value":"{{ipv6}}"},{"name":"limit-rate","value":"{{limit-rate}}"},{"name":"max-redirs","value":"{{max-redirs}}"},{"name":"netrc","value":"{{netrc}}"},{"name":"netrc-file","value":"{{netrc-file}}"},{"name":"netrc-optional","value":"{{netrc-optional}}"},{"name":"output","value":"{{output}}"},{"name":"path-as-is","value":"{{path-as-is}}"},{"name":"proxy","value":"{{proxy}}"},{"name":"repeat","value":"{{repeat}}"},{"name":"resolve","value":"{{resolve}}"},{"name":"retry","value":"{{retry}}"},{"name":"retry-interval","value":"{{retry-interval}}"},{"name":"skip","value":"{{skip}}"},{"name":"unix-socket","value":"{{socket-file}}"},{"name":"user","value":"{{user}}"},{"name":"verbose","value":"{{verbose}}"},{"name":"very-verbose","value":"{{very-verbose}}"}]}}]} +{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/hello","options":[{"name":"aws-sigv4","value":"aws:amz:eu-central-1:sts"},{"name":"cacert","value":"cacertfile"},{"name":"cert","value":"certfile"},{"name":"cert","value":"certfile:qU114@q,[\"NO"},{"name":"key","value":"keyfile"},{"name":"compressed","value":false},{"name":"connect-to","value":"example.com:443:example.net:8443"},{"value":60,"unit":"s","name":"connect-timeout"},{"name":"delay","value":1000},{"value":1000,"unit":"ms","name":"delay"},{"value":1,"unit":"s","name":"delay"},{"name":"location","value":false},{"name":"location-trusted","value":false},{"name":"header","value":"key: value"},{"name":"http1.0","value":false},{"name":"http1.1","value":false},{"name":"http2","value":false},{"name":"http3","value":false},{"name":"insecure","value":false},{"name":"ipv4","value":false},{"name":"ipv6","value":false},{"name":"limit-rate","value":1000},{"name":"max-redirs","value":10},{"name":"netrc","value":false},{"name":"netrc-file","value":"netrcfile"},{"name":"netrc-optional","value":false},{"name":"output","value":"output.txt"},{"name":"path-as-is","value":false},{"name":"proxy","value":"http://proxy.example"},{"name":"repeat","value":-1},{"name":"repeat","value":5},{"name":"resolve","value":"example.com:443:127.0.0.1"},{"name":"retry","value":0},{"name":"retry","value":-1},{"name":"retry","value":4},{"name":"retry-interval","value":1000},{"value":1000,"unit":"ms","name":"retry-interval"},{"value":1,"unit":"s","name":"retry-interval"},{"name":"skip","value":false},{"name":"unix-socket","value":"build/unix_socket.sock"},{"name":"user","value":"bob:secret"},{"name":"variable","value":"user=null"},{"name":"variable","value":"status=true"},{"name":"variable","value":"count=2"},{"name":"variable","value":"score=7.7"},{"name":"variable","value":"name=Bob"},{"name":"variable","value":"name=Bob"},{"name":"verbose","value":false},{"name":"very-verbose","value":false}]}},{"request":{"method":"GET","url":"http://localhost:8000/hello","options":[{"name":"aws-sigv4","value":"{{aws-sigv4}}"},{"name":"cacert","value":"{{cacert}}"},{"name":"cert","value":"{{cert}}"},{"name":"key","value":"{{key}}"},{"name":"compressed","value":"{{compressed}}"},{"name":"connect-to","value":"{{connect-to}}"},{"name":"connect-timeout","value":"{{connect-timeout}}"},{"name":"delay","value":"{{delay}}"},{"name":"location","value":"{{location}}"},{"name":"location-trusted","value":"{{location-trusted}}"},{"name":"header","value":"{{header}}"},{"name":"http1.0","value":"{{http10}}"},{"name":"http1.1","value":"{{http11}}"},{"name":"http2","value":"{{http2}}"},{"name":"http3","value":"{{http3}}"},{"name":"insecure","value":"{{insecure}}"},{"name":"ipv4","value":"{{ipv4}}"},{"name":"ipv6","value":"{{ipv6}}"},{"name":"limit-rate","value":"{{limit-rate}}"},{"name":"max-redirs","value":"{{max-redirs}}"},{"name":"netrc","value":"{{netrc}}"},{"name":"netrc-file","value":"{{netrc-file}}"},{"name":"netrc-optional","value":"{{netrc-optional}}"},{"name":"output","value":"{{output}}"},{"name":"path-as-is","value":"{{path-as-is}}"},{"name":"proxy","value":"{{proxy}}"},{"name":"repeat","value":"{{repeat}}"},{"name":"resolve","value":"{{resolve}}"},{"name":"retry","value":"{{retry}}"},{"name":"retry-interval","value":"{{retry-interval}}"},{"name":"skip","value":"{{skip}}"},{"name":"unix-socket","value":"{{socket-file}}"},{"name":"user","value":"{{user}}"},{"name":"verbose","value":"{{verbose}}"},{"name":"very-verbose","value":"{{very-verbose}}"}]}}]} diff --git a/integration/hurlfmt/tests_export/options.lint.hurl b/integration/hurlfmt/tests_export/options.lint.hurl index 2dd49829701..8d2e8557e2e 100644 --- a/integration/hurlfmt/tests_export/options.lint.hurl +++ b/integration/hurlfmt/tests_export/options.lint.hurl @@ -13,6 +13,7 @@ delay: 1000ms delay: 1s location: false location-trusted: false +header: key: value http1.0: false http1.1: false http2: false @@ -61,6 +62,7 @@ connect-timeout: {{connect-timeout}} delay: {{delay}} location: {{location}} location-trusted: {{location-trusted}} +header: {{header}} http1.0: {{http10}} http1.1: {{http11}} http2: {{http2}} diff --git a/packages/hurl/README.md b/packages/hurl/README.md index 45006ee69f3..bfaa17d35d2 100644 --- a/packages/hurl/README.md +++ b/packages/hurl/README.md @@ -1262,7 +1262,7 @@ will follow a redirection only for the second entry. | --file-root <DIR> | Set root directory to import files in Hurl. This is used for files in multipart form data, request body and response output.
When it is not explicitly defined, files are relative to the Hurl file's directory.

This is a cli-only option.
| | --from-entry <ENTRY_NUMBER> | Execute Hurl file from ENTRY_NUMBER (starting at 1).

This is a cli-only option.
| | --glob <GLOB> | Specify input files that match the given glob pattern.

Multiple glob flags may be used. This flag supports common Unix glob patterns like *, ? and [].
However, to avoid your shell accidentally expanding glob patterns before Hurl handles them, you must use single quotes or double quotes around each pattern.

This is a cli-only option.
| -| -H, --header <HEADER> | Add an extra header to include in information sent. Can be used several times in a command

Do not add newlines or carriage returns

This is a cli-only option.
| +| -H, --header <HEADER> | Add an extra header to include in information sent. Can be used several times in a command

Do not add newlines or carriage returns
| | -0, --http1.0 | Tells Hurl to use HTTP version 1.0 instead of using its internally preferred HTTP version.
| | --http1.1 | Tells Hurl to use HTTP version 1.1.
| | --http2 | Tells Hurl to use HTTP version 2.
For HTTPS, this means Hurl negotiates HTTP/2 in the TLS handshake. Hurl does this by default.
For HTTP, this means Hurl attempts to upgrade the request to HTTP/2 using the Upgrade: request header.
| diff --git a/packages/hurl/src/runner/options.rs b/packages/hurl/src/runner/options.rs index 227c6519fa0..0125fbedff8 100644 --- a/packages/hurl/src/runner/options.rs +++ b/packages/hurl/src/runner/options.rs @@ -90,6 +90,10 @@ pub fn get_entry_options( eval_duration_option(value, variables, DurationUnit::MilliSecond)?; entry_options.delay = value; } + OptionKind::Header(value) => { + let value = eval_template(value, variables)?; + entry_options.headers.push(value); + } // HTTP version options (such as http1.0, http1.1, http2 etc...) are activated // through a flag. In an `[Options]` section, the signification of such a flag is: // diff --git a/packages/hurl_core/src/ast/core.rs b/packages/hurl_core/src/ast/core.rs index e39f3ab7302..773b5808751 100644 --- a/packages/hurl_core/src/ast/core.rs +++ b/packages/hurl_core/src/ast/core.rs @@ -788,6 +788,7 @@ pub enum OptionKind { ConnectTo(Template), ConnectTimeout(DurationOption), Delay(DurationOption), + Header(Template), Http10(BooleanOption), Http11(BooleanOption), Http2(BooleanOption), @@ -830,6 +831,7 @@ impl OptionKind { OptionKind::Delay(_) => "delay", OptionKind::FollowLocation(_) => "location", OptionKind::FollowLocationTrusted(_) => "location-trusted", + OptionKind::Header(_) => "header", OptionKind::Http10(_) => "http1.0", OptionKind::Http11(_) => "http1.1", OptionKind::Http2(_) => "http2", @@ -870,6 +872,7 @@ impl OptionKind { OptionKind::Delay(value) => value.to_string(), OptionKind::FollowLocation(value) => value.to_string(), OptionKind::FollowLocationTrusted(value) => value.to_string(), + OptionKind::Header(value) => value.to_string(), OptionKind::Http10(value) => value.to_string(), OptionKind::Http11(value) => value.to_string(), OptionKind::Http2(value) => value.to_string(), diff --git a/packages/hurl_core/src/format/html.rs b/packages/hurl_core/src/format/html.rs index 539bb38ec3e..779a239027c 100644 --- a/packages/hurl_core/src/format/html.rs +++ b/packages/hurl_core/src/format/html.rs @@ -232,6 +232,7 @@ impl HtmlFormatter { OptionKind::Delay(value) => self.fmt_duration_option(value), OptionKind::FollowLocation(value) => self.fmt_bool_option(value), OptionKind::FollowLocationTrusted(value) => self.fmt_bool_option(value), + OptionKind::Header(value) => self.fmt_template(value), OptionKind::Http10(value) => self.fmt_bool_option(value), OptionKind::Http11(value) => self.fmt_bool_option(value), OptionKind::Http2(value) => self.fmt_bool_option(value), diff --git a/packages/hurl_core/src/parser/error.rs b/packages/hurl_core/src/parser/error.rs index e18061e84bd..ba1206d24e4 100644 --- a/packages/hurl_core/src/parser/error.rs +++ b/packages/hurl_core/src/parser/error.rs @@ -163,6 +163,7 @@ impl DisplaySourceError for ParseError { "connect-to", "delay", "insecure", + "header", "http1.0", "http1.1", "http2", diff --git a/packages/hurl_core/src/parser/option.rs b/packages/hurl_core/src/parser/option.rs index b27b7c42618..996b4bb20f4 100644 --- a/packages/hurl_core/src/parser/option.rs +++ b/packages/hurl_core/src/parser/option.rs @@ -55,6 +55,7 @@ pub fn parse(reader: &mut Reader) -> ParseResult { "connect-timeout" => option_connect_timeout(reader)?, "delay" => option_delay(reader)?, "insecure" => option_insecure(reader)?, + "header" => option_header(reader)?, "http1.0" => option_http_10(reader)?, "http1.1" => option_http_11(reader)?, "http2" => option_http_2(reader)?, @@ -147,6 +148,11 @@ fn option_follow_location_trusted(reader: &mut Reader) -> ParseResult ParseResult { + let value = unquoted_template(reader)?; + Ok(OptionKind::Header(value)) +} + fn option_http_10(reader: &mut Reader) -> ParseResult { let value = non_recover(boolean_option, reader)?; Ok(OptionKind::Http10(value)) diff --git a/packages/hurlfmt/src/format/json.rs b/packages/hurlfmt/src/format/json.rs index 6e153c78a28..62cad33bfa1 100644 --- a/packages/hurlfmt/src/format/json.rs +++ b/packages/hurlfmt/src/format/json.rs @@ -314,6 +314,7 @@ impl ToJson for EntryOption { OptionKind::Delay(value) => value.to_json(), OptionKind::FollowLocation(value) => value.to_json(), OptionKind::FollowLocationTrusted(value) => value.to_json(), + OptionKind::Header(value) => JValue::String(value.to_string()), OptionKind::Http10(value) => value.to_json(), OptionKind::Http11(value) => value.to_json(), OptionKind::Http2(value) => value.to_json(), diff --git a/packages/hurlfmt/src/format/token.rs b/packages/hurlfmt/src/format/token.rs index 611206db1bb..5a6ba2ca95d 100644 --- a/packages/hurlfmt/src/format/token.rs +++ b/packages/hurlfmt/src/format/token.rs @@ -953,6 +953,7 @@ impl Tokenizable for OptionKind { OptionKind::Delay(value) => value.tokenize(), OptionKind::FollowLocation(value) => value.tokenize(), OptionKind::FollowLocationTrusted(value) => value.tokenize(), + OptionKind::Header(value) => value.tokenize(), OptionKind::Http10(value) => value.tokenize(), OptionKind::Http11(value) => value.tokenize(), OptionKind::Http2(value) => value.tokenize(),