graphql 2.5.4 → 2.5.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/query_complexity.rb +2 -2
  3. data/lib/graphql/dataloader/active_record_association_source.rb +14 -2
  4. data/lib/graphql/dataloader/null_dataloader.rb +7 -0
  5. data/lib/graphql/execution/interpreter/resolve.rb +3 -3
  6. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +11 -1
  7. data/lib/graphql/execution/interpreter/runtime.rb +136 -49
  8. data/lib/graphql/execution/interpreter.rb +1 -1
  9. data/lib/graphql/language/lexer.rb +9 -2
  10. data/lib/graphql/language/nodes.rb +5 -1
  11. data/lib/graphql/language/parser.rb +1 -0
  12. data/lib/graphql/query/context.rb +1 -1
  13. data/lib/graphql/query/partial.rb +179 -0
  14. data/lib/graphql/query.rb +55 -43
  15. data/lib/graphql/schema/addition.rb +3 -1
  16. data/lib/graphql/schema/argument.rb +5 -0
  17. data/lib/graphql/schema/build_from_definition.rb +5 -1
  18. data/lib/graphql/schema/directive.rb +33 -1
  19. data/lib/graphql/schema/field.rb +8 -0
  20. data/lib/graphql/schema/input_object.rb +28 -21
  21. data/lib/graphql/schema/member/has_arguments.rb +2 -2
  22. data/lib/graphql/schema/member/has_interfaces.rb +2 -2
  23. data/lib/graphql/schema/member/type_system_helpers.rb +16 -2
  24. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  25. data/lib/graphql/schema/scalar.rb +1 -6
  26. data/lib/graphql/schema/timeout.rb +19 -2
  27. data/lib/graphql/schema/validator/required_validator.rb +15 -6
  28. data/lib/graphql/schema/visibility/migration.rb +2 -2
  29. data/lib/graphql/schema/visibility/profile.rb +74 -10
  30. data/lib/graphql/schema/visibility.rb +30 -17
  31. data/lib/graphql/schema/warden.rb +13 -5
  32. data/lib/graphql/schema.rb +53 -22
  33. data/lib/graphql/static_validation/all_rules.rb +1 -1
  34. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +6 -2
  35. data/lib/graphql/tracing/notifications_trace.rb +3 -1
  36. data/lib/graphql/tracing/null_trace.rb +1 -1
  37. data/lib/graphql/tracing/perfetto_trace.rb +1 -1
  38. data/lib/graphql/type_kinds.rb +1 -0
  39. data/lib/graphql/version.rb +1 -1
  40. data/lib/graphql.rb +1 -1
  41. metadata +19 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1fcafc24832a376132e4f15c981133f693685ad070918284587f775d05dd44d7
4
- data.tar.gz: 41bba1c56371a6d4ad643f75cece09cc13474dcf02b24dce0b31a1c8c89ee480
3
+ metadata.gz: 5c2ef756861a779063236ee9f8745d30ae3494138674239c8f638556d7f5105e
4
+ data.tar.gz: 23592e61f76acb0be2a4c60d953a829e8f1d46534633513ac44797a5d3386d2a
5
5
  SHA512:
6
- metadata.gz: 308136d27234edd6c84310f1e18d5a5841cdbcbf7ca8fe56eb9dd070fa52c10ad6d4d46022b51d22fe4adbcae9f7428ea6a6d7b756eef5d2be1f1337393a3f3b
7
- data.tar.gz: a0a3027d2f155daa8e52156765f6f6dcd53b35057ba010cc9e0ece130c283e2a5292c020eee81882074b6e7524c0085670dba9d47337619279915df18e097655
6
+ metadata.gz: 5c8db1d976343d569aa1fec1cee02de2c72947e0937fcd9917f8f6e078204efd373d244954e9e03b8dc7fd20066dcf7533bf4b52c706dba0f5640b60ae0465f0
7
+ data.tar.gz: 38422c543b159cc7243ac4dcc9139ed95b2583cab7942c60e4c557b89d6a4ffaa8fb1df94286fa3fbe4b918f5c116364d5fbe5c2afa611ba9d46d7ac56c18e61
@@ -28,14 +28,14 @@ module GraphQL
28
28
  end
