Skip to content

Commit

Permalink
Merge pull request #1 from rmosolgo/start-migrating
Browse files Browse the repository at this point in the history
Fix for GraphQL-Ruby 2.1+
  • Loading branch information
rmosolgo authored Jan 24, 2024
2 parents 2c61176 + 27ef61f commit d5aa3b1
Show file tree
Hide file tree
Showing 25 changed files with 327 additions and 135 deletions.
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

0 comments on commit d5aa3b1

Please sign in to comment.