ghostdogpr / caliban

Functional GraphQL library for Scala

Home Page:https://ghostdogpr.github.io/caliban/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

ZStream.fail from a subscription has wrong GraphQL structure

lachezar opened this issue · comments

Example API:

object ExampleApi {

  case class Queries(test: ZIO[Any, Nothing, String]) // Not important
  case class Mutations(test: UUID => ZIO[Any, Nothing, String]) // Not important
  case class Subscriptions(test: UUID => ZStream[Any, CalibanError, String])

  val api: GraphQL[ExampleService] =
    graphQL[ExampleService, Queries, Mutations, Subscriptions](
      RootResolver(
        Queries(ZIO.succeed("hello")),// Not important
        Mutations(uuid => ZIO.succeed("hello")),// Not important
        Subscriptions(uuid =>
          ZStream.when(uuid == new UUID(0, 0)) {
            ZStream.fail(ExecutionError(s"error $uuid"))
          } ++ ZStream.succeed(s"hello $uuid")
        )
      )
    )
}

When making a subscription from a client with parameter "00000000-0000-0000-0000-00000000001" the result will be { "data": { "test": "hello 00000000-0000-0000-0000-000000000001" } } as expected.

When you pass an invalid uuid the error that is returned has correct structure

{
  "data": {
    "test": null
  },
  "errors": [
    {
      "message": "Can't parse 00000000-0000-0000-0000- into a UUID",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "test"
      ]
    }
  ]
} 

However if you pass as parameter "00000000-0000-0000-0000-00000000000" then the reported error has incorrect format:

{
      "message": "error 00000000-0000-0000-0000-00000000000"
....

The "data": {"test": null }, is missing from the response and the GraphQL client does not accept it.

There is no example of error handling for subscriptions in the example app, and I might be doing something wrong, but I had no luck raising an error in a subscription :(

I don't quite follow, what is the query that is causing the bad error message?

I don't quite follow, what is the query that is causing the bad error message?

The issue happens when the subscription returns with ZStream.fail(...), then the response structure is incorrect.
The example I gave from above just shows three different cases:

  1. Success (ZStream.succeed(...))
  2. Error (due to validation)
  3. Error (due to business logic, e.g. ZStream.fail(...))

The bug happens in case (3) and a query that yields it is

subscription {
  test(value: "00000000-0000-0000-0000-00000000000") 
}

But any subscription that returns ZStream.fail(...) will create the wrong structure described from above.

My goal is to be able to fail from a subscription (e.g. due to external changes, due to invalid authentication, due to database error, etc) and at the moment I can't accomplish this.

From the moment the stream is started, errors will be reported using the graphql websocket protocol, so it is intended that you don't get the usual error.

Now depending on the protocol you are using, it should be something like this https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md#error or like that https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md#gql_error. Is that not the case?

From the moment the stream is started, errors will be reported using the graphql websocket protocol, so it is intended that you don't get the usual error.

Now depending on the protocol you are using, it should be something like this https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md#error or like that https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md#gql_error. Is that not the case?

I did some more tests and I think Caliban handles correctly the errors - both for Websockets and GraphQL-ws protocols. This is the payload that is send when the error happens:

 Websocket  
{"id":"1","type":"error","payload":[{"message":"error 00000000-0000-0000-0000-000000000000","locations":[{"line":12,"column":3}],"path":["test"]}]}

graphql-ws 
{"id":"d1323c1b-97e5-4b7e-9ed1-45b74e3258b3","type":"error","payload":[{"message":"error 00000000-0000-0000-0000-000000000000","locations":[{"line":12,"column":3}],"path":["test"]}]}

I used a GraphQL client called Altair that was presenting these errors in a bad manner, so I got suspicious that something is wrong with Caliban, but it seems that is not the case.

Thanks for sharing the links to the actual specification for GraphQL web sockets, I failed to find them myself 🥲