How to use Absinthe.MiddleWare to catch exception?

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.

8 Likes