lib/graphql/execution/lookahead.rb in graphql-1.9.0.pre1 vs lib/graphql/execution/lookahead.rb in graphql-1.9.0.pre2
- old
+ new
@@ -34,16 +34,22 @@
# @param query [GraphQL::Query]
# @param ast_nodes [Array<GraphQL::Language::Nodes::Field>, Array<GraphQL::Language::Nodes::OperationDefinition>]
# @param field [GraphQL::Schema::Field] if `ast_nodes` are fields, this is the field definition matching those nodes
# @param root_type [Class] if `ast_nodes` are operation definition, this is the root type for that operation
def initialize(query:, ast_nodes:, field: nil, root_type: nil)
- @ast_nodes = ast_nodes
+ @ast_nodes = ast_nodes.freeze
@field = field
@root_type = root_type
@query = query
end
+ # @return [Array<GraphQL::Language::Nodes::Field>]
+ attr_reader :ast_nodes
+
+ # @return [GraphQL::Schema::Field]
+ attr_reader :field
+
# True if this node has a selection on `field_name`.
# If `field_name` is a String, it is treated as a GraphQL-style (camelized)
# field name and used verbatim. If `field_name` is a Symbol, it is
# treated as a Ruby-style (underscored) name and camelized before comparing.
#
@@ -92,10 +98,49 @@
else
NULL_LOOKAHEAD
end
end
+ # Like {#selection}, but for all nodes.
+ # It returns a list of Lookaheads for all Selections
+ #
+ # If `arguments:` is provided, each provided key/value will be matched
+ # against the arguments in each selection. This method will filter the selections
+ # if any of the given `arguments:` do not match the given selection.
+ #
+ # @example getting the name of a selection
+ # def articles(lookahead:)
+ # next_lookaheads = lookahead.selections # => [#<GraphQL::Execution::Lookahead ...>, ...]
+ # next_lookaheads.map(&:name) #=> [:full_content, :title]
+ # end
+ #
+ # @param arguments [Hash] Arguments which must match in the selection
+ # @return [Array<GraphQL::Execution::Lookahead>]
+ def selections(arguments: nil)
+ subselections_by_name = {}
+ @ast_nodes.each do |node|
+ find_selections(subselections_by_name, node.selections, arguments)
+ end
+
+ # Items may be filtered out if `arguments` doesn't match
+ subselections_by_name.values.select(&:selected?)
+ end
+
+ # The method name of the field.
+ # It returns the method_sym of the Lookahead's field.
+ #
+ # @example getting the name of a selection
+ # def articles(lookahead:)
+ # article.selection(:full_content).name # => :full_content
+ # # ...
+ # end
+ #
+ # @return [Symbol]
+ def name
+ @field && @field.original_name
+ end
+
# This is returned for {Lookahead#selection} when a non-existent field is passed
class NullLookahead < Lookahead
# No inputs required here.
def initialize
end
@@ -109,10 +154,14 @@
end
def selection(*)
NULL_LOOKAHEAD
end
+
+ def selections(*)
+ []
+ end
end
# A singleton, so that misses don't come with overhead.
NULL_LOOKAHEAD = NullLookahead.new
@@ -133,10 +182,26 @@
else
keyword
end
end
+ def find_selections(subselections_by_name, ast_selections, arguments)
+ ast_selections.each do |ast_selection|
+ case ast_selection
+ when GraphQL::Language::Nodes::Field
+ subselections_by_name[ast_selection.name] ||= selection(ast_selection.name, arguments: arguments)
+ when GraphQL::Language::Nodes::InlineFragment
+ find_selections(subselections_by_name, ast_selection.selections, arguments)
+ when GraphQL::Language::Nodes::FragmentSpread
+ frag_defn = @query.fragments[ast_selection.name] || raise("Invariant: Can't look ahead to nonexistent fragment #{ast_selection.name} (found: #{@query.fragments.keys})")
+ find_selections(subselections_by_name, frag_defn.selections, arguments)
+ else
+ raise "Invariant: Unexpected selection type: #{ast_selection.class}"
+ end
+ end
+ end
+
# If a selection on `node` matches `field_name` (which is backed by `field_defn`)
# and matches the `arguments:` constraints, then add that node to `matches`
def find_selected_nodes(node, field_name, field_defn, arguments:, matches:)
case node
when GraphQL::Language::Nodes::Field
@@ -155,14 +220,14 @@
matches << node
end
end
end
when GraphQL::Language::Nodes::InlineFragment
- node.selections.find { |s| find_selected_nodes(s, field_name, field_defn, arguments: arguments, matches: matches) }
+ node.selections.each { |s| find_selected_nodes(s, field_name, field_defn, arguments: arguments, matches: matches) }
when GraphQL::Language::Nodes::FragmentSpread
- frag_defn = @query.fragments[node.name]
- frag_defn.selections.find { |s| find_selected_nodes(s, field_name, field_defn, arguments: arguments, matches: matches) }
+ frag_defn = @query.fragments[node.name] || raise("Invariant: Can't look ahead to nonexistent fragment #{node.name} (found: #{@query.fragments.keys})")
+ frag_defn.selections.each { |s| find_selected_nodes(s, field_name, field_defn, arguments: arguments, matches: matches) }
else
raise "Unexpected selection comparison on #{node.class.name} (#{node})"
end
end
@@ -257,17 +322,17 @@
# TODO dedup with interpreter
module FieldHelpers
module_function
- def get_field(schema, owner_type, field_name )
+ def get_field(schema, owner_type, field_name)
field_defn = owner_type.get_field(field_name)
field_defn ||= if owner_type == schema.query.metadata[:type_class] && (entry_point_field = schema.introspection_system.entry_point(name: field_name))
entry_point_field.metadata[:type_class]
elsif (dynamic_field = schema.introspection_system.dynamic_field(name: field_name))
dynamic_field.metadata[:type_class]
else
- raise "Invariant: no field for #{owner_type}.#{field_name}"
+ nil
end
field_defn
end
end