Absinthe: Feature: Complex errors

Created on 5 Nov 2016  路  13Comments  路  Source: absinthe-graphql/absinthe

The spec (http://facebook.github.io/graphql/#sec-Errors) defines errors as a map with a mandatory "message" key, but other keys are fine.

All that is passed as {:error} by a resolver gets converted to a char, as seen by the protocol String.Chars not implemented error when you pass something else.

One should be able to pass both a map or a list of maps, like this:

{:error, %{message: "msg", code: "error_code"}

{:error, [ %{message: "err1", ...}, %{message: ....} ] }

This is a really crucial feature for me for proper error reporting.

Also, I have no idea how to handle changeset errors, because passing either {:ok, changeset} or {:error, changeset} with invalid changesets gives be the same string.chars not implemented error.

It would be trivial to write a little converter if errors as mentioned above would be accepted.

As a workaround I will create some Error struct and implement the String.Chars protocol, but I think this should be supported out of the box.

Starter WIP

Most helpful comment

Changesets are hard to encode into JSON. I tried to achieve this before, but, errors is considering for developers to debug the application (since it's lack of i18n, it's useless for end users).

Here is my solution:

def response({status, payload}) do
    case payload do
      %Ecto.Changeset{} = changeset ->
        {
          status,
          message: "Validation failed.",
          changeset: %{
            errors: changeset
            |> Ecto.Changeset.traverse_errors(fn
              {msg, opts} -> String.replace(msg, "%{count}", to_string(opts[:count]))
              msg -> msg
            end),
            action: changeset.action
          }
        }
      _ -> {status, payload}
    end
  end

and in resolvers:
```
def create(%{blog: blog_params}, _) do
%Blog{}
|> Blog.changeset(blog_params)
|> Repo.insert
|> response
end

def update(%{id: id, blog: blog_params}, _) do
case Repo.get(Blog, id) do
nil -> {:error, "Blog with id #{id} not found."}
blog ->
blog
|> Repo.preload(:variants)
|> Blog.changeset(blog_params)
|> Repo.update
|> response
end
```

The custom attributes returning to end-users is supported on master branch of absinthe.
Hope that can help you. 馃嵒

All 13 comments

We have no problem supporting "more complex" errors, provided they don't break spec, and are always open to well-structured PRs (with tests, especially) that we can have discussions around.

I created a PR for that: https://github.com/absinthe-graphql/absinthe/pull/201
Please give any feedback to make it acceptable for merge.

Closed by #201, will be part of the next release.

Reopening this -- as @tlvenn pointed out to me on Slack, the "multiple" errors version of this isn't implemented.

@benwilson512 Do you have any strong (execution-related) reservations about resolutions returning multiple errors? There doesn't appear to be any spec-level restrictions.

Changesets are hard to encode into JSON. I tried to achieve this before, but, errors is considering for developers to debug the application (since it's lack of i18n, it's useless for end users).

Here is my solution:

def response({status, payload}) do
    case payload do
      %Ecto.Changeset{} = changeset ->
        {
          status,
          message: "Validation failed.",
          changeset: %{
            errors: changeset
            |> Ecto.Changeset.traverse_errors(fn
              {msg, opts} -> String.replace(msg, "%{count}", to_string(opts[:count]))
              msg -> msg
            end),
            action: changeset.action
          }
        }
      _ -> {status, payload}
    end
  end

and in resolvers:
```
def create(%{blog: blog_params}, _) do
%Blog{}
|> Blog.changeset(blog_params)
|> Repo.insert
|> response
end

def update(%{id: id, blog: blog_params}, _) do
case Repo.get(Blog, id) do
nil -> {:error, "Blog with id #{id} not found."}
blog ->
blog
|> Repo.preload(:variants)
|> Blog.changeset(blog_params)
|> Repo.update
|> response
end
```

The custom attributes returning to end-users is supported on master branch of absinthe.
Hope that can help you. 馃嵒

@quangbuule yes, it is (and out in the latest release) -- we're discussing whether or not multiple errors should be allowed to be returned from a field resolver. BTW, if you'd like to discuss Ecto changeset errors in more detail, https://github.com/absinthe-graphql/absinthe_ecto/issues would be a great place to do it. That's where all the Ecto-specific stuff will go.

Multiple errors are being handled in #248.

To send back changeset errors, I assume it would be best to rely on Phoenix's generated web/views/changeset_view.ex?

{:error, Map.merge(%{
  message: "Validation failed."
}, App.ChangesetView.render("error.json", %{changeset: changeset}))}

Or change web/views/changeset_view.ex to include the message by default:

def render("error.json", %{changeset: changeset}) do
  %{message: "Validation failed.", errors: translate_errors(changeset)}
end

In the resolver:

{:error, App.ChangesetView.render("error.json", %{changeset: changeset})}

@clessg We don't recommend using Phoenix views for formatting changesets, although I suppose it may work -- in the past we've handled this with a function, and something more comprehensive is planned for the absinthe_ecto project. Ideas are probably best put into its Issues: https://github.com/absinthe-graphql/absinthe_ecto/issues -- my guess is that comprehensive Ecto changeset error handling may rely on the resolution middleware work that @benwilson512 is completing.

Multiple errors supported by #248, now merged into master, will be released as part of v1.2.4.

Thanks @bruce !

@quangbuule i'm dealing with changeset encoding issue and your solution works like charm, saved my day, thanks 馃憤

Was this page helpful?
0 / 5 - 0 ratings

Related issues

kenips picture kenips  路  4Comments

rawkode picture rawkode  路  4Comments

tlvenn picture tlvenn  路  4Comments

ivawzh picture ivawzh  路  3Comments

bruce picture bruce  路  4Comments