lib/graphql/execution/lookahead.rb in graphql-2.1.3 vs lib/graphql/execution/lookahead.rb in graphql-2.1.4
- old
+ new
@@ -78,10 +78,26 @@
# @return [Boolean]
def selects?(field_name, selected_type: @selected_type, arguments: nil)
selection(field_name, selected_type: selected_type, arguments: arguments).selected?
end
+ # True if this node has a selection with alias matching `alias_name`.
+ # If `alias_name` is a String, it is treated as a GraphQL-style (camelized)
+ # field name and used verbatim. If `alias_name` is a Symbol, it is
+ # treated as a Ruby-style (underscored) name and camelized before comparing.
+ #
+ # If `arguments:` is provided, each provided key/value will be matched
+ # against the arguments in the next selection. This method will return false
+ # if any of the given `arguments:` are not present and matching in the next selection.
+ # (But, the next selection may contain _more_ than the given arguments.)
+ # @param alias_name [String, Symbol]
+ # @param arguments [Hash] Arguments which must match in the selection
+ # @return [Boolean]
+ def selects_alias?(alias_name, arguments: nil)
+ alias_selection(alias_name, arguments: arguments).selected?
+ end
+
# @return [Boolean] True if this lookahead represents a field that was requested
def selected?
true
end
@@ -103,36 +119,43 @@
.possible_types(selected_type)
.map { |t| @query.warden.fields(t) }
.tap(&:flatten!)
end
+
if (match_by_orig_name = all_fields.find { |f| f.original_name == field_name })
match_by_orig_name
else
# Symbol#name is only present on 3.0+
sym_s = field_name.respond_to?(:name) ? field_name.name : field_name.to_s
guessed_name = Schema::Member::BuildType.camelize(sym_s)
@query.get_field(selected_type, guessed_name)
end
end
+ lookahead_for_selection(next_field_defn, selected_type, arguments)
+ end
- if next_field_defn
- next_nodes = []
- @ast_nodes.each do |ast_node|
- ast_node.selections.each do |selection|
- find_selected_nodes(selection, next_field_defn, arguments: arguments, matches: next_nodes)
- end
- end
+ # Like {#selection}, but for aliases.
+ # It returns a null object (check with {#selected?})
+ # @return [GraphQL::Execution::Lookahead]
+ def alias_selection(alias_name, selected_type: @selected_type, arguments: nil)
+ alias_cache_key = [alias_name, arguments]
+ return alias_selections[key] if alias_selections.key?(alias_name)
- if next_nodes.any?
- Lookahead.new(query: @query, ast_nodes: next_nodes, field: next_field_defn, owner_type: selected_type)
- else
- NULL_LOOKAHEAD
- end
- else
- NULL_LOOKAHEAD
+ alias_node = lookup_alias_node(ast_nodes, alias_name)
+ return NULL_LOOKAHEAD unless alias_node
+
+ next_field_defn = @query.get_field(selected_type, alias_node.name)
+
+ alias_arguments = @query.arguments_for(alias_node, next_field_defn)
+ if alias_arguments.is_a?(::GraphQL::Execution::Interpreter::Arguments)
+ alias_arguments = alias_arguments.keyword_arguments
end
+
+ return NULL_LOOKAHEAD if arguments && arguments != alias_arguments
+
+ alias_selections[alias_cache_key] = lookahead_for_selection(next_field_defn, selected_type, alias_arguments, alias_name)
end
# Like {#selection}, but for all nodes.
# It returns a list of Lookaheads for all Selections
#
@@ -256,11 +279,11 @@
on_type = @query.get_type(t.name)
subselections_on_type = subselections_by_type[on_type] ||= {}
end
find_selections(subselections_by_type, subselections_on_type, on_type, 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})")
+ frag_defn = lookup_fragment(ast_selection)
# Again, assuming a valid AST
on_type = @query.get_type(frag_defn.type.name)
subselections_on_type = subselections_by_type[on_type] ||= {}
find_selections(subselections_by_type, subselections_on_type, on_type, frag_defn.selections, arguments)
else
@@ -269,27 +292,27 @@
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_defn, arguments:, matches:)
+ def find_selected_nodes(node, field_name, field_defn, arguments:, matches:, alias_name: NOT_CONFIGURED)
return if skipped_by_directive?(node)
case node
when GraphQL::Language::Nodes::Field
- if node.name == field_defn.graphql_name
+ if node.name == field_name && (NOT_CONFIGURED.equal?(alias_name) || node.alias == alias_name)
if arguments.nil? || arguments.empty?
# No constraint applied
matches << node
elsif arguments_match?(arguments, field_defn, node)
matches << node
end
end
when GraphQL::Language::Nodes::InlineFragment
- node.selections.each { |s| find_selected_nodes(s, field_defn, arguments: arguments, matches: matches) }
+ node.selections.each { |s| find_selected_nodes(s, field_name, field_defn, arguments: arguments, matches: matches, alias_name: alias_name) }
when GraphQL::Language::Nodes::FragmentSpread
- 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_defn, arguments: arguments, matches: matches) }
+ frag_defn = lookup_fragment(node)
+ frag_defn.selections.each { |s| find_selected_nodes(s, field_name, field_defn, arguments: arguments, matches: matches, alias_name: alias_name) }
else
raise "Unexpected selection comparison on #{node.class.name} (#{node})"
end
end
@@ -303,9 +326,53 @@
end
# Make sure the constraint is present with a matching value
query_kwargs.key?(arg_name_sym) && query_kwargs[arg_name_sym] == arg_value
end
+ end
+
+ def lookahead_for_selection(field_defn, selected_type, arguments, alias_name = NOT_CONFIGURED)
+ return NULL_LOOKAHEAD unless field_defn
+
+ next_nodes = []
+ field_name = field_defn.name
+ @ast_nodes.each do |ast_node|
+ ast_node.selections.each do |selection|
+ find_selected_nodes(selection, field_name, field_defn, arguments: arguments, matches: next_nodes, alias_name: alias_name)
+ end
+ end
+
+ return NULL_LOOKAHEAD if next_nodes.empty?
+
+ Lookahead.new(query: @query, ast_nodes: next_nodes, field: field_defn, owner_type: selected_type)
+ end
+
+ def alias_selections
+ return @alias_selections if defined?(@alias_selections)
+ @alias_selections ||= {}
+ end
+
+ def lookup_alias_node(nodes, name)
+ return if nodes.empty?
+
+ nodes.flat_map(&:children)
+ .flat_map { |child| unwrap_fragments(child) }
+ .find { |child| child.is_a?(GraphQL::Language::Nodes::Field) && child.alias == name }
+ end
+
+ def unwrap_fragments(node)
+ case node
+ when GraphQL::Language::Nodes::InlineFragment
+ node.children
+ when GraphQL::Language::Nodes::FragmentSpread
+ lookup_fragment(node).children
+ else
+ [node]
+ end
+ end
+
+ def lookup_fragment(ast_selection)
+ @query.fragments[ast_selection.name] || raise("Invariant: Can't look ahead to nonexistent fragment #{ast_selection.name} (found: #{@query.fragments.keys})")
end
end
end
end