idi527
How to get the error message from absinthe
Right now i get an unnecessary (to me) In field "signUp": in front of the actual message:
{
"errors": [
{
"message": "In field \"signUp\": should be at least 3 character(s)",
...
"key": "username"
},
{
"message": "In field \"signUp\": should be at least 6 character(s)",
...
"key": "password"
}
]
...
}
But i would like to just get "should be at least 6 character(s)" in the "message" fields. Is this possible? Or is there some other way to access this message?
Marked As Solved
benwilson512
I wonder if there is a shorter solution which doesn’t require defining a new type.
Unfortunately no not at the moment. The reason returning list_of(:valication_error) didn’t work is that list_of is really a type modifier as far as GraphQL is concerned, rather than a proper generic type of the form List<T>. Practically speaking I think the reason is that any given key that is requested should always be a list of a type or a regular type, but never sometimes one and sometimes the other.
We are looking to perhaps support some schema tools that would make this pattern less verbose and more first class, but at the moment I believe what you have here is the best for now.
Also Liked
benwilson512
Hey there! Your solution is definitely one approach.
By way of analogy, you can kind of think of GraphQL errors like exceptions in a regular programming language. They aren’t return values from the field, but rather field execution errors that bubble up to the top.
For errors like you’re trying to expose there’s another pattern that uses a union type to represent that a field may return validation errors. Here’s an example:
union :signup_result do
types [:validation_error, :user]
resolve_type fn
%User{} -> :user
_ -> :validation_error
end
end
mutation do
field :signup, :signup_result do
end
end
And here’s how you’d use this in a document:
mutation SignUp {
signUp {
... on ValidationError {
__typename
field
message
}
... on Success {
# stuff
}
}
}
This is a lot like the difference between Elixir functions that return {:ok, result} | {:error, reason} and functions that simply return the result or raise.
idi527
I’ve replaced list_of(:validation_error) with :validation_errors
union :validation_result do
types [:validation_errors, :session]
resolve_type fn
%{token: _, user: _}, _ ->
:session
_, _ ->
:validation_errors
end
end
added validation_errors type
object :validation_errors do
field :errors, list_of(:validation_error)
end
and updated session.ex to return %{errors: ...} map
def sign_in(_parent, %{username: username, password: password}, _info) do
case verify(username, password) do
{:ok, token, user} ->
{:ok, %{token: token, user: user}}
{:error, _msg} ->
{:ok, %{errors: [%{message: "incorrect login credentials", field: :password}]}}
end
end
def sign_up(_parent, user_params, _info) do
case Store.Users.insert(user_params) do
{:ok, user} ->
token = Phoenix.Token.sign(Web.Endpoint, "user", user.id)
{:ok, %{token: token, user: user}}
{:error, changeset} ->
{:ok, %{errors: translate(changeset.errors)}}
end
end
and now it works. The document I send now is
mutation SignUp($username: String!, $password: String!) {
signUp(username: $username, password: $password) {
... on Session {
token
user {
id
username
}
}
... on ValidationErrors {
errors {
field
message
}
}
}
}
I wonder if there is a shorter solution which doesn’t require defining a new type.
idi527
I’ve decided to add an additional "error" field to put the error message in.
{
"errors": [
{
"message": "In field \"signUp\": should be at least 3 character(s)",
...
"key": "username",
"error": "should be at least 3 character(s)"
},
{
"message": "In field \"signUp\": should be at least 6 character(s)",
...
"key": "password",
"error": "should be at least 6 character(s)"
}
]
...
}
idi527
I’ve tried the union operator but whenever I supply an invalid username/password pair so that to get ValidationError I keep getting this error instead
[error] #PID<0.726.0> running Web.Endpoint terminated
Server: localhost:4000 (http)
Request: POST /api
** (exit) an exception was raised:
** (CaseClauseError) no case clause matching: %Absinthe.Type.List{of_type: :validation_error}
(absinthe) lib/absinthe/type/union.ex:91: Absinthe.Type.Union.resolve_type/3
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:389: Absinthe.Phase.Document.Execution.Resolution.passes_type_condition?/4
(elixir) lib/enum.ex:2718: Enum.do_all?/2
(elixir) lib/enum.ex:814: anonymous fn/3 in Enum.filter/2
(elixir) lib/enum.ex:1755: Enum."-reduce/3-lists^foldl/2-0-"/3
(elixir) lib/enum.ex:814: Enum.filter/2
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:174: Absinthe.Phase.Document.Execution.Resolution.resolve_fields/4
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:133: Absinthe.Phase.Document.Execution.Resolution.walk_result/5
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:186: Absinthe.Phase.Document.Execution.Resolution.do_resolve_fields/5
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:133: Absinthe.Phase.Document.Execution.Resolution.walk_result/5
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:101: Absinthe.Phase.Document.Execution.Resolution.perform_resolution/3
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:78: Absinthe.Phase.Document.Execution.Resolution.resolve_current/3
(absinthe) lib/absinthe/pipeline.ex:204: Absinthe.Pipeline.run_phase/3
(absinthe_plug) lib/absinthe/plug.ex:158: Absinthe.Plug.execute/2
(absinthe_plug) lib/absinthe/plug.ex:125: Absinthe.Plug.call/2
(phoenix) lib/phoenix/router/route.ex:161: Phoenix.Router.Route.forward/4
(phoenix) lib/phoenix/router.ex:277: Phoenix.Router.__call__/1
(web) lib/web/endpoint.ex:1: Web.Endpoint.plug_builder_call/2
(web) lib/plug/debugger.ex:123: Web.Endpoint."call (overridable 3)"/2
(web) lib/web/endpoint.ex:1: Web.Endpoint.call/2
Here’s my schema.ex
union :validation_result do
types [list_of(:validation_error), :session]
resolve_type fn
%{token: _, user: _}, _ ->
:session
_, _ ->
list_of(:validation_error)
end
end
mutation do
field :sign_up, type: :validation_result do
arg :username, non_null(:string)
arg :password, non_null(:string)
resolve &Session.sign_up/3
end
field :sign_in, type: :validation_result do
arg :username, non_null(:string)
arg :password, non_null(:string)
resolve &Session.sign_in/3
end
end
schema/types.ex
object :user do
field :id, :id
field :username, :string
end
object :session do
field :token, :string
field :user, :user
end
object :validation_error do
field :field, :string
field :message, :string
end
and resolver/session.ex
defmodule Web.Resolver.Session do
import Comeonin.Bcrypt, only: [checkpw: 2, dummy_checkpw: 0]
import Web.ErrorHelpers, only: [translate_error: 1]
def sign_in(_parent, %{username: username, password: password}, _info) do
case verify(username, password) do
{:ok, token, user} ->
{:ok, %{token: token, user: user}}
{:error, _msg} ->
{:ok, [%{message: "incorrect login credentials", field: :password}]}
end
end
def sign_up(_parent, user_params, _info) do
case Store.Users.insert(user_params) do
{:ok, user} ->
token = Phoenix.Token.sign(Web.Endpoint, "user", user.id)
{:ok, %{token: token, user: user}}
{:error, changeset} ->
{:ok, translate(changeset.errors)}
end
end
defp verify(username, given_password) do
user = Store.Users.get_by(username: username)
cond do
user && checkpw(given_password, user.password_hash) ->
token = Phoenix.Token.sign(Web.Endpoint, "user", user.id)
{:ok, token, user}
user ->
{:error, :unauthorized}
true ->
dummy_checkpw()
{:error, :not_found}
end
end
defp translate(errors) do
Enum.map(errors, fn {field, error} ->
%{field: field, message: translate_error(error)}
end)
end
end
and the document I send
mutation SignUp($username: String!, $password: String!) {
signUp(username: $username, password: $password) {
... on Session {
token
user {
id
username
}
}
... on ValidationError {
field
message
}
}
}
benwilson512
Hey! You may be confused about what gets passed to the resolve_type function.
You should just be doing:
resolve_type fn
%Order{}, _ -> :order
%{error: _}, _ -> :error
end







