Search code examples
scalaziocaliban

Is it possible to use GraphQLRequest for a client in Caliban?


I wanted to test my Caliban Http4s Webservice.

In Http4sAdapter it uses GraphQLRequest to model a Request Body.

case class GraphQLRequest(
   query: String,
   operationName: Option[String],
   variables: Option[Map[String, InputValue]])
...
query    <- req.attemptAs[GraphQLRequest].value.absolve
...

So I thought on the client side I could use it as well.

A simple Request works:

 GraphQLRequest("""query{
                         |  characters(origin: EARTH) {
                         |    name
                         |    nicknames
                         |    origin
                         |  }
                         |}""".stripMargin, None, None)

But if I use variables it doesn't:

GraphQLRequest("""query($origin: String){
                 |  characters(origin: $origin) {
                 |    name
                 |    nicknames
                 |    origin
                 |  }
                 |}""".stripMargin, None, Some(Map("origin" -> StringValue("EARTH"))))

It just hangs - there is not even an exception.

I tried with 0.4.2 and 0.5.0.

I added a Gist to show the client code. It uses Circe and Sttp: Client Gist


Solution

  • The main problem can be seen in your gist: the variables encoded into JSON are not what is expected by the server.

    You currently have:

    "origin": {
      "StringValue": {
        "value": "EARTH"
      }
    }
    

    and you should only have:

    "origin": "EARTH"
    

    That value is an InputValue in Caliban, and Caliban provides an Encoder for Circe. However you seem to be using Circe generic auto-derivation which is not picking up Caliban's Encoder but instead tries to derive InputValue on its own, giving an incorrect result.

    I recommend using semi-auto derivation in circe-generic or even using circe-derivation which will pick up Caliban's Encoder properly. Example with circe-generic:

    val req = GraphQLRequest(
      """query($origin: String){
        |  characters(origin: $origin) {
        |    name
        |    nicknames
        |    origin
        |  }
        |}""".stripMargin,
      None,
      Some(Map("origin" -> StringValue("EARTH")))
    )
    
    import io.circe.syntax._
    import io.circe.generic.semiauto._
    implicit val enc = deriveEncoder[GraphQLRequest]
    
    println(req.asJson)
    

    gives the expected JSON:

    {
      "query" : "query($origin: String){\n  characters(origin: $origin) {\n    name\n    nicknames\n    origin\n  }\n}",
      "operationName" : null,
      "variables" : {
        "origin" : "EARTH"
      }
    }