fix: synthetically cast attributes in read forms

fix: raise explicitly on non-existent action
This commit is contained in:
Zach Daniel 2022-05-09 15:45:57 -04:00
parent 728df80432
commit 4c013819d0
3 changed files with 77 additions and 3 deletions

View file

@ -81,6 +81,8 @@ defmodule AshPhoenix.Form.Auto do
end
def related(resource, action, auto_opts) do
passed_in_action = action
action =
if is_atom(action) do
Ash.Resource.Info.action(resource, action)
@ -88,6 +90,10 @@ defmodule AshPhoenix.Form.Auto do
action
end
if is_nil(action) && is_atom(passed_in_action) do
raise "No such action :#{passed_in_action} for #{inspect(resource)}"
end
action.arguments
|> Enum.reject(& &1.private?)
|> Enum.filter(&(&1.type in [{:array, :map}, :map, Ash.Type.Map, {:array, Ash.Type.Map}]))

View file

@ -639,14 +639,53 @@ defmodule AshPhoenix.Form do
Ash.Query.for_read(
resource,
action,
opts[:params] || %{},
params || %{},
query_opts
)
|> add_errors_for_unhandled_params(params)
}
|> set_changed?()
|> set_validity()
end
defp add_errors_for_unhandled_params(%{action: nil} = query, _params), do: query
defp add_errors_for_unhandled_params(query, params) do
arguments = Enum.map(query.action.arguments, &to_string(&1.name))
remaining_params = Map.drop(params, arguments)
Enum.reduce(remaining_params, query, fn {key, value}, query ->
attribute = Ash.Resource.Info.public_attribute(query.resource, key)
if attribute do
case Ash.Changeset.cast_input(attribute.type, value, attribute.constraints, query) do
{:ok, casted} ->
%{query | params: Map.put(query.params, key, casted)}
{:error, error} ->
messages =
if Keyword.keyword?(error) do
[error]
else
List.wrap(error)
end
messages
|> Enum.reduce(query, fn message, query ->
message
|> Ash.Changeset.error_to_exception_opts(attribute)
|> Enum.reduce(query, fn opts, query ->
Ash.Query.add_error(query, Ash.Error.Changes.InvalidAttribute.exception(opts))
end)
end)
end
else
query
end
end)
end
@doc "A utility to get the list of attributes the action underlying the form accepts"
def attributes(form) do
AshPhoenix.Form.Auto.accepted_attributes(form.resource, form.source.action)
@ -760,6 +799,7 @@ defmodule AshPhoenix.Form do
params,
source_opts
)
|> add_errors_for_unhandled_params(params)
end
%{
@ -2078,8 +2118,26 @@ defmodule AshPhoenix.Form do
end
defp add_form_resource_and_action(opts, config, key, trail) do
default =
cond do
config[:create_action] && (config[:create_resource] || config[:resource]) ->
:create
config[:read_action] && (config[:read_resource] || config[:resource]) ->
:read
config[:update_action] && (config[:update_resource] || config[:resource]) ->
:update
config[:destroy_action] && (config[:destroy_resource] || config[:resource]) ->
:destroy
true ->
:create
end
action =
case opts[:type] || :create do
case opts[:type] || default do
:create ->
config[:create_action] ||
raise AshPhoenix.Form.NoActionConfigured,
@ -2106,7 +2164,7 @@ defmodule AshPhoenix.Form do
end
resource =
case opts[:type] || :create do
case opts[:type] || default do
:create ->
config[:create_resource] || config[:resource] ||
raise AshPhoenix.Form.NoResourceConfigured, path: Enum.reverse(trail, [key])

View file

@ -23,6 +23,16 @@ defmodule AshPhoenix.FormTest do
end
end
test "a read will validate attributes" do
form =
Post
|> Form.for_read(:read)
|> Form.validate(%{"text" => [1, 2, 3]})
|> form_for("action")
assert form.errors[:text] == {"is invalid", []}
end
test "validation errors are attached to fields" do
form = Form.for_create(PostWithDefault, :create, api: Api)
form = AshPhoenix.Form.validate(form, %{"text" => ""}, errors: form.submitted_once?)