Skip to content

Implement schema extend keyword #1

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

Closed
wants to merge 1 commit into from
Closed
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ gh-pages/
tmp/*
__*.db
node_modules/
.byebug_history
yarn.lock
OperationStoreClient.js
spec/integration/tmp
Expand Down
62 changes: 62 additions & 0 deletions lib/graphql/language/printer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,12 @@ def print_scalar_type_definition(scalar_type)
out << print_directives(scalar_type.directives)
end

def print_scalar_type_extension(scalar_type)
out = ''.dup
out << "extend scalar #{scalar_type.name}"
out << print_directives(scalar_type.directives)
end

def print_object_type_definition(object_type)
out = print_description(object_type)
out << "type #{object_type.name}"
Expand All @@ -169,6 +175,14 @@ def print_object_type_definition(object_type)
out << print_field_definitions(object_type.fields)
end

def print_object_type_extension(object_type)
out = ''.dup
out << "extend type #{object_type.name}"
out << " implements " << object_type.interfaces.map(&:name).join(" & ") unless object_type.interfaces.empty?
out << print_directives(object_type.directives)
out << print_field_definitions(object_type.fields)
end

def print_input_value_definition(input_value)
out = "#{input_value.name}: #{print_node(input_value.type)}".dup
out << " = #{print_node(input_value.default_value)}" unless input_value.default_value.nil?
Expand Down Expand Up @@ -204,13 +218,27 @@ def print_interface_type_definition(interface_type)
out << print_field_definitions(interface_type.fields)
end

def print_interface_type_extension(interface_type)
out = ''.dup
out << "extend interface #{interface_type.name}"
out << print_directives(interface_type.directives)
out << print_field_definitions(interface_type.fields)
end

def print_union_type_definition(union_type)
out = print_description(union_type)
out << "union #{union_type.name}"
out << print_directives(union_type.directives)
out << " = " + union_type.types.map(&:name).join(" | ")
end

def print_union_type_extension(union_type)
out = ''.dup
out << "extend union #{union_type.name}"
out << print_directives(union_type.directives)
out << " = " + union_type.types.map(&:name).join(" | ")
end

def print_enum_type_definition(enum_type)
out = print_description(enum_type)
out << "enum #{enum_type.name}#{print_directives(enum_type.directives)} {\n"
Expand All @@ -221,6 +249,16 @@ def print_enum_type_definition(enum_type)
out << "}"
end

def print_enum_type_extension(enum_type)
out = ''.dup
out << "extend enum #{enum_type.name}#{print_directives(enum_type.directives)} {\n"
enum_type.values.each.with_index do |value, i|
out << print_description(value, indent: ' ', first_in_block: i == 0)
out << print_enum_value_definition(value)
end
out << "}"
end

def print_enum_value_definition(enum_value)
out = " #{enum_value.name}".dup
out << print_directives(enum_value.directives)
Expand All @@ -239,6 +277,18 @@ def print_input_object_type_definition(input_object_type)
out << "}"
end

def print_input_object_type_extension(input_object_type)
out = ''.dup
out << "extend input #{input_object_type.name}"
out << print_directives(input_object_type.directives)
out << " {\n"
input_object_type.fields.each.with_index do |field, i|
out << print_description(field, indent: ' ', first_in_block: i == 0)
out << " #{print_input_value_definition(field)}\n"
end
out << "}"
end

def print_directive_definition(directive)
out = print_description(directive)
out << "directive @#{directive.name}"
Expand Down Expand Up @@ -342,6 +392,18 @@ def print_node(node, indent: "")
print_input_object_type_definition(node)
when Nodes::DirectiveDefinition
print_directive_definition(node)
when Nodes::ScalarTypeExtension
print_scalar_type_extension(node)
when Nodes::ObjectTypeExtension
print_object_type_extension(node)
when Nodes::InterfaceTypeExtension
print_interface_type_extension(node)
when Nodes::UnionTypeExtension
print_union_type_extension(node)
when Nodes::EnumTypeExtension
print_enum_type_extension(node)
when Nodes::InputObjectTypeExtension
print_input_object_type_extension(node)
when FalseClass, Float, Integer, NilClass, String, TrueClass, Symbol
GraphQL::Language.serialize(node)
when Array
Expand Down
34 changes: 34 additions & 0 deletions lib/graphql/schema/build_from_definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,21 @@ def build(document, default_resolve: DefaultResolve)
types = {}
types.merge!(GraphQL::Schema::BUILT_IN_TYPES)
directives = {}
extensions = []
type_resolver = ->(type) { -> { resolve_type(types, type) } }

document.definitions.each do |definition|
case definition
when GraphQL::Language::Nodes::SchemaDefinition
raise InvalidDocumentError.new('Must provide only one schema definition.') if schema_definition
schema_definition = definition
when GraphQL::Language::Nodes::ScalarTypeExtension,
GraphQL::Language::Nodes::ObjectTypeExtension,
GraphQL::Language::Nodes::InterfaceTypeExtension,
GraphQL::Language::Nodes::UnionTypeExtension,
GraphQL::Language::Nodes::EnumTypeExtension,
GraphQL::Language::Nodes::InputObjectTypeExtension
extensions << definition
when GraphQL::Language::Nodes::EnumTypeDefinition
types[definition.name] = build_enum_type(definition, type_resolver)
when GraphQL::Language::Nodes::ObjectTypeDefinition
Expand All @@ -66,6 +74,32 @@ def build(document, default_resolve: DefaultResolve)

directives = GraphQL::Schema.default_directives.merge(directives)

for ext in extensions
type = types[ext.name]
case ext
when GraphQL::Language::Nodes::ScalarTypeExtension
# TODO: Where are the directives on the scalar type?
when GraphQL::Language::Nodes::ObjectTypeExtension
type.fields.merge! Hash[build_fields(ext.fields, type_resolver, default_resolve: default_resolve)]
type.interfaces.concat ext.interfaces.map{ |interface_name| type_resolver.call(interface_name) }
when GraphQL::Language::Nodes::InterfaceTypeExtension
type.fields.merge! Hash[build_fields(ext.fields, type_resolver, default_resolve: nil)]
when GraphQL::Language::Nodes::UnionTypeExtension
type.possible_types.concat ext.types.map { |x|types[x.name] }
when GraphQL::Language::Nodes::EnumTypeExtension
ext.values.each do |enum_value_definition|
type.add_value EnumType::EnumValue.define(
name: enum_value_definition.name,
value: enum_value_definition.name,
deprecation_reason: build_deprecation_reason(enum_value_definition.directives),
description: enum_value_definition.description,
)
end
when GraphQL::Language::Nodes::InputObjectTypeExtension
type.arguments.merge! Hash[build_input_arguments(ext, type_resolver)]
end
end

if schema_definition
if schema_definition.query
raise InvalidDocumentError.new("Specified query type \"#{schema_definition.query}\" not found in document.") unless types[schema_definition.query]
Expand Down
104 changes: 104 additions & 0 deletions spec/graphql/language/extend_document_from_schema_definition_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# frozen_string_literal: true
require "spec_helper"

describe GraphQL::Language::DocumentFromSchemaDefinition do
let(:subject) { GraphQL::Language::DocumentFromSchemaDefinition }

describe "#document" do
let(:schema_idl) { <<-GRAPHQL
scalar SomeScalar
extend scalar SomeScalar @foo

type Foo {
name: String
}

type User {
name: String
}

type Bar {
name: String
}

union SomeUnion = User | Foo
extend union SomeUnion = Bar

extend type User {
email: String!
}

interface SomeInterface {
otherField: String
}

extend interface SomeInterface {
newField: String
}

input SomeInput {
fooArg: String
newField: String
}

extend input SomeInput {
newField: String
}

enum SomeEnum {
ONE
}

extend enum SomeEnum {
NEW_ENUM
}

type Query {
user: User
}

GRAPHQL
}

let(:expected_document) { GraphQL.parse(schema_idl) }
let(:schema) { GraphQL::Schema.from_definition(schema_idl) }

let(:document) {
subject.new(
schema
).document
}

it "returns the IDL without introspection, built ins and schema root" do
assert equivalent_node?(expected_document, document)
end

it "print GraphQL::Language::Document with extend" do
document = GraphQL.parse(schema_idl)
GraphQL::Language::Printer.new.print(document)
end

it "generates GraphQL::Language::Document from the GraphQL::Schema build" do
end
end

private

def equivalent_node?(expected, node)
return false unless expected.is_a?(node.class)

if expected.respond_to?(:children) && expected.respond_to?(:scalars)
children_equal = expected.children.all? do |expected_child|
node.children.find { |child| equivalent_node?(expected_child, child) }
end

scalars_equal = expected.children.all? do |expected_child|
node.children.find { |child| equivalent_node?(expected_child, child) }
end

children_equal && scalars_equal
else
expected == node
end
end
end