Module: GraphWeaver::Selection

Includes:
Kernel
Included in:
Codegen, Testing::Anonymizer, Testing::FakeClient
Defined in:
lib/graph_weaver/selection.rb

Overview

Shared query-selection walking — the rules Codegen, FakeClient, and the cassette Anonymizer all follow, in one place so they can't drift: how fragments flatten into selections, and when a type condition applies. Hosts set @schema and call load_operation before walking.

Instance Method Summary collapse

Instance Method Details

#applies?(condition, type) ⇒ Boolean

A fragment's type condition applies when it names this type exactly, or an interface/union this type belongs to (... on Named { ... }).

Returns:

  • (Boolean)


57
58
59
60
61
62
63
64
65
66
# File 'lib/graph_weaver/selection.rb', line 57

def applies?(condition, type)
  return true if condition.nil? || condition == type.graphql_name

  condition_type = @schema.get_type(condition)
  return false unless condition_type

  kind = condition_type.kind.name
  (kind == "INTERFACE" || kind == "UNION") &&
    @schema.possible_types(condition_type).include?(type)
end

#each_field(type, selections, &block) ⇒ Object

Flatten a selection set as seen by type, yielding (result_key, field_node) per field: plain fields yield directly; inline fragments and named spreads recurse when their type condition applies.



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/graph_weaver/selection.rb', line 37

def each_field(type, selections, &block)
  selections.each do |selection|
    case selection
    when GraphQL::Language::Nodes::Field
      yield(selection.alias || selection.name, selection)
    when GraphQL::Language::Nodes::InlineFragment
      each_field(type, selection.selections, &block) if applies?(selection.type&.name, type)
    when GraphQL::Language::Nodes::FragmentSpread
      fragment = @fragments.fetch(selection.name) do
        raise ArgumentError, "unknown fragment: #{selection.name}"
      end
      each_field(type, fragment.selections, &block) if applies?(fragment.type.name, type)
    else
      raise GraphWeaver::Error, "unsupported selection: #{selection.class}"
    end
  end
end

#load_operation(query) ⇒ Object

Parse a query, stash its fragment definitions for the walk, and return the operation.



16
17
18
19
20
21
22
23
# File 'lib/graph_weaver/selection.rb', line 16

def load_operation(query)
  doc = GraphQL.parse(query)
  @fragments = doc.definitions
    .grep(GraphQL::Language::Nodes::FragmentDefinition)
    .to_h { |fragment| [fragment.name, fragment] }

  doc.definitions.grep(GraphQL::Language::Nodes::OperationDefinition).first
end

#operation_root_type(operation) ⇒ Object

The schema type an operation's selections start from.



26
27
28
29
30
31
32
# File 'lib/graph_weaver/selection.rb', line 26

def operation_root_type(operation)
  case operation&.operation_type
  when "query", nil then @schema.query
  when "mutation" then @schema.mutation
  else raise GraphWeaver::Error, "unsupported operation: #{operation.operation_type}"
  end
end