sikanhe / reason-graphql

GraphQL server in pure Reason (Bucklescript)

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Robust error type

leeor opened this issue · comments

I am now working through an issue where I need to return an error from a resolve() function that is more robust than a simple string.

TL;DR: Errors returned from resolve functions are converted to GraphQL errors, but sometimes, we need to turn them into transfer protocol errors, e.g., HTTP 401 responses.

As an example use case, consider an API that has a set of mutations and queries, some of which are publicly accessible while others require to be called by an authenticated user.
These latter APIs require the ability to return an error to the client to signal that authentication is required. However, the execute layer cannot return an error that is not a string.

I can hack it by adding a facility to the context which will record such errors, but I think a more direct solution should be available.

Looking at the code, the error case of the resolve function is bound by the way errors are converted to a Map in errorResponse().

I'm interested in your opinion on the matter, and whether you already have any plans to address it or an existing approach you are using.

Depend on the use case. Using HTTP status code is mostly an anti-pattern for GraphQL. A query should almost always return 200. GraphQL Error should also be reserved for exceptional cases. With the power of types you no longer need to use a long list of numbers to tell frontend what to do.

Use Case 1): If the error is non exceptional, meaning that an error can be one of many response types for a mutation

In this case, then the graphql resolve function should return either an Object containing optional data and error, or a Union type of possible response types (Sort of like Result). Then we can handle all the cases on the frontend.

object Mutation {
   login(username: String, password: String): LoginResult
}

union LoginResult = 
   | LoginSuccess
   | LoginFailed

object LoginSuccess = {
   authToken: String,
   redirectTo: String 
}

object LoginFailed = [
   error: String // this can be further strengthened by using an enum type
}

On the frontend you can simply ask for the type and show user error based on the return type:

client.mutate().then(response => {
  if (response.data.typename === "LoginSuccess") {
    localStorage.setItem("authtoken", response.data.authToken); 
    router.redirect(response.data.redirectTo);
 } else {
   this.setState({ showRrror: response.data.error })
 }
})
  1. If the usecase is exceptional, (not in the normal user flow, ie. a malicious user is trying to access fields that are unauthorized), then you should raise a graphql Error or return Null for that field. We basically should not be "optimizing" the UX for these users anyway.

Thank you, I'll do that.