module GraphQL::InternalRepresentation::Rewrite

While visiting an AST, build a normalized, flattened tree of {InternalRepresentation::Node}s.

No unions or interfaces are present in this tree, only object types.

Selections from the AST are attached to the object types they apply to.

Inline fragments and fragment spreads are preserved in {InternalRepresentation::Node#ast_spreads}, where they can be used to check for the presence of directives. This might not be sufficient for future directives, since the selections' grouping is lost.

The rewritten query tree serves as the basis for the `FieldsWillMerge` validation.

Constants

NO_DIRECTIVES

Attributes

rewrite_document[R]

Public Class Methods

new(*) click to toggle source
Calls superclass method
# File lib/graphql/internal_representation/rewrite.rb, line 24
def initialize(*)
  super
  @query = context.query
  @rewrite_document = InternalRepresentation::Document.new
  # Hash<Nodes::FragmentSpread => Set<InternalRepresentation::Node>>
  # A record of fragment spreads and the irep nodes that used them
  @rewrite_spread_parents = Hash.new { |h, k| h[k] = Set.new }
  # Hash<Nodes::FragmentSpread => Scope>
  @rewrite_spread_scopes = {}
  # Array<Set<InternalRepresentation::Node>>
  # The current point of the irep_tree during visitation
  @rewrite_nodes_stack = []
  # Array<Scope>
  @rewrite_scopes_stack = []
  @rewrite_skip_nodes = Set.new

  # Resolve fragment spreads.
  # Fragment definitions got their own irep trees during visitation.
  # Those nodes are spliced in verbatim (not copied), but this is OK
  # because fragments are resolved from the "bottom up", each fragment
  # can be shared between its usages.
  context.on_dependency_resolve do |defn_ast_node, spread_ast_nodes, frag_ast_node|
    frag_name = frag_ast_node.name
    fragment_node = @rewrite_document.fragment_definitions[frag_name]

    if fragment_node
      spread_ast_nodes.each do |spread_ast_node|
        parent_nodes = @rewrite_spread_parents[spread_ast_node]
        parent_scope = @rewrite_spread_scopes[spread_ast_node]
        parent_nodes.each do |parent_node|
          parent_node.deep_merge_node(fragment_node, scope: parent_scope, merge_self: false)
        end
      end
    end
  end
end

Public Instance Methods

on_field(ast_node, ast_parent) click to toggle source
Calls superclass method
# File lib/graphql/internal_representation/rewrite.rb, line 120
def on_field(ast_node, ast_parent)
  if skip?(ast_node)
    @rewrite_skip_nodes.add(ast_node)
  end

  if @rewrite_skip_nodes.empty?
    node_name = ast_node.alias || ast_node.name
    parent_nodes = @rewrite_nodes_stack.last
    next_nodes = []

    field_defn = context.field_definition
    if field_defn.nil?
      # It's a non-existent field
      new_scope = nil
    else
      field_return_type = field_defn.type
      @rewrite_scopes_stack.last.each do |scope_type|
        parent_nodes.each do |parent_node|
          node = parent_node.scoped_children[scope_type][node_name] ||= Node.new(
            parent: parent_node,
            name: node_name,
            owner_type: scope_type,
            query: @query,
            return_type: field_return_type,
          )
          node.ast_nodes << ast_node
          node.definitions << field_defn
          next_nodes << node
        end
      end
      new_scope = Scope.new(@query, field_return_type.unwrap)
    end

    @rewrite_nodes_stack.push(next_nodes)
    @rewrite_scopes_stack.push(new_scope)
  end

  super

  if @rewrite_skip_nodes.empty?
    @rewrite_nodes_stack.pop
    @rewrite_scopes_stack.pop
  end

  if @rewrite_skip_nodes.include?(ast_node)
    @rewrite_skip_nodes.delete(ast_node)
  end
end
on_fragment_definition(ast_node, parent) click to toggle source
Calls superclass method
# File lib/graphql/internal_representation/rewrite.rb, line 71
def on_fragment_definition(ast_node, parent)
  push_root_node(ast_node, @rewrite_document.fragment_definitions) { super }
end
on_fragment_spread(ast_node, ast_parent) click to toggle source
Calls superclass method
# File lib/graphql/internal_representation/rewrite.rb, line 169
def on_fragment_spread(ast_node, ast_parent)
  if @rewrite_skip_nodes.empty? && !skip?(ast_node)
    # Register the irep nodes that depend on this AST node:
    @rewrite_spread_parents[ast_node].merge(@rewrite_nodes_stack.last)
    @rewrite_spread_scopes[ast_node] = @rewrite_scopes_stack.last
  end
  super
end
on_inline_fragment(node, parent) click to toggle source
Calls superclass method
# File lib/graphql/internal_representation/rewrite.rb, line 97
def on_inline_fragment(node, parent)
  # Inline fragments provide two things to the rewritten tree:
  # - They _may_ narrow the scope by their type condition
  # - They _may_ apply their directives to their children
  if skip?(node)
    @rewrite_skip_nodes.add(node)
  end

  if @rewrite_skip_nodes.empty?
    @rewrite_scopes_stack.push(@rewrite_scopes_stack.last.enter(context.type_definition))
  end

  super

  if @rewrite_skip_nodes.empty?
    @rewrite_scopes_stack.pop
  end

  if @rewrite_skip_nodes.include?(node)
    @rewrite_skip_nodes.delete(node)
  end
end
on_operation_definition(ast_node, parent) click to toggle source
Calls superclass method
# File lib/graphql/internal_representation/rewrite.rb, line 67
def on_operation_definition(ast_node, parent)
  push_root_node(ast_node, @rewrite_document.operation_definitions) { super }
end
operations() click to toggle source

@return [Hash<String, Node>] Roots of this query

# File lib/graphql/internal_representation/rewrite.rb, line 62
def operations
  GraphQL::Deprecation.warn "#{self.class}#operations is deprecated; use `document.operation_definitions` instead"
  @document.operation_definitions
end
push_root_node(ast_node, definitions) { || ... } click to toggle source
# File lib/graphql/internal_representation/rewrite.rb, line 75
def push_root_node(ast_node, definitions)
  # Either QueryType or the fragment type condition
  owner_type = context.type_definition
  defn_name = ast_node.name

  node = Node.new(
    parent: nil,
    name: defn_name,
    owner_type: owner_type,
    query: @query,
    ast_nodes: [ast_node],
    return_type: owner_type,
  )

  definitions[defn_name] = node
  @rewrite_scopes_stack.push(Scope.new(@query, owner_type))
  @rewrite_nodes_stack.push([node])
  yield
  @rewrite_nodes_stack.pop
  @rewrite_scopes_stack.pop
end
skip?(ast_node) click to toggle source
# File lib/graphql/internal_representation/rewrite.rb, line 178
def skip?(ast_node)
  dir = ast_node.directives
  dir.any? && !GraphQL::Execution::DirectiveChecks.include?(dir, @query)
end