kronky
kronky copied to clipboard
Getting BadMapError when Ecto.Changeset error is a unique constraint
Here is the stack trace:
Request: POST /api/graphiql
** (exit) an exception was raised:
** (BadMapError) expected a map, got: {:constraint, :unique}
(elixir) lib/map.ex:437: Map.get({:constraint, :unique}, :key, nil)
(absinthe) lib/absinthe/middleware/map_get.ex:9: Absinthe.Middleware.MapGet.call/2
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:209: Absinthe.Phase.Document.Execution.Resolution.reduce_resolution/1
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:168: Absinthe.Phase.Document.Execution.Resolution.do_resolve_field/4
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:153: Absinthe.Phase.Document.Execution.Resolution.do_resolve_fields/6
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:72: Absinthe.Phase.Document.Execution.Resolution.walk_result/5
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:98: Absinthe.Phase.Document.Execution.Resolution.walk_results/6
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:87: Absinthe.Phase.Document.Execution.Resolution.walk_result/5
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:257: Absinthe.Phase.Document.Execution.Resolution.build_result/4
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:153: Absinthe.Phase.Document.Execution.Resolution.do_resolve_fields/6
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:72: Absinthe.Phase.Document.Execution.Resolution.walk_result/5
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:98: Absinthe.Phase.Document.Execution.Resolution.walk_results/6
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:87: Absinthe.Phase.Document.Execution.Resolution.walk_result/5
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:257: Absinthe.Phase.Document.Execution.Resolution.build_result/4
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:153: Absinthe.Phase.Document.Execution.Resolution.do_resolve_fields/6
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:72: Absinthe.Phase.Document.Execution.Resolution.walk_result/5
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:257: Absinthe.Phase.Document.Execution.Resolution.build_result/4
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:153: Absinthe.Phase.Document.Execution.Resolution.do_resolve_fields/6
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:72: Absinthe.Phase.Document.Execution.Resolution.walk_result/5
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:53: Absinthe.Phase.Document.Execution.Resolution.perform_resolution/3
** (EXIT from #PID<0.448.0>) shell process exited with reason: an exception was raised:
** (ErlangError) Erlang error: {{%BadMapError{term: {:constraint, :unique}}, [{Map, :get, [{:constraint, :unique}, :key, nil], [file: 'lib/map.ex', line: 437]}, {Absinthe.Middleware.MapGet, :call, 2, [file: 'lib/absinthe/middleware/map_get.ex', line: 9]}, {Absinthe.Phase.Document.Execution.Resolution, :reduce_resolution, 1, [file: 'lib/absinthe/phase/document/execution/resolution.ex', line: 209]}, {Absinthe.Phase.Document.Execution.Resolution, :do_resolve_field, 4, [file: 'lib/absinthe/phase/document/execution/resolution.ex', line: 168]}, {Absinthe.Phase.Document.Execution.Resolution, :do_resolve_fields, 6, [file: 'lib/absinthe/phase/document/execution/resolution.ex', line: 153]}, {Absinthe.Phase.Document.Execution.Resolution, :walk_result, 5, [file: 'lib/absinthe/phase/document/execution/resolution.ex', line: 72]}, {Absinthe.Phase.Document.Execution.Resolution, :walk_results, 6, [file: 'lib/absinthe/phase/document/execution/resolution.ex', line: 98]}, {Absinthe.Phase.Document.Execution.Resolution, :walk_result, 5, [file: 'lib/absinthe/phase/document/execution/resolution.ex', line: 87]}, {Absinthe.Phase.Document.Execution.Resolution, :build_result, 4, [file: 'lib/absinthe/phase/document/execution/resolution.ex', line: 257]}, {Absinthe.Phase.Document.Execution.Resolution, :do_resolve_fields, 6, [file: 'lib/absinthe/phase/document/execution/resolution.ex', line: 153]}, {Absinthe.Phase.Document.Execution.Resolution, :walk_result, 5, [file: 'lib/absinthe/phase/document/execution/resolution.ex', line: 72]}, {Absinthe.Phase.Document.Execution.Resolution, :walk_results, 6, [file: 'lib/absinthe/phase/document/execution/resolution.ex', line: 98]}, {Absinthe.Phase.Document.Execution.Resolution, :walk_result, 5, [file: 'lib/absinthe/phase/document/execution/resolution.ex', line: 87]}, {Absinthe.Phase.Document.Execution.Resolution, :build_result, 4, [file: 'lib/absinthe/phase/document/execution/resolution.ex', line: 257]}, {Absinthe.Phase.Document.Execution.Resolution, :do_resolve_fields, 6, [file: 'lib/absinthe/phase/document/execution/resolution.ex', line: 153]}, {Absinthe.Phase.Document.Execution.Resolution, :walk_result, 5, [file: 'lib/absinthe/phase/document/execution/resolution.ex', line: 72]}, {Absinthe.Phase.Document.Execution.Resolution, :build_result, 4, [file: 'lib/absinthe/phase/document/execution/resolution.ex', line: 257]}, {Absinthe.Phase.Document.Execution.Resolution, :do_resolve_fields, 6, [file: 'lib/absinthe/phase/document/execution/resolution.ex', line: 153]}, {Absinthe.Phase.Document.Execution.Resolution, :walk_result, 5, [file: 'lib/absinthe/phase/document/execution/resolution.ex', line: 72]}, {Absinthe.Phase.Document.Execution.Resolution, :perform_resolution, 3, [file: 'lib/absinthe/phase/document/execution/resolution.ex', line: 53]}]}, {MyAppWeb.Endpoint, :call, [%Plug.Conn{adapter: {Plug.Cowboy.Conn, :...}, assigns: %{}, before_send: [], body_params: %Plug.Conn.Unfetched{aspect: :body_params}, cookies: %Plug.Conn.Unfetched{aspect: :cookies}, halted: false, host: "localhost", method: "POST", owner: #PID<0.448.0>, params: %Plug.Conn.Unfetched{aspect: :params}, path_info: ["api", "graphiql"], path_params: %{}, port: 4000, private: %{}, query_params: %Plug.Conn.Unfetched{aspect: :query_params}, query_string: "", remote_ip: {127, 0, 0, 1}, req_cookies: %Plug.Conn.Unfetched{aspect: :cookies}, req_headers: [{"accept", "application/json"}, {"accept-encoding", "gzip, deflate, br"}, {"accept-language", "en-US,en;q=0.9"}, {"connection", "keep-alive"}, {"content-length", "583"}, {"content-type", "application/json"}, {"host", "localhost:4000"}, {"origin", "http://localhost:4000"}, {"referer", "http://localhost:4000/api/graphiql"}, {"user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.108 Safari/537.36"}], request_path: "/api/graphiql", resp_body: nil, resp_cookies: %{}, resp_headers: [{"cache-control", "max-age=0, private, must-revalidate"}], scheme: :http, script_name: [], secret_key_base: nil, state: :unset, status: nil}, []]}}
(phoenix) lib/phoenix/endpoint/cowboy2_handler.ex:42: Phoenix.Endpoint.Cowboy2Handler.init/2
(cowboy) /Users/terence/Workspace/myapp/deps/cowboy/src/cowboy_handler.erl:41: :cowboy_handler.execute/2
(cowboy) /Users/terence/Workspace/myapp/deps/cowboy/src/cowboy_stream_h.erl:296: :cowboy_stream_h.execute/3
(cowboy) /Users/terence/Workspace/myapp/deps/cowboy/src/cowboy_stream_h.erl:274: :cowboy_stream_h.request_process/3
(stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
I was able to make this package work properly on all Ecto.Changeset validations, but for some reason, it keeps getting this exception when it fails the unique constraint check.
Here is the schema:
defmodule MyApp.Authentication.User do
@moduledoc """
User schema
"""
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :email, :string
field :first_name, :string
field :last_name, :string
field :password, :string, virtual: true
field :password_hash, :string
timestamps()
end
@doc false
def registration_changeset(user, attrs) do
user
|> cast(attrs, [:email, :password, :first_name, :last_name])
|> validate_required([:email, :password])
|> validate_format(:email, ~r/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/)
|> unique_constraint(:email)
|> validate_length(:password, min: 8, max: 30)
|> validate_confirmation(:password)
|> hash_password()
end
defp hash_password(%Ecto.Changeset{valid?: true, changes: %{password: pass}} = changeset) do
put_change(changeset, :password_hash, Pbkdf2.hash_pwd_salt(pass))
end
defp hash_password(changeset), do: changeset
end
This is the schema:
defmodule MyAppWeb.Schema.AuthenticationTypes do
use Absinthe.Schema.Notation
import Kronky.Payload
alias MyAppWeb.Resolvers.Authentication
object :user, description: "A user" do
field :id, :id, description: "Unique identifier"
field :email, :string, description: "User's email address"
field :first_name, :string, description: "User's first name"
field :last_name, :string, description: "User's last name"
field :inserted_at, :naive_datetime, description: "Date and time when user was created"
end
object :session, description: "Details for the user's session" do
field :authentication_token, :string, description: "Unique session token to be used for subsequent requests"
field :user, :user, description: "User payload"
end
input_object :user_sign_up_input, description: "Parameters for user registration" do
field :email, non_null(:string), description: "Email address of the user"
field :password, non_null(:string), description: "Hard password that should be at least 8 characters"
field :password_confirmation, non_null(:string), description: "Repeat the password to ensure correctness"
field :first_name, :string, description: "Optional first name"
field :last_name, :string, description: "Optional last name"
end
payload_object(:session_payload, :session)
object :authentication_mutations do
field :sign_up, :session_payload, description: "Registers a new user" do
arg :input, non_null(:user_sign_up_input)
resolve &Authentication.sign_up/3
middleware &build_payload/2
end
end
end
This is the resolver:
defmodule MyAppWeb.Resolvers.Authentication do
alias MyApp.Authentication
alias MyApp.Authentication.User
alias MyApp.Guardian
def sign_up(_parent, args, _resolution) do
case Authentication.create_user(args[:input]) do
{:ok, %User{} = user} ->
{:ok, token, _claims} = Guardian.encode_and_sign(user)
{:ok, %{authentication_token: token, user: user}}
{:error, %Ecto.Changeset{} = changeset} ->
{:ok, changeset}
end
end
end
Am I supposed to be doing something to not get that error on a unique constraint or is this something that's not addresses by this package?