Current section
Files
Jump to
Current section
Files
lib/absinthe/federation/schema/entities_field.ex
defmodule Absinthe.Federation.Schema.EntitiesField do
@moduledoc false
alias Absinthe.Blueprint
alias Absinthe.Blueprint.Schema.FieldDefinition
alias Absinthe.Blueprint.Schema.InputValueDefinition
alias Absinthe.Blueprint.TypeReference.List, as: ListType
alias Absinthe.Blueprint.TypeReference.Name
alias Absinthe.Blueprint.TypeReference.NonNull
alias Absinthe.Schema.Notation
# TODO: Fix __reference__ typespec upstream in absinthe
@type input_value_definition :: %InputValueDefinition{
name: String.t(),
description: nil | String.t(),
type: Blueprint.TypeReference.t(),
default_value: nil | Blueprint.Input.t(),
default_value_blueprint: Blueprint.Draft.t(),
directives: [Blueprint.Directive.t()],
source_location: nil | Blueprint.SourceLocation.t(),
# The struct module of the parent
placement: :argument_definition | :input_field_definition,
# Added by phases
flags: Blueprint.flags_t(),
errors: [Absinthe.Phase.Error.t()],
__reference__: nil | map()
}
@type field_definition :: %FieldDefinition{
name: String.t(),
identifier: atom,
description: nil | String.t(),
deprecation: nil | Blueprint.Schema.Deprecation.t(),
arguments: [input_value_definition()],
type: Blueprint.TypeReference.t(),
directives: [Blueprint.Directive.t()],
source_location: nil | Blueprint.SourceLocation.t(),
# Added by DSL
description: nil | String.t(),
middleware: [any],
# Added by phases
flags: Blueprint.flags_t(),
errors: [Absinthe.Phase.Error.t()],
triggers: [],
module: nil | module(),
function_ref: nil | function(),
default_value: nil | any(),
config: nil,
complexity: nil,
__reference__: nil | map(),
__private__: []
}
@spec build() :: field_definition()
def build() do
%FieldDefinition{
__reference__: Notation.build_reference(__ENV__),
description: """
Returns a non-nullable list of _Entity types
and have a single argument with an argument name of representations
and type [_Any!]! (non-nullable list of non-nullable _Any scalars).
The _entities field on the query root must allow a list of _Any scalars
which are "representations" of entities from external services.
These representations should be validated with the following rules:
- Any representation without a __typename: String field is invalid.
- Representations must contain at least the fields defined in the fieldset of a @key directive on the base type.
""",
identifier: :_entities,
module: __MODULE__,
name: "_entities",
type: %NonNull{
of_type: %ListType{
of_type: %Name{
name: "_Entity"
}
}
},
middleware: [{Absinthe.Resolution, &__MODULE__.resolver/3}],
arguments: build_arguments()
}
end
def resolver(parent, %{representations: representations}, resolution) do
Enum.reduce_while(representations, {:ok, []}, &entity_accumulator(&1, &2, parent, resolution))
end
defp entity_accumulator(representation, {:ok, entities}, parent, %{schema: schema} = resolution) do
typename = Map.get(representation, "__typename")
schema
|> Absinthe.Schema.lookup_type(typename)
|> resolve_representation(parent, representation, resolution)
|> case do
{:ok, entity} ->
{:cont, {:ok, entities ++ [entity]}}
{:error, _} = error ->
{:halt, error}
end
end
defp entity_accumulator(_representation, result, _parent, _schema), do: result
defp resolve_representation(
%struct_type{fields: fields},
parent,
representation,
resolution
)
when struct_type in [Absinthe.Type.Object, Absinthe.Type.Interface],
do: resolve_reference(fields[:_resolve_reference], parent, representation, resolution)
defp resolve_representation(_schema_type, _parent, representation, _schema),
do:
{:error,
"The _entities resolver tried to load an entity for type '#{Map.get(representation, "__typename")}', but no object type of that name was found in the schema"}
defp resolve_reference(nil, _parent, representation, _resolution), do: {:ok, representation}
defp resolve_reference(%{middleware: middleware}, parent, representation, %{schema: schema} = resolution) do
args = for {key, val} <- representation, into: %{}, do: {String.to_atom(key), val}
middleware
|> Absinthe.Middleware.unshim(schema)
|> Enum.filter(&only_resolver_middleware/1)
|> List.first()
|> case do
{_, resolve_ref_func} when is_function(resolve_ref_func, 2) ->
resolve_ref_func.(args, resolution)
{_, resolve_ref_func} when is_function(resolve_ref_func, 3) ->
resolve_ref_func.(parent, args, resolution)
_ ->
{:ok, representation}
end
end
defp only_resolver_middleware({{Absinthe.Resolution, :call}, _}), do: true
defp only_resolver_middleware(_), do: false
defp build_arguments(), do: [build_argument()]
defp build_argument(),
do: %InputValueDefinition{
__reference__: Notation.build_reference(__ENV__),
identifier: :representations,
module: __MODULE__,
name: "representations",
placement: :argument_definition,
type: %NonNull{
of_type: %ListType{
of_type: %NonNull{
of_type: %Name{
name: "_Any"
}
}
}
}
}
end