hurl icon indicating copy to clipboard operation
hurl copied to clipboard

GraphQL body type

Open dalejefferson-rnf opened this issue 3 years ago • 2 comments

I would like to use Hurl to test our GraphQL api but I can't find a good pattern to create the requests other than just justing JSON. While JSON does work it's a bit clunky, it would be great if GraphQL was supported natively.

This is an example query

POST https://localhost:8080/graphql
{
  "query": "{person {name age}}"
}

Once you get lots of properties and nesting it gets pretty ugly as you can't put new lines in graphql

POST https://localhost:8080/graphql
{
  "query": "{person {name age friends {name friends {name}}}}"
}

It would be great if GraphQL was a supported body type so we could just do

POST https://localhost:8080/graphql
{
  person {
    name 
     age
   }
}

This would be converted into a valid json object (adding a query key and escaping the new lines)

Using external graphQL files would also be great

POST https://localhost:8080/graphql
graphql,person.graphql

We would also need a way of providing variables

POST https://localhost:8080/graphql
[GraphQLParams]
message: hello
query Echo($message: String) {
  echo(message: $message)
}

And mutations

POST https://localhost:8080/graphql
[GraphQLParams]
thing: hello
mutation UpdateThing($thing: String) {
  updateThing(thing: $thing)
}

dalejefferson-rnf avatar Mar 17 '22 10:03 dalejefferson-rnf

Hi @dalejefferson-rnf

Definitively a good idea to support GraphQL body. We've not very versed in GraphQl, nor heavy user of it so we've a lot of question:

  • how can we support the following GraphQL query:
query HeroComparison($first: Int = 3) {
  leftComparison: hero(episode: EMPIRE) {
    ...comparisonFields
  }
  rightComparison: hero(episode: JEDI) {
    ...comparisonFields
  }
}

fragment comparisonFields on Character {
  name
  friendsConnection(first: $first) {
    totalCount
    edges {
      node {
        name
      }
    }
  }
}

I don't know if fragments are heavily used in GraphQL but this possible Hurl syntax can be tricky to parse:

POST https://localhost:8080/graphql
query HeroComparison($first: Int = 3) {
  leftComparison: hero(episode: EMPIRE) {
    ...comparisonFields
  }
  rightComparison: hero(episode: JEDI) {
    ...comparisonFields
  }
}

fragment comparisonFields on Character {
  name
  friendsConnection(first: $first) {
    totalCount
    edges {
      node {
        name
      }
    }
  }
}

