Packages
absinthe
1.1.6
1.11.0
1.10.2
1.10.1
1.10.0
1.9.1
1.9.0
1.8.0
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.8
1.6.7
retired
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.6.0-rc.1
1.6.0-rc.0
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.5.0-rc.5
1.5.0-rc.4
1.5.0-rc.3
1.5.0-rc.2
1.5.0-rc.1
1.5.0-rc.0
1.5.0-beta.2
1.5.0-beta.1
1.5.0-beta.0
1.5.0-alpha.4
1.5.0-alpha.3
1.5.0-alpha.2
1.5.0-alpha.1
1.5.0-alpha.0
1.4.16
1.4.15
1.4.14
1.4.13
1.4.12
1.4.11
1.4.10
1.4.9
1.4.8
retired
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.4.0-rc.3
1.4.0-rc.2
1.4.0-rc.1
1.4.0-rc.0
1.4.0-beta.5
1.4.0-beta.4
1.4.0-beta.3
1.4.0-beta.2
1.4.0-beta.1
1.3.2
1.3.1
1.3.0
1.3.0-rc.0
1.3.0-beta.2
1.3.0-beta.1
1.3.0-beta.0
1.2.6
1.2.5
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.2.0-rc.0
1.2.0-beta.0
1.2.0-alpha0
1.2.0-alpha.2
1.2.0-alpha.1
1.1.11
1.1.10
1.1.9
1.1.8
1.1.7
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.0
0.5.2
0.5.1
0.5.0
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.2.3
0.2.2
0.2.1
0.1.0
GraphQL for Elixir
Current section
Files
Jump to
Current section
Files
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