vt-elixir/ja_serializer

issue rendering some ecto changeset errors

Closed this issue · 2 comments

Hi, i'm doing some uniqueness validations using Changeset.unsafe_validate_unique with the following code, but ja_serializer is throwing me an error when parsing that error.

error:

** (ArgumentError) cannot convert the given list to a string.

To be converted to a string, a list must contain only:

  * strings
  * integers representing Unicode codepoints
  * or a list containing one of these three elements

Please check the given list or call inspect/1 to get the list representation, got:

[:email]

code: conn = post(conn, api_registration_path(conn, :create), payload)
stacktrace:
  (elixir) lib/list.ex:821: List.to_string/1
  (ja_serializer) lib/ja_serializer/ecto_error_serializer.ex:48: anonymous fn/2 in JaSerializer.EctoErrorSerializer.format_each/2
  (elixir) lib/enum.ex:1899: Enum."-reduce/3-lists^foldl/2-0-"/3
  (ja_serializer) lib/ja_serializer/ecto_error_serializer.ex:45: JaSerializer.EctoErrorSerializer.format_each/2
  (elixir) lib/enum.ex:1294: Enum."-map/2-lists^map/1-0-"/2
  (ja_serializer) lib/ja_serializer/ecto_error_serializer.ex:37: JaSerializer.EctoErrorSerializer.format/2
  (phoenix) lib/phoenix/view.ex:332: Phoenix.View.render_to_iodata/3
  (phoenix) lib/phoenix/controller.ex:740: Phoenix.Controller.do_render/4
  (twd) lib/twd_web/controllers/coherence/registration_controller.ex:1: TwdWeb.Coherence.RegistrationController.action/2
  (twd) lib/twd_web/controllers/coherence/registration_controller.ex:1: TwdWeb.Coherence.RegistrationController.phoenix_controller_pipeline/2
  (twd) lib/twd_web/endpoint.ex:1: TwdWeb.Endpoint.instrument/4
  (phoenix) lib/phoenix/router.ex:278: Phoenix.Router.__call__/1
  (twd) lib/twd_web/endpoint.ex:1: TwdWeb.Endpoint.plug_builder_call/2
  (twd) lib/twd_web/endpoint.ex:1: TwdWeb.Endpoint.call/2
  (phoenix) lib/phoenix/test/conn_test.ex:224: Phoenix.ConnTest.dispatch/5
  test/controllers/coherence/registration_controller_test.exs:337: (test)

and this is my code

def changeset(model, params \\ %{}) do
  model
  |> cast(params, @allowed_fields)
  |> validate_required(@required_fields)
  |> update_change(:email, &String.downcase/1)
  |> validate_format(:email, ~r/@/)
  |> unsafe_validate_unique([:email], Repo)
  |> unique_constraint(:email)
end

# controller code
%User{}
|> User.changeset(params)
|> Registration.register_new_user(plan)
|> case do
  {:ok, user} ->
    ...
  {:error, %Ecto.Changeset{} = changeset} ->
    conn
    |> put_status(:unprocessable_entity)
    |> render(TwdWeb.ChangesetView, "error.json-api", changeset: changeset)
end

# TwdWeb.ChangesetView
def render("error.json-api", %{changeset: changeset}) do
  JaSerializer.EctoErrorSerializer.format(changeset)
end

this is the changeset error that the unsafe_validate_unique returns

{:error,
 #Ecto.Changeset<
   action: nil,
   changes: %{
     email: "test@test.com",
     lastname: "lastname",
     name: "name",
     password: "rubbish",
     password_confirmation: "rubbish",
     password_hash: "$2b$12$WH2wtNjndDbVd0m5Qpv/rucozXigSvDfawm3d0VC0fRUIGyrctLrS",
     stripe_plan_id: "mochilero",
     username: "test"
   },
   errors: [
     email: {"has already been taken",
      [validation: :unsafe_unique, fields: [:email]]}
   ],
   data: #Twd.User<>,
   valid?: false
 >}

while doing some debugging i found that the line who throws that error is https://github.com/vt-elixir/ja_serializer/blob/master/lib/ja_serializer/ecto_error_serializer.ex#L47
who is being called with {:email, {"has already been taken", [validation: :unsafe_unique, fields: [:email]]}} BUT a normal changelog error, like the one presence validation returns is in the following format {:email, {"can't be blank", [validation: :required]}} let me know if i was clear or need more info.

EDIT: i think the error is when trying to format fields: [:email], adding :fields -> acc to the case seems to work. let me know if i can make a PR with that solution

title = Enum.reduce(vals, message, fn {key, value}, acc ->
  case key do
    :type -> acc
    :fields -> acc
    _ -> String.replace(acc, "%{#{key}}", to_string(value))
  end
end)

thanks

@sescobb27 this would be helpful to have a PR for. I'm also running into the issue that unique validations are causing errors when serialized.

@rtablada done, see #276