mirror of
https://github.com/ash-project/ash_phoenix.git
synced 2024-09-19 23:02:48 +12:00
fix: properly track form params for nested unions
fix: produce type errors for wrapped union values
This commit is contained in:
parent
e295fb72f7
commit
3a7d436079
5 changed files with 231 additions and 67 deletions
|
@ -127,6 +127,29 @@ defmodule AshPhoenix.Form.Auto do
|
|||
|
||||
constraints = unwrap_union(type, constraints)
|
||||
|
||||
data =
|
||||
case form_type do
|
||||
:list ->
|
||||
fn parent ->
|
||||
if parent do
|
||||
Map.get(parent, attr.name) || []
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
:single ->
|
||||
fn parent ->
|
||||
if parent do
|
||||
case Map.get(parent, attr.name) do
|
||||
[value | _] -> value
|
||||
[] -> nil
|
||||
value -> value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
updater = fn opts, data, params ->
|
||||
{type, constraints} = determine_type(constraints, data, params)
|
||||
|
||||
|
@ -137,31 +160,6 @@ defmodule AshPhoenix.Form.Auto do
|
|||
{AshPhoenix.Form.WrappedValue, [], true}
|
||||
end
|
||||
|
||||
data =
|
||||
case form_type do
|
||||
:list ->
|
||||
fn parent ->
|
||||
if parent do
|
||||
Map.get(parent, attr.name) || []
|
||||
else
|
||||
[]
|
||||
end
|
||||
|> Enum.map(&wrap_value(&1, fake_embedded?))
|
||||
end
|
||||
|
||||
:single ->
|
||||
fn parent ->
|
||||
if parent do
|
||||
case Map.get(parent, attr.name) do
|
||||
[value | _] -> value
|
||||
[] -> nil
|
||||
value -> value
|
||||
end
|
||||
end
|
||||
|> wrap_value(fake_embedded?)
|
||||
end
|
||||
end
|
||||
|
||||
prepare_source =
|
||||
if fake_embedded? do
|
||||
fn source ->
|
||||
|
@ -207,7 +205,6 @@ defmodule AshPhoenix.Form.Auto do
|
|||
prepare_source: prepare_source,
|
||||
transform_params: transform_params,
|
||||
embed?: true,
|
||||
data: data,
|
||||
forms: [],
|
||||
updater: fn opts ->
|
||||
Keyword.update!(opts, :forms, fn forms ->
|
||||
|
@ -223,6 +220,7 @@ defmodule AshPhoenix.Form.Auto do
|
|||
|
||||
{attr.name,
|
||||
[
|
||||
data: data,
|
||||
type: form_type,
|
||||
updater: updater
|
||||
]}
|
||||
|
@ -230,12 +228,6 @@ defmodule AshPhoenix.Form.Auto do
|
|||
|> Keyword.new()
|
||||
end
|
||||
|
||||
defp wrap_value(value, true) do
|
||||
%AshPhoenix.Form.WrappedValue{value: value}
|
||||
end
|
||||
|
||||
defp wrap_value(value, _), do: value
|
||||
|
||||
defp determine_type(constraints, _data, %{"_union_type" => union_type} = params) do
|
||||
constraints[:types]
|
||||
|> Enum.find(fn {key, _value} ->
|
||||
|
@ -288,24 +280,32 @@ defmodule AshPhoenix.Form.Auto do
|
|||
end
|
||||
|
||||
defp tags_equal(config, key, params) do
|
||||
case config[:tag_value] || key do
|
||||
value when is_atom(value) ->
|
||||
params[to_string(config[:tag])] == to_string(value) ||
|
||||
params[to_string(config[:tag])] == value
|
||||
if is_map(params) do
|
||||
case config[:tag_value] || key do
|
||||
value when is_atom(value) ->
|
||||
params[to_string(config[:tag])] == to_string(value) ||
|
||||
params[to_string(config[:tag])] == value
|
||||
|
||||
value ->
|
||||
params[to_string(config[:tag])] == value
|
||||
value ->
|
||||
params[to_string(config[:tag])] == value
|
||||
end
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
defp tags_equal_data(config, key, data) do
|
||||
case config[:tag_value] || key do
|
||||
value when is_atom(value) ->
|
||||
data[config[:tag]] == to_string(value) ||
|
||||
data[config[:tag]] == value
|
||||
if is_struct(data) do
|
||||
case config[:tag_value] || key do
|
||||
value when is_atom(value) ->
|
||||
data[config[:tag]] == to_string(value) ||
|
||||
data[config[:tag]] == value
|
||||
|
||||
value ->
|
||||
data[config[:tag]] == value
|
||||
value ->
|
||||
data[config[:tag]] == value
|
||||
end
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -375,8 +375,18 @@ defmodule AshPhoenix.Form do
|
|||
def for_action(resource_or_data, action, opts) do
|
||||
{resource, data} =
|
||||
case resource_or_data do
|
||||
module when is_atom(resource_or_data) -> {module, module.__struct__()}
|
||||
%resource{} = data -> {resource, data}
|
||||
module when is_atom(resource_or_data) ->
|
||||
{module, module.__struct__()}
|
||||
|
||||
%resource{} = data ->
|
||||
if Ash.Resource.Info.resource?(resource) do
|
||||
{resource, data}
|
||||
else
|
||||
{AshPhoenix.Form.WrappedValue, %AshPhoenix.Form.WrappedValue{value: data}}
|
||||
end
|
||||
|
||||
value ->
|
||||
{AshPhoenix.Form.WrappedValue, %AshPhoenix.Form.WrappedValue{value: value}}
|
||||
end
|
||||
|
||||
type =
|
||||
|
@ -1121,7 +1131,7 @@ defmodule AshPhoenix.Form do
|
|||
|> Enum.reduce(forms, fn {params, index}, forms ->
|
||||
case Enum.find(form.forms[key] || [], &matcher.(&1, params, form, key, index)) do
|
||||
nil ->
|
||||
opts = update_opts(opts, nil, form_params)
|
||||
opts = update_opts(opts, nil, params)
|
||||
|
||||
new_form =
|
||||
cond do
|
||||
|
@ -3201,6 +3211,7 @@ defmodule AshPhoenix.Form do
|
|||
tenant: form.opts[:tenant],
|
||||
accessing_from: config[:managed_relationship],
|
||||
transform_params: config[:transform_params],
|
||||
prepare_source: config[:prepare_source],
|
||||
warn_on_unhandled_errors?: form.warn_on_unhandled_errors?,
|
||||
forms: config[:forms] || [],
|
||||
data: opts[:data],
|
||||
|
|
|
@ -15,4 +15,103 @@ defmodule AshPhoenix.Form.WrappedValue do
|
|||
primary? true
|
||||
end
|
||||
end
|
||||
|
||||
changes do
|
||||
change fn changeset, _ ->
|
||||
if Ash.Changeset.changing_attribute?(changeset, :value) do
|
||||
value = Ash.Changeset.get_attribute(changeset, :value)
|
||||
|
||||
with {:ok, casted} <-
|
||||
Ash.Type.Helpers.cast_input(
|
||||
changeset.context.type,
|
||||
value,
|
||||
changeset.context.constraints,
|
||||
changeset
|
||||
),
|
||||
{:constrained, {:ok, casted}} when not is_nil(casted) <-
|
||||
{:constrained,
|
||||
Ash.Type.apply_constraints(
|
||||
changeset.context.type,
|
||||
casted,
|
||||
changeset.context.constraints
|
||||
)} do
|
||||
Ash.Changeset.force_change_attribute(changeset, :value, casted)
|
||||
else
|
||||
{:constrained, {:ok, nil}} ->
|
||||
Ash.Changeset.force_change_attribute(changeset, :value, nil)
|
||||
|
||||
{:constrained, {:error, error}, argument} ->
|
||||
add_invalid_errors(value, :attribute, changeset, :value, error)
|
||||
|
||||
{:error, error} ->
|
||||
add_invalid_errors(value, :attributes, changeset, :value, error)
|
||||
end
|
||||
else
|
||||
changeset
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp add_invalid_errors(value, type, changeset, attribute, message) do
|
||||
messages =
|
||||
if Keyword.keyword?(message) do
|
||||
[message]
|
||||
else
|
||||
List.wrap(message)
|
||||
end
|
||||
|
||||
Enum.reduce(messages, changeset, fn message, changeset ->
|
||||
if is_exception(message) do
|
||||
error =
|
||||
message
|
||||
|> Ash.Error.to_ash_error()
|
||||
|
||||
errors =
|
||||
case error do
|
||||
%class{errors: errors}
|
||||
when class in [
|
||||
Ash.Error.Invalid,
|
||||
Ash.Error.Unknown,
|
||||
Ash.Error.Forbidden,
|
||||
Ash.Error.Framework
|
||||
] ->
|
||||
errors
|
||||
|
||||
error ->
|
||||
[error]
|
||||
end
|
||||
|
||||
Enum.reduce(errors, changeset, fn error, changeset ->
|
||||
Ash.Changeset.add_error(changeset, Ash.Error.set_path(error, attribute))
|
||||
end)
|
||||
else
|
||||
opts = Ash.Type.Helpers.error_to_exception_opts(message, %{name: attribute})
|
||||
|
||||
exception =
|
||||
case type do
|
||||
:attribute -> InvalidAttribute
|
||||
:argument -> InvalidArgument
|
||||
end
|
||||
|
||||
Enum.reduce(opts, changeset, fn opts, changeset ->
|
||||
error =
|
||||
exception.exception(
|
||||
value: value,
|
||||
field: Keyword.get(opts, :field),
|
||||
message: Keyword.get(opts, :message),
|
||||
vars: opts
|
||||
)
|
||||
|
||||
error =
|
||||
if opts[:path] do
|
||||
Ash.Error.set_path(error, opts[:path])
|
||||
else
|
||||
error
|
||||
end
|
||||
|
||||
Ash.Changeset.add_error(changeset, error)
|
||||
end)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -56,15 +56,15 @@ defmodule AshPhoenix.AutoFormTest do
|
|||
|
||||
describe "single unions" do
|
||||
test "a form can be added for a union" do
|
||||
Post
|
||||
|> AshPhoenix.Form.for_create(:create,
|
||||
api: Api,
|
||||
forms: [
|
||||
auto?: true
|
||||
]
|
||||
)
|
||||
|> AshPhoenix.Form.add_form(:union, params: %{"type" => "foo"})
|
||||
|> form_for("action")
|
||||
Post
|
||||
|> AshPhoenix.Form.for_create(:create,
|
||||
api: Api,
|
||||
forms: [
|
||||
auto?: true
|
||||
]
|
||||
)
|
||||
|> AshPhoenix.Form.add_form(:union, params: %{"type" => "foo"})
|
||||
|> form_for("action")
|
||||
end
|
||||
|
||||
test "a form can be removed from a union" do
|
||||
|
@ -113,20 +113,72 @@ defmodule AshPhoenix.AutoFormTest do
|
|||
|
||||
test "a form can be removed from a union" do
|
||||
form =
|
||||
Post
|
||||
|> AshPhoenix.Form.for_create(:create,
|
||||
api: Api,
|
||||
forms: [
|
||||
auto?: true
|
||||
]
|
||||
)
|
||||
|> AshPhoenix.Form.add_form(:union_array, params: %{"type" => "foo"})
|
||||
|> form_for("action")
|
||||
Post
|
||||
|> AshPhoenix.Form.for_create(:create,
|
||||
api: Api,
|
||||
forms: [
|
||||
auto?: true
|
||||
]
|
||||
)
|
||||
|> AshPhoenix.Form.add_form(:union_array, params: %{"type" => "foo"})
|
||||
|> form_for("action")
|
||||
|
||||
AshPhoenix.Form.remove_form(form, [:union_array, 0])
|
||||
end
|
||||
end
|
||||
|
||||
test "validating a form with valid values works" do
|
||||
form =
|
||||
Post
|
||||
|> AshPhoenix.Form.for_create(:create,
|
||||
api: Api,
|
||||
forms: [
|
||||
auto?: true
|
||||
]
|
||||
)
|
||||
|> AshPhoenix.Form.add_form(:union_array, params: %{"type" => "foo"})
|
||||
|> form_for("action")
|
||||
|
||||
assert %{union_array: [%Ash.Union{value: %{value: "abc"}}]} =
|
||||
form
|
||||
|> AshPhoenix.Form.validate(%{
|
||||
"text" => "text",
|
||||
"union_array" => %{
|
||||
"0" => %{
|
||||
"type" => "foo",
|
||||
"value" => "abc"
|
||||
}
|
||||
}
|
||||
})
|
||||
|> AshPhoenix.Form.submit!()
|
||||
end
|
||||
|
||||
test "validating a form with an invalid value works" do
|
||||
form =
|
||||
Post
|
||||
|> AshPhoenix.Form.for_create(:create,
|
||||
api: Api,
|
||||
forms: [
|
||||
auto?: true
|
||||
]
|
||||
)
|
||||
|> AshPhoenix.Form.add_form(:union_array, params: %{"type" => "foo"})
|
||||
|> form_for("action")
|
||||
|
||||
assert_raise Ash.Error.Invalid, ~r/must match the pattern/, fn ->
|
||||
form
|
||||
|> AshPhoenix.Form.validate(%{
|
||||
"text" => "text",
|
||||
"union_array" => %{
|
||||
"0" => %{
|
||||
"type" => "foo",
|
||||
"value" => "def"
|
||||
}
|
||||
}
|
||||
})
|
||||
|> AshPhoenix.Form.submit!()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp auto_forms(resource, action) do
|
||||
[forms: Auto.auto(resource, action)]
|
||||
|
|
|
@ -7,6 +7,8 @@ defmodule AshPhoenix.Test.Foo do
|
|||
default "foo"
|
||||
end
|
||||
|
||||
attribute :value, :string
|
||||
attribute :value, :string do
|
||||
constraints match: ~r/abc/
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue