improvement: add error hints for NoFormConfigured errors

This commit is contained in:
Zach Daniel 2024-04-11 18:46:52 -04:00
parent 9bb24372fe
commit 1f511de8ca
3 changed files with 86 additions and 14 deletions

View file

@ -3233,6 +3233,8 @@ defmodule AshPhoenix.Form do
defp do_remove_form(form, [key], trail) when not is_integer(key) do
unless form.form_keys[key] do
raise AshPhoenix.Form.NoFormConfigured,
resource: form.resource,
action: form.action,
field: key,
available: Keyword.keys(form.form_keys || []),
path: Enum.reverse(trail)
@ -3277,6 +3279,8 @@ defmodule AshPhoenix.Form do
defp do_remove_form(form, [key, i], trail) when is_integer(i) do
unless form.form_keys[key] do
raise AshPhoenix.Form.NoFormConfigured,
resource: form.resource,
action: form.action,
field: key,
available: Keyword.keys(form.form_keys || []),
path: Enum.reverse(trail)
@ -3319,6 +3323,8 @@ defmodule AshPhoenix.Form do
defp do_remove_form(form, [key, i | rest], trail) when is_integer(i) do
unless form.form_keys[key] do
raise AshPhoenix.Form.NoFormConfigured,
resource: form.resource,
action: form.action,
field: key,
available: Keyword.keys(form.form_keys || []),
path: Enum.reverse(trail)
@ -3337,6 +3343,8 @@ defmodule AshPhoenix.Form do
defp do_remove_form(form, [key | rest], trail) do
unless form.form_keys[key] do
raise AshPhoenix.Form.NoFormConfigured,
resource: form.resource,
action: form.action,
field: key,
available: Keyword.keys(form.form_keys || []),
path: Enum.reverse(trail)
@ -3357,6 +3365,8 @@ defmodule AshPhoenix.Form do
defp do_add_form(form, [key, i | rest], opts, trail, transform_errors) when is_integer(i) do
unless form.form_keys[key] do
raise AshPhoenix.Form.NoFormConfigured,
resource: form.resource,
action: form.action,
field: key,
available: Keyword.keys(form.form_keys || []),
path: Enum.reverse(trail)
@ -3389,6 +3399,8 @@ defmodule AshPhoenix.Form do
config =
form.form_keys[key] ||
raise AshPhoenix.Form.NoFormConfigured,
resource: form.resource,
action: form.action,
field: key,
available: Keyword.keys(form.form_keys || []),
path: Enum.reverse(trail)
@ -3478,6 +3490,8 @@ defmodule AshPhoenix.Form do
defp do_add_form(form, [key | rest], opts, trail, transform_errors) do
unless form.form_keys[key] do
raise AshPhoenix.Form.NoFormConfigured,
resource: form.resource,
action: form.action,
field: key,
available: Keyword.keys(form.form_keys || []),
path: Enum.reverse(trail)
@ -4739,6 +4753,8 @@ defmodule AshPhoenix.Form do
def to_form(form, _phoenix_form, field, opts) do
unless Keyword.has_key?(form.form_keys, field) do
raise AshPhoenix.Form.NoFormConfigured,
resource: form.resource,
action: form.action,
field: field,
available: Keyword.keys(form.form_keys || [])
end

View file

@ -1,12 +1,18 @@
defmodule AshPhoenix.Form.NoFormConfigured do
@moduledoc "Raised when attempting to refer to a form but no nested form with that name was configured."
defexception [:field, :available, :path]
defexception [:field, :available, :path, :action, :resource]
def exception(opts) do
%__MODULE__{field: opts[:field], available: opts[:available], path: List.wrap(opts[:paths])}
%__MODULE__{
field: opts[:field],
available: opts[:available],
path: List.wrap(opts[:paths]),
action: Ash.Resource.Info.action(opts[:resource], opts[:action]),
resource: opts[:resource]
}
end
def message(%{field: field, available: available, path: path}) do
def message(%{field: field, available: available, path: path} = error) do
path_message =
if path do
"at path #{inspect(path)}"
@ -16,6 +22,7 @@ defmodule AshPhoenix.Form.NoFormConfigured do
"""
#{field} #{path_message} must be configured in the form to be used with `inputs_for`. For example:
#{hint(error)}
Available forms:
@ -55,4 +62,52 @@ defmodule AshPhoenix.Form.NoFormConfigured do
)
"""
end
defp hint(error) do
cond do
argument = Enum.find(error.action.arguments, &(&1.name == error.field)) ->
"""
There is an argument called `#{argument.name}` on the action `#{inspect(error.resource)}.#{error.action.name}`.
Perhaps you are missing a `change manage_relationship` for that argument, or it is not a type that can have forms generated for it?
"""
attribute = Ash.Resource.Info.attribute(error.resource, error.field) ->
if attribute.name in error.action.accept do
"""
There is a attribute called `#{attribute.name}` on the resource `#{inspect(error.resource)}`, but it is
not accepted by `#{inspect(error.resource)}.#{error.action.name}`.
Perhaps you meant to add that attribute to the `accept` list, or you meant to make it `public? true`?
"""
else
"""
There is a attribute called `#{attribute.name}` on the resource `#{inspect(error.resource)}`, and it is
accepted by `#{inspect(error.resource)}.#{error.action.name}`.
Perhaps it is not a type that can have forms generated for it?
"""
end
relationship = Ash.Resource.Info.attribute(error.resource, error.field) ->
"""
There is a relationship called `#{relationship.name}` on the resource `#{inspect(error.resource)}`.
Perhaps you are missing an argument with `change manage_relationship` in the
action #{inspect(error.resource)}.#{error.action.name}?
"""
true ->
"""
There is a no attribute or relationship called `#{error.field}` on the resource `#{inspect(error.resource)}`, and
no argument called `#{error.field}` on `#{inspect(error.resource)}.#{error.action.name}`.
Perhaps you have a typo?
"""
end
end
end

View file

@ -1201,18 +1201,19 @@ defmodule AshPhoenix.FormTest do
end
describe "`inputs_for` with no configuration" do
# phoenix changes make this not work anymore
# test "it should raise an error" do
# form =
# Post
# |> Form.for_create(:create)
# |> Form.validate(%{text: "text"})
# |> form_for("action")
test "it should raise an error" do
form =
Post
|> Form.for_create(:create)
|> Form.validate(%{text: "text"})
|> form_for("action")
# assert_raise AshPhoenix.Form.NoFormConfigured, fn ->
# inputs_for(form, :post) == []
# end
# end
assert_raise AshPhoenix.Form.NoFormConfigured,
~r/There is a no attribute or relationship called `post` on the resource `AshPhoenix.Test.Post`/,
fn ->
AshPhoenix.Form.add_form(form, :post)
end
end
end
describe "inputs_for` relationships" do