29
29
  when nil
30
30
  subject.logger.warn <<~GRAPHQL
31
- GraphQL-Ruby's complexity cost system is getting some "breaking fixes" in a future version. See the migration notes at https://graphql-ruby.org/api-docs/#{GraphQL::VERSION}/Schema.html#complexity_cost_cacluation_mode-class_method
31
+ GraphQL-Ruby's complexity cost system is getting some "breaking fixes" in a future version. See the migration notes at https://graphql-ruby.org/api-doc/#{GraphQL::VERSION}/GraphQL/Schema.html#complexity_cost_calculation_mode_for-class_method
32
32
 
33
33
  To opt into the future behavior, configure your schema (#{subject.schema.name ? subject.schema.name : subject.schema.ancestors}) with:
34
34
 
35
35
  complexity_cost_calculation_mode(:future) # or `:legacy`, `:compare`
36
36
 
37
37
  GRAPHQL
38
- max_possible_complexity
38
+ max_possible_complexity(mode: :legacy)
39
39
  else
40
40
  raise ArgumentError, "Expected `:future`, `:legacy`, `:compare`, or `nil` from `#{query.schema}.complexity_cost_calculation_mode_for` but got: #{query.schema.complexity_cost_calculation_mode.inspect}"
41
41
  end
@@ -31,7 +31,12 @@ module GraphQL
31
31
  def fetch(records)
32
32
  record_classes = Set.new.compare_by_identity
33
33
  associated_classes = Set.new.compare_by_identity
34
+ scoped_fetch = !@scope.nil?
34
35
  records.each do |record|
36
+ if scoped_fetch
37
+ assoc = record.association(@association)
38
+ assoc.reset
39
+ end
35
40
  if record_classes.add?(record.class)
36
41
  reflection = record.class.reflect_on_association(@association)
37
42
  if !reflection.polymorphic? && reflection.klass
@@ -48,9 +53,16 @@ module GraphQL
48
53
 
49
54
  ::ActiveRecord::Associations::Preloader.new(records: records, associations: @association, available_records: available_records, scope: @scope).call
50
55
 
51
- loaded_associated_records = records.map { |r| r.public_send(@association) }
56
+ loaded_associated_records = records.map { |r|
57
+ assoc = r.association(@association)
58
+ lar = assoc.target
59
+ if scoped_fetch
60
+ assoc.reset
61
+ end
62
+ lar
63
+ }
52
64
 
53
- if @scope.nil?
65
+ if !scoped_fetch
54
66
  # Don't cache records loaded via scope because they might have reduced `SELECT`s
55
67
  # Could check .select_values here?
56
68
  records_by_model = {}
@@ -9,8 +9,11 @@ module GraphQL
9
9
  class NullDataloader < Dataloader
10
10
  # These are all no-ops because code was
11
11
  # executed synchronously.
12
+
13
+ def initialize(*); end
12
14
  def run; end
13
15
  def run_isolated; yield; end
16
+ def clear_cache; end
14
17
  def yield(_source)
15
18
  raise GraphQL::Error, "GraphQL::Dataloader is not running -- add `use GraphQL::Dataloader` to your schema to use Dataloader sources."
16
19
  end
@@ -19,6 +22,10 @@ module GraphQL
19
22
  yield
20
23
  nil
21
24
  end
25
+
26
+ def with(*)
27
+ raise GraphQL::Error, "GraphQL::Dataloader is not running -- add `use GraphQL::Dataloader` to your schema to use Dataloader sources."
28
+ end
22
29
  end
23
30
  end
24
31
  end
@@ -23,9 +23,9 @@ module GraphQL
23
23
  if smallest_depth
24
24
  lazies = lazies_at_depth.delete(smallest_depth)
25
25
  if !lazies.empty?
26
- dataloader.append_job {
27
- lazies.each(&:value) # resolve these Lazy instances
28
- }
26
+ lazies.each do |l|
27
+ dataloader.append_job { l.value }
28
+ end
29
29
  # Run lazies _and_ dataloader, see if more are enqueued
30
30
  dataloader.run
31
31
  resolve_each_depth(lazies_at_depth, dataloader)
@@ -21,15 +21,25 @@ module GraphQL
21
21
  @graphql_metadata = nil
22
22
  @graphql_selections = selections
23
23
  @graphql_is_eager = is_eager
24
+ @base_path = nil
24
25
  end
25
26
 
27
+ # TODO test full path in Partial
28
+ attr_writer :base_path
29
+
26
30
  def path
27
31
  @path ||= build_path([])
28
32
  end
29
33
 
30
34
  def build_path(path_array)
31
35
  graphql_result_name && path_array.unshift(graphql_result_name)
32
- @graphql_parent ? @graphql_parent.build_path(path_array) : path_array
36
+ if @graphql_parent
37
+ @graphql_parent.build_path(path_array)
38
+ elsif @base_path
39
+ @base_path + path_array
40
+ else
41
+ path_array
42
+ end
33
43
  end
34
44
 
35
45
  attr_accessor :graphql_dead
@@ -57,53 +57,142 @@ module GraphQL
57
57
  end
58
58
 
59
59
  def final_result
60
- @response && @response.graphql_result_data
60
+ @response.respond_to?(:graphql_result_data) ? @response.graphql_result_data : @response
61
61
  end
62
62
 
63
63
  def inspect
64
64
  "#<#{self.class.name} response=#{@response.inspect}>"
65
65
  end
66
66
 
67
- # This _begins_ the execution. Some deferred work
68
- # might be stored up in lazies.
69
67
  # @return [void]
70
68
  def run_eager
71
- root_operation = query.selected_operation
72
- root_op_type = root_operation.operation_type || "query"
73
- root_type = schema.root_type_for_operation(root_op_type)
74
- runtime_object = root_type.wrap(query.root_value, context)
75
- runtime_object = schema.sync_lazy(runtime_object)
76
- is_eager = root_op_type == "mutation"
77
- @response = GraphQLResultHash.new(nil, root_type, runtime_object, nil, false, root_operation.selections, is_eager, root_operation, nil, nil)
78
- st = get_current_runtime_state
79
- st.current_result = @response
80
-
81
- if runtime_object.nil?
82
- # Root .authorized? returned false.
83
- @response = nil
69
+ root_type = query.root_type
70
+ case query
71
+ when GraphQL::Query
72
+ ast_node = query.selected_operation
73
+ selections = ast_node.selections
74
+ object = query.root_value
75
+ is_eager = ast_node.operation_type == "mutation"
76
+ base_path = nil
77
+ when GraphQL::Query::Partial
78
+ ast_node = query.ast_nodes.first
79
+ selections = query.ast_nodes.map(&:selections).inject(&:+)
80
+ object = query.object
81
+ is_eager = false
82
+ base_path = query.path
84
83
  else
85
- call_method_on_directives(:resolve, runtime_object, root_operation.directives) do # execute query level directives
86
- each_gathered_selections(@response) do |selections, is_selection_array, ordered_result_keys|
87
- @response.ordered_result_keys ||= ordered_result_keys
88
- if is_selection_array
89
- selection_response = GraphQLResultHash.new(nil, root_type, runtime_object, nil, false, selections, is_eager, root_operation, nil, nil)
90
- selection_response.ordered_result_keys = ordered_result_keys
91
- final_response = @response
92
- else
93
- selection_response = @response
94
- final_response = nil
95
- end
84
+ raise ArgumentError, "Unexpected Runnable, can't execute: #{query.class} (#{query.inspect})"
85
+ end
86
+ object = schema.sync_lazy(object) # TODO test query partial with lazy root object
87
+ runtime_state = get_current_runtime_state
88
+ case root_type.kind.name
89
+ when "OBJECT"
90
+ object_proxy = root_type.wrap(object, context)
91
+ object_proxy = schema.sync_lazy(object_proxy)
92
+ if object_proxy.nil?
93
+ @response = nil
94
+ else
95
+ @response = GraphQLResultHash.new(nil, root_type, object_proxy, nil, false, selections, is_eager, ast_node, nil, nil)
96
+ @response.base_path = base_path
97
+ runtime_state.current_result = @response
98
+ call_method_on_directives(:resolve, object, ast_node.directives) do
99
+ each_gathered_selections(@response) do |selections, is_selection_array, ordered_result_keys|
100
+ @response.ordered_result_keys ||= ordered_result_keys
101
+ if is_selection_array
102
+ selection_response = GraphQLResultHash.new(nil, root_type, object_proxy, nil, false, selections, is_eager, ast_node, nil, nil)
103
+ selection_response.ordered_result_keys = ordered_result_keys
104
+ final_response = @response
105
+ else
106
+ selection_response = @response
107
+ final_response = nil
108
+ end
96
109
 
97
- @dataloader.append_job {
98
- evaluate_selections(
99
- selections,
100
- selection_response,
101
- final_response,
102
- nil,
110
+ @dataloader.append_job {
111
+ evaluate_selections(
112
+ selections,
113
+ selection_response,
114
+ final_response,
115
+ nil,
116
+ )
117
+ }
118
+ end
119
+ end
120
+ end
121
+ when "LIST"
122
+ inner_type = root_type.unwrap
123
+ case inner_type.kind.name
124
+ when "SCALAR", "ENUM"
125
+ result_name = ast_node.alias || ast_node.name
126
+ field_defn = query.field_definition
127
+ owner_type = field_defn.owner
128
+ selection_result = GraphQLResultHash.new(nil, owner_type, nil, nil, false, EmptyObjects::EMPTY_ARRAY, false, ast_node, nil, nil)
129
+ selection_result.base_path = base_path
130
+ selection_result.ordered_result_keys = [result_name]
131
+ runtime_state = get_current_runtime_state
132
+ runtime_state.current_result = selection_result
133
+ runtime_state.current_result_name = result_name
134
+ continue_value = continue_value(object, field_defn, false, ast_node, result_name, selection_result)
135
+ if HALT != continue_value
136
+ continue_field(continue_value, owner_type, field_defn, root_type, ast_node, nil, false, nil, nil, result_name, selection_result, false, runtime_state) # rubocop:disable Metrics/ParameterLists
137
+ end
138
+ @response = selection_result[result_name]
139
+ else
140
+ @response = GraphQLResultArray.new(nil, root_type, nil, nil, false, selections, false, ast_node, nil, nil)
141
+ @response.base_path = base_path
142
+ idx = nil
143
+ object.each do |inner_value|
144
+ idx ||= 0
145
+ this_idx = idx
146
+ idx += 1
147
+ @dataloader.append_job do
148
+ runtime_state.current_result_name = this_idx
149
+ runtime_state.current_result = @response
150
+ continue_field(
151
+ inner_value, root_type, nil, inner_type, nil, @response.graphql_selections, false, object_proxy,
152
+ nil, this_idx, @response, false, runtime_state
103
153
  )
104
- }
154
+ end
105
155
  end
106
156
  end
157
+ when "SCALAR", "ENUM"
158
+ result_name = ast_node.alias || ast_node.name
159
+ field_defn = query.field_definition
160
+ owner_type = field_defn.owner
161
+ selection_result = GraphQLResultHash.new(nil, owner_type, nil, nil, false, EmptyObjects::EMPTY_ARRAY, false, ast_node, nil, nil)
162
+ selection_result.ordered_result_keys = [result_name]
163
+ selection_result.base_path = base_path
164
+ runtime_state = get_current_runtime_state
165
+ runtime_state.current_result = selection_result
166
+ runtime_state.current_result_name = result_name
167
+ continue_value = continue_value(object, field_defn, false, ast_node, result_name, selection_result)
168
+ if HALT != continue_value
169
+ continue_field(continue_value, owner_type, field_defn, query.root_type, ast_node, nil, false, nil, nil, result_name, selection_result, false, runtime_state) # rubocop:disable Metrics/ParameterLists
170
+ end
171
+ @response = selection_result[result_name]
172
+ when "UNION", "INTERFACE"
173
+ resolved_type, _resolved_obj = resolve_type(root_type, object)
174
+ resolved_type = schema.sync_lazy(resolved_type)
175
+ object_proxy = resolved_type.wrap(object, context)
176
+ object_proxy = schema.sync_lazy(object_proxy)
177
+ @response = GraphQLResultHash.new(nil, resolved_type, object_proxy, nil, false, selections, false, query.ast_nodes.first, nil, nil)
178
+ @response.base_path = base_path
179
+ each_gathered_selections(@response) do |selections, is_selection_array, ordered_result_keys|
180
+ @response.ordered_result_keys ||= ordered_result_keys
181
+ if is_selection_array == true
182
+ raise "This isn't supported yet"
183
+ end
184
+
185
+ @dataloader.append_job {
186
+ evaluate_selections(
187
+ selections,
188
+ @response,
189
+ nil,
190
+ runtime_state,
191
+ )
192
+ }
193
+ end
194
+ else
195
+ raise "Invariant: unsupported type kind for partial execution: #{root_type.kind.inspect} (#{root_type})"
107
196
  end
108
197
  nil
109
198
  end
@@ -209,8 +298,6 @@ module GraphQL
209
298
  end
210
299
 
211
300
  call_method_on_directives(:resolve, selections_result.graphql_application_value, directives) do
212
- finished_jobs = 0
213
- enqueued_jobs = gathered_selections.size
214
301
  gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
215
302
  # Field resolution may pause the fiber,
216
303
  # so it wouldn't get to the `Resolve` call that happens below.
@@ -221,12 +308,6 @@ module GraphQL
221
308
  evaluate_selection(
222
309
  result_name, field_ast_nodes_or_ast_node, selections_result
223
310
  )
224
- finished_jobs += 1
225
- if finished_jobs == enqueued_jobs
226
- if target_result
227
- selections_result.merge_into(target_result)
228
- end
229
- end
230
311
  @dataloader.clear_cache
231
312
  }
232
313
  else
@@ -234,15 +315,12 @@ module GraphQL
234
315
  evaluate_selection(
235
316
  result_name, field_ast_nodes_or_ast_node, selections_result
236
317
  )
237
- finished_jobs += 1
238
- if finished_jobs == enqueued_jobs
239
- if target_result
240
- selections_result.merge_into(target_result)
241
- end
242
- end
243
318
  }
