diff --git a/README.md b/README.md index 43bc1a383..003cf4ff4 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,17 @@ class BaseObject < GraphQL::Schema::Object end ``` +Include the `ApolloFederation::Interface` module in your base interface module: + +```ruby +module BaseInterface + include GraphQL::Schema::Interface + include ApolloFederation::Interface + + field_class BaseField +end +``` + Finally, include the `ApolloFederation::Schema` module in your schema: ```ruby diff --git a/lib/apollo-federation.rb b/lib/apollo-federation.rb index 5083a6c98..7eb4c2144 100644 --- a/lib/apollo-federation.rb +++ b/lib/apollo-federation.rb @@ -3,6 +3,7 @@ require 'apollo-federation/version' require 'apollo-federation/schema' require 'apollo-federation/object' +require 'apollo-federation/interface' require 'apollo-federation/field' require 'apollo-federation/tracing/proto' require 'apollo-federation/tracing/node_map' diff --git a/lib/apollo-federation/federated_document_from_schema_definition.rb b/lib/apollo-federation/federated_document_from_schema_definition.rb index b4f9e5c38..d79b4ec25 100644 --- a/lib/apollo-federation/federated_document_from_schema_definition.rb +++ b/lib/apollo-federation/federated_document_from_schema_definition.rb @@ -26,6 +26,11 @@ def build_object_type_node(object_type) merge_directives(object_node, object_type.metadata[:federation_directives]) end + def build_interface_type_node(interface_type) + field_node = super + merge_directives(field_node, interface_type.metadata[:federation_directives]) + end + def build_field_node(field_type) field_node = super merge_directives(field_node, field_type.metadata[:federation_directives]) diff --git a/lib/apollo-federation/interface.rb b/lib/apollo-federation/interface.rb new file mode 100644 index 000000000..57c215b9b --- /dev/null +++ b/lib/apollo-federation/interface.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'apollo-federation/has_directives' + +module ApolloFederation + module Interface + def self.included(klass) + klass.definition_methods do + include DefinitionMethods + end + end + + module DefinitionMethods + include HasDirectives + + def extend_type + add_directive(name: 'extends') + end + + def key(fields:) + add_directive( + name: 'key', + arguments: [ + name: 'fields', + values: fields, + ], + ) + end + end + end +end diff --git a/lib/apollo-federation/object.rb b/lib/apollo-federation/object.rb index b24e939ad..b4a1083e2 100644 --- a/lib/apollo-federation/object.rb +++ b/lib/apollo-federation/object.rb @@ -11,7 +11,6 @@ def self.included(klass) module ClassMethods include HasDirectives - # TODO: We should support extending interfaces at some point def extend_type add_directive(name: 'extends') end diff --git a/lib/apollo-federation/schema.rb b/lib/apollo-federation/schema.rb index eac147ab0..65087a062 100644 --- a/lib/apollo-federation/schema.rb +++ b/lib/apollo-federation/schema.rb @@ -29,7 +29,7 @@ def to_graphql end possible_entities = orig_defn.types.values.select do |type| - !type.introspection? && !type.default_scalar? && + !type.introspection? && !type.default_scalar? && type.is_a?(GraphQL::ObjectType) && type.metadata[:federation_directives]&.any? { |directive| directive[:name] == 'key' } end diff --git a/spec/apollo-federation/service_field_spec.rb b/spec/apollo-federation/service_field_spec.rb index ee2079164..cc9f2f004 100644 --- a/spec/apollo-federation/service_field_spec.rb +++ b/spec/apollo-federation/service_field_spec.rb @@ -5,6 +5,7 @@ require 'apollo-federation/schema' require 'apollo-federation/field' require 'apollo-federation/object' +require 'apollo-federation/interface' RSpec.describe ApolloFederation::ServiceField do let(:base_schema) do @@ -144,6 +145,64 @@ def execute_sdl(schema) ) end + it 'returns valid SDL for interface types' do + base_interface = Module.new do + include GraphQL::Schema::Interface + include ApolloFederation::Interface + + graphql_name 'Interface' + end + + product = Module.new do + include base_interface + + graphql_name 'Product' + + key fields: :upc + field :upc, String, null: false + end + + book = Class.new(base_object) do + implements product + + graphql_name 'Book' + + extend_type + + key fields: :upc + field :upc, String, null: false, external: true + end + + pen = Class.new(base_object) do + implements product + + graphql_name 'Pen' + + key fields: :upc + field :upc, String, null: false + end + + schema = Class.new(base_schema) do + orphan_types book, pen + end + + expect(execute_sdl(schema)).to match_sdl( + <<~GRAPHQL, + type Book implements Product @extends @key(fields: "upc") { + upc: String! @external + } + + type Pen implements Product @key(fields: "upc") { + upc: String! + } + + interface Product @key(fields: "upc") { + upc: String! + } + GRAPHQL + ) + end + context 'when a Query object is provided' do it 'returns valid SDL for @key directives' do product = Class.new(base_object) do