graphql 1.12.11 → 1.12.15

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.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9e46a535c108ff0ae582fcfa598b21cda8faad15142629b1cdae772f3d24cd7e
4
- data.tar.gz: 3185ec249d624c6b23920d9d9fb47167ac031b7645053ff705e3c04acc0fa571
3
+ metadata.gz: f858de6eaac686676c159e87ae2f200b6c69b9b6c1da8e7459595b49f4f44e7a
4
+ data.tar.gz: afd8686f7897896919bef91076285f0763e49076759036352bd212d41c1bb47f
5
5
  SHA512:
6
- metadata.gz: 989c1074a6de63a31002d2bd92da719e1b08682d3eefe361add671a75c1991173b13cc6516f8ede8ff5bcf7499935e514cd8d7d57a2940e85160bc427538e99c
7
- data.tar.gz: cb40ba585e721d28856951312755ef84bdd698a7299b1a383dd4f9db77d8b137e8d21ee9a8784b14780b4230a5a5325520d0ceadf90f6bd055781cc7159257b5
6
+ metadata.gz: 6774f423bdf61cf07d842ca00f517068139a86da2d6e0d6b7e15e7970aaf5ce1b07e58d78c0a13a1ccf0a8db87c48d4061e151ca42bc21731e26958ef96ad922
7
+ data.tar.gz: 31ca7b94bacf8df5a8c27c0e3c2f831362970e49c65c8b0a284095c1bd392af26347cecc78e60dc91ff4581a9a3d55dc08204eb0230ed641f2907e009a4f7f3e
@@ -7,6 +7,7 @@ module GraphQL
7
7
  super
8
8
  @used_fields = Set.new
9
9
  @used_deprecated_fields = Set.new
10
+ @used_deprecated_arguments = Set.new
10
11
  end
11
12
 
12
13
  def on_leave_field(node, parent, visitor)
@@ -14,14 +15,36 @@ module GraphQL
14
15
  field = "#{visitor.parent_type_definition.graphql_name}.#{field_defn.graphql_name}"
15
16
  @used_fields << field
16
17
  @used_deprecated_fields << field if field_defn.deprecation_reason
18
+
19
+ extract_deprecated_arguments(visitor.query.arguments_for(node, visitor.field_definition).argument_values)
17
20
  end
18
21
 
19
22
  def result
20
23
  {
21
24
  used_fields: @used_fields.to_a,
22
- used_deprecated_fields: @used_deprecated_fields.to_a
25
+ used_deprecated_fields: @used_deprecated_fields.to_a,
26
+ used_deprecated_arguments: @used_deprecated_arguments.to_a,
23
27
  }
24
28
  end
29
+
30
+ private
31
+
32
+ def extract_deprecated_arguments(argument_values)
33
+ argument_values.each_pair do |_argument_name, argument|
34
+ if argument.definition.deprecation_reason
35
+ @used_deprecated_arguments << argument.definition.path
36
+ end
37
+
38
+ if argument.definition.type.kind.input_object?
39
+ extract_deprecated_arguments(argument.value.arguments.argument_values)
40
+ elsif argument.definition.type.list?
41
+ argument
42
+ .value
43
+ .select { |value| value.respond_to?(:arguments) }
44
+ .each { |value| extract_deprecated_arguments(value.arguments.argument_values) }
45
+ end
46
+ end
47
+ end
25
48
  end
26
49
  end
27
50
  end
@@ -144,7 +144,7 @@ module GraphQL
144
144
  end
145
145
 
146
146
  def value_at(runtime, path)
147
- response = runtime.response
147
+ response = runtime.final_result
148
148
  path.each do |key|
149
149
  if response && (response = response[key])
150
150
  next
@@ -15,14 +15,17 @@ module GraphQL
15
15
  # No query context yet
16
16
  nil
17
17
  when "validate", "analyze_query", "execute_query", "execute_query_lazy"
18
- query = metadata[:query] || metadata[:queries].first
19
18
  push_key = []
20
- push_data = query
21
- multiplex = query.multiplex
19
+ if (query = metadata[:query]) || ((queries = metadata[:queries]) && (query = queries.first))
20
+ push_data = query
21
+ multiplex = query.multiplex
22
+ elsif (multiplex = metadata[:multiplex])
23
+ push_data = multiplex.queries.first
24
+ end
22
25
  when "execute_field", "execute_field_lazy"
23
26
  query = metadata[:query] || raise(ArgumentError, "Add `legacy: true` to use GraphQL::Backtrace without the interpreter runtime.")
24
27
  multiplex = query.multiplex
25
- push_key = metadata[:path].reject { |i| i.is_a?(Integer) }
28
+ push_key = metadata[:path]
26
29
  parent_frame = multiplex.context[:graphql_backtrace_contexts][push_key[0..-2]]
27
30
 
28
31
  if parent_frame.is_a?(GraphQL::Query)
@@ -10,6 +10,7 @@ module GraphQL
10
10
  # These are all no-ops because code was
11
11
  # executed sychronously.
12
12
  def run; end
13
+ def run_isolated; yield; end
13
14
  def yield; end
14
15
 
15
16
  def append_job
@@ -77,6 +77,21 @@ module GraphQL
77
77
  nil
78
78
  end
79
79
 
80
+ # Use a self-contained queue for the work in the block.
81
+ def run_isolated
82
+ prev_queue = @pending_jobs
83
+ @pending_jobs = []
84
+ res = nil
85
+ # Make sure the block is inside a Fiber, so it can `Fiber.yield`
86
+ append_job {
87
+ res = yield
88
+ }
89
+ run
90
+ res
91
+ ensure
92
+ @pending_jobs = prev_queue
93
+ end
94
+
80
95
  # @api private Move along, move along
81
96
  def run
82
97
  # At a high level, the algorithm is:
@@ -28,11 +28,12 @@ module GraphQL
28
28
  end
29
29
 
30
30
  def fetch(ast_node, argument_owner, parent_object)
31
- @storage[ast_node][argument_owner][parent_object]
32
31
  # If any jobs were enqueued, run them now,
33
32
  # since this might have been called outside of execution.
34
33
  # (The jobs are responsible for updating `result` in-place.)
