diff --git a/lib/absinthe/blueprint/schema/interface_type_definition.ex b/lib/absinthe/blueprint/schema/interface_type_definition.ex index b1fd11be9e..26dfbcd048 100644 --- a/lib/absinthe/blueprint/schema/interface_type_definition.ex +++ b/lib/absinthe/blueprint/schema/interface_type_definition.ex @@ -45,13 +45,15 @@ defmodule Absinthe.Blueprint.Schema.InterfaceTypeDefinition do identifier: type_def.identifier, resolve_type: type_def.resolve_type, definition: type_def.module, - interfaces: type_def.interfaces + interfaces: Enum.map(type_def.interfaces, &Blueprint.TypeReference.to_type(&1, schema)) } end def find_implementors(iface, type_defs) do + type_ref = %Blueprint.TypeReference.Identifier{id: iface.identifier} + for %Schema.ObjectTypeDefinition{} = obj <- type_defs, - iface.identifier in obj.interfaces, + type_ref in obj.interfaces, do: obj.identifier end diff --git a/lib/absinthe/blueprint/schema/object_type_definition.ex b/lib/absinthe/blueprint/schema/object_type_definition.ex index 8e7bb9d7bd..cec8837496 100644 --- a/lib/absinthe/blueprint/schema/object_type_definition.ex +++ b/lib/absinthe/blueprint/schema/object_type_definition.ex @@ -47,7 +47,7 @@ defmodule Absinthe.Blueprint.Schema.ObjectTypeDefinition do name: type_def.name, description: type_def.description, fields: build_fields(type_def, schema), - interfaces: type_def.interfaces, + interfaces: Enum.map(type_def.interfaces, &Blueprint.TypeReference.to_type(&1, schema)), definition: type_def.module, is_type_of: type_def.is_type_of, __private__: type_def.__private__ diff --git a/lib/absinthe/blueprint/type_reference.ex b/lib/absinthe/blueprint/type_reference.ex index a60d7a22a0..7c7ff81993 100644 --- a/lib/absinthe/blueprint/type_reference.ex +++ b/lib/absinthe/blueprint/type_reference.ex @@ -45,6 +45,10 @@ defmodule Absinthe.Blueprint.TypeReference do name end + def name(%__MODULE__.Identifier{id: id}) do + name(id) + end + def name(name) do name |> to_string() |> String.capitalize() end diff --git a/lib/absinthe/language/interface_type_definition.ex b/lib/absinthe/language/interface_type_definition.ex index 444808963d..8e6a895a84 100644 --- a/lib/absinthe/language/interface_type_definition.ex +++ b/lib/absinthe/language/interface_type_definition.ex @@ -36,7 +36,10 @@ defmodule Absinthe.Language.InterfaceTypeDefinition do defp interfaces(interfaces, doc) do interfaces |> Absinthe.Blueprint.Draft.convert(doc) - |> Enum.map(&(&1.name |> Macro.underscore() |> String.to_atom())) + |> Enum.map(fn interface -> + id = interface.name |> Macro.underscore() |> String.to_atom() + %Absinthe.Blueprint.TypeReference.Identifier{id: id} + end) end defp source_location(%{loc: nil}), do: nil diff --git a/lib/absinthe/language/object_type_definition.ex b/lib/absinthe/language/object_type_definition.ex index 4ca8d55603..77011219fe 100644 --- a/lib/absinthe/language/object_type_definition.ex +++ b/lib/absinthe/language/object_type_definition.ex @@ -39,7 +39,10 @@ defmodule Absinthe.Language.ObjectTypeDefinition do defp interfaces(interfaces, doc) do interfaces |> Absinthe.Blueprint.Draft.convert(doc) - |> Enum.map(&(&1.name |> Macro.underscore() |> String.to_atom())) + |> Enum.map(fn interface -> + id = interface.name |> Macro.underscore() |> String.to_atom() + %Absinthe.Blueprint.TypeReference.Identifier{id: id} + end) end end end diff --git a/lib/absinthe/phase/schema/introspection.ex b/lib/absinthe/phase/schema/introspection.ex index b0e8081e7a..3c831f3fb2 100644 --- a/lib/absinthe/phase/schema/introspection.ex +++ b/lib/absinthe/phase/schema/introspection.ex @@ -63,7 +63,7 @@ defmodule Absinthe.Phase.Schema.Introspection do name: "__typename", identifier: :__typename, module: __MODULE__, - type: :string, + type: %Absinthe.Blueprint.TypeReference.Identifier{id: :string}, description: "The name of the object type currently being queried.", complexity: 0, triggers: %{}, @@ -80,7 +80,7 @@ defmodule Absinthe.Phase.Schema.Introspection do __reference__: Absinthe.Schema.Notation.build_reference(__ENV__), name: "__type", identifier: :__type, - type: :__type, + type: %Absinthe.Blueprint.TypeReference.Identifier{id: :__type}, module: __MODULE__, description: "Represents scalars, interfaces, object types, unions, enums in the system", triggers: %{}, @@ -105,7 +105,7 @@ defmodule Absinthe.Phase.Schema.Introspection do %FieldDefinition{ name: "__schema", identifier: :__schema, - type: :__schema, + type: %Absinthe.Blueprint.TypeReference.Identifier{id: :__schema}, module: __MODULE__, description: "Represents the schema", triggers: %{}, diff --git a/lib/absinthe/phase/schema/validation/interfaces_must_resolve_types.ex b/lib/absinthe/phase/schema/validation/interfaces_must_resolve_types.ex index 5c774dce44..263232214f 100644 --- a/lib/absinthe/phase/schema/validation/interfaces_must_resolve_types.ex +++ b/lib/absinthe/phase/schema/validation/interfaces_must_resolve_types.ex @@ -14,7 +14,7 @@ defmodule Absinthe.Phase.Schema.Validation.InterfacesMustResolveTypes do schema.type_definitions |> Enum.filter(&match?(%Blueprint.Schema.ObjectTypeDefinition{}, &1)) |> Enum.flat_map(fn obj -> - for iface <- obj.interfaces do + for %{id: iface} <- obj.interfaces do {iface, obj} end end) diff --git a/lib/absinthe/phase/schema/validation/no_interface_cycles.ex b/lib/absinthe/phase/schema/validation/no_interface_cycles.ex index ec361e4edd..e658c904fa 100644 --- a/lib/absinthe/phase/schema/validation/no_interface_cycles.ex +++ b/lib/absinthe/phase/schema/validation/no_interface_cycles.ex @@ -42,7 +42,7 @@ defmodule Absinthe.Phase.Schema.Validation.NoInterfaceCyles do defp vertex(%Schema.InterfaceTypeDefinition{} = implementor, graph) do :digraph.add_vertex(graph, implementor.identifier) - for interface <- implementor.interfaces do + for interface <- Enum.map(implementor.interfaces, & &1.id) do edge(implementor, interface, graph) end diff --git a/lib/absinthe/phase/schema/validation/object_interfaces_must_be_valid.ex b/lib/absinthe/phase/schema/validation/object_interfaces_must_be_valid.ex index 8aca52041f..a9d13ad67a 100644 --- a/lib/absinthe/phase/schema/validation/object_interfaces_must_be_valid.ex +++ b/lib/absinthe/phase/schema/validation/object_interfaces_must_be_valid.ex @@ -44,13 +44,14 @@ defmodule Absinthe.Phase.Schema.Validation.ObjectInterfacesMustBeValid do implemented_by, visited ) do - current_interface = all_interfaces[object_interface] + current_interface = all_interfaces[object_interface.id] - if current_interface && current_interface.identifier in object.interfaces do + if current_interface && + %Blueprint.TypeReference.Identifier{id: current_interface.identifier} in object.interfaces do case current_interface do %{interfaces: interfaces} = interface -> # to prevent walking in cycles we need to filter out visited interfaces - interfaces = Enum.filter(interfaces, &(&1 not in visited)) + interfaces = interfaces |> Enum.filter(&(&1 not in visited)) check_transitive_interfaces(object, tail ++ interfaces, all_interfaces, interface, [ object_interface | visited @@ -64,7 +65,7 @@ defmodule Absinthe.Phase.Schema.Validation.ObjectInterfacesMustBeValid do else detail = %{ object: object.identifier, - interface: object_interface, + interface: object_interface.id, implemented_by: implemented_by } @@ -93,7 +94,7 @@ defmodule Absinthe.Phase.Schema.Validation.ObjectInterfacesMustBeValid do def explanation(%{object: obj, interface: interface, implemented_by: nil}) do """ - Type "#{obj}" must implement interface type "#{interface}" + Type "#{obj}" must implement interface type "#{inspect(interface)}" #{@description} """ diff --git a/lib/absinthe/phase/schema/validation/object_must_implement_interfaces.ex b/lib/absinthe/phase/schema/validation/object_must_implement_interfaces.ex index d25fff0b27..185dc5887a 100644 --- a/lib/absinthe/phase/schema/validation/object_must_implement_interfaces.ex +++ b/lib/absinthe/phase/schema/validation/object_must_implement_interfaces.ex @@ -36,7 +36,7 @@ defmodule Absinthe.Phase.Schema.Validation.ObjectMustImplementInterfaces do end defp validate_objects(%struct{} = object, ifaces, types) when struct in @interface_types do - Enum.reduce(object.interfaces, object, fn ident, object -> + Enum.reduce(object.interfaces, object, fn %{id: ident}, object -> case Map.fetch(ifaces, ident) do {:ok, iface} -> validate_object(object, iface, types) _ -> object @@ -162,6 +162,7 @@ defmodule Absinthe.Phase.Schema.Validation.ObjectMustImplementInterfaces do types ) do %{interfaces: field_type_interfaces} = Map.get(types, field_type) + field_type_interfaces = Enum.map(field_type_interfaces, & &1.id) (interface_ident in field_type_interfaces && :ok) || {:error, field_ident} end @@ -196,6 +197,15 @@ defmodule Absinthe.Phase.Schema.Validation.ObjectMustImplementInterfaces do :ok end + defp check_covariant( + %Blueprint.TypeReference.Identifier{id: id}, + %Blueprint.TypeReference.Identifier{id: id}, + _field_ident, + _types + ) do + :ok + end + defp check_covariant( %Blueprint.TypeReference.Name{name: iface_name}, %Blueprint.TypeReference.Name{name: type_name}, @@ -208,6 +218,15 @@ defmodule Absinthe.Phase.Schema.Validation.ObjectMustImplementInterfaces do check_covariant(itype, type, field_ident, types) end + defp check_covariant( + %Blueprint.TypeReference.Identifier{id: iface_identifier}, + %Blueprint.TypeReference.Identifier{id: type_identifier}, + field_ident, + types + ) do + check_covariant(iface_identifier, type_identifier, field_ident, types) + end + defp check_covariant(nil, _, field_ident, _), do: {:error, field_ident} defp check_covariant(_, nil, field_ident, _), do: {:error, field_ident} diff --git a/lib/absinthe/phase/schema/validation/type_references_exist.ex b/lib/absinthe/phase/schema/validation/type_references_exist.ex index badac8900c..49792b4d12 100644 --- a/lib/absinthe/phase/schema/validation/type_references_exist.ex +++ b/lib/absinthe/phase/schema/validation/type_references_exist.ex @@ -131,6 +131,10 @@ defmodule Absinthe.Phase.Schema.Validation.TypeReferencesExist do name end + defp inner_type(%Absinthe.Blueprint.TypeReference.Identifier{id: id}) do + id + end + defp unwrapped?(value) when is_binary(value) or is_atom(value), do: {:ok, value} defp unwrapped?(%Absinthe.Blueprint.TypeReference.Name{name: name}), do: {:ok, name} defp unwrapped?(%Absinthe.Blueprint.TypeReference.Identifier{id: id}), do: {:ok, id} diff --git a/lib/absinthe/schema/notation.ex b/lib/absinthe/schema/notation.ex index 56bc5b9d54..18bc58acba 100644 --- a/lib/absinthe/schema/notation.ex +++ b/lib/absinthe/schema/notation.ex @@ -502,12 +502,25 @@ defmodule Absinthe.Schema.Notation do |> Keyword.delete(:directives) |> Keyword.delete(:args) |> Keyword.delete(:meta) + |> Keyword.update(:type, nil, &wrap_type/1) |> Keyword.update(:description, nil, &wrap_in_unquote/1) |> Keyword.update(:default_value, nil, &wrap_in_unquote/1) {attrs, block} end + defp wrap_type(type) when is_atom(type) do + %Absinthe.Blueprint.TypeReference.Identifier{id: type} + end + + defp wrap_type(%{of_type: type} = type_reference) do + %{type_reference | of_type: wrap_type(type)} + end + + defp wrap_type(%Absinthe.Blueprint.TypeReference.Identifier{} = type) do + type + end + # FIELDS @placement {:field, [under: [:input_object, :interface, :object, :schema_declaration]]} @doc """ @@ -748,7 +761,7 @@ defmodule Absinthe.Schema.Notation do ``` """ defmacro arg(identifier, type, attrs) do - {attrs, block} = handle_arg_attrs(identifier, type, attrs) + {attrs, block} = handle_arg_attrs(identifier, Keyword.put(attrs, :type, type), __CALLER__) __CALLER__ |> recordable!(:arg, @placement[:arg]) @@ -761,7 +774,7 @@ defmodule Absinthe.Schema.Notation do See `arg/3` """ defmacro arg(identifier, attrs) when is_list(attrs) do - {attrs, block} = handle_arg_attrs(identifier, nil, attrs) + {attrs, block} = handle_arg_attrs(identifier, attrs, __CALLER__) __CALLER__ |> recordable!(:arg, @placement[:arg]) @@ -769,7 +782,7 @@ defmodule Absinthe.Schema.Notation do end defmacro arg(identifier, type) do - {attrs, block} = handle_arg_attrs(identifier, type, []) + {attrs, block} = handle_arg_attrs(identifier, [type: type], __CALLER__) __CALLER__ |> recordable!(:arg, @placement[:arg]) @@ -1318,7 +1331,8 @@ defmodule Absinthe.Schema.Notation do end defmacro non_null(type) do - %Absinthe.Blueprint.TypeReference.NonNull{of_type: expand_ast(type, __CALLER__)} + type = type |> expand_ast(__CALLER__) |> build_identifier() + %Absinthe.Blueprint.TypeReference.NonNull{of_type: type} end @doc """ @@ -1327,7 +1341,8 @@ defmodule Absinthe.Schema.Notation do See `field/3` for examples """ defmacro list_of(type) do - %Absinthe.Blueprint.TypeReference.List{of_type: expand_ast(type, __CALLER__)} + type = type |> expand_ast(__CALLER__) |> build_identifier() + %Absinthe.Blueprint.TypeReference.List{of_type: type} end @placement {:import_fields, [under: [:input_object, :interface, :object]]} @@ -1561,15 +1576,16 @@ defmodule Absinthe.Schema.Notation do defp reason(msg) when is_binary(msg), do: [reason: msg] defp reason(msg), do: raise(ArgumentError, "Invalid reason: #{msg}") - def handle_arg_attrs(identifier, type, raw_attrs) do + def handle_arg_attrs(identifier, raw_attrs, caller) do block = block_from_directive_attrs(raw_attrs) attrs = raw_attrs + |> expand_ast(caller) |> Keyword.put_new(:name, to_string(identifier)) - |> Keyword.put_new(:type, type) |> Keyword.delete(:directives) |> Keyword.delete(:deprecate) + |> Keyword.update(:type, nil, &wrap_type/1) |> Keyword.update(:description, nil, &wrap_in_unquote/1) |> Keyword.update(:default_value, nil, &wrap_in_unquote/1) @@ -1711,7 +1727,8 @@ defmodule Absinthe.Schema.Notation do @doc false # Record an implemented interface in the current scope def record_interface!(env, type) do - type = expand_ast(type, env) + type = type |> expand_ast(env) |> build_identifier + put_attr(env.module, {:interface, type}) end @@ -1735,8 +1752,9 @@ defmodule Absinthe.Schema.Notation do Enum.each(types, &record_type!(env, &1)) end - defp record_type!(env, type) do - type = expand_ast(type, env) + def record_type!(env, type) do + type = type |> expand_ast(env) |> build_identifier() + put_attr(env.module, {:type, type}) end @@ -1917,6 +1935,18 @@ defmodule Absinthe.Schema.Notation do } end + def build_identifier(%wrapper{} = type) + when wrapper in [ + Absinthe.Blueprint.TypeReference.NonNull, + Absinthe.Blueprint.TypeReference.List + ] do + type + end + + def build_identifier(type) when is_atom(type) or is_binary(type) do + %Absinthe.Blueprint.TypeReference.Identifier{id: type} + end + @scope_map %{ Schema.ObjectTypeDefinition => :object, Schema.FieldDefinition => :field, diff --git a/lib/absinthe/schema/notation/sdl_render.ex b/lib/absinthe/schema/notation/sdl_render.ex index baac5a2429..613cf25d34 100644 --- a/lib/absinthe/schema/notation/sdl_render.ex +++ b/lib/absinthe/schema/notation/sdl_render.ex @@ -114,9 +114,6 @@ defmodule Absinthe.Schema.Notation.SDL.Render do defp render(%Blueprint.Schema.UnionTypeDefinition{} = union_type, type_definitions) do Enum.map(union_type.types, fn - identifier when is_atom(identifier) -> - render(%Blueprint.TypeReference.Identifier{id: identifier}, type_definitions) - %Blueprint.TypeReference.Name{} = ref -> render(ref, type_definitions) @@ -246,10 +243,6 @@ defmodule Absinthe.Schema.Notation.SDL.Render do raise "Unexpected nil" end - defp render(identifier, type_definitions) when is_atom(identifier) do - render(%Blueprint.TypeReference.Identifier{id: identifier}, type_definitions) - end - # SDL Syntax Helpers defp directives([], _) do @@ -329,11 +322,18 @@ defmodule Absinthe.Schema.Notation.SDL.Render do interface_names = case interface do %{interface_blueprints: [], interfaces: identifiers} -> - Enum.map(identifiers, fn identifier -> - Enum.find_value(type_definitions, fn - %{identifier: ^identifier, name: name} -> name - _ -> nil - end) + Enum.map(identifiers, fn + %{id: identifier} -> + Enum.find_value(type_definitions, fn + %{identifier: ^identifier, name: name} -> name + _ -> nil + end) + + identifier -> + Enum.find_value(type_definitions, fn + %{identifier: ^identifier, name: name} -> name + _ -> nil + end) end) %{interface_blueprints: blueprints} -> diff --git a/test/absinthe/language/object_type_definition_test.exs b/test/absinthe/language/object_type_definition_test.exs index bd3b882305..46a5710db9 100644 --- a/test/absinthe/language/object_type_definition_test.exs +++ b/test/absinthe/language/object_type_definition_test.exs @@ -54,7 +54,7 @@ defmodule Absinthe.Language.ObjectTypeDefinitionTest do assert %Blueprint.Schema.ObjectTypeDefinition{ name: "Person", - interfaces: [:entity], + interfaces: [%Blueprint.TypeReference.Identifier{id: :entity}], interface_blueprints: [%Blueprint.TypeReference.Name{name: "Entity"}] } = rep end @@ -72,7 +72,7 @@ defmodule Absinthe.Language.ObjectTypeDefinitionTest do assert %Blueprint.Schema.ObjectTypeDefinition{ name: "Person", - interfaces: [:entity], + interfaces: [%Blueprint.TypeReference.Identifier{id: :entity}], interface_blueprints: [%Blueprint.TypeReference.Name{name: "Entity"}], directives: [%{name: "description"}] } = rep diff --git a/test/absinthe/phase/document/validation/arguments_of_correct_type_test.exs b/test/absinthe/phase/document/validation/arguments_of_correct_type_test.exs index 4eb2b69867..4656d47ec7 100644 --- a/test/absinthe/phase/document/validation/arguments_of_correct_type_test.exs +++ b/test/absinthe/phase/document/validation/arguments_of_correct_type_test.exs @@ -1003,7 +1003,6 @@ defmodule Absinthe.Phase.Document.Validation.ArgumentsOfCorrectTypeTest do ) end - @tag :o test "Invalid scalar input on mutation, no suggestion, custom error" do assert_fails_validation( """ diff --git a/test/absinthe/schema/notation/experimental/argument_test.exs b/test/absinthe/schema/notation/experimental/argument_test.exs index a4e2a03ac7..ee2c88323c 100644 --- a/test/absinthe/schema/notation/experimental/argument_test.exs +++ b/test/absinthe/schema/notation/experimental/argument_test.exs @@ -28,12 +28,12 @@ defmodule Absinthe.Schema.Notation.Experimental.ArgumentTest do describe "arg" do test "with a bare type" do - assert %{name: "plain", description: nil, type: :string, identifier: :plain} = + assert %{name: "plain", description: nil, type: %{id: :string}, identifier: :plain} = lookup_argument(Definition, :obj, :field, :plain) end test "with attrs" do - assert %{name: "HasAttrs", type: :boolean, identifier: :with_attrs} = + assert %{name: "HasAttrs", type: %{id: :boolean}, identifier: :with_attrs} = lookup_argument(Definition, :obj, :field, :with_attrs) end diff --git a/test/absinthe/schema/notation/experimental/field_test.exs b/test/absinthe/schema/notation/experimental/field_test.exs index df786121b9..77cbd0e941 100644 --- a/test/absinthe/schema/notation/experimental/field_test.exs +++ b/test/absinthe/schema/notation/experimental/field_test.exs @@ -38,22 +38,22 @@ defmodule Absinthe.Schema.Notation.Experimental.FieldTest do describe "field" do test "without a body and with a bare type" do - assert %{name: "plain", description: nil, type: :string, identifier: :plain} = + assert %{name: "plain", description: nil, type: %{id: :string}, identifier: :plain} = lookup_field(Definition, :obj, :plain) end test "with a body and with a bare type" do - assert %{name: "with_block", type: :string, identifier: :with_block} = + assert %{name: "with_block", type: %{id: :string}, identifier: :with_block} = lookup_field(Definition, :obj, :with_block) end test "with attrs and without a body" do - assert %{name: "HasAttrs", type: :boolean, identifier: :with_attrs} = + assert %{name: "HasAttrs", type: %{id: :boolean}, identifier: :with_attrs} = lookup_field(Definition, :obj, :with_attrs) end test "with attrs and with a body" do - assert %{name: "HasAttrsAndBody", type: :boolean, identifier: :with_attrs_and_body} = + assert %{name: "HasAttrsAndBody", type: %{id: :boolean}, identifier: :with_attrs_and_body} = lookup_field(Definition, :obj, :with_attrs_and_body) end