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

Fix for GraphQL-Ruby 2.1+ #1

Merged
merged 4 commits into from
Jan 24, 2024
Merged
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
23 changes: 19 additions & 4 deletions lib/graphql/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
require "active_support/inflector"
require "active_support/notifications"
require "graphql"
require "graphql/client/type_stack"
require "graphql/client/collocated_enforcement"
require "graphql/client/definition_variables"
require "graphql/client/definition"
Expand Down Expand Up @@ -425,12 +426,26 @@ def sliced_definitions(document_dependencies, doc, source_location:)
end.to_h
end

class GatherNamesVisitor < GraphQL::Language::Visitor
def initialize(node)
@names = []
super
end

attr_reader :names

def on_fragment_spread(node, parent)
@names << node.name
super
end
end

def find_definition_dependencies(node)
names = []
visitor = Language::Visitor.new(node)
visitor[Language::Nodes::FragmentSpread] << -> (node, parent) { names << node.name }
visitor = GatherNamesVisitor.new(node)
visitor.visit
names.uniq
names = visitor.names
names.uniq!
names
end

def deep_freeze_json_object(obj)
Expand Down
96 changes: 54 additions & 42 deletions lib/graphql/client/definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -147,41 +147,58 @@ def new(obj, errors = Errors.new)
# Internal: Nodes AST indexes.
def indexes
@indexes ||= begin
visitor = GraphQL::Language::Visitor.new(document)
definitions = index_node_definitions(visitor)
spreads = index_spreads(visitor)
visitor = DefinitionVisitor.new(document)
visitor.visit
{ definitions: definitions, spreads: spreads }
{ definitions: visitor.definitions, spreads: visitor.spreads }
end
end

private
class DefinitionVisitor < GraphQL::Language::Visitor
attr_reader :spreads, :definitions

def cast_object(obj)
if obj.class.is_a?(GraphQL::Client::Schema::ObjectType)
unless obj._spreads.include?(definition_node.name)
raise TypeError, "#{definition_node.name} is not included in #{obj.source_definition.name}"
end
schema_class.cast(obj.to_h, obj.errors)
else
raise TypeError, "unexpected #{obj.class}"
end
def initialize(doc)
super
@spreads = {}
@definitions = {}
@current_definition = nil
end

EMPTY_SET = Set.new.freeze
def on_field(node, parent)
@definitions[node] = @current_definition
@spreads[node] = get_spreads(node)
super
end

def index_spreads(visitor)
spreads = {}
on_node = ->(node, _parent) do
node_spreads = flatten_spreads(node).map(&:name)
spreads[node] = node_spreads.empty? ? EMPTY_SET : Set.new(node_spreads).freeze
end
def on_fragment_definition(node, parent)
@current_definition = node
@definitions[node] = @current_definition
@spreads[node] = get_spreads(node)
super
ensure
@current_definition = nil
end

visitor[GraphQL::Language::Nodes::Field] << on_node
visitor[GraphQL::Language::Nodes::FragmentDefinition] << on_node
visitor[GraphQL::Language::Nodes::OperationDefinition] << on_node
def on_operation_definition(node, parent)
@current_definition = node
@definitions[node] = @current_definition
@spreads[node] = get_spreads(node)
super
ensure
@current_definition = nil
end

spreads
def on_inline_fragment(node, parent)
@definitions[node] = @current_definition
super
end

private

EMPTY_SET = Set.new.freeze

def get_spreads(node)
node_spreads = flatten_spreads(node).map(&:name)
node_spreads.empty? ? EMPTY_SET : Set.new(node_spreads).freeze
end

def flatten_spreads(node)
Expand All @@ -198,24 +215,19 @@ def flatten_spreads(node)
end
spreads
end
end

private

def index_node_definitions(visitor)
current_definition = nil
enter_definition = ->(node, _parent) { current_definition = node }
leave_definition = ->(node, _parent) { current_definition = nil }

visitor[GraphQL::Language::Nodes::FragmentDefinition].enter << enter_definition
visitor[GraphQL::Language::Nodes::FragmentDefinition].leave << leave_definition
visitor[GraphQL::Language::Nodes::OperationDefinition].enter << enter_definition
visitor[GraphQL::Language::Nodes::OperationDefinition].leave << leave_definition

