From ac3e2dc9ac287b0c80519413f459186d4ef87081 Mon Sep 17 00:00:00 2001 From: Rebecca Le <543859+sevenseacat@users.noreply.github.com> Date: Wed, 23 Nov 2022 16:42:06 +0800 Subject: [PATCH] feat: add custom HTTP status codes for specific types of errors that can be thrown (#62) --- lib/ash_phoenix/plug/exception.ex | 27 +++++++++++++++++++++++++++ test/plug_test.exs | 27 +++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 lib/ash_phoenix/plug/exception.ex create mode 100644 test/plug_test.exs diff --git a/lib/ash_phoenix/plug/exception.ex b/lib/ash_phoenix/plug/exception.ex new file mode 100644 index 0000000..5390d88 --- /dev/null +++ b/lib/ash_phoenix/plug/exception.ex @@ -0,0 +1,27 @@ +errors = [ + {Ash.Error.Invalid.InvalidPrimaryKey, 400}, + {Ash.Error.Query.InvalidArgument, 400}, + {Ash.Error.Query.InvalidFilterValue, 400}, + {Ash.Error.Query.NotFound, 404} +] + +# Individual errors can have their own status codes that will propagate to the top-level +# wrapper error +for {module, status_code} <- errors do + defimpl Plug.Exception, for: module do + def status(_exception), do: unquote(status_code) + def actions(_exception), do: [] + end +end + +# Top-level Ash errors will use the highest status code of all of the wrapped child errors +defimpl Plug.Exception, + for: [Ash.Error.Invalid, Ash.Error.Forbidden, Ash.Error.Framework, Ash.Error.Unknown] do + def status(%{errors: errors} = _exception) do + errors + |> Enum.map(&Plug.Exception.status/1) + |> Enum.max() + end + + def actions(_exception), do: [] +end diff --git a/test/plug_test.exs b/test/plug_test.exs new file mode 100644 index 0000000..1e65097 --- /dev/null +++ b/test/plug_test.exs @@ -0,0 +1,27 @@ +defmodule AshPhoenix.PlugTest do + use ExUnit.Case + + describe "status/1" do + test "for individual errors" do + error = %Ash.Error.Query.NotFound{} + assert 404 == Plug.Exception.status(error) + end + + test "for top-level errors wrapping several errors" do + error_custom_code = %Ash.Error.Query.NotFound{} + + # This is something that should never happen so will never have a custom status code + error_default_code = %Ash.Error.Framework.SynchronousEngineStuck{} + + error = %Ash.Error.Invalid{errors: [error_custom_code]} + assert 404 == Plug.Exception.status(error) + + error = %Ash.Error.Invalid{errors: [error_default_code]} + assert 500 == Plug.Exception.status(error) + + # The highest error code is used when there are multiple child errors + error = %Ash.Error.Invalid{errors: [error_default_code, error_custom_code]} + assert 500 == Plug.Exception.status(error) + end + end +end