Announcing Hurl 3.0.0

The Hurl team is happy to announce Hurl 3.0.0!

Hurl is a command line tool powered by curl, that runs HTTP requests defined in a simple plain text format:

GET https://example.org/api/tests/4567

HTTP 200
[Asserts]
header "x-foo" contains "bar"
certificate "Expire-Date" daysAfterNow > 15
jsonpath "$.status" == "RUNNING"    # Check the status code
jsonpath "$.tests" count == 25      # Check the number of items
jsonpath "$.id" matches /\d{4}/     # Check the format of the id

What’s New in This Release

Checking SSL Attributes (Expiration Date, Issuer, etc...)

In Hurl 3.0.0, we can now check various attributes of a SSL certificate:

# Check attributes of the SSL certificate            
GET https://example.org

HTTP 200
[Asserts]
certificate "Subject" == "CN=example.org"
certificate "Issuer" == "C=US, O=Let's Encrypt, CN=R3"
certificate "Expire-Date" daysAfterNow > 15
certificate "Serial-Number" matches /[\da-f]+/

The following properties are available to check: Subject, Issuer, Start-Date, Expire-Date and Serial-Number. Using certificate asserts, you can simply create a cron job that will warn you if your certificate expires soon.

If you want more information on the SSL layer, you can also use --very-verbose option that will output curl debug logs, including SSL informations:

$ echo 'HEAD https://hurl.dev' | hurl --very-verbose
* Options:
*     fail fast: true
...
*
* Request can be run with the following curl command:
* curl 'https://hurl.dev' --head
*
**   Trying 145.239.78.213:443...
** Connected to hurl.dev (145.239.78.213) port 443 (#0)
** ALPN, offering h2
** ALPN, offering http/1.1
** successfully set certificate verify locations:
**  CAfile: /etc/ssl/cert.pem
**  CApath: none
** (304) (OUT), TLS handshake, Client hello (1):
** (304) (IN), TLS handshake, Server hello (2):
** TLSv1.2 (IN), TLS handshake, Certificate (11):
** TLSv1.2 (IN), TLS handshake, Server key exchange (12):
** TLSv1.2 (IN), TLS handshake, Server finished (14):
** TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
...

Working with Date Values

Introduced in Hurl 2.0.0, filters allow you to transform the data extracted from an HTTP response, whether in asserts or in captures:

GET https://example.org/api

HTTP 200
[Captures]
name: jsonpath "$user.id" replace /\d/ "x"
[Asserts]
header "x-servers" split "," count == 2
header "x-servers" split "," nth 0 == "rec1"
header "x-servers" split "," nth 1 == "rec3"
jsonpath "$.books" count == 12

Now, we have filters to work on dates: daysAfterNow, daysBeforeNow, format and toDate. For instance, let’s say we have a JSON response like this:

{
  "published": "2023-01-23T18:25:43.511Z"
}

We can do the following:

  • extract the published string from the response => jsonpath "$.published"
  • transform this value to a date variable => toDate "%Y-%m-%dT%H:%M:%S%.fZ"
  • reformat this date to the day of the week => format "%A"

So, our Hurl test becomes:

GET https://example.org/books/123
HTTP 200
[Asserts]
jsonpath "$.published" == "2023-01-23T18:25:43.511Z"
jsonpath "$.published" toDate "%Y-%m-%dT%H:%M:%S%.fZ" format "%A" == "Monday"

ISO 8601 / RFC 3339 date and time format have shorthand format %+ so we can write:

GET https://example.org/books/123
HTTP 200
[Asserts]
jsonpath "$.published" == "2023-01-23T18:25:43.511Z"
jsonpath "$.published" toDate "%+" format "%A" == "Monday"

Some queries are natively returning date values: expiration date of SSL certificates or expiration date of cookies for instance:

GET https://example.org
HTTP 200
[Asserts]
cookie "LSID[Expires]" format "%a, %d %b %Y %H:%M:%S" == "Thu, 13 Jan 2078 22:23:01"
certificate "Expire-Date" daysAfterNow > 15

Convert curl to Hurl

hurlfmt is our Swiss knife for working with Hurl formatted files. We use it to convert an Hurl file to a JSON file:

$ hurlfmt --out json test.hurl | jq
{
  "entries": [
    {
      "request": {
        "method": "GET",
        "url": "http://localhost:8000/test.json"
      },
      "response": {
        "status": 200,
...

This can be useful if you want to convert your big Hurl tests suite to a solution that uses a different format.

With 3.0.0, we’ve added the ability to convert curl command lines to Hurl.

For instance, using hurlfmt:

$ hurlfmt --in curl curl.txt

We can convert this chain of curl commands:

curl http://localhost:8000/hello
curl http://localhost:8000/custom-headers -H 'Fruit:Raspberry' -H 'Fruit:Apple' -H 'Fruit:Banana' -H 'Fruit: Grape' -H 'Color:Green'
curl --header 'Content-Type: application/json' --data $'{"name": "Bob","password":"secret","age": 30}' 'http://localhost:8000/post-json'
curl --header 'Content-Type:' --data '@tests_ok/data.bin' 'http://localhost:8000/post-file'
curl --location 'http://localhost:8000/redirect-absolute'
curl -k https://localhost:8001/hello

to this Hurl file:

GET http://localhost:8000/hello

GET http://localhost:8000/custom-headers
Fruit: Raspberry
Fruit: Apple
Fruit: Banana
Fruit: Grape
Color: Green

POST http://localhost:8000/post-json
Content-Type: application/json
```
{"name": "Bob","password":"secret","age": 30}
```

POST http://localhost:8000/post-file
Content-Type:
file, tests_ok/data.bin;

GET http://localhost:8000/redirect-absolute
[Options]
location: true

GET https://localhost:8001/hello
[Options]
insecure: true

From your browser to Hurl

Another use case is to convert a request played in your favorite browser to Hurl format and replay it with Hurl. Using Firefox, in the Developer Tools / Network tab, you can export an HTTP request to a curl command:

Export request to curl in Firefox

Then using hurlfmt, you will be able to convert this request to Hurl. We’re very happy to have implemented this feature and we’re looking forward to feedback!

Simplify Hurl’s Rust APIs

Hurl purpose has always been to be an excellent command line application, whether you want to get datas from a website or you want to add integration tests in A CI/CD pipeline. Some people (to our surprise), are also using Hurl’s crate (a library in the Rust world) to integrate Hurl in there own program. With 3.0.0, we’ve given some attention to the APIs exposed by Hurl’s crate and, now, it’s even simpler to run an Hurl content and get result.

A minimal Rust sample using the run method:

// A simple Hurl sample
let content = r#"
GET http://localhost:8000/hello
HTTP 200
"#;

// Define runner options and logger
let options = RunnerOptionsBuilder::new()
    .follow_location(true)
    .verbosity(Some(Verbosity::Verbose))
    .build();
let logger = LoggerBuilder::new().build();

// Set variables
let mut variables = HashMap::default();
variables.insert("name".to_string(), Value::String("toto".to_string()));

// Run the Hurl sample
let result = runner::run(
    content,
    &options,
    &variables,
    &logger
);

To run a Hurl content, you have to provide the content as a string slice, a logger, some variables and runner options. Runner options and logger are created using the Rust builder pattern: this way we can add more options without breaking binary compatibility (thanks @robjtede).

That’s All

There are other improvements and bug fixes, you can check a complete list in our release note. If you like Hurl, don’t hesitate to give us a star on GitHub or share it on Twitter!

We’ll be happy to hear from you, either for enhancement requests or for sharing your success story using Hurl!