35
- @dataloader.run
34
+ @dataloader.run_isolated do
35
+ @storage[ast_node][argument_owner][parent_object]
36
+ end
36
37
  # Ack, the _hash_ is updated, but the key is eventually
37
38
  # overridden with an immutable arguments instance.
38
39
  # The first call queues up the job,
@@ -34,7 +34,10 @@ module GraphQL
34
34
  next_results = []
35
35
  while results.any?
36
36
  result_value = results.shift
37
- if result_value.is_a?(Hash)
37
+ if result_value.is_a?(Runtime::GraphQLResultHash) || result_value.is_a?(Hash)
38
+ results.concat(result_value.values)
39
+ next
40
+ elsif result_value.is_a?(Runtime::GraphQLResultArray)
38
41
  results.concat(result_value.values)
39
42
  next
40
43
  elsif result_value.is_a?(Array)
@@ -46,7 +49,8 @@ module GraphQL
46
49
  # Since this field returned another lazy,
47
50
  # add it to the same queue
48
51
  results << loaded_value
49
- elsif loaded_value.is_a?(Hash) || loaded_value.is_a?(Array)
52
+ elsif loaded_value.is_a?(Runtime::GraphQLResultHash) || loaded_value.is_a?(Runtime::GraphQLResultArray) ||
53
+ loaded_value.is_a?(Hash) || loaded_value.is_a?(Array)
50
54
  # Add these values in wholesale --
51
55
  # they might be modified by later work in the dataloader.
52
56
  next_results << loaded_value
@@ -10,8 +10,6 @@ module GraphQL
10
10
  class Runtime
11
11
 
12
12
  module GraphQLResult
13
- # These methods are private concerns of GraphQL-Ruby,
14
- # they aren't guaranteed to continue working in the future.
15
13
  attr_accessor :graphql_dead, :graphql_parent, :graphql_result_name
16
14
  # Although these are used by only one of the Result classes,
17
15
  # it's handy to have the methods implemented on both (even though they just return `nil`)
@@ -20,14 +18,116 @@ module GraphQL
20
18
  attr_accessor :graphql_non_null_field_names
21
19
  # @return [nil, true]
22
20
  attr_accessor :graphql_non_null_list_items
21
+
22
+ # @return [Hash] Plain-Ruby result data (`@graphql_metadata` contains Result wrapper objects)
23
+ attr_accessor :graphql_result_data
23
24
  end
24
25
 
25
- class GraphQLResultHash < Hash
26
+ class GraphQLResultHash
27
+ def initialize
28
+ # Jump through some hoops to avoid creating this duplicate hash if at all possible.
29
+ @graphql_metadata = nil
30
+ @graphql_result_data = {}
31
+ end
32
+
26
33
  include GraphQLResult
34
+
35
+ attr_accessor :graphql_merged_into
36
+
37
+ def []=(key, value)
38
+ # This is a hack.
39
+ # Basically, this object is merged into the root-level result at some point.
40
+ # But the problem is, some lazies are created whose closures retain reference to _this_
41
+ # object. When those lazies are resolved, they cause an update to this object.
42
+ #
43
+ # In order to return a proper top-level result, we have to update that top-level result object.
44
+ # In order to return a proper partial result (eg, for a directive), we have to update this object, too.
45
+ # Yowza.
46
+ if (t = @graphql_merged_into)
47
+ t[key] = value
48
+ end
49
+
50
+ if value.respond_to?(:graphql_result_data)
51
+ @graphql_result_data[key] = value.graphql_result_data
52
+ # If we encounter some part of this response that requires metadata tracking,
53
+ # then create the metadata hash if necessary. It will be kept up-to-date after this.
54
+ (@graphql_metadata ||= @graphql_result_data.dup)[key] = value
55
+ else
56
+ @graphql_result_data[key] = value
57
+ # keep this up-to-date if it's been initialized
58
+ @graphql_metadata && @graphql_metadata[key] = value
59
+ end
60
+
61
+ value
62
+ end
63
+
64
+ def delete(key)
65
+ @graphql_metadata && @graphql_metadata.delete(key)
66
+ @graphql_result_data.delete(key)
67
+ end
68
+
69
+ def each
70
+ (@graphql_metadata || @graphql_result_data).each { |k, v| yield(k, v) }
71
+ end
72
+
73
+ def values
74
+ (@graphql_metadata || @graphql_result_data).values
75
+ end
76
+
77
+ def key?(k)
78
+ @graphql_result_data.key?(k)
79
+ end
80
+
81
+ def [](k)
82
+ (@graphql_metadata || @graphql_result_data)[k]
83
+ end
27
84
  end
28
85
 
29
- class GraphQLResultArray < Array
86
+ class GraphQLResultArray
30
87
  include GraphQLResult
88
+
89
+ def initialize
90
+ # Avoid this duplicate allocation if possible -
91
+ # but it will require some work to keep it up-to-date if it's created.
92
+ @graphql_metadata = nil
93
+ @graphql_result_data = []
94
+ end
95
+
96
+ def graphql_skip_at(index)
97
+ # Mark this index as dead. It's tricky because some indices may already be storing
98
+ # `Lazy`s. So the runtime is still holding indexes _before_ skipping,
99
+ # this object has to coordinate incoming writes to account for any already-skipped indices.
100
+ @skip_indices ||= []
101
+ @skip_indices << index
102
+ offset_by = @skip_indices.count { |skipped_idx| skipped_idx < index}
103
+ delete_at_index = index - offset_by
104
+ @graphql_metadata && @graphql_metadata.delete_at(delete_at_index)
105
+ @graphql_result_data.delete_at(delete_at_index)
106
+ end
107
+
108
+ def []=(idx, value)
109
+ if @skip_indices
110
+ offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx }
111
+ idx -= offset_by
112
+ end
113
+ if value.respond_to?(:graphql_result_data)
114
+ @graphql_result_data[idx] = value.graphql_result_data
115
+ (@graphql_metadata ||= @graphql_result_data.dup)[idx] = value
116
+ else
117
+ @graphql_result_data[idx] = value
118
+ @graphql_metadata && @graphql_metadata[idx] = value
119
+ end
120
+
121
+ value
122
+ end
123
+
124
+ def values
125
+ (@graphql_metadata || @graphql_result_data)
126
+ end
127
+ end
128
+
129
+ class GraphQLSelectionSet < Hash
130
+ attr_accessor :graphql_directives
31
131
  end
