Current section
Files
Jump to
Current section
Files
lib/phase.ex
defmodule AbsintheOneOf.Phase do
@moduledoc false
@behaviour Absinthe.Phase
@impl true
@spec run(
blueprint :: Absinthe.Blueprint.node_t(),
config :: any()
) :: {:ok, Absinthe.Blueprint.node_t()}
def run(blueprint, _config) do
{:ok, Absinthe.Blueprint.prewalk(blueprint, &prewalk/1)}
end
@spec prewalk(node :: Absinthe.Blueprint.node_t()) :: Absinthe.Blueprint.node_t()
defp prewalk(%Absinthe.Blueprint.Input.Argument{} = node) do
case find_invalid(node) do
{nil, 0} ->
node
{invalid_node, count} ->
Absinthe.Phase.put_error(node, error(invalid_node, count))
end
end
defp prewalk(node), do: node
@spec find_invalid(node :: Absinthe.Blueprint.node_t()) ::
{nil | Absinthe.Blueprint.node_t(), integer()}
defp find_invalid(
%{
input_value: %Absinthe.Blueprint.Input.Value{
normalized: %Absinthe.Blueprint.Input.Object{fields: fields}
}
} = node
) do
Enum.reduce_while(
fields,
if valid?(node) do
{nil, 0}
else
{node, Enum.count(fields)}
end,
fn field, {invalid_node, count} ->
if invalid_node do
{:halt, {invalid_node, count}}
else
{:cont, find_invalid(field)}
end
end
)
end
defp find_invalid(_node), do: {nil, 0}
@spec valid?(node :: Absinthe.Blueprint.node_t()) :: boolean()
defp valid?(
%{
input_value: %Absinthe.Blueprint.Input.Value{
normalized: %Absinthe.Blueprint.Input.Object{
schema_node: %{} = schema_node,
fields: fields
}
}
} = _node
) do
schema_node = Absinthe.Type.unwrap(schema_node)
private = Map.get(schema_node, :__private__, [])
!(Keyword.get(private, :one_of, false) && Enum.count(fields) != 1)
end
defp valid?(_node), do: true
@spec error(node :: Absinthe.Blueprint.node_t(), count :: integer()) :: Absinthe.Phase.Error.t()
defp error(node, count) do
%Absinthe.Phase.Error{
phase: __MODULE__,
message:
"OneOf Object \"#{node.name}\" must have exactly one non-null field but got #{count}."
}
end
end