HTTP/* 200

Maybe we can extand, in a first time, our multiline strings:

POST https://localhost:8080/graphql
```graphql
query HeroComparison($first: Int = 3) {
  leftComparison: hero(episode: EMPIRE) {
    ...comparisonFields
  }
  rightComparison: hero(episode: JEDI) {
    ...comparisonFields
  }
}

fragment comparisonFields on Character {
  name
  friendsConnection(first: $first) {
    totalCount
    edges {
      node {
        name
      }
    }
  }
}
```
HTTP/* 200

Whatever is between

```graphql 
xxx 
```

is considered as a GraphQL query.

Note: we could also extend multiline string to support hex and base64 which are only one one line today:

```base64
MIIEowIBAAKCAQEAwgilgLyb/oSShncjuaueb5mgmeOLZ34g9BMoRJdeBo52aA0L
T9KPoHPiOdsLDheAdXci3xwxtV3+bbEEEtChTnoWJwAXJ5wXT/eJdysPlNo2NSRG
USPb5PW8+vhsOeHpJiQZUOWAUJNfMys0l3jwC7fkCrBs20+sCJxb
```
```hex
2d2d2d2d2d424547494e205253412050
2d2d2d2d2d424547494e205253412050
2d2d2d2d2d424547494e205253412050
```
  • should we have a short hand syntax for "simple" GraphQL query:

Given a "simple" GraphQL query (without fragments etc...), do we support this syntax:

POST https://localhost:8080/graphql
{
  person {
    name 
     age
   }
}

Syntax seems natural and there seems to be no problem differentiating with JSON body when we parse it. In detail, we'll have to check error message but it can be doable. Our question: is this kind of "simple" queries common in GraphQL usage ? If yes, we can support graphql xxx in a first time, and support this syntax in a second time.

Regarding variables (GraphQL variables), we should see if we need a specific section or if we could use the graphql :

POST https://localhost:8080/graphql
```graphql
query HeroNameAndFriends($episode: Episode) {
  hero(episode: $episode) {
    name
    friends {
      name
    }
  }
}
{
  "episode": "JEDI"
}
```

jcamiel avatar Mar 17 '22 14:03 jcamiel

Thanks for the prompt response @jcamiel

Yes I see the problem how will you know when the graphQL ends, the multiline string solution works.

Fragments are used lots, but they are used to share code between queries or as shorthand, you can always inline the fragment like below. (I'm not a GraphQL expert there may be other reasons).

In this example it just saved you having to write the body twice, you would likely split this test into two anyway with Hurl.

query HeroComparison($first: Int = 3) {
  leftComparison: hero(episode: EMPIRE) {
      name
    friendsConnection(first: $first) {
      totalCount
      edges {
        node {
          name
        }
      }
    }
    }
    rightComparison: hero(episode: JEDI) {
      name
    friendsConnection(first: $first) {
      totalCount
      edges {
        node {
          name
        }
      }
    }
  }
}

So you could have a limited set of syntax which add a lot of value and use external files for complex queries.

  1. Shorthand syntax (this is optional, you can write this as a named query)
{
   person {
       name
   }
}

2a. With operation query

query getPerson {
    person {
       name
   }
}

2b. With operation mutation

mutation updateThing($thing: String) {
  updateThing(thing: $thing)
}

With 2 you have query|mutation terminated by a closing brace which could be parsed.

I also like the idea of externalising the graphQL files (We generally do this in our client code) so we get full IDE support (Autocompletion), validation, formatting and even testing queries used by the client.

Keeping the variables separate would be valuable for this use-case, you could fire the same query with different variables.

POST https://localhost:8080/graphql
[GraphQLVariables]
first: 10
graphql,heroComparison.graphql

POST https://localhost:8080/graphql
[GraphQLVariables]
first: 5
graphql,heroComparison.graphql

heroComparison.graphql

query HeroComparison($first: Int = 3) {
  leftComparison: hero(episode: EMPIRE) {
    ...comparisonFields
  }
  rightComparison: hero(episode: JEDI) {
    ...comparisonFields
  }
}

fragment comparisonFields on Character {
  name
  friendsConnection(first: $first) {
    totalCount
    edges {
      node {
        name
      }
    }
  }
}

someClientCode.js

import query from 'heroComparison.graphql'

If I was writing tests I would likely use the simple shorthand syntax and inline any variables.

Shorthand

{
    getPerson(name: "Dale") {
       name
   }
}

With operation name and variables. This does not add much value in a test (in my opinion) I would write my production queries like this thou.

query getPerson($name: String) {
    getPerson(name: $name) {
       name
   }
}

dalejefferson-rnf avatar Mar 17 '22 16:03 dalejefferson-rnf

Any update about GraphQL body type support? This is the only feature I am waiting for :)

Thank you for hurl! :heart:

azzamsa avatar Nov 08 '22 16:11 azzamsa

Hi @azzamsa, It's one of the features that we've prioritized for our next version! I'm not an expert on GraphQL so maybe I will ping people in this thread for feedback as soon as we've something working.

jcamiel avatar Nov 08 '22 17:11 jcamiel

@dalejefferson-rnf @azzamsa I would be happy if you could test the GraphQL features and gives me feedback on it. You'll have to be able to build Hurl on the last master (see Building from sources). I can help if you need.

The syntax adopted for GraphQL is an extension of the multiline string body that we already supported.

  • Unnamed query:
POST http://localhost:8000/graphql
```graphql
{
  allFilms {
    films {
      title
      director
      releaseDate
    }
  }
}
```

Any GraphQL queries should be supported without issues.

  • Query with variables:
POST http://localhost:8000/graphql
```graphql
query Person($id: ID!) {
  person(id: $id) {
    name
  }
}

variables {
  "id": "cGVvcGxlOjQ="
}
```

Variables is a JSON object, that can also be templatized with Hurl variables (à la Inception):

POST http://localhost:8000/graphql
[Options]
variable: id=cGVvcGxlOjQ=
```graphql
query Person($id: ID!) {
  person(id: $id) {
    name
  }
}

variables {
  "id": "{{id}}"
}
```

For the moment, we haven't yet addressed injecting GraphQL queries from files but we have a syntax in mind that will be elegant and simple without introducing new concept!

If you can't test it, that's not a problem, we'll release a version soon that will support it and you will be able to test it then.

jcamiel avatar Nov 27 '22 16:11 jcamiel

@jcamiel Very glad finally. It lands on the master!

Currently, my requirement is not complicated. This worked well for me https://github.com/azzamsa/tin/blob/master/tests/tests.hurl

This also worked well:


# meta
POST http://127.0.0.1:8000/graphql

``graphql
mutation {
  createUser (input: {name: "hurl"}) {
		id
		name
		fullName
  }
}
``

HTTP/1.1 200
[Asserts]
status == 200
jsonpath "$.data.createUser.name" == "hurl"

It is very hard to read without syntax highlighting, so I made a simple one https://github.com/azzamsa/emacs-hurl

Thanks a lot for Hurl!

azzamsa avatar Dec 14 '22 18:12 azzamsa

Thanks @azzamsa for the feedback, We'll try to improve Hurl tooling support in 2023. Don't hesitate to make a PR on Hurl with your first draft of emacs support, we have a folder for various editor with basic support here https://github.com/Orange-OpenSource/hurl/tree/master/contrib (see also CONTRIBUTING.md)

jcamiel avatar Dec 14 '22 21:12 jcamiel

We'll try to improve Hurl tooling support in 2023.

Glad to hear it.

I have just basic knowledge of building emacs mode for syntax #1105

azzamsa avatar Dec 16 '22 11:12 azzamsa

👋 So happy this is live now but I think I've found a bug and was curious if others are seeing this as well

https://github.com/Orange-OpenSource/hurl/issues/1218

My server is not able to process requests with hurls encoding of variables (a json object encoded within a string). Nased on graphql.org docs it looks like other servers might not was well

softprops avatar Jan 27 '23 22:01 softprops