diff --git a/lib/ash/action_input.ex b/lib/ash/action_input.ex index 1b39c8266..0347d05d7 100644 --- a/lib/ash/action_input.ex +++ b/lib/ash/action_input.ex @@ -126,7 +126,7 @@ defmodule Ash.ActionInput do input = Enum.reduce(opts[:private_arguments] || %{}, input, fn {k, v}, input -> - Ash.ActionInput.set_argument(input, k, v) + Ash.ActionInput.set_private_argument(input, k, v) end) input @@ -252,6 +252,34 @@ defmodule Ash.ActionInput do end end + @doc """ + Sets a private argument value + """ + @spec set_private_argument(input :: t(), name :: atom, value :: term()) :: t() + def set_private_argument(input, name, value) do + argument = + Enum.find( + input.action.arguments, + &(&1.name == name || to_string(&1.name) == name) + ) + + cond do + is_nil(argument) -> + input + + argument.public? -> + add_invalid_errors( + value, + input, + argument, + "can't set public arguments with set_private_argument/3" + ) + + true -> + set_argument(input, name, value) + end + end + @doc """ Deep merges the provided map into the input context that can be used later diff --git a/lib/ash/changeset/changeset.ex b/lib/ash/changeset/changeset.ex index 39e7a63e6..0ac19e468 100644 --- a/lib/ash/changeset/changeset.ex +++ b/lib/ash/changeset/changeset.ex @@ -1587,7 +1587,7 @@ defmodule Ash.Changeset do changeset = Enum.reduce(opts[:private_arguments] || %{}, changeset, fn {k, v}, changeset -> - Ash.Changeset.set_argument(changeset, k, v) + set_private_argument_for_action(changeset, k, v) end) changeset @@ -1928,7 +1928,7 @@ defmodule Ash.Changeset do changeset = Enum.reduce(opts[:private_arguments] || %{}, changeset, fn {k, v}, changeset -> - Ash.Changeset.set_argument(changeset, k, v) + set_private_argument_for_action(changeset, k, v) end) changeset = @@ -5080,6 +5080,53 @@ defmodule Ash.Changeset do do_set_argument(changeset, argument, value) end + @doc """ + Add a private argument to the changeset, which will be provided to the action. + """ + @spec set_private_argument(t(), atom, term) :: t() + def set_private_argument(changeset, argument, value) do + do_set_private_argument( + changeset, + argument, + value, + "can't set public arguments with set_private_argument/3" + ) + end + + defp set_private_argument_for_action(changeset, argument, value) do + do_set_private_argument( + changeset, + argument, + value, + "can't set public arguments using the private_arguments option." + ) + end + + defp do_set_private_argument(changeset, name, value, error_msg) do + argument = + Enum.find( + changeset.action.arguments, + &(&1.name == name || to_string(&1.name) == name) + ) + + cond do + is_nil(argument) -> + changeset + + argument.public? -> + add_invalid_errors( + value, + :argument, + changeset, + argument, + error_msg + ) + + true -> + set_argument(changeset, name, value) + end + end + @doc """ Add an argument to the changeset, which will be provided to the action. diff --git a/test/actions/create_test.exs b/test/actions/create_test.exs index 73d8c30c2..79f68bda9 100644 --- a/test/actions/create_test.exs +++ b/test/actions/create_test.exs @@ -289,7 +289,7 @@ defmodule Ash.Test.Actions.CreateTest do end create :create_with_private_argument do - argument :private_name, :string, allow_nil?: false + argument :private_name, :string, allow_nil?: false, public?: false accept [:title] change set_attribute(:private_name, arg(:private_name)) @@ -584,7 +584,7 @@ defmodule Ash.Test.Actions.CreateTest do end test "allows setting private arguments" do - assert %Post{title: "title", private_name: "private"} = + assert %Post{title: "title"} = Post |> Ash.Changeset.for_create(:create_with_private_argument, %{title: "title"}, private_arguments: %{private_name: "private"} diff --git a/test/changeset/changeset_test.exs b/test/changeset/changeset_test.exs index a2a8f1694..f75ee5cc6 100644 --- a/test/changeset/changeset_test.exs +++ b/test/changeset/changeset_test.exs @@ -66,6 +66,10 @@ defmodule Ash.Test.Changeset.ChangesetTest do create :with_name_validation do validate present(:name), message: "this validates the name is present" end + + create :create_with_private_argument do + argument :ip_address_public, :string, allow_nil?: false, public?: true + end end attributes do @@ -1220,6 +1224,42 @@ defmodule Ash.Test.Changeset.ChangesetTest do }).errors end + test "private_arguments are validated" do + assert [ + %Ash.Error.Changes.InvalidArgument{ + class: :invalid, + field: :ip_address_public, + message: "can't set public arguments using the private_arguments option." + } + ] = + Ash.Changeset.for_create(Category, :create_with_private_argument, %{}, + private_arguments: %{ip_address_public: "123"} + ).errors + + assert [ + %Ash.Error.Changes.InvalidArgument{ + class: :invalid, + field: :ip_address_public, + message: "can't set public arguments with set_private_argument/3" + }, + %Ash.Error.Changes.Required{ + class: :invalid, + field: :ip_address_public + } + ] = + Ash.Changeset.for_create(Category, :create_with_private_argument, %{}) + |> Ash.Changeset.set_private_argument(:ip_address_public, "123") + |> Map.get(:errors) + + assert [] = + Ash.Changeset.for_create( + Category, + :create_with_private_argument, + %{ip_address_public: "123"}, + private_arguments: %{} + ).errors + end + test "for_action works the same as calling for_" do changeset_1 = Category