32
132
 
33
133
  # @return [GraphQL::Query]
@@ -39,9 +139,6 @@ module GraphQL
39
139
  # @return [GraphQL::Query::Context]
40
140
  attr_reader :context
41
141
 
42
- # @return [Hash]
43
- attr_reader :response
44
-
45
142
  def initialize(query:)
46
143
  @query = query
47
144
  @dataloader = query.multiplex.dataloader
@@ -50,6 +147,14 @@ module GraphQL
50
147
  @multiplex_context = query.multiplex.context
51
148
  @interpreter_context = @context.namespace(:interpreter)
52
149
  @response = GraphQLResultHash.new
150
+ # Identify runtime directives by checking which of this schema's directives have overridden `def self.resolve`
151
+ @runtime_directive_names = []
152
+ noop_resolve_owner = GraphQL::Schema::Directive.singleton_class
153
+ schema.directives.each do |name, dir_defn|
154
+ if dir_defn.method(:resolve).owner != noop_resolve_owner
155
+ @runtime_directive_names << name
156
+ end
157
+ end
53
158
  # A cache of { Class => { String => Schema::Field } }
54
159
  # Which assumes that MyObject.get_field("myField") will return the same field
55
160
  # during the lifetime of a query
@@ -58,10 +163,24 @@ module GraphQL
58
163
  @lazy_cache = {}
59
164
  end
60
165
 
166
+ def final_result
167
+ @response && @response.graphql_result_data
168
+ end
169
+
61
170
  def inspect
62
171
  "#<#{self.class.name} response=#{@response.inspect}>"
63
172
  end
64
173
 
174
+ def tap_or_each(obj_or_array)
175
+ if obj_or_array.is_a?(Array)
176
+ obj_or_array.each do |item|
177
+ yield(item, true)
178
+ end
179
+ else
180
+ yield(obj_or_array, false)
181
+ end
182
+ end
183
+
65
184
  # This _begins_ the execution. Some deferred work
66
185
  # might be stored up in lazies.
67
186
  # @return [void]
@@ -78,20 +197,40 @@ module GraphQL
78
197
  # Root .authorized? returned false.
79
198
  @response = nil
80
199
  else
81
- resolve_with_directives(object_proxy, root_operation) do # execute query level directives
200
+ resolve_with_directives(object_proxy, root_operation.directives) do # execute query level directives
82
201
  gathered_selections = gather_selections(object_proxy, root_type, root_operation.selections)
83
- # Make the first fiber which will begin execution
84
- @dataloader.append_job {
85
- evaluate_selections(
86
- path,
87
- context.scoped_context,
88
- object_proxy,
89
- root_type,
90
- root_op_type == "mutation",
91
- gathered_selections,
92
- @response,
93
- )
94
- }
202
+ # This is kind of a hack -- `gathered_selections` is an Array if any of the selections
203
+ # require isolation during execution (because of runtime directives). In that case,
204
+ # make a new, isolated result hash for writing the result into. (That isolated response
205
+ # is eventually merged back into the main response)
206
+ #
207
+ # Otherwise, `gathered_selections` is a hash of selections which can be
208
+ # directly evaluated and the results can be written right into the main response hash.
209
+ tap_or_each(gathered_selections) do |selections, is_selection_array|
210
+ if is_selection_array
211
+ selection_response = GraphQLResultHash.new
212
+ final_response = @response
213
+ else
214
+ selection_response = @response
215
+ final_response = nil
216
+ end
217
+
218
+ @dataloader.append_job {
219
+ set_all_interpreter_context(query.root_value, nil, nil, path)
220
+ resolve_with_directives(object_proxy, selections.graphql_directives) do
221
+ evaluate_selections(
222
+ path,
223
+ context.scoped_context,
224
+ object_proxy,
225
+ root_type,
226
+ root_op_type == "mutation",
227
+ selections,
228
+ selection_response,
229
+ final_response,
230
+ )
231
+ end
232
+ }
233
+ end
95
234
  end
96
235
  end
97
236
  delete_interpreter_context(:current_path)
@@ -101,15 +240,36 @@ module GraphQL
101
240
  nil
102
241
  end
103
242
 
104
- def gather_selections(owner_object, owner_type, selections, selections_by_name = {})
243
+ # @return [void]
244
+ def deep_merge_selection_result(from_result, into_result)
245
+ from_result.each do |key, value|
246
+ if !into_result.key?(key)
247
+ into_result[key] = value
248
+ else
249
+ case value
250
+ when GraphQLResultHash
251
+ deep_merge_selection_result(value, into_result[key])
252
+ else
253
+ # We have to assume that, since this passed the `fields_will_merge` selection,
254
+ # that the old and new values are the same.
255
+ # There's no special handling of arrays because currently, there's no way to split the execution
256
+ # of a list over several concurrent flows.
257
+ into_result[key] = value
258
+ end
259
+ end
260
+ end
261
+ from_result.graphql_merged_into = into_result
262
+ nil
263
+ end
264
+
265
+ def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = GraphQLSelectionSet.new)
105
266
  selections.each do |node|
106
267
  # Skip gathering this if the directive says so
107
268
  if !directives_include?(node, owner_object, owner_type)
108
269
  next
109
270
  end
110
271
 
111
- case node
112
- when GraphQL::Language::Nodes::Field
272
+ if node.is_a?(GraphQL::Language::Nodes::Field)
113
273
  response_key = node.alias || node.name
114
274
  selections = selections_by_name[response_key]
115
275
  # if there was already a selection of this field,
@@ -125,52 +285,77 @@ module GraphQL
125
285
  # No selection was found for this field yet
126
286
  selections_by_name[response_key] = node
127
287
  end
