improvement: return invalid primary key errors for Api.get when the input can't be cast

This commit is contained in:
Zach Daniel 2022-11-21 02:51:37 -05:00
parent a479970e23
commit 865f9aa253
3 changed files with 59 additions and 9 deletions

View file

@ -9,8 +9,10 @@ defmodule Ash.Error.Invalid.InvalidPrimaryKey do
def code(_), do: "invalid_primary_key"
def message(%{resource: resource, value: value}) do
"invalid primary key #{inspect(value)} provided for resource #{inspect(resource)}"
def message(%{resource: _resource, value: value}) do
# TODO: removed the resource so as not to leak it, but it would be nice to show it internally somehow
# perhaps an `internal_message` that is only shown on raised exceptions
"invalid primary key #{inspect(value)} provided"
end
end
end

View file

@ -409,14 +409,26 @@ defmodule Ash.Filter do
case {primary_key, id} do
{[field], [{field, value}]} ->
{:ok, %{field => value}}
case cast_value(resource, field, value, id) do
{:ok, value} ->
{:ok, %{field => value}}
{:error, error} ->
{:error, error}
end
{[field], value} when not keyval? ->
{:ok, %{field => value}}
case cast_value(resource, field, value, id) do
{:ok, value} ->
{:ok, %{field => value}}
{:error, error} ->
{:error, error}
end
{fields, value} ->
if keyval? do
with :error <- get_keys(value, fields),
with :error <- get_keys(value, fields, resource),
:error <- get_identity_filter(resource, id) do
{:error, InvalidPrimaryKey.exception(resource: resource, value: id)}
end
@ -426,16 +438,30 @@ defmodule Ash.Filter do
end
end
defp get_keys(value, fields) do
defp get_keys(value, fields, resource) do
original_value = value
Enum.reduce_while(fields, {:ok, %{}}, fn field, {:ok, vals} ->
case fetch(value, field) do
{:ok, value} ->
{:cont, {:ok, Map.put(vals, field, value)}}
case cast_value(resource, field, value, original_value) do
{:ok, value} ->
{:cont, {:ok, Map.put(vals, field, value)}}
{:error, error} ->
{:halt, {:error, error}}
end
:error ->
case fetch(value, to_string(field)) do
{:ok, value} ->
{:cont, {:ok, Map.put(vals, field, value)}}
case cast_value(resource, field, value, original_value) do
{:ok, value} ->
{:cont, {:ok, Map.put(vals, field, value)}}
{:error, error} ->
{:error, error}
end
:error ->
{:halt, :error}
@ -444,6 +470,22 @@ defmodule Ash.Filter do
end)
end
defp cast_value(resource, field, value, id) do
attribute = Ash.Resource.Info.attribute(resource, field)
if attribute do
case Ash.Type.cast_input(attribute.type, value, attribute.constraints) do
{:ok, value} ->
{:ok, value}
_ ->
{:error, InvalidPrimaryKey.exception(resource: resource, value: id)}
end
else
{:error, InvalidPrimaryKey.exception(resource: resource, value: id)}
end
end
defp fetch(val, key) when is_map(val), do: Map.fetch(val, key)
defp fetch(val, key) when is_list(val) and is_atom(key), do: Keyword.fetch(val, key)
defp fetch(_, _), do: :error
@ -454,7 +496,7 @@ defmodule Ash.Filter do
|> Enum.find_value(
:error,
fn identity ->
case get_keys(id, identity.keys) do
case get_keys(id, identity.keys, resource) do
{:ok, key} ->
{:ok, key}

View file

@ -143,6 +143,12 @@ defmodule Ash.Test.Actions.ReadTest do
assert ^post = strip_metadata(Api.get!(Post, post.id))
end
test "it gives an invalid primary key error when invalid input is provided" do
assert_raise Ash.Error.Invalid, ~r/invalid primary key "not good"/, fn ->
Api.get!(Post, "not good")
end
end
test "it raises when there is no matching record" do
res =
assert_raise Ash.Error.Invalid, fn ->