definitions = {}
on_node = ->(node, _parent) { definitions[node] = current_definition }
visitor[GraphQL::Language::Nodes::Field] << on_node
visitor[GraphQL::Language::Nodes::FragmentDefinition] << on_node
visitor[GraphQL::Language::Nodes::InlineFragment] << on_node
visitor[GraphQL::Language::Nodes::OperationDefinition] << on_node
definitions
def cast_object(obj)
if obj.class.is_a?(GraphQL::Client::Schema::ObjectType)
unless obj._spreads.include?(definition_node.name)
raise TypeError, "#{definition_node.name} is not included in #{obj.source_definition.name}"
end
schema_class.cast(obj.to_h, obj.errors)
else
raise TypeError, "unexpected #{obj.class}"
end
end
end
end
Expand Down
29 changes: 18 additions & 11 deletions lib/graphql/client/definition_variables.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,33 @@ def self.variables(schema, document, definition_name = nil)

sliced_document = GraphQL::Language::DefinitionSlice.slice(document, definition_name)

visitor = GraphQL::Language::Visitor.new(sliced_document)
type_stack = GraphQL::StaticValidation::TypeStack.new(schema, visitor)
visitor = VariablesVisitor.new(sliced_document, schema: schema)
visitor.visit
visitor.variables
end

class VariablesVisitor < GraphQL::Language::Visitor
prepend GraphQL::Client::TypeStack

def initialize(*_args, **_kwargs)
super
@variables = {}
end

variables = {}
attr_reader :variables

visitor[GraphQL::Language::Nodes::VariableIdentifier] << ->(node, parent) do
if definition = type_stack.argument_definitions.last
existing_type = variables[node.name.to_sym]
def on_variable_identifier(node, parent)
if definition = @argument_definitions.last
existing_type = @variables[node.name.to_sym]

if existing_type && existing_type.unwrap != definition.type.unwrap
raise GraphQL::Client::ValidationError, "$#{node.name} was already declared as #{existing_type.unwrap}, but was #{definition.type.unwrap}"
elsif !(existing_type && existing_type.kind.non_null?)
variables[node.name.to_sym] = definition.type
@variables[node.name.to_sym] = definition.type
end
end
super
end

visitor.visit

variables
end

# Internal: Detect all variables used in a given operation or fragment
Expand Down
54 changes: 34 additions & 20 deletions lib/graphql/client/document_types.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,41 @@
# frozen_string_literal: true
require "graphql"
require "graphql/client/type_stack"

module GraphQL
class Client
# Internal: Use schema to detect definition and field types.
module DocumentTypes
class AnalyzeTypesVisitor < GraphQL::Language::Visitor
prepend GraphQL::Client::TypeStack
attr_reader :fields

def initialize(*a, **kw)
@fields = {}
super
end

def on_operation_definition(node, _parent)
@fields[node] = @object_types.last
super
end

def on_fragment_definition(node, _parent)
@fields[node] = @object_types.last
super
end

def on_inline_fragment(node, _parent)
@fields[node] = @object_types.last
super
end

def on_field(node, _parent)
@fields[node] = @field_definitions.last.type
super
end
end

# Internal: Detect all types used in a given document
#
# schema - A GraphQL::Schema
Expand All @@ -20,32 +51,15 @@ def self.analyze_types(schema, document)
raise TypeError, "expected schema to be a GraphQL::Language::Nodes::Document, but was #{document.class}"
end

visitor = GraphQL::Language::Visitor.new(document)
type_stack = GraphQL::StaticValidation::TypeStack.new(schema, visitor)

fields = {}

visitor[GraphQL::Language::Nodes::OperationDefinition] << ->(node, _parent) do
fields[node] = type_stack.object_types.last
end
visitor[GraphQL::Language::Nodes::FragmentDefinition] << ->(node, _parent) do
fields[node] = type_stack.object_types.last
end
visitor[GraphQL::Language::Nodes::InlineFragment] << ->(node, _parent) do
fields[node] = type_stack.object_types.last
end
visitor[GraphQL::Language::Nodes::Field] << ->(node, _parent) do
fields[node] = type_stack.field_definitions.last.type
end
visitor = AnalyzeTypesVisitor.new(document, schema: schema)
visitor.visit

fields
visitor.fields
rescue StandardError => err
if err.is_a?(TypeError)
raise
end
# FIXME: TypeStack my crash on invalid documents
fields
visitor.fields
end
end
end
Expand Down
Loading
Loading