Skip to content

Commit

Permalink
improvement: properly return nested errors with for_path: :all
Browse files Browse the repository at this point in the history
  • Loading branch information
zachdaniel committed Nov 28, 2023
1 parent bace1b1 commit c292848
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 69 deletions.
93 changes: 52 additions & 41 deletions lib/ash_phoenix/form/form.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1634,8 +1634,6 @@ defmodule AshPhoenix.Form do
before_submit = opts[:before_submit] || (& &1)

if form.valid? || opts[:force?] do
form = clear_errors(form)

unless form.api do
raise """
No Api configured, but one is required to submit the form.
Expand Down Expand Up @@ -1732,7 +1730,7 @@ defmodule AshPhoenix.Form do
if opts[:raise?] do
raise Ash.Error.to_error_class(query.errors, query: query)
else
query = %{(query || original_changeset_or_query) | errors: []}
query = query || original_changeset_or_query

errors =
error
Expand All @@ -1754,7 +1752,7 @@ defmodule AshPhoenix.Form do
if opts[:raise?] do
raise Ash.Error.to_error_class(changeset.errors, changeset: changeset)
else
changeset = %{(changeset || original_changeset_or_query) | errors: []}
changeset = changeset || original_changeset_or_query

errors =
error
Expand Down Expand Up @@ -2137,20 +2135,16 @@ defmodule AshPhoenix.Form do
gather_errors(form, opts[:format])

[] ->
errors =
if form.errors do
if form.just_submitted? do
form.submit_errors
else
transform_errors(form, form.source.errors, [], form.form_keys)
end
else
[]
end
if form.errors do
errors =
transform_errors(form, form.source.errors, [], form.form_keys)

errors
|> List.wrap()
|> format_errors(opts[:format])
errors
|> List.wrap()
|> format_errors(opts[:format])
else
[]
end

path ->
form
Expand Down Expand Up @@ -2199,12 +2193,42 @@ defmodule AshPhoenix.Form do
forms when is_list(forms) ->
forms
|> Enum.with_index()
|> Enum.reduce(acc, fn {form, i}, acc ->
gather_errors(form, format, acc, trail ++ [key, i])
|> Enum.reduce(acc, fn {nested_form, i}, acc ->
nested_errors =
form.source.errors
|> Enum.filter(&List.starts_with?(&1.path || [], [key, i]))
|> Enum.map(fn form ->
%{form | path: Enum.drop(form.path, 1)}
end)

nested_form = %{
nested_form
| source: %{
nested_form.source
| errors: Enum.uniq(nested_errors ++ (nested_form.source.errors || []))
}
}

gather_errors(nested_form, format, acc, trail ++ [key, i])
end)

form ->
gather_errors(form, format, acc, trail ++ [key])
nested_form ->
nested_errors =
form.source.errors
|> Enum.filter(&List.starts_with?(&1.path || [], [key]))
|> Enum.map(fn form ->
%{form | path: Enum.drop(form.path, 1)}
end)

nested_form = %{
nested_form
| source: %{
nested_form.source
| errors: Enum.uniq(nested_errors ++ (nested_form.source.errors || []))
}
}

gather_errors(nested_form, format, acc, trail ++ [key])
end
end)
end
Expand Down Expand Up @@ -3575,26 +3599,6 @@ defmodule AshPhoenix.Form do

defp expand_error(other), do: List.wrap(other)

defp clear_errors(nil), do: nil

defp clear_errors(forms) when is_list(forms) do
Enum.map(forms, &clear_errors/1)
end

defp clear_errors(form) do
%{
form
| forms:
Map.new(form.forms, fn {k, v} ->
{k, clear_errors(v)}
end),
source: %{
form.source
| errors: []
}
}
end

@doc """
A utility for parsing paths of nested forms in query encoded format.
Expand Down Expand Up @@ -4619,6 +4623,13 @@ defmodule AshPhoenix.Form do
end

@impl true
@spec input_type(AshPhoenix.Form.t(), any(), atom() | binary()) ::
:checkbox
| :date_select
| :datetime_select
| :number_input
| :text_input
| :time_select
def input_type(%{resource: resource, action: action}, _, field) do
attribute = Ash.Resource.Info.attribute(resource, field)

Expand Down
19 changes: 18 additions & 1 deletion lib/ash_phoenix/form_data/helpers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,24 @@ defmodule AshPhoenix.FormData.Helpers do
end)
end

defp unwrap_errors(errors) do
Enum.flat_map(errors, &unwrap_error/1)
end

defp unwrap_error(%class{errors: errors})
when class in [
Ash.Error.Invalid,
Ash.Error.Forbidden,
Ash.Error.Unknown,
Ash.Error.Framework
],
do: unwrap_errors(errors)

defp unwrap_error(error), do: [error]

def transform_errors(form, errors, path_filter \\ nil, form_keys \\ []) do
errors = unwrap_errors(errors)

additional_path_filters =
form_keys
|> Enum.filter(fn {_key, config} -> config[:type] == :list end)
Expand Down Expand Up @@ -110,7 +127,7 @@ defmodule AshPhoenix.FormData.Helpers do
Logger.warning("""
Unhandled error in form submission for #{inspect(form.resource)}.#{form.action}
This error was unhandled because it did not implement the `AshPhoenix.FormData.Error` protocol.
This error was unhandled because #{inspect(error.__struct__)} does not implement the `AshPhoenix.FormData.Error` protocol.
#{Exception.format(:error, error)}
""")
Expand Down
31 changes: 4 additions & 27 deletions test/form_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,6 @@ defmodule AshPhoenix.FormTest do
|> Form.for_create(:create_author_required, api: Api, forms: [auto?: true])
|> Form.validate(%{"list_of_ints" => %{"0" => %{"map" => "of stuff"}}})

# TODO: this might be wrong
assert AshPhoenix.Form.value(form, :list_of_ints) == %{"0" => %{"map" => "of stuff"}}
end
end
Expand Down Expand Up @@ -795,33 +794,11 @@ defmodule AshPhoenix.FormTest do
|> Form.validate(%{"embedded_argument" => %{"value" => "[email protected]"}})
|> form_for("action")

[nested_form] = inputs_for(form, :embedded_argument)

# This is the top level error with a path to the nest form.
assert [
%Ash.Error.Changes.InvalidArgument{
field: :value,
message: "must match email",
value: "[email protected]",
path: [:embedded_argument],
class: :invalid
}
] = form.source.source.errors

assert form.errors == []

# This is the error on the nested form.
assert [
%Ash.Error.Changes.InvalidArgument{
field: :value,
message: "must match email",
value: "[email protected]",
path: [],
class: :invalid
}
] = nested_form.source.source.errors
assert AshPhoenix.Form.errors(form, for_path: [:embedded_argument]) == [
value: "must match email"
]

assert nested_form.errors == [{:value, {"must match email", []}}]
assert AshPhoenix.Form.errors(form) == []
end
end

Expand Down

0 comments on commit c292848

Please sign in to comment.