128
- when GraphQL::Language::Nodes::InlineFragment
129
- if node.type
130
- type_defn = schema.get_type(node.type.name)
131
- # Faster than .map{}.include?()
132
- query.warden.possible_types(type_defn).each do |t|
288
+ else
289
+ # This is an InlineFragment or a FragmentSpread
290
+ if @runtime_directive_names.any? && node.directives.any? { |d| @runtime_directive_names.include?(d.name) }
291
+ next_selections = GraphQLSelectionSet.new
292
+ next_selections.graphql_directives = node.directives
293
+ if selections_to_run
294
+ selections_to_run << next_selections
295
+ else
296
+ selections_to_run = []
297
+ selections_to_run << selections_by_name
298
+ selections_to_run << next_selections
299
+ end
300
+ else
301
+ next_selections = selections_by_name
302
+ end
303
+
304
+ case node
305
+ when GraphQL::Language::Nodes::InlineFragment
306
+ if node.type
307
+ type_defn = schema.get_type(node.type.name)
308
+
309
+ # Faster than .map{}.include?()
310
+ query.warden.possible_types(type_defn).each do |t|
311
+ if t == owner_type
312
+ gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
313
+ break
314
+ end
315
+ end
316
+ else
317
+ # it's an untyped fragment, definitely continue
318
+ gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
319
+ end
320
+ when GraphQL::Language::Nodes::FragmentSpread
321
+ fragment_def = query.fragments[node.name]
322
+ type_defn = schema.get_type(fragment_def.type.name)
323
+ possible_types = query.warden.possible_types(type_defn)
324
+ possible_types.each do |t|
133
325
  if t == owner_type
134
- gather_selections(owner_object, owner_type, node.selections, selections_by_name)
326
+ gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections)
135
327
  break
136
328
  end
137
329
  end
138
330
  else
139
- # it's an untyped fragment, definitely continue
140
- gather_selections(owner_object, owner_type, node.selections, selections_by_name)
331
+ raise "Invariant: unexpected selection class: #{node.class}"
141
332
  end
142
- when GraphQL::Language::Nodes::FragmentSpread
143
- fragment_def = query.fragments[node.name]
144
- type_defn = schema.get_type(fragment_def.type.name)
145
- possible_types = query.warden.possible_types(type_defn)
146
- possible_types.each do |t|
147
- if t == owner_type
148
- gather_selections(owner_object, owner_type, fragment_def.selections, selections_by_name)
149
- break
150
- end
151
- end
152
- else
153
- raise "Invariant: unexpected selection class: #{node.class}"
154
333
  end
155
334
  end
156
- selections_by_name
335
+ selections_to_run || selections_by_name
157
336
  end
158
337
 
159
338
  NO_ARGS = {}.freeze
160
339
 
161
340
  # @return [void]
162
- def evaluate_selections(path, scoped_context, owner_object, owner_type, is_eager_selection, gathered_selections, selections_result)
341
+ def evaluate_selections(path, scoped_context, owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result) # rubocop:disable Metrics/ParameterLists
163
342
  set_all_interpreter_context(owner_object, nil, nil, path)
164
343
 
344
+ finished_jobs = 0
345
+ enqueued_jobs = gathered_selections.size
165
346
  gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
166
347
  @dataloader.append_job {
167
348
  evaluate_selection(
168
349
  path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_selection, selections_result
169
350
  )
351
+ finished_jobs += 1
352
+ if target_result && finished_jobs == enqueued_jobs
353
+ deep_merge_selection_result(selections_result, target_result)
354
+ end
170
355
  }
171
356
  end
172
357
 
173
- nil
358
+ selections_result
174
359
  end
175
360
 
176
361
  attr_reader :progress_path
@@ -292,12 +477,17 @@ module GraphQL
292
477
  # Optimize for the case that field is selected only once
293
478
  if field_ast_nodes.nil? || field_ast_nodes.size == 1
294
479
  next_selections = ast_node.selections
480
+ directives = ast_node.directives
295
481
  else
296
482
  next_selections = []
297
- field_ast_nodes.each { |f| next_selections.concat(f.selections) }
483
+ directives = []
484
+ field_ast_nodes.each { |f|
485
+ next_selections.concat(f.selections)
486
+ directives.concat(f.directives)
487
+ }
298
488
  end
299
489
 
300
- field_result = resolve_with_directives(object, ast_node) do
490
+ field_result = resolve_with_directives(object, directives) do
301
491
  # Actually call the field resolver and capture the result
302
492
  app_result = begin
303
493
  query.with_error_handling do
@@ -329,15 +519,7 @@ module GraphQL
329
519
  end
330
520
 
331
521
  def dead_result?(selection_result)
332
- r = selection_result
333
- while r
334
- if r.graphql_dead
335
- return true
336
- else
337
- r = r.graphql_parent
338
- end
339
- end
340
- false
522
+ selection_result.graphql_dead || ((parent = selection_result.graphql_parent) && parent.graphql_dead)
341
523
  end
342
524
 
343
525
  def set_result(selection_result, result_name, value)
@@ -361,9 +543,7 @@ module GraphQL
361
543
  @response = nil
362
544
  else
363
545
  set_result(parent, name_in_parent, nil)
364
- # This is odd, but it's how it used to work. Even if `parent` _would_ accept
365
- # a `nil`, it's marked dead. TODO: check the spec, is there a reason for this?
366
- parent.graphql_dead = true
546
+ set_graphql_dead(selection_result)
367
547
  end
368
548
  else
369
549
  selection_result[result_name] = value
@@ -371,6 +551,21 @@ module GraphQL
371
551
  end
372
552
  end
373
553
 
554
+ # Mark this node and any already-registered children as dead,
555
+ # so that it accepts no more writes.
556
+ def set_graphql_dead(selection_result)
557
+ case selection_result
558
+ when GraphQLResultArray
559
+ selection_result.graphql_dead = true
560
+ selection_result.values.each { |v| set_graphql_dead(v) }
561
+ when GraphQLResultHash
562
+ selection_result.graphql_dead = true
563
+ selection_result.each { |k, v| set_graphql_dead(v) }
564
+ else
565
+ # It's a scalar, no way to mark it dead.
566
+ end
567
+ end
568
+
374
569
  HALT = Object.new