244
319
  end
245
320
  end
321
+ if target_result
322
+ selections_result.merge_into(target_result)
323
+ end
246
324
  selections_result
247
325
  end
248
326
  end
@@ -468,7 +546,7 @@ module GraphQL
468
546
  path
469
547
  end
470
548
 
471
- HALT = Object.new
549
+ HALT = Object.new.freeze
472
550
  def continue_value(value, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
473
551
  case value
474
552
  when nil
@@ -586,6 +664,8 @@ module GraphQL
586
664
  when "SCALAR", "ENUM"
587
665
  r = begin
588
666
  current_type.coerce_result(value, context)
667
+ rescue GraphQL::ExecutionError => ex_err
668
+ return continue_value(ex_err, field, is_non_null, ast_node, result_name, selection_result)
589
669
  rescue StandardError => err
590
670
  query.handle_or_reraise(err)
591
671
  end
@@ -737,6 +817,13 @@ module GraphQL
737
817
  else
738
818
  dir_defn = @schema_directives.fetch(dir_node.name)
739
819
  raw_dir_args = arguments(nil, dir_defn, dir_node)
820
+ if !raw_dir_args.is_a?(GraphQL::ExecutionError)
821
+ begin
822
+ dir_defn.validate!(raw_dir_args, context)
823
+ rescue GraphQL::ExecutionError => err
824
+ raw_dir_args = err
825
+ end
826
+ end
740
827
  dir_args = continue_value(
741
828
  raw_dir_args, # value
742
829
  nil, # field
@@ -26,7 +26,7 @@ module GraphQL
26
26
  query = case opts
27
27
  when Hash
28
28
  schema.query_class.new(schema, nil, **opts)
29
- when GraphQL::Query
29
+ when GraphQL::Query, GraphQL::Query::Partial
30
30
  opts
31
31
  else
32
32
  raise "Expected Hash or GraphQL::Query, not #{opts.class} (#{opts.inspect})"
@@ -20,6 +20,11 @@ module GraphQL
20
20
  @finished
21
21
  end
22
22
 
23
+ def freeze
24
+ @scanner = nil
25
+ super
26
+ end
27
+
23
28
  attr_reader :pos, :tokens_count
24
29
 
25
30
  def advance
@@ -242,7 +247,7 @@ module GraphQL
242
247
  :SCALAR,
243
248
  nil,
244
249
  :FRAGMENT
245
- ]
250
+ ].freeze
246
251
 
