improvement: properly return nested errors with for_path: :all

This commit is contained in:
Zach Daniel 2023-11-28 15:31:10 -05:00
parent bace1b1ec4
commit c292848c57
3 changed files with 74 additions and 69 deletions

View file

@ -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.
@ -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
@ -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
@ -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
@ -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
@ -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.
@ -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)

View file

@ -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)
@ -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)}
""")

View file

@ -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
@ -795,33 +794,11 @@ defmodule AshPhoenix.FormTest do
|> Form.validate(%{"embedded_argument" => %{"value" => "you@example.com"}})
|> form_for("action")
[nested_form] = inputs_for(form, :embedded_argument)
assert AshPhoenix.Form.errors(form, for_path: [:embedded_argument]) == [
value: "must match email"
]
# 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: "you@example.com",
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: "you@example.com",
path: [],
class: :invalid
}
] = nested_form.source.source.errors
assert nested_form.errors == [{:value, {"must match email", []}}]
assert AshPhoenix.Form.errors(form) == []
end
end