Hurl 2.0.0, the GraphQL Edition

Hurl plus GraphQL equals love Hurl plus GraphQL equals love

Christmas has been busy for the Hurl team! We’re happy to announce Hurl 2.0.0, with a lot of new features and bug fixes. If you don’t know Hurl, it’s a command line tool powered by curl that runs HTTP requests defined in a simple text format:

# Create a new cat:
POST https://example.org/api/cats
{
 "name": "Kitty",
 "color": "black"
}
HTTP 201
[Captures]
cat-id: jsonpath "$.id"


# Get our new cat:
GET https://example.org/api/cats/{{cat-id}}
HTTP 200                                
[Asserts]
jsonpath "$.lives" == 9

Hurl can be used to get data like curl, or as a testing tool for JSON/XML apis and HTML content.

So, what’s new in 2.0.0?

GraphQL Query Support

Hurl can now be used to send GraphQL query request very easily. The syntax is natural and use ``` body with a graphql hint.

For instance, using GitHub GraphQL APIs, we can check the number of stars of a project:

POST https://api.github.com/graphql
Authorization: bearer {{token}}
```graphql
{
  repository(owner: "Orange-OpenSource", name: "hurl") {
    stargazerCount
  }
}
```
HTTP 200

And this Hurl file can be run like this:

$ hurl --variable token=FOO github.hurl | jq
{
  "data": {
    "repository": {
      "stargazerCount": 3578
    }
  }
}

It’s a very “Markdowny” syntax which re-use existing Hurl multiline body.

GraphQL queries, of course, can use GraphQL variables:

POST https://api.starwars.com/graphql
```graphql
query HeroNameAndFriends($episode: Episode) {
  hero(episode: $episode) {
    name
    friends {
      name
    }
  }
}

variables {
  "episode": "JEDI"
}
```
HTTP 200

And, as any multiline body, GraphQL queries can be templatized with Hurl variables!

POST https://api.starwars.com/graphql
```graphql
query HeroNameAndFriends($episode: Episode) {
  hero(episode: $episode) {
    name
    friends {
      name
    }
  }
}

variables {
  "episode": {{episode}}
}
```
HTTP 200

As GraphQL APIs return JSON, we can add asserts on the HTTP response:

POST https://api.starwars.com/graphql
```graphql
{
  human(id: "1000") {
    name
    height
  }
}
```

HTTP 200
[Asserts]
jsonpath "$.data.human.name" == "Luke Skywalker"
jsonpath "$.data.human.height" == 1.72

Processing Data with Filters

Sometimes you want to process response body before capturing data or adding tests.

Let’s say we have an endpoint that returns an HTTP header x-servers containing a joined list of servers (for instance rec1.org,rec3.org). You want to test the value of this header: number of server, value of server 1 etc... You can now use filters to process the response headers and add clean asserts:

GET https://example.org/api

HTTP 200
[Asserts]
header "x-servers" split "," count == 2
header "x-servers" split "," nth 0 == "rec1.org"
header "x-servers" split "," nth 1 == "rec3.org"
jsonpath "$.books" count == 12

In this sample, header "x-servers" extracts the HTTP header from the response. Then, we have a chain of two filters split "," nth 0. The first filter split turns the header to a list, and the second filter, nth, returns an element of the list. Then we have our final test with == "rec1.org".

header "x-servers"query split "," nth 02 filters == "rec1.org"predicate

Filters can also be applied on captures:

GET https://example.org/api

HTTP 200
[Captures]
name: jsonpath "$user.id" replace /\d/ "x" # Make our user anonymous

Let’s take a final example, with a live existing service that returns a CSV of stock exchanges https://csvbase.com/meripaterson/stock-exchanges

First, we split our response body by lines and save it in a variable lines.

GET https://csvbase.com/meripaterson/stock-exchanges

HTTP 200
[Captures]
rows: body split "\n"

Then, we create two other variables australia and algeria that are row 7 and 18.

GET https://csvbase.com/meripaterson/stock-exchanges

HTTP 200
[Captures]
rows: body split "\n"
australia: variable "rows" nth 7 split ","
algeria: variable "rows" nth 18 split ","

Now that we have our row 7 and 18, we can add assert on specifics columns:

GET https://csvbase.com/meripaterson/stock-exchanges

HTTP 200
[Captures]
lines: body split "\n"
australia: variable "lines" nth 7 split ","
algeria: variable "lines" nth 18 split ","
[Asserts]
variable "australia" nth 1 == "Australia & Oceania"
variable "australia" nth 2 == "Australia"
variable "algeria" nth 1 == "Africa"
variable "algeria" nth 2 == "Algeria"

Et voilà Partying Face!

Filters can be used to transform the HTTP response data easily. We have included a first round of filters count, htmlEscape, htmlUnescape, nth, regex, replace, split, toInt, urlDecode and urlEncode. Don’t hesitate to give us some feedbacks on this nice new feature!

Various curl Options

Under the hood, Hurl uses curl for HTTP transfers. We have implemented the following curl options: -E/--cert, --connect-to, --key, --resolve and --ssl-no-revoke. For instance, given this Hurl file hello.hurl:

GET http://foo.com/hello
HTTP 200
`Hello World!`

GET http://bar.com/hello
HTTP 200
`Hello World!`

GET http://baz.com/hello
HTTP 200
`Hello World!`

You can force the resolution of foo.com, bar.com and baz.com to localhost with --connect-to:

$ hurl --connect-to foo.com:80:localhost:8000 \
       --connect-to bar.com:80:localhost:8000 \
       --connect-to baz.com:80:localhost:8000 \
       hello.hurl

Minor Syntax Changes

Astute readers have already spot some minor syntax changes. Instead of HTTP/* for wildcard HTTP version test, you can simply write HTTP now (the first syntax still work but is deprecated)

# New Syntax
GET https://foo.com
HTTP 200

# Deprecated syntax
GET https://foo.com
HTTP/* 200

If you want to add precise tests on HTTP version, you can still use HTTP/1.0, HTTP/1.1 and HTTP/2:

GET https://foo.com
HTTP/2 200

GET https://bar.com
HTTP/1.1 200

GET https://baz.com
HTTP/1.0 200

Thanks to @jmoore34 for this idea, it’s obvious now you see it!

Finally, plain text body have been tweaked, to allow the use of a language hint like graphql or json:

  • Post a CSV:

    POST https://example.com/csv
    ```
    line1,red,1
    line2,green,2
    line3,green,3
    ```
  • Use a GraphQL query

    POST https://example.com/graphql
    ```graphql
    {
      human {
        name
        }
    }
    ```
  • Post a JSON body

    POST https://example.com/json
    ```json
    {
      "name": "toto",
      "age": 18
    }
    ```
  • Post a JSON body without ``` (this shorter syntax is prefered over the previous one)

    POST https://example.com/json
    {
      "name": "toto",
      "age": 18
    }
  • And finally, post a single string without newline:

    POST https://example.com/csv
    `Hello world!`

You may notice that the syntax takes huge inspiration from Markdown and that’s very intentional!

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!