247
252
  # This produces a unique integer for bytes 2 and 3 of each keyword string
248
253
  # See https://tenderlovemaking.com/2023/09/02/fast-tokenizers-with-stringscanner.html
@@ -271,7 +276,8 @@ module GraphQL
271
276
  PUNCTUATION_NAME_FOR_BYTE = Punctuation.constants.each_with_object([]) { |name, arr|
272
277
  punct = Punctuation.const_get(name)
273
278
  arr[punct.ord] = name
274
- }
279
+ }.freeze
280
+
275
281
 
276
282
  QUOTE = '"'
277
283
  UNICODE_DIGIT = /[0-9A-Za-z]/
@@ -321,6 +327,7 @@ module GraphQL
321
327
  punct = Punctuation.const_get(punct_name)
322
328
  FIRST_BYTES[punct.ord] = ByteFor::PUNCTUATION
323
329
  end
330
+ FIRST_BYTES.freeze
324
331
 
325
332
 
326
333
  # Replace any escaped unicode or whitespace with the _actual_ characters
@@ -83,7 +83,11 @@ module GraphQL
83
83
 
84
84
  def to_query_string(printer: GraphQL::Language::Printer.new)
85
85
  if printer.is_a?(GraphQL::Language::Printer)
86
- @query_string ||= printer.print(self)
86
+ if frozen?
87
+ @query_string || printer.print(self)
88
+ else
89
+ @query_string ||= printer.print(self)
90
+ end
87
91
  else
