andreas / ocaml-graphql-server

GraphQL servers in OCaml

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Propagating errors along with data

dwwoelfel opened this issue · comments

Is there a way for the resolver functions to propagate errors? Ideally, each resolver could return both data and errors, which would be collected at the top level with information about where the error occurred (https://facebook.github.io/graphql/#sec-Errors).

Right now, we're throwing exceptions from the resolvers, but this means that 1. we can't notify the caller about non-fatal errors, and 2. the graphql stack can't add the path to the error when the error is specific to a certain field.

We'd like to return something like this to the client:

{
  "errors": [
    {
      "message": "Name for character with ID 1002 could not be fetched.",
      "locations": [ { "line": 6, "column": 7 } ],
      "path": [ "hero", "heroFriends", 1, "name" ]
    }
  ],
  "data": {
    "hero": {
      "name": "R2-D2",
      "heroFriends": [
        {
          "id": "1000",
          "name": "Luke Skywalker"
        },
        {
          "id": "1002",
          "name": null
        },
        {
          "id": "1003",
          "name": "Leia Organa"
        }
      ]
    }
  }
}

The story around error handling is indeed lacking right now. As you mention, a resolver should be able indicate an error occurred, and this error should propagate up until a nullable ancestor is found. I'm currently leaning towards an exception-based approach, as I fear enforcing all resolvers to return a result-type is too cumbersome. I imagine it would be somewhat like monitors in Async.

As it stands, is there any way to respond with error messaging other than "Error: Internal Server Error"? Is the only option to create wrapper types that have optional error/value properties? Something like,

Book {
  id: ID!
  title: String!
  author: String!
}

Error {
  code: Int!
  message: String!
}

BookOrError {
  error: Error
  book: Book
}

Just want to confirm before I build all these wrapper types.

@donut If you want a short-term solution (aka a hack!), I would suggest:

  1. Use a map keyed with a unique id per request to store errors.
  2. If an error occurs in a resolve-function, return None and put an error in that map.
  3. In the callback-function to Cohttp.Server.create, add errors to the GraphQL response from the error map and clear out the request id.

Obviously that's not viable long-term. I'm currently torn between multiple solutions for error handling. One approach is to force all resolve-functions to return a result-type. This seems a bit clumsy in the happy case. Another approach is to have an exception handler, so resolve-functions can raise exceptions, which are turned into GraphQL errors (what I alluded to in my previous comment). A final thought is to somehow generalize the value returned from resolve-functions, such that it could be adapted for different use cases (vague, yes).

Is there a pattern in GraphQL to provide strongly typed errors?

Is there a pattern in GraphQL to provide strongly typed errors?

I haven't seen anyone do what @donut describes above, if that's what you mean. I understood it mostly as a bandaid to the missing error handling in ocaml-graphql-server. I think #69 is the "right" approach.

I mean something like result type on the client side where the type of error is an enum/union.

When parsing nullable fields and a null occurs, the client could look at the path property of values in errors. If so, it could yield a value ('a, string option) result instead of 'a option. It's a bit tricky though, since an error might not be present. Alternatively you could use a type such as [`Ok of 'a | `Null | `Error of string].

I don't think so, generally. GraphQL supports partial-success, so all successful data is included under the data key in the response JSON, and errors is more or less free-form. Because there may be multiple errors in different parts of the response tree, community practice seems to be for errors to be an array, each item including a path key that points to the part of the tree where the error occured, and then the rest is free-form (machine-readable errors, or human-facing errors, etc.). I don't think any of this comes under the schema, and there's no standardization at all here.

@andreas thank you, I didn't phrase my questions correctly but @sgrove gave an answer for what I had in my mind.

It's an interesting area to discover because there are potentially different ways you'd like to represent an error returned from the server. Although indeed, there's some complexity to it.

I'm closing this one with the merge of #69.