375
570
  def continue_value(path, value, parent_type, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
376
571
  case value
@@ -390,11 +585,13 @@ module GraphQL
390
585
  # to avoid the overhead of checking three different classes
391
586
  # every time.
392
587
  if value.is_a?(GraphQL::ExecutionError)
393
- if !dead_result?(selection_result)
588
+ if selection_result.nil? || !dead_result?(selection_result)
394
589
  value.path ||= path
395
590
  value.ast_node ||= ast_node
396
591
  context.errors << value
397
- set_result(selection_result, result_name, nil)
592
+ if selection_result
593
+ set_result(selection_result, result_name, nil)
594
+ end
398
595
  end
399
596
  HALT
400
597
  elsif value.is_a?(GraphQL::UnauthorizedError)
@@ -407,6 +604,17 @@ module GraphQL
407
604
  end
408
605
  continue_value(path, next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
409
606
  elsif GraphQL::Execution::Execute::SKIP == value
607
+ # It's possible a lazy was already written here
608
+ case selection_result
609
+ when GraphQLResultHash
610
+ selection_result.delete(result_name)
611
+ when GraphQLResultArray
612
+ selection_result.graphql_skip_at(result_name)
613
+ when nil
614
+ # this can happen with directives
615
+ else
616
+ raise "Invariant: unexpected result class #{selection_result.class} (#{selection_result.inspect})"
617
+ end
410
618
  HALT
411
619
  else
412
620
  # What could this actually _be_? Anyhow,
@@ -416,13 +624,15 @@ module GraphQL
416
624
  when Array
417
625
  # It's an array full of execution errors; add them all.
418
626
  if value.any? && value.all? { |v| v.is_a?(GraphQL::ExecutionError) }
419
- if !dead_result?(selection_result)
627
+ if selection_result.nil? || !dead_result?(selection_result)
420
628
  value.each_with_index do |error, index|
421
629
  error.ast_node ||= ast_node
422
- error.path ||= path + (field.type.list? ? [index] : [])
630
+ error.path ||= path + ((field && field.type.list?) ? [index] : [])
423
631
  context.errors << error
424
632
  end
425
- set_result(selection_result, result_name, nil)
633
+ if selection_result
634
+ set_result(selection_result, result_name, nil)
635
+ end
426
636
  end
427
637
  HALT
428
638
  else
@@ -488,8 +698,39 @@ module GraphQL
488
698
  response_hash.graphql_result_name = result_name
489
699
  set_result(selection_result, result_name, response_hash)
490
700
  gathered_selections = gather_selections(continue_value, current_type, next_selections)
491
- evaluate_selections(path, context.scoped_context, continue_value, current_type, false, gathered_selections, response_hash)
492
- response_hash
701
+ # There are two possibilities for `gathered_selections`:
702
+ # 1. All selections of this object should be evaluated together (there are no runtime directives modifying execution).
703
+ # This case is handled below, and the result can be written right into the main `response_hash` above.
704
+ # In this case, `gathered_selections` is a hash of selections.
705
+ # 2. Some selections of this object have runtime directives that may or may not modify execution.
706
+ # That part of the selection is evaluated in an isolated way, writing into a sub-response object which is
707
+ # eventually merged into the final response. In this case, `gathered_selections` is an array of things to run in isolation.
708
+ # (Technically, it's possible that one of those entries _doesn't_ require isolation.)
709
+ tap_or_each(gathered_selections) do |selections, is_selection_array|
710
+ if is_selection_array
711
+ this_result = GraphQLResultHash.new
712
+ this_result.graphql_parent = selection_result
713
+ this_result.graphql_result_name = result_name
714
+ final_result = response_hash
715
+ else
716
+ this_result = response_hash
717
+ final_result = nil
718
+ end
719
+ set_all_interpreter_context(continue_value, nil, nil, path) # reset this mutable state
720
+ resolve_with_directives(continue_value, selections.graphql_directives) do
721
+ evaluate_selections(
722
+ path,
723
+ context.scoped_context,
724
+ continue_value,
725
+ current_type,
726
+ false,
727
+ selections,
728
+ this_result,
729
+ final_result,
730
+ )
731
+ this_result
732
+ end
733
+ end
493
734
  end
494
735
  end
495
736
  when "LIST"
@@ -534,13 +775,13 @@ module GraphQL
534
775
  end
535
776
  end
536
777
 
537
- def resolve_with_directives(object, ast_node, &block)
538
- return yield if ast_node.directives.empty?
539
- run_directive(object, ast_node, 0, &block)
778
+ def resolve_with_directives(object, directives, &block)
779
+ return yield if directives.nil? || directives.empty?
780
+ run_directive(object, directives, 0, &block)
540
781
  end
541
782
 
542
- def run_directive(object, ast_node, idx, &block)
543
- dir_node = ast_node.directives[idx]
783
+ def run_directive(object, directives, idx, &block)
784
+ dir_node = directives[idx]
544
785
  if !dir_node
545
786
  yield
546
787
  else
@@ -548,9 +789,24 @@ module GraphQL
548
789
  if !dir_defn.is_a?(Class)
549
790
  dir_defn = dir_defn.type_class || raise("Only class-based directives are supported (not `@#{dir_node.name}`)")
550
791
  end
551
- dir_args = arguments(nil, dir_defn, dir_node).keyword_arguments
552
- dir_defn.resolve(object, dir_args, context) do
553
- run_directive(object, ast_node, idx + 1, &block)
792
+ raw_dir_args = arguments(nil, dir_defn, dir_node)
793
+ dir_args = continue_value(
794
+ @context[:current_path], # path
795
+ raw_dir_args, # value
796
+ dir_defn, # parent_type
797
+ nil, # field
798
+ false, # is_non_null
799
+ dir_node, # ast_node
800
+ nil, # result_name
801
+ nil, # selection_result
802
+ )
803
+
804
+ if dir_args == HALT
805
+ nil
806
+ else
807
+ dir_defn.resolve(object, dir_args, context) do
808
+ run_directive(object, directives, idx + 1, &block)
809
+ end
554
810
  end
555
811
  end
556
812
  end
@@ -559,7 +815,7 @@ module GraphQL
559
815
  def directives_include?(node, graphql_object, parent_type)
560
816
  node.directives.each do |dir_node|
561
817
  dir_defn = schema.directives.fetch(dir_node.name).type_class || raise("Only class-based directives are supported (not #{dir_node.name.inspect})")
562
- args = arguments(graphql_object, dir_defn, dir_node).keyword_arguments
818
+ args = arguments(graphql_object, dir_defn, dir_node)
563
819
  if !dir_defn.include?(graphql_object, args, context)
564
820
  return false
565
821
  end
@@ -18,7 +18,7 @@ module GraphQL
18
18
  def execute(_operation, _root_type, query)
19
19
  runtime = evaluate(query)
20
20
  sync_lazies(query: query)
21
- runtime.response
21
+ runtime.final_result
22
22
  end
23
23
 
24
24
  def self.use(schema_class)
@@ -56,7 +56,7 @@ module GraphQL
56
56
 
57
57
  def self.finish_query(query, _multiplex)
58
58
  {
59
- "data" => query.context.namespace(:interpreter)[:runtime].response
59
+ "data" => query.context.namespace(:interpreter)[:runtime].final_result
60
60
  }
61
61
  end
62
62
 
@@ -87,7 +87,7 @@ module GraphQL
87
87
  final_values = queries.map do |query|
88
88
  runtime = query.context.namespace(:interpreter)[:runtime]
89
89
  # it might not be present if the query has an error
90
- runtime ? runtime.response : nil
90
+ runtime ? runtime.final_result : nil
91
91
  end
92
92
  final_values.compact!
93
93
  tracer.trace("execute_query_lazy", {multiplex: multiplex, query: query}) do
@@ -48,7 +48,11 @@ module GraphQL
48
48
  end
49
49
  end
50
50
 
51
- if @value.is_a?(StandardError)
51
+ # `SKIP` was made into a subclass of `GraphQL::Error` to improve runtime performance
52
+ # (fewer clauses in a hot `case` block), but now it requires special handling here.
53
+ # I think it's still worth it for the performance win, but if the number of special
54
+ # cases grows, then maybe it's worth rethinking somehow.
55
+ if @value.is_a?(StandardError) && @value != GraphQL::Execution::Execute::SKIP
52
56
  raise @value
53
57
  else
54
58
  @value
data/lib/graphql/query.rb CHANGED
@@ -270,7 +270,7 @@ module GraphQL
270
270
  # @return [String, nil] Returns nil if the query is invalid.
271
271
  def sanitized_query_string(inline_variables: true)
272
272
  with_prepared_ast {
273
- GraphQL::Language::SanitizedPrinter.new(self, inline_variables: inline_variables).sanitized_query_string
273
+ schema.sanitized_printer.new(self, inline_variables: inline_variables).sanitized_query_string
274
274
  }
275
275
  end
276
276
 
@@ -47,10 +47,16 @@ module GraphQL
47
47
  # _while_ building the schema.
48
48
  # It will dig for a type if it encounters a custom type. This could be a problem if there are cycles.
49
49
  directive_type_resolver = nil
50
- directive_type_resolver = build_resolve_type(GraphQL::Schema::BUILT_IN_TYPES, directives, ->(type_name) {
50
+ directive_type_resolver = build_resolve_type(types, directives, ->(type_name) {
51
51
  types[type_name] ||= begin
52
52
  defn = document.definitions.find { |d| d.respond_to?(:name) && d.name == type_name }
53
- build_definition_from_node(defn, directive_type_resolver, default_resolve)
53
+ if defn
54
+ build_definition_from_node(defn, directive_type_resolver, default_resolve)
55
+ elsif (built_in_defn = GraphQL::Schema::BUILT_IN_TYPES[type_name])
56
+ built_in_defn
57
+ else
58
+ raise "No definition for #{type_name.inspect} found in schema document or built-in types. Add a definition for it or remove it."
59
+ end
54
60
  end
55
61
  })
56
62
 
@@ -39,7 +39,7 @@ module GraphQL
39
39
  transform_name = arguments[:by]
40
40
  if TRANSFORMS.include?(transform_name) && return_value.respond_to?(transform_name)
41
41
  return_value = return_value.public_send(transform_name)
42
- response = context.namespace(:interpreter)[:runtime].response
42
+ response = context.namespace(:interpreter)[:runtime].final_result
43
43
  *keys, last = path
44
44
  keys.each do |key|
45
45
  if response && (response = response[key])
@@ -24,6 +24,15 @@ module GraphQL
24
24
  extend GraphQL::Schema::Member::ValidatesInput
25
25
 
26
26
  class UnresolvedValueError < GraphQL::EnumType::UnresolvedValueError
27
+ def initialize(value:, enum:, context:)
28
+ fix_message = ", but this isn't a valid value for `#{enum.graphql_name}`. Update the field or resolver to return one of `#{enum.graphql_name}`'s values instead."
29
+ message = if (cp = context[:current_path]) && (cf = context[:current_field])
30
+ "`#{cf.path}` returned `#{value.inspect}` at `#{cp.join(".")}`#{fix_message}"
31
+ else
32
+ "`#{value.inspect}` was returned for `#{enum.graphql_name}`#{fix_message}"
33
+ end
34
+ super(message)
35
+ end
27
36
  end
28
37
 
29
38
  class << self
@@ -100,7 +109,7 @@ module GraphQL
100
109
  if enum_value
101
110
  enum_value.graphql_name
102
111
  else
103
- raise(self::UnresolvedValueError, "Can't resolve enum #{graphql_name} for #{value.inspect}")
112
+ raise self::UnresolvedValueError.new(enum: self, value: value, context: ctx)
104
113
  end
105
114
  end
106
115
 
@@ -11,6 +11,14 @@ module GraphQL
11
11
 
12
12
  include GraphQL::Dig
13
13
 
14
+ # @return [GraphQL::Query::Context] The context for this query
15
+ attr_reader :context
16
+ # @return [GraphQL::Query::Arguments, GraphQL::Execution::Interpereter::Arguments] The underlying arguments instance
17
+ attr_reader :arguments
18
+
19
+ # Ruby-like hash behaviors, read-only
20
+ def_delegators :@ruby_style_hash, :keys, :values, :each, :map, :any?, :empty?
21
+
14
22
  def initialize(arguments = nil, ruby_kwargs: nil, context:, defaults_used:)
15
23
  @context = context
16
24
  if ruby_kwargs
@@ -54,19 +62,8 @@ module GraphQL
54
62
  @maybe_lazies = maybe_lazies
55
63
  end
56
64
 
57
- # @return [GraphQL::Query::Context] The context for this query
58
- attr_reader :context
59
-
60
- # @return [GraphQL::Query::Arguments, GraphQL::Execution::Interpereter::Arguments] The underlying arguments instance
61
- attr_reader :arguments
62
-
63
- # Ruby-like hash behaviors, read-only
64
- def_delegators :@ruby_style_hash, :keys, :values, :each, :map, :any?, :empty?
65
-
66
65
  def to_h
67
- @ruby_style_hash.inject({}) do |h, (key, value)|
68
- h.merge(key => unwrap_value(value))
69
- end
66
+ unwrap_value(@ruby_style_hash)
70
67
  end
71
68
 
72
69
  def to_hash
@@ -91,8 +88,8 @@ module GraphQL
91
88
  when Array
92
89
  value.map { |item| unwrap_value(item) }
93
90
  when Hash
94
- value.inject({}) do |h, (key, value)|
95
- h.merge(key => unwrap_value(value))
91
+ value.reduce({}) do |h, (key, value)|
92
+ h.merge!(key => unwrap_value(value))
96
93
  end
97
94
  when InputObject
98
95
  value.to_h
@@ -162,7 +159,6 @@ module GraphQL
162
159
  # @api private
163
160
  INVALID_OBJECT_MESSAGE = "Expected %{object} to be a key-value object responding to `to_h` or `to_unsafe_h`."
164
161
 
165
-
166
162
  def validate_non_null_input(input, ctx)
167
163
  result = GraphQL::Query::InputValidationResult.new
168
164
 
@@ -124,6 +124,7 @@ module GraphQL
124
124
  end
125
125
 
126
126
  def camelize(string)
127
+ return string if string == '_'
127
128
  return string unless string.include?("_")
128
129
  camelized = string.split('_').map(&:capitalize).join
129
130
  camelized[0] = camelized[0].downcase
@@ -4,37 +4,32 @@ module GraphQL
4
4
  # Used to convert your {GraphQL::Schema} to a GraphQL schema string
5
5
  #
6
6
  # @example print your schema to standard output (via helper)
7
- # MySchema = GraphQL::Schema.define(query: QueryType)
8
7
  # puts GraphQL::Schema::Printer.print_schema(MySchema)
9
8
  #
10
9
  # @example print your schema to standard output
11
- # MySchema = GraphQL::Schema.define(query: QueryType)
12
10
  # puts GraphQL::Schema::Printer.new(MySchema).print_schema
13
11
  #
14
12
  # @example print a single type to standard output
15
- # query_root = GraphQL::ObjectType.define do
16
- # name "Query"
13
+ # class Types::Query < GraphQL::Schema::Object
17
14
  # description "The query root of this schema"
18
15
  #
19
- # field :post do
20
- # type post_type
21
- # resolve ->(obj, args, ctx) { Post.find(args["id"]) }
22
- # end
16
+ # field :post, Types::Post, null: true
23
17
  # end
24
18
  #
25
- # post_type = GraphQL::ObjectType.define do
26
- # name "Post"
19
+ # class Types::Post < GraphQL::Schema::Object
27
20
  # description "A blog post"
28
21
  #
29
- # field :id, !types.ID
30
- # field :title, !types.String
31
- # field :body, !types.String
22
+ # field :id, ID, null: false
23
+ # field :title, String, null: false
24
+ # field :body, String, null: false
32
25
  # end
33
26
  #
34
- # MySchema = GraphQL::Schema.define(query: query_root)
27
+ # class MySchema < GraphQL::Schema
28
+ # query(Types::Query)
29
+ # end
35
30
  #
36
31
  # printer = GraphQL::Schema::Printer.new(MySchema)
37
- # puts printer.print_type(post_type)
32
+ # puts printer.print_type(Types::Post)
38
33
  #
39
34
  class Printer < GraphQL::Language::Printer
40
35
  attr_reader :schema, :warden
@@ -87,7 +82,7 @@ module GraphQL
87
82
 
88
83
  # Return a GraphQL schema string for the defined types in the schema
89
84
  def print_schema
90
- print(@document)
85
+ print(@document) + "\n"
91
86
  end
92
87
 
93
88
  def print_type(type)
@@ -307,10 +307,15 @@ module GraphQL
307
307
  arguments: arguments,
308
308
  null: null,
309
309
  complexity: complexity,
310
- extensions: extensions,
311
310
  broadcastable: broadcastable?,
312
311
  }
313
312
 
313
+ # If there aren't any, then the returned array is `[].freeze`,
314
+ # but passing that along breaks some user code.
315
+ if (exts = extensions).any?
316
+ field_opts[:extensions] = exts
317
+ end
318
+
314
319
  if has_max_page_size?
315
320
  field_opts[:max_page_size] = max_page_size
316
321
  end
@@ -1631,6 +1631,14 @@ module GraphQL
1631
1631
  find_inherited_value(:multiplex_analyzers, EMPTY_ARRAY) + own_multiplex_analyzers
1632
1632
  end
1633
1633
 
1634
+ def sanitized_printer(new_sanitized_printer = nil)
1635
+ if new_sanitized_printer
1636
+ @own_sanitized_printer = new_sanitized_printer
1637
+ else
1638
+ @own_sanitized_printer || GraphQL::Language::SanitizedPrinter
1639
+ end
1640
+ end
1641
+
1634
1642
  # Execute a query on itself.
1635
1643
  # @see {Query#initialize} for arguments.
1636
1644
  # @return [Hash] query result, ready to be serialized as JSON
@@ -95,6 +95,14 @@ module GraphQL
95
95
  @action_cable = action_cable
96
96
  @action_cable_coder = action_cable_coder
97
97
  @serializer = serializer
98
+ @serialize_with_context = case @serializer.method(:load).arity
99
+ when 1
100
+ false
101
+ when 2
102
+ true
103
+ else
104
+ raise ArgumentError, "#{@serializer} must repond to `.load` accepting one or two arguments"
105
+ end
98
106
  @transmit_ns = namespace
99
107
  super
100
108
  end
@@ -154,7 +162,7 @@ module GraphQL
154
162
  # so just run it once, then deliver the result to every subscriber
155
163
  first_event = events.first
156
164
  first_subscription_id = first_event.context.fetch(:subscription_id)
157
- object ||= @serializer.load(message)
165
+ object ||= load_action_cable_message(message, first_event.context)
158
166
  result = execute_update(first_subscription_id, first_event, object)
159
167
  # Having calculated the result _once_, send the same payload to all subscribers
160
168
  events.each do |event|
@@ -167,6 +175,18 @@ module GraphQL
167
175
  end
168
176
  end
169
177
 
178
+ # This is called to turn an ActionCable-broadcasted string (JSON)
179
+ # into a query-ready application object.
180
+ # @param message [String] n ActionCable-broadcasted string (JSON)
181
+ # @param context [GraphQL::Query::Context] the context of the first event for a given subscription fingerprint
182
+ def load_action_cable_message(message, context)
183
+ if @serialize_with_context
184
+ @serializer.load(message, context)
185
+ else
186
+ @serializer.load(message)
187
+ end
188
+ end
189
+
170
190
  # Return the query from "storage" (in memory)
171
191
  def read_subscription(subscription_id)
172
192
  query = @subscriptions[subscription_id]
@@ -3,6 +3,7 @@
3
3
  module GraphQL
4
4
  module Types
5
5
  module Relay
6
+ # Include this module to your root Query type to get a Relay-compliant `node(id: ID!): Node` field that uses the schema's `object_from_id` hook.
6
7
  module HasNodeField
7
8
  def self.included(child_class)
8
9
  child_class.field(**field_options, &field_block)
@@ -12,7 +13,6 @@ module GraphQL
12
13
  def field_options
13
14
  {
14
15
  name: "node",
15
- owner: nil,
16
16
  type: GraphQL::Types::Relay::Node,
17
17
  null: true,
18
18
  description: "Fetches an object given its ID.",
@@ -3,6 +3,7 @@
3
3
  module GraphQL
4
4
  module Types
5
5
  module Relay
6
+ # Include this module to your root Query type to get a Relay-style `nodes(id: ID!): [Node]` field that uses the schema's `object_from_id` hook.
6
7
  module HasNodesField
7
8
  def self.included(child_class)
8
9
  child_class.field(**field_options, &field_block)
@@ -12,7 +13,6 @@ module GraphQL
12
13
  def field_options
13
14
  {
14
15
  name: "nodes",
15
- owner: nil,
16
16
  type: [GraphQL::Types::Relay::Node, null: true],
17
17
  null: false,
18
18
  description: "Fetches a list of objects given a list of IDs.",
@@ -6,7 +6,7 @@ module GraphQL
6
6
  # or use it for inspiration for your own field definition.
7
7
  #
8
8
  # @example Adding this field directly
9
- # add_field(GraphQL::Types::Relay::NodeField)
9
+ # include GraphQL::Types::Relay::HasNodeField
10
10
  #
11
11
  # @example Implementing a similar field in your own Query root
12
12
  #
@@ -19,7 +19,7 @@ module GraphQL
19
19
  # context.schema.object_from_id(id, context)
20
20
  # end
21
21
  #
22
- NodeField = GraphQL::Schema::Field.new(**HasNodeField.field_options, &HasNodeField.field_block)
22
+ NodeField = GraphQL::Schema::Field.new(owner: nil, **HasNodeField.field_options, &HasNodeField.field_block)
23
23
  end
24
24
  end
25
25
  end
@@ -6,7 +6,7 @@ module GraphQL
6
6
  # or use it for inspiration for your own field definition.
7
7
  #
8
8
  # @example Adding this field directly
9
- # add_field(GraphQL::Types::Relay::NodesField)
9
+ # include GraphQL::Types::Relay::HasNodesField
10
10
  #
11
11
  # @example Implementing a similar field in your own Query root
12
12
  #
@@ -21,7 +21,7 @@ module GraphQL
21
21
  # end
22
22
  # end
23
23
  #
24
- NodesField = GraphQL::Schema::Field.new(**HasNodesField.field_options, &HasNodesField.field_block)
24
+ NodesField = GraphQL::Schema::Field.new(owner: nil, **HasNodesField.field_options, &HasNodesField.field_block)
25
25
  end
26
26
  end
27
27
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.12.11"
3
+ VERSION = "1.12.15"
4
4
  end
data/readme.md CHANGED
@@ -2,9 +2,6 @@
2
2
 
3
3
  [![CI Suite](https://github.com/rmosolgo/graphql-ruby/actions/workflows/ci.yaml/badge.svg)](https://github.com/rmosolgo/graphql-ruby/actions/workflows/ci.yaml)
4
4
  [![Gem Version](https://badge.fury.io/rb/graphql.svg)](https://rubygems.org/gems/graphql)
5
- [![Code Climate](https://codeclimate.com/github/rmosolgo/graphql-ruby/badges/gpa.svg)](https://codeclimate.com/github/rmosolgo/graphql-ruby)
6
- [![Test Coverage](https://codeclimate.com/github/rmosolgo/graphql-ruby/badges/coverage.svg)](https://codeclimate.com/github/rmosolgo/graphql-ruby)
7
- [![built with love](https://cloud.githubusercontent.com/assets/2231765/6766607/d07992c6-cfc9-11e4-813f-d9240714dd50.png)](https://rmosolgo.github.io/react-badges/)
8
5
 
9
6
  A Ruby implementation of [GraphQL](https://graphql.org/).
10
7
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.12.11
4
+ version: 1.12.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-28 00:00:00.000000000 Z
11
+ date: 2021-08-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: benchmark-ips
@@ -24,20 +24,6 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
- - !ruby/object:Gem::Dependency
28
- name: codeclimate-test-reporter
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '0.4'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '0.4'
41
27
  - !ruby/object:Gem::Dependency
42
28
  name: concurrent-ruby
43
29
  requirement: !ruby/object:Gem::Requirement
@@ -699,7 +685,7 @@ metadata:
699
685
  source_code_uri: https://github.com/rmosolgo/graphql-ruby
700
686
  bug_tracker_uri: https://github.com/rmosolgo/graphql-ruby/issues
701
687
  mailing_list_uri: https://tinyletter.com/graphql-ruby
702
- post_install_message:
688
+ post_install_message:
703
689
  rdoc_options: []
704
690
  require_paths:
705
691
  - lib
@@ -714,8 +700,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
714
700
  - !ruby/object:Gem::Version
715
701
  version: '0'
716
702
  requirements: []
717
- rubygems_version: 3.1.4
718
- signing_key:
703
+ rubygems_version: 3.2.15
704
+ signing_key:
719
705
  specification_version: 4
720
706
  summary: A GraphQL language and runtime for Ruby
721
707
  test_files: []