88
92
  printer.print(self)
89
93
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "strscan"
4
4
  require "graphql/language/nodes"
5
+ require "graphql/tracing/null_trace"
5
6
 
6
7
  module GraphQL
7
8
  module Language
@@ -84,7 +84,7 @@ module GraphQL
84
84
 
85
85
  attr_writer :types
86
86
 
87
- RUNTIME_METADATA_KEYS = Set.new([:current_object, :current_arguments, :current_field, :current_path])
87
+ RUNTIME_METADATA_KEYS = Set.new([:current_object, :current_arguments, :current_field, :current_path]).freeze
88
88
  # @!method []=(key, value)
89
89
  # Reassign `key` to the hash passed to {Schema#execute} as `context:`
90
90
 
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Query
4
+ # This class is _like_ a {GraphQL::Query}, except it can run on an arbitrary path within a query string.
5
+ #
6
+ # It depends on a "parent" {Query}.
7
+ #
8
+ # During execution, it calls query-related tracing hooks but passes itself as `query:`.
9
+ #
10
+ # The {Partial} will use your {Schema.resolve_type} hook to find the right GraphQL type to use for
11
+ # `object` in some cases.
12
+ #
13
+ # @see Query#run_partials Run via {Query#run_partials}
14
+ class Partial
15
+ include Query::Runnable
16
+
17
+ # @param path [Array<String, Integer>] A path in `query.query_string` to start executing from
18
+ # @param object [Object] A starting object for execution
19
+ # @param query [GraphQL::Query] A full query instance that this partial is based on. Caches are shared.
20
+ # @param context [Hash] Extra context values to merge into `query.context`, if provided
21
+ # @param fragment_node [GraphQL::Language::Nodes::InlineFragment, GraphQL::Language::Nodes::FragmentDefinition]
22
+ def initialize(path: nil, object:, query:, context: nil, fragment_node: nil, type: nil)
23
+ @path = path
24
+ @object = object
25
+ @query = query
26
+ @schema = query.schema
27
+ context_vals = @query.context.to_h
28
+ if context
29
+ context_vals = context_vals.merge(context)
30
+ end
31
+ @context = GraphQL::Query::Context.new(query: self, schema: @query.schema, values: context_vals)
32
+ @multiplex = nil
33
+ @result_values = nil
34
+ @result = nil
35
+
36
+ if fragment_node
37
+ @ast_nodes = [fragment_node]
38
+ @root_type = type || raise(ArgumentError, "Pass `type:` when using `node:`")
39
+ # This is only used when `@leaf`
40
+ @field_definition = nil
41
+ elsif path.nil?
42
+ raise ArgumentError, "`path:` is required if `node:` is not given; add `path:`"
43
+ else
44
+ set_type_info_from_path
45
+ end
46
+
47
+ @leaf = @root_type.unwrap.kind.leaf?
48
+ end
49
+
50
+ def leaf?
51
+ @leaf
52
+ end
53
+
54
+ attr_reader :context, :query, :ast_nodes, :root_type, :object, :field_definition, :path, :schema
55
+
56
+ attr_accessor :multiplex, :result_values
57
+
58
+ class Result < GraphQL::Query::Result
59
+ def path
60
+ @query.path
61
+ end
62
+
63
+ # @return [GraphQL::Query::Partial]
64
+ def partial
65
+ @query
66
+ end
67
+ end
68
+
69
+ def result
70
+ @result ||= Result.new(query: self, values: result_values)
71
+ end
72
+
73
+ def current_trace
74
+ @query.current_trace
75
+ end
76
+
77
+ def types
78
+ @query.types
79
+ end
80
+
81
+ def resolve_type(...)
82
+ @query.resolve_type(...)
83
+ end
84
+
85
+ def variables
86
+ @query.variables
87
+ end
88
+
89
+ def fragments
90
+ @query.fragments
91
+ end
92
+
93
+ def valid?
94
+ @query.valid?
95
+ end
96
+
97
+ def analyzers
98
+ EmptyObjects::EMPTY_ARRAY
99
+ end
100
+
101
+ def analysis_errors=(_ignored)
102
+ # pass
103
+ end
104
+
105
+ def subscription?
106
+ @query.subscription?
107
+ end
108
+
109
+ def selected_operation
110
+ ast_nodes.first
111
+ end
112
+
113
+ def static_errors
114
+ @query.static_errors
115
+ end
116
+
117
+ def selected_operation_name
118
+ @query.selected_operation_name
119
+ end
120
+
121
+ private
122
+
123
+ def set_type_info_from_path
124
+ selections = [@query.selected_operation]
125
+ type = @query.root_type
126
+ parent_type = nil
127
+ field_defn = nil
128
+
129
+ @path.each do |name_in_doc|
130
+ if name_in_doc.is_a?(Integer)
131
+ if type.list?
132
+ type = type.unwrap
133
+ next
134
+ else
135
+ raise ArgumentError, "Received path with index `#{name_in_doc}`, but type wasn't a list. Type: #{type.to_type_signature}, path: #{@path}"
136
+ end
137
+ end
138
+
139
+ next_selections = []
140
+ selections.each do |selection|
141
+ selections_to_check = []
142
+ selections_to_check.concat(selection.selections)
143
+ while (sel = selections_to_check.shift)
144
+ case sel
145
+ when GraphQL::Language::Nodes::InlineFragment
146
+ selections_to_check.concat(sel.selections)
147
+ when GraphQL::Language::Nodes::FragmentSpread
148
+ fragment = @query.fragments[sel.name]
149
+ selections_to_check.concat(fragment.selections)
150
+ when GraphQL::Language::Nodes::Field
151
+ if sel.alias == name_in_doc || sel.name == name_in_doc
152
+ next_selections << sel
153
+ end
154
+ else
155
+ raise "Unexpected selection in partial path: #{sel.class}, #{sel.inspect}"
156
+ end
157
+ end
158
+ end
159
+
160
+ if next_selections.empty?
161
+ raise ArgumentError, "Path `#{@path.inspect}` is not present in this query. `#{name_in_doc.inspect}` was not found. Try a different path or rewrite the query to include it."
162
+ end
163
+ field_name = next_selections.first.name
164
+ field_defn = @schema.get_field(type, field_name, @query.context) || raise("Invariant: no field called #{field_name} on #{type.graphql_name}")
165
+ parent_type = type
166
+ type = field_defn.type
167
+ if type.non_null?
168
+ type = type.of_type
169
+ end
170
+ selections = next_selections
171
+ end
172
+
173
+ @ast_nodes = selections
174
+ @root_type = type
175
+ @field_definition = field_defn
176
+ end
177
+ end
178
+ end
179
+ end