Current section

Files

Jump to
absinthe lib absinthe type interface.ex
Raw

lib/absinthe/type/interface.ex

defmodule Absinthe.Type.Interface do
@moduledoc """
A defined interface type that represent a list of named fields and their
arguments.
Fields on an interface have the same rules as fields on an
`Absinthe.Type.Object`.
If an `Absinthe.Type.Object` lists an interface in its `:interfaces` entry, it
it guarantees that it defines the same fields and arguments that the
interface does.
Because sometimes it's for the interface to determine the implementing type of
a resolved object, you must either:
* Provide a `:resolve_type` function on the interface
* Provide a `:is_type_of` function on each implementing type
```
interface :named_entity do
field :name, :string
resolve_type fn
%{age: _}, _ -> {:ok, :person}
%{employee_count: _}, _ -> {:ok, :business}
_ -> :error
end
end
object :person do
field :name, :string
field :age, :string
interface :named_entity
end
object :business do
field :name, :string
field :employee_count, :integer
interface :named_entity
end
```
* `:name` - The name of the interface type. Should be a TitleCased `binary`. Set automatically.
* `:description` - A nice description for introspection.
* `:fields` - A map of `Absinthe.Type.Field` structs. See `Absinthe.Schema.Notation.field/1` and
* `:args` - A map of `Absinthe.Type.Argument` structs. See `Absinthe.Schema.Notation.arg/2`.
* `:resolve_type` - A function used to determine the implementing type of a resolved object. See also `Absinthe.Type.Object`'s `:is_type_of`.
The `:resolve_type` function will be passed two arguments; the object whose type needs to be identified, and the `Absinthe.Execution` struct providing the full execution context.
The `:__reference__` key is for internal use.
"""
use Absinthe.Introspection.Kind
alias Absinthe.Type
alias Absinthe.Execution
alias Absinthe.Schema
@type t :: %{name: binary, description: binary, fields: map, resolve_type: ((any, Absinthe.Execution.t) -> atom | nil), __reference__: Type.Reference.t}
defstruct name: nil, description: nil, fields: nil, resolve_type: nil, __reference__: nil
def build(%{attrs: attrs}) do
fields = Type.Field.build(attrs[:fields] || [])
quote do: %unquote(__MODULE__){unquote_splicing(attrs), fields: unquote(fields)}
end
@spec resolve_type(Type.Interface.t, any, Execution.Field.t) :: Type.t | nil
def resolve_type(%{resolve_type: nil, __reference__: %{identifier: ident}}, obj, %{schema: schema}) do
implementors = Schema.implementors(schema, ident)
Enum.find(implementors, fn
%{is_type_of: nil} ->
false
type ->
type.is_type_of.(obj)
end)
end
def resolve_type(%{resolve_type: resolver}, obj, %{schema: schema} = env) do
case resolver.(obj, env) do
nil ->
nil
ident when is_atom(ident) ->
Schema.lookup_type(schema, ident)
end
end
@doc """
Whether the interface (or implementors) are correctly configured to resolve
objects.
"""
@spec type_resolvable?(Schema.t, t) :: boolean
def type_resolvable?(schema, %{resolve_type: nil} = iface) do
Schema.implementors(schema, iface)
|> Enum.all?(&(&1.is_type_of))
end
def type_resolvable?(_, %{resolve_type: _}) do
true
end
@doc false
@spec member?(t, Type.t) :: boolean
def member?(%{__reference__: %{identifier: ident}}, %{interfaces: ifaces}) do
ident in ifaces
end
def member?(_, _) do
false
end
@spec implements?(Type.Interface.t, Type.Object.t) :: boolean
def implements?(interface, type) do
# Convert the submap into a list of key-value pairs where each key
# is a list representing the keypath of its corresponding value.
flatten_with_list_keys(interface.fields)
# Check that every keypath has the same value in both maps
# (assumes that `nil` is not a legitimate value)
|> Enum.all?(fn
{keypath, val} when val != nil ->
flat = keypath |> List.flatten
ignore_implementing_keypath?(flat) || (safe_get_in(type.fields, flat) == val)
{_keypath, nil} ->
true
end)
end
# Try to get a value, ignoring errors
@spec safe_get_in(any, list) :: any
defp safe_get_in(target, keypath) do
try do
get_in(target, keypath)
rescue
FunctionClauseError ->
nil
end
end
@ignore [:description, :__reference__]
defp ignore_implementing_keypath?(keypath) when is_list(keypath) do
keypath
|> Enum.any?(&ignore_implementing_keypath?/1)
end
defp ignore_implementing_keypath?(keypath) when is_atom(keypath) do
Enum.member?(@ignore, keypath)
end
defp flatten_with_list_keys(map) do
Enum.flat_map(Map.to_list(map), fn
{:__struct__, _} ->
[]
{key, map} when is_map(map) ->
for {subkey, val} <- flatten_with_list_keys(map), do: {[key | [subkey]], val}
other ->
[other]
end)
end
end