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

Eliminate bare atom identifiers #1198

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
6 changes: 4 additions & 2 deletions lib/absinthe/blueprint/schema/interface_type_definition.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion lib/absinthe/blueprint/schema/object_type_definition.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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__
Expand Down
4 changes: 4 additions & 0 deletions lib/absinthe/blueprint/type_reference.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion lib/absinthe/language/interface_type_definition.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion lib/absinthe/language/object_type_definition.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 3 additions & 3 deletions lib/absinthe/phase/schema/introspection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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: %{},
Expand All @@ -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: %{},
Expand All @@ -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: %{},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}

Expand Down Expand Up @@ -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}
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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},
Expand All @@ -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}

Expand Down
4 changes: 4 additions & 0 deletions lib/absinthe/phase/schema/validation/type_references_exist.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
50 changes: 40 additions & 10 deletions lib/absinthe/schema/notation.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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 """
Expand Down Expand Up @@ -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])
Expand All @@ -761,15 +774,15 @@ 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])
|> record!(Schema.InputValueDefinition, identifier, attrs, block)
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])
Expand Down Expand Up @@ -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 """
Expand All @@ -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]]}
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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

Expand All @@ -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

Expand Down Expand Up @@ -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,
Expand Down
24 changes: 12 additions & 12 deletions lib/absinthe/schema/notation/sdl_render.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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} ->
Expand Down
4 changes: 2 additions & 2 deletions test/absinthe/language/object_type_definition_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Loading