To be clear, Absinthe has been doing what action_fallback does since long before action_fallback existed. action_fallback isn’t about exceptions it’s about non %Plug.Conn{}
return values, which is exactly how the book guides you towards using Absinthe middleware.
In my own projects I basically always add to my MyApp.Repo
module these functions:
def fetch(query, id) do
query
|> Ecto.Query.where(id: ^id)
|> fetch
end
def fetch(query) do
case all(query) do
[] -> {:error, query}
[obj] -> {:ok, obj}
_ -> raise "Expected one or no items, got many items #{inspect(query)}"
end
end
This would let you do:
def update_package(_, %{input: params}, _) do
with {:ok, package} <- Repo.fetch(Core.Package, params.id),
{:ok, package} <- Core.update_package(p, params) do
{:ok, %{package: package}}
end
end
Now, if the package isn’t found it’ll return {:error, %EctoQuery{}}
so I then define middleware that runs after resolvers to convert that shape into a nice error message:
%{from: %{source: {_, queryable}}} = query
schema = queryable |> Module.split() |> List.last()
{:error, "#{schema} not found"}
This to me is a much cleaner approach than exceptions. And unlike action_fallback
we didn’t need to create a different mechanism to handle it, since Absinthe middleware doesn’t halt the way plug
does.
Catching exceptions is still possible by replacing the Absinthe.Resolution middleware with your own middleware that calls the resolver function inside of a try block.