improvement: much more readable errors when building loads

This commit is contained in:
Zach Daniel 2022-11-21 01:37:58 -05:00
parent d16b7057a3
commit a479970e23
5 changed files with 45 additions and 40 deletions

View file

@ -169,11 +169,11 @@ defmodule Ash.Error do
case stacktrace do
%Stacktrace{stacktrace: nil} ->
{:current_stacktrace, stacktrace} = Process.info(self(), :current_stacktrace)
%Stacktrace{stacktrace: stacktrace}
%Stacktrace{stacktrace: Enum.drop(stacktrace, 2)}
nil ->
{:current_stacktrace, stacktrace} = Process.info(self(), :current_stacktrace)
%Stacktrace{stacktrace: stacktrace}
%Stacktrace{stacktrace: Enum.drop(stacktrace, 2)}
stacktrace ->
%Stacktrace{stacktrace: stacktrace}

View file

@ -37,7 +37,9 @@ defmodule Ash.Error.Exception do
case Process.info(self(), :current_stacktrace) do
{:current_stacktrace, stacktrace} ->
super(
Keyword.put_new(opts, :stacktrace, %Ash.Error.Stacktrace{stacktrace: stacktrace})
Keyword.put_new(opts, :stacktrace, %Ash.Error.Stacktrace{
stacktrace: Enum.drop(stacktrace, 2)
})
)
_ ->

View file

@ -12,7 +12,18 @@ defmodule Ash.Error.Load.InvalidQuery do
def class(_), do: :invalid
def message(%{query: query, load_path: load_path}) do
"Invalid query: #{inspect(query)} at #{Enum.join(load_path, ".")}"
errors_by_path =
query.errors
|> Enum.group_by(&List.wrap(&1.path))
|> Enum.sort_by(&Enum.count(elem(&1, 0)))
|> Enum.map(fn {path, error} ->
{List.wrap(load_path) ++ path, error}
end)
"Invalid query\n" <>
Enum.map_join(errors_by_path, "\n", fn {key, errors} ->
Enum.map_join(errors, "\n", &"* #{inspect(key)} - #{Exception.message(&1)}")
end)
end
def stacktrace(_), do: nil

View file

@ -870,7 +870,7 @@ defmodule Ash.Query do
end
true ->
add_error(query, :load, "Invalid load #{inspect(field)}")
add_error(query, :load, Ash.Error.Query.InvalidLoad.exception(load: field))
end
field, query ->
@ -969,7 +969,7 @@ defmodule Ash.Query do
end
true ->
add_error(query, :load, "Could not load #{inspect(field)}")
add_error(query, :load, Ash.Error.Query.InvalidLoad.exception(load: field))
end
end
@ -1577,7 +1577,7 @@ defmodule Ash.Query do
%{query | load: new_loads}
else
{:error, errors} ->
Enum.reduce(errors, query, &add_error(&2, :load, &1))
Enum.reduce(errors, query, &add_error(&2, &1))
end
end
@ -1594,14 +1594,8 @@ defmodule Ash.Query do
[] ->
[]
_errors ->
[
{:error,
InvalidQuery.exception(
query: load_query,
load_path: Enum.reverse(path)
)}
]
errors ->
Enum.map(errors, &Ash.Error.set_path(&1, path))
end
end
@ -1610,6 +1604,8 @@ defmodule Ash.Query do
end
defp do_validate_load(query, loads, path) when is_list(loads) do
query = to_query(query)
loads
|> List.wrap()
|> Enum.flat_map(fn
@ -1617,44 +1613,32 @@ defmodule Ash.Query do
case Ash.Resource.Info.relationship(query.resource, key) do
nil ->
[
{:error,
NoSuchRelationship.exception(
resource: query.resource,
relationship: key,
load_path: Enum.reverse(path)
)}
NoSuchRelationship.exception(
resource: query.resource,
relationship: key,
load_path: Enum.reverse(path)
)
]
relationship ->
cond do
!Ash.Resource.Info.primary_action(relationship.destination, :read) ->
[
{:error,
NoReadAction.exception(
resource: relationship.destination,
when: "loading relationship #{relationship.name}"
)}
NoReadAction.exception(
resource: relationship.destination,
when: "loading relationship #{relationship.name}"
)
]
relationship.type == :many_to_many &&
!Ash.Resource.Info.primary_action(relationship.through, :read) ->
[
{:error,
NoReadAction.exception(
resource: relationship.destination,
when: "loading relationship #{relationship.name}"
)}
NoReadAction.exception(
resource: relationship.destination,
when: "loading relationship #{relationship.name}"
)
]
match?(%Ash.Query{}, value) ->
validate_matching_query_and_continue(
value,
query.resource,
key,
path,
relationship
)
true ->
validate_matching_query_and_continue(
value,

View file

@ -527,6 +527,14 @@ defmodule Ash.Test.Actions.LoadTest do
assert Enum.sort([category1.id, category2.id]) == Enum.sort([id1, id2])
end
test "it produces a nice error message on loading invalid loads" do
assert_raise Ash.Error.Invalid, ~r/:non_existent_thing is not a valid load/, fn ->
Post
|> Ash.Query.load(categories: [posts: :non_existent_thing])
|> Api.read!(authorize?: true)
end
end
test "it allows loading nested many to many relationships" do
category1 =
Category