Skip to content

Commit

Permalink
Remove federated types/fields from sdl render (#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
kdawgwilk authored Sep 24, 2021
1 parent 637849d commit 4b7270e
Show file tree
Hide file tree
Showing 9 changed files with 180 additions and 104 deletions.
24 changes: 24 additions & 0 deletions lib/absinthe/federation.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,28 @@ defmodule Absinthe.Federation do
end
"""

@spec remove_federated_types_pipeline(schema :: Absinthe.Schema.t()) :: Absinthe.Pipeline.t()
def remove_federated_types_pipeline(schema) do
schema
|> Absinthe.Pipeline.for_schema(prototype_schema: schema.__absinthe_prototype_schema__())
|> Absinthe.Pipeline.upto({Absinthe.Phase.Schema.Validation.Result, pass: :final})
|> Absinthe.Schema.apply_modifiers(schema)
|> Absinthe.Pipeline.without(__MODULE__.Schema.Phase.AddFederatedTypes)
|> Absinthe.Pipeline.insert_before(
Absinthe.Phase.Schema.ApplyDeclaration,
__MODULE__.Schema.Phase.RemoveResolveReferenceFields
)
end

@spec to_federated_sdl(schema :: Absinthe.Schema.t()) :: String.t()
def to_federated_sdl(schema) do
pipeline = remove_federated_types_pipeline(schema)

# we can be assertive here, since this same pipeline was already used to
# successfully compile the schema.
{:ok, bp, _} = Absinthe.Pipeline.run(schema.__absinthe_blueprint__(), pipeline)

Absinthe.Schema.Notation.SDL.Render.inspect(bp, %{pretty: true})
end
end
3 changes: 2 additions & 1 deletion lib/absinthe/federation/schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ defmodule Absinthe.Federation.Schema do
"""
def pipeline(pipeline) do
pipeline
|> Pipeline.insert_after(TypeImports, __MODULE__.Phase)
|> Pipeline.insert_after(TypeImports, __MODULE__.Phase.AddFederatedDirectives)
|> Pipeline.insert_after(TypeImports, __MODULE__.Phase.AddFederatedTypes)
end
end
94 changes: 0 additions & 94 deletions lib/absinthe/federation/schema/phase.ex
Original file line number Diff line number Diff line change
@@ -1,97 +1,3 @@
defmodule Absinthe.Federation.Schema.Phase do
@moduledoc false

use Absinthe.Phase

alias Absinthe.Blueprint
alias Absinthe.Blueprint.Schema
alias Absinthe.Federation.Schema.Directive
alias Absinthe.Federation.Schema.EntitiesField
alias Absinthe.Federation.Schema.EntityUnion
alias Absinthe.Federation.Schema.ServiceField
alias Absinthe.Type

@dialyzer {:nowarn_function, add_directive: 2}

def run(%Blueprint{} = blueprint, _) do
blueprint = Blueprint.postwalk(blueprint, &collect_types/1)
{:ok, blueprint}
end

@spec collect_types(Blueprint.node_t()) :: Blueprint.node_t()
defp collect_types(%Schema.SchemaDefinition{type_definitions: type_definitions} = node) do
entity_union = EntityUnion.build(node)

%{node | type_definitions: [entity_union | type_definitions]}
end

defp collect_types(%Schema.ObjectTypeDefinition{identifier: :query, fields: fields} = node) do
service_field = ServiceField.build()
entities_field = EntitiesField.build()
%{node | fields: [service_field, entities_field] ++ fields}
end

defp collect_types(%{__private__: _private} = node) do
meta = Type.meta(node)
maybe_add_directives(node, meta)
end

defp collect_types(node), do: node

@spec maybe_add_directives(term(), any()) :: term()
defp maybe_add_directives(node, meta) do
node
|> maybe_add_key_directive(meta)
|> maybe_add_external_directive(meta)
|> maybe_add_requires_directive(meta)
|> maybe_add_provides_directive(meta)
|> maybe_add_extends_directive(meta)
end

@spec maybe_add_key_directive(term(), map()) :: term()
defp maybe_add_key_directive(node, %{key_fields: fields}) do
directive = Directive.build("key", fields: fields)

add_directive(node, directive)
end

defp maybe_add_key_directive(node, _meta), do: node

defp maybe_add_external_directive(node, %{external: true}) do
directive = Directive.build("external")

add_directive(node, directive)
end

defp maybe_add_external_directive(node, _meta), do: node

defp maybe_add_requires_directive(node, %{requires_fields: fields}) do
directive = Directive.build("requires", fields: fields)

add_directive(node, directive)
end

defp maybe_add_requires_directive(node, _meta), do: node

defp maybe_add_provides_directive(node, %{provides_fields: fields}) do
directive = Directive.build("provides", fields: fields)

add_directive(node, directive)
end

defp maybe_add_provides_directive(node, _meta), do: node

defp maybe_add_extends_directive(node, %{extends: true}) do
directive = Directive.build("extends")

add_directive(node, directive)
end

defp maybe_add_extends_directive(node, _meta), do: node

defp add_directive(%{directives: directives} = node, directive) do
%{node | directives: [directive | directives]}
end

defp add_directive(node, _directive), do: node
end
80 changes: 80 additions & 0 deletions lib/absinthe/federation/schema/phase/add_federated_directives.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
defmodule Absinthe.Federation.Schema.Phase.AddFederatedDirectives do
@moduledoc false

use Absinthe.Phase

alias Absinthe.Blueprint
alias Absinthe.Federation.Schema.Directive
alias Absinthe.Type

@dialyzer {:nowarn_function, add_directive: 2}

def run(%Blueprint{} = blueprint, _) do
blueprint = Blueprint.postwalk(blueprint, &collect_types/1)
{:ok, blueprint}
end

defp collect_types(%{__private__: _private} = node) do
meta = Type.meta(node)
maybe_add_directives(node, meta)
end

defp collect_types(node), do: node

@spec maybe_add_directives(term(), any()) :: term()
defp maybe_add_directives(node, meta) do
node
|> maybe_add_key_directive(meta)
|> maybe_add_external_directive(meta)
|> maybe_add_requires_directive(meta)
|> maybe_add_provides_directive(meta)
|> maybe_add_extends_directive(meta)
end

@spec maybe_add_key_directive(term(), map()) :: term()
defp maybe_add_key_directive(node, %{key_fields: fields}) do
directive = Directive.build("key", fields: fields)

add_directive(node, directive)
end

defp maybe_add_key_directive(node, _meta), do: node

defp maybe_add_external_directive(node, %{external: true}) do
directive = Directive.build("external")

add_directive(node, directive)
end

defp maybe_add_external_directive(node, _meta), do: node

defp maybe_add_requires_directive(node, %{requires_fields: fields}) do
directive = Directive.build("requires", fields: fields)

add_directive(node, directive)
end

defp maybe_add_requires_directive(node, _meta), do: node

defp maybe_add_provides_directive(node, %{provides_fields: fields}) do
directive = Directive.build("provides", fields: fields)

add_directive(node, directive)
end

defp maybe_add_provides_directive(node, _meta), do: node

defp maybe_add_extends_directive(node, %{extends: true}) do
directive = Directive.build("extends")

add_directive(node, directive)
end

defp maybe_add_extends_directive(node, _meta), do: node

defp add_directive(%{directives: directives} = node, directive) do
%{node | directives: [directive | directives]}
end

defp add_directive(node, _directive), do: node
end
35 changes: 35 additions & 0 deletions lib/absinthe/federation/schema/phase/add_federated_types.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
defmodule Absinthe.Federation.Schema.Phase.AddFederatedTypes do
@moduledoc """
https://www.apollographql.com/docs/federation/federation-spec/#query_service
The federation schema modifications (i.e. new types and directive definitions) should not be included in this SDL.
"""

use Absinthe.Phase

alias Absinthe.Blueprint
alias Absinthe.Blueprint.Schema
alias Absinthe.Federation.Schema.EntitiesField
alias Absinthe.Federation.Schema.EntityUnion
alias Absinthe.Federation.Schema.ServiceField

def run(%Blueprint{} = blueprint, _) do
blueprint = Blueprint.postwalk(blueprint, &collect_types/1)
{:ok, blueprint}
end

@spec collect_types(Blueprint.node_t()) :: Blueprint.node_t()
defp collect_types(%Schema.SchemaDefinition{type_definitions: type_definitions} = node) do
entity_union = EntityUnion.build(node)

%{node | type_definitions: [entity_union | type_definitions]}
end

defp collect_types(%Schema.ObjectTypeDefinition{identifier: :query, fields: fields} = node) do
service_field = ServiceField.build()
entities_field = EntitiesField.build()
%{node | fields: [service_field, entities_field] ++ fields}
end

defp collect_types(node), do: node
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
defmodule Absinthe.Federation.Schema.Phase.RemoveResolveReferenceFields do
@moduledoc false

use Absinthe.Phase

alias Absinthe.Blueprint

def run(%Blueprint{} = blueprint, _) do
blueprint = Blueprint.postwalk(blueprint, &remove_resolve_reference_fields/1)
{:ok, blueprint}
end

@spec remove_resolve_reference_fields(Blueprint.node_t()) :: Blueprint.node_t()
defp remove_resolve_reference_fields(%{fields: fields} = node) when is_list(fields) do
remove_field(node, :_resolve_reference)
end

defp remove_resolve_reference_fields(node), do: node

defp remove_field(%{fields: fields} = node, field) when is_list(fields) and is_atom(field) do
filtered_fields = Enum.reject(fields, &(&1.identifier == field))
%{node | fields: filtered_fields}
end
end
3 changes: 1 addition & 2 deletions lib/absinthe/federation/schema/service_field.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ defmodule Absinthe.Federation.Schema.ServiceField do

alias Absinthe.Blueprint.Schema.FieldDefinition
alias Absinthe.Blueprint.TypeReference.NonNull
alias Absinthe.Schema
alias Absinthe.Schema.Notation

def build() do
Expand All @@ -27,6 +26,6 @@ defmodule Absinthe.Federation.Schema.ServiceField do
end

def resolver(_parent, _args, %{schema: schema} = _resolution) do
{:ok, %{sdl: Schema.to_sdl(schema)}}
{:ok, %{sdl: Absinthe.Federation.to_federated_sdl(schema)}}
end
end
6 changes: 1 addition & 5 deletions lib/absinthe/mix/tasks/absinthe.federation.schema.sdl.ex
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,7 @@ defmodule Mix.Tasks.Absinthe.Federation.Schema.Sdl do
end

def generate_schema(%Options{schema: schema}) do
pipeline =
schema
|> Absinthe.Pipeline.for_schema(prototype_schema: schema.__absinthe_prototype_schema__())
|> Absinthe.Pipeline.upto({Absinthe.Phase.Schema.Validation.Result, pass: :final})
|> Absinthe.Schema.apply_modifiers(schema)
pipeline = Absinthe.Federation.remove_federated_types_pipeline(schema)

with {:ok, blueprint, _phases} <-
Absinthe.Pipeline.run(
Expand Down
15 changes: 13 additions & 2 deletions test/absinthe/federation/schema/service_field_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,15 @@ defmodule Absinthe.Federation.Schema.ServiceFieldTest do
use Absinthe.Federation.Schema

query do
field :current_user, :user
end

object :user do
key_fields("id")
field :id, non_null(:id)

field :_resolve_reference, :user do
end
end
end

Expand All @@ -96,7 +105,7 @@ defmodule Absinthe.Federation.Schema.ServiceFieldTest do
refute is_nil(sdl)
end

test "returns proper sdl" do
test "returns sdl with federated types/fields removed" do
query = """
{
_service {
Expand All @@ -107,7 +116,9 @@ defmodule Absinthe.Federation.Schema.ServiceFieldTest do

assert %{data: %{"_service" => %{"sdl" => sdl}}} = Absinthe.run!(query, TestSchema)

assert sdl =~ "_service: _Service"
assert sdl =~ "query: RootQueryType"
refute sdl =~ "_service: _Service"
refute sdl =~ "_resolveReference"
end
end
end

0 comments on commit 4b7270e

Please sign in to comment.