Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Override "__" fields from schema #648

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions lib/absinthe/blueprint.ex
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,70 @@ defmodule Absinthe.Blueprint do

%{blueprint | operations: ops}
end

@doc """
Append the given field or fields to the given type
"""
def extend_fields(blueprint = %Blueprint{}, ext_blueprint = %Blueprint{}) do
ext_types = types_by_name(ext_blueprint)

schema_defs =
for schema_def = %{type_definitions: type_defs} <- blueprint.schema_definitions do
type_defs =
for type_def <- type_defs do
case ext_types[type_def.name] do
nil ->
type_def

%{fields: new_fields} ->
%{type_def | fields: type_def.fields ++ new_fields}
end
end

%{schema_def | type_definitions: type_defs}
end

%{blueprint | schema_definitions: schema_defs}
end

def extend_fields(blueprint, ext_blueprint) when is_atom(ext_blueprint) do
extend_fields(blueprint, ext_blueprint.__absinthe_blueprint__)
end

def add_field(blueprint = %Blueprint{}, type_def_name, new_field) do
schema_defs =
for schema_def = %{type_definitions: type_defs} <- blueprint.schema_definitions do
type_defs =
for type_def <- type_defs do
if type_def.name == type_def_name do
%{type_def | fields: type_def.fields ++ List.wrap(new_field)}
else
type_def
end
end

%{schema_def | type_definitions: type_defs}
end

%{blueprint | schema_definitions: schema_defs}
end

def find_field(%{fields: fields}, name) do
Enum.find(fields, fn field = %{name: field_name} -> field_name == name end)
end

@doc """
Index the types by their name
"""
def types_by_name(blueprint = %Blueprint{}) do
for schema_def = %{type_definitions: type_defs} <- blueprint.schema_definitions,
type_def <- type_defs,
into: %{} do
{type_def.name, type_def}
end
end

def types_by_name(module) when is_atom(module) do
types_by_name(module.__absinthe_blueprint__)
end
end
1 change: 1 addition & 0 deletions lib/absinthe/blueprint/schema/enum_type_definition.ex
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ defmodule Absinthe.Blueprint.Schema.EnumTypeDefinition do
value = %Absinthe.Type.Enum.Value{
name: value_def.name,
value: value_def.value,
enum_identifier: type_def.identifier,
__reference__: value_def.__reference__,
description: value_def.description,
deprecation: value_def.deprecation
Expand Down
45 changes: 42 additions & 3 deletions lib/absinthe/blueprint/schema/union_type_definition.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule Absinthe.Blueprint.Schema.UnionTypeDefinition do
@moduledoc false

alias Absinthe.Blueprint
alias Absinthe.{Blueprint, Type}

@enforce_keys [:name]
defstruct [
Expand All @@ -10,6 +10,7 @@ defmodule Absinthe.Blueprint.Schema.UnionTypeDefinition do
:module,
description: nil,
resolve_type: nil,
fields: [],
directives: [],
types: [],
source_location: nil,
Expand All @@ -31,17 +32,55 @@ defmodule Absinthe.Blueprint.Schema.UnionTypeDefinition do
errors: [Absinthe.Phase.Error.t()]
}

def build(type_def, _schema) do
%Absinthe.Type.Union{
def build(type_def, schema) do
%Type.Union{
name: type_def.name,
description: type_def.description,
identifier: type_def.identifier,
types: type_def.types |> Enum.sort(),
fields: build_fields(type_def, schema),
definition: type_def.module,
resolve_type: type_def.resolve_type
}
end

def build_fields(type_def, schema) do
for field_def <- type_def.fields, into: %{} do
field = %Type.Field{
identifier: field_def.identifier,
middleware: field_def.middleware,
deprecation: field_def.deprecation,
description: field_def.description,
complexity: field_def.complexity,
config: field_def.complexity,
triggers: field_def.triggers,
name: field_def.name,
type: Blueprint.TypeReference.to_type(field_def.type, schema),
args: build_args(field_def, schema),
definition: field_def.module,
__reference__: field_def.__reference__,
__private__: field_def.__private__
}

{field.identifier, field}
end
end

def build_args(field_def, schema) do
Map.new(field_def.arguments, fn arg_def ->
arg = %Type.Argument{
identifier: arg_def.identifier,
name: arg_def.name,
description: arg_def.description,
type: Blueprint.TypeReference.to_type(arg_def.type, schema),
default_value: arg_def.default_value,
deprecation: arg_def.deprecation
}

{arg_def.identifier, arg}
end)
end

@doc false
def functions(), do: [:resolve_type]
end
5 changes: 4 additions & 1 deletion lib/absinthe/phase/document/execution/resolution.ex
Original file line number Diff line number Diff line change
Expand Up @@ -364,13 +364,16 @@ defmodule Absinthe.Phase.Document.Execution.Resolution do
{[], _} ->
raise Absinthe.Resolution.result_error(error_value, bp_field, source)

{[message: message, path: error_path], extra} ->
put_error(result, error(bp_field, message, Enum.reverse(error_path) ++ path, Map.new(extra)))

{[message: message], extra} ->
put_error(result, error(bp_field, message, path, Map.new(extra)))
end
end

defp split_error_value(error_value) when is_list(error_value) or is_map(error_value) do
Keyword.split(Enum.to_list(error_value), [:message])
Keyword.split(Enum.to_list(error_value), [:message, :path])
end

defp split_error_value(error_value) when is_binary(error_value) do
Expand Down
3 changes: 0 additions & 3 deletions lib/absinthe/phase/schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -217,9 +217,6 @@ defmodule Absinthe.Phase.Schema do
# Given a schema type, lookup a child field definition
@spec find_schema_field(nil | Type.t(), String.t(), Absinthe.Schema.t(), Absinthe.Adapter.t()) ::
nil | Type.Field.t()
defp find_schema_field(_, "__" <> introspection_field, _, _) do
Absinthe.Introspection.Field.meta(introspection_field)
end

defp find_schema_field(%{of_type: type}, name, schema, adapter) do
find_schema_field(type, name, schema, adapter)
Expand Down
106 changes: 106 additions & 0 deletions lib/absinthe/phase/schema/decorate.ex
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,117 @@ defmodule Absinthe.Phase.Schema.Decorate do
end

@impl __MODULE__.Decorator

def apply_decoration(node, {:description, text}) do
%{node | description: text}
end

def apply_decoration(node, {:resolve, resolver}) do
%{node | middleware: [{Absinthe.Resolution, resolver}]}
end

def apply_decoration(
node = %{fields: fields},
{:add_fields, new_fields}
)
when is_list(new_fields) do
new_fields = new_fields |> List.wrap()

new_field_names = Enum.map(new_fields, & &1.name)

filtered_fields =
fields
|> Enum.reject(fn %{name: field_name} -> field_name in new_field_names end)

%{node | fields: filtered_fields ++ new_fields}
end

def apply_decoration(
node = %{fields: fields},
{:del_fields, del_field_name}
) do
filtered_fields =
fields
|> Enum.reject(fn %{name: field_name} -> field_name == del_field_name end)

%{node | fields: filtered_fields}
end

@decoration_level1 [
Blueprint.Schema.DirectiveDefinition,
Blueprint.Schema.EnumTypeDefinition,
Blueprint.Schema.InputObjectTypeDefinition,
Blueprint.Schema.InterfaceTypeDefinition,
Blueprint.Schema.ObjectTypeDefinition,
Blueprint.Schema.ScalarTypeDefinition,
Blueprint.Schema.UnionTypeDefinition
]

@decoration_level2 [
Blueprint.Schema.FieldDefinition,
Blueprint.Schema.EnumValueDefinition
]

@decoration_level3 [
Blueprint.Schema.InputValueDefinition
]

def apply_decoration(%Absinthe.Blueprint{} = root, %{} = sub_decorations) do
{root, _} =
Blueprint.prewalk(root, nil, fn
%module{identifier: ident} = node, nil when module in @decoration_level1 ->
case Map.fetch(sub_decorations, ident) do
:error ->
{node, nil}

{:ok, type_decorations} ->
{apply_decorations(node, type_decorations, __MODULE__), nil}
end

node, nil ->
{node, nil}
end)

root
end

def apply_decoration(%module{} = root, %{} = sub_decorations)
when module in @decoration_level1 do
{root, _} =
Blueprint.prewalk(root, nil, fn
%module{identifier: ident} = node, nil when module in @decoration_level2 ->
case Map.fetch(sub_decorations, ident) do
:error ->
{node, nil}

{:ok, type_decorations} ->
{apply_decorations(node, type_decorations, __MODULE__), nil}
end

node, nil ->
{node, nil}
end)

root
end

def apply_decoration(%module{} = root, %{} = sub_decorations)
when module in @decoration_level2 do
{root, _} =
Blueprint.prewalk(root, nil, fn
%module{identifier: ident} = node, nil when module in @decoration_level3 ->
case Map.fetch(sub_decorations, ident) do
:error ->
{node, nil}

{:ok, type_decorations} ->
{apply_decorations(node, type_decorations, __MODULE__), nil}
end

node, nil ->
{node, nil}
end)

root
end
end
3 changes: 2 additions & 1 deletion lib/absinthe/phase/schema/inline_functions.ex
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ defmodule Absinthe.Phase.Schema.InlineFunctions do
end
end

def inline_middleware(%Type.Object{} = type, schema) do
def inline_middleware(%type_name{} = type, schema)
when type_name in [Type.Object, Type.Union, Type.Interface] do
Map.update!(type, :fields, fn fields ->
fields =
Enum.map(fields, fn {field_ident, field} ->
Expand Down
Loading