Skip to content

Commit

Permalink
Override "__" fields from schema (#648)
Browse files Browse the repository at this point in the history
* Support for deep decorations using maps

* Add in test failure for camelized field name resolution

Getting error:

    "Cannot query field \"upcasedTitle\" on type \"Post\".
    Did you mean \"upcasedTitle\"?"

* First pass at overriding "__" fields from schema

* Add Helpers for extending types

* WIP Add actual "__fields" to objects

There seems to be an issue with fragments

* Fix Union and Interface types

* Re-add custom introspection types

* Fix formatting

* First pass at fixing camelized field

* Run Formatter

* First pass at decorator adding fields

* Drop fields from decorator

* Fix pipeline order

* Record Enum Identifier for Enum Values

This allows mapping the Enum Value back to its type

* Append path to error message

This allows mutations to identify the input field which was in error

* Fix doc typo
  • Loading branch information
michaelkschmidt authored and benwilson512 committed Apr 18, 2019
1 parent b7b7102 commit 6a8f585
Show file tree
Hide file tree
Showing 22 changed files with 938 additions and 35 deletions.
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

0 comments on commit 6a8f585

Please sign in to comment.