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
-
#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 { ... }). -
#each_field(type, selections, visiting = Set.new, &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. -
#gather(type, selections) ⇒ Object
each_field grouped by result key: repeated selections of one field (
a { x } a { y }, or the same field reached through two fragments) collect together, so callers MERGE their sub-selections rather than last-writer-wins. -
#load_operation(query) ⇒ Object
Parse a query, stash its fragment definitions for the walk, and return the operation.
-
#operation_root_type(operation) ⇒ Object
The schema type an operation's selections start from.
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 { ... }).
74 75 76 77 78 79 80 81 82 83 |
# File 'lib/graph_weaver/selection.rb', line 74 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, visiting = Set.new, &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 54 55 56 57 58 59 |
# File 'lib/graph_weaver/selection.rb', line 37 def each_field(type, selections, visiting = Set.new, &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, visiting, &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 if visiting.include?(selection.name) raise GraphWeaver::Error, "fragment cycle through #{selection.name}" end if applies?(fragment.type.name, type) each_field(type, fragment.selections, visiting | [selection.name], &block) end else raise GraphWeaver::Error, "unsupported selection: #{selection.class}" end end end |
#gather(type, selections) ⇒ Object
each_field grouped by result key: repeated selections of one field
(a { x } a { y }, or the same field reached through two fragments)
collect together, so callers MERGE their sub-selections rather than
last-writer-wins. Codegen relies on this; FakeClient/Anonymizer must too,
or they'd fabricate/keep a shape the generated struct can't cast.
66 67 68 69 70 |
# File 'lib/graph_weaver/selection.rb', line 66 def gather(type, selections) out = {} each_field(type, selections) { |key, node| (out[key] ||= []) << node } out 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 |