graphql 1.12.17 → 1.12.18

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6c9089e4578454f473996553a4771e4086e51b2b2db17fc31a579ab28019bd39
4
- data.tar.gz: f45a70c81394f35c86d1610491f106e39899bc2c927a6a6d13e9e6533bc3bc85
3
+ metadata.gz: 5d1cfa93dc97adf13e461a45a00899f5ecc3e27d66c54c33d1a319b027665b78
4
+ data.tar.gz: 5d8cf2b669619dd15058400e15342ff225ba02716c5d8c9959c74260d4f51b32
5
5
  SHA512:
6
- metadata.gz: b7231b5e00a336439d15d469a383cd7e211e305961d4a4b431e8a97f5e483819bbd87de3d7cfffc9b6677b8175f700e012cd9ee4a0c92fa47005455a14893d3c
7
- data.tar.gz: 98495c2b24d65ef90d7f5e1036956349ea79d03ea192de6ed06e517657f49b4e0e9c748c8a28d54aac24e96b42f52eea5d4cbfbca801a4389b1515804296831a
6
+ metadata.gz: d7ff04a88e0ae883c382aa6559b52c38dbbc7d513537aaf506e89dd7880c227b6b032f7329c1c9e7b0fb9692b3e0e6fdbaac9df9fc82e536fb1e12b9cff2f65c
7
+ data.tar.gz: 3276674be3f8b0eb05d5c8904cad3ef20697bf5b9efcb133bd7c283d7bd704d52cd75f87a46f6c1fd988faf6c439c796d186280f4e397f8dc5184ae7d17c333b
@@ -37,7 +37,7 @@ module GraphQL
37
37
 
38
38
  if argument.definition.type.kind.input_object?
39
39
  extract_deprecated_arguments(argument.value.arguments.argument_values)
40
- elsif argument.definition.type.list?
40
+ elsif argument.definition.type.list? && !argument.value.nil?
41
41
  argument
42
42
  .value
43
43
  .select { |value| value.respond_to?(:arguments) }
@@ -107,6 +107,8 @@ module GraphQL
107
107
  [*batch_args, **batch_kwargs]
108
108
  end
109
109
 
110
+ attr_reader :pending_keys
111
+
110
112
  private
111
113
 
112
114
  # Reads and returns the result for the key from the internal cache, or raises an error if the result was an error
@@ -90,6 +90,16 @@ module GraphQL
90
90
  # Use a self-contained queue for the work in the block.
91
91
  def run_isolated
92
92
  prev_queue = @pending_jobs
93
+ prev_pending_keys = {}
94
+ @source_cache.each do |source_class, batched_sources|
95
+ batched_sources.each do |batch_args, batched_source_instance|
96
+ if batched_source_instance.pending?
97
+ prev_pending_keys[batched_source_instance] = batched_source_instance.pending_keys.dup
98
+ batched_source_instance.pending_keys.clear
99
+ end
100
+ end
101
+ end
102
+
93
103
  @pending_jobs = []
94
104
  res = nil
95
105
  # Make sure the block is inside a Fiber, so it can `Fiber.yield`
@@ -100,6 +110,9 @@ module GraphQL
100
110
  res
101
111
  ensure
102
112
  @pending_jobs = prev_queue
113
+ prev_pending_keys.each do |source_instance, pending_keys|
114
+ source_instance.pending_keys.concat(pending_keys)
115
+ end
103
116
  end
104
117
 
105
118
  # @api private Move along, move along
@@ -38,9 +38,17 @@ module GraphQL
38
38
  end
39
39
  end
40
40
 
41
- TYPE_CLASSES.each do |type_class|
42
- refine type_class.singleton_class do
43
- include Methods
41
+ if defined?(::Refinement) && Refinement.private_method_defined?(:import_methods)
42
+ TYPE_CLASSES.each do |type_class|
43
+ refine type_class.singleton_class do
44
+ import_methods Methods
45
+ end
46
+ end
47
+ else
48
+ TYPE_CLASSES.each do |type_class|
49
+ refine type_class.singleton_class do
50
+ include Methods
51
+ end
44
52
  end
45
53
  end
46
54
  end
@@ -260,38 +260,67 @@ module GraphQL
260
260
  type.coerce_input(value, context)
261
261
  end
262
262
 
263
- # TODO this should probably be inside after_lazy
264
- if loads && !from_resolver?
265
- loaded_value = if type.list?
266
- loaded_values = coerced_value.map { |val| owner.load_application_object(self, loads, val, context) }
267
- context.schema.after_any_lazies(loaded_values) { |result| result }
268
- else
269
- context.query.with_error_handling do
270
- owner.load_application_object(self, loads, coerced_value, context)
263
+ # If this isn't lazy, then the block returns eagerly and assigns the result here
264
+ # If it _is_ lazy, then we write the lazy to the hash, then update it later
265
+ argument_values[arg_key] = context.schema.after_lazy(coerced_value) do |resolved_coerced_value|
266
+ if loads && !from_resolver?
267
+ loaded_value = context.query.with_error_handling do
268
+ load_and_authorize_value(owner, coerced_value, context)
271
269
  end
272
270
  end
273
- end
274
271
 
275
- coerced_value = if loaded_value
276
- loaded_value
277
- else
278
- coerced_value
279
- end
272
+ maybe_loaded_value = loaded_value || resolved_coerced_value
273
+ context.schema.after_lazy(maybe_loaded_value) do |resolved_loaded_value|
274
+ owner.validate_directive_argument(self, resolved_loaded_value)
275
+ prepared_value = context.schema.error_handler.with_error_handling(context) do
276
+ prepare_value(parent_object, resolved_loaded_value, context: context)
277
+ end
280
278
 
281
- # If this isn't lazy, then the block returns eagerly and assigns the result here
282
- # If it _is_ lazy, then we write the lazy to the hash, then update it later
283
- argument_values[arg_key] = context.schema.after_lazy(coerced_value) do |coerced_value|
284
- owner.validate_directive_argument(self, coerced_value)
285
- prepared_value = context.schema.error_handler.with_error_handling(context) do
286
- prepare_value(parent_object, coerced_value, context: context)
279
+ # TODO code smell to access such a deeply-nested constant in a distant module
280
+ argument_values[arg_key] = GraphQL::Execution::Interpreter::ArgumentValue.new(
281
+ value: prepared_value,
282
+ definition: self,
283
+ default_used: default_used,
284
+ )
287
285
  end
286
+ end
287
+ end
288
288
 
289
- # TODO code smell to access such a deeply-nested constant in a distant module
290
- argument_values[arg_key] = GraphQL::Execution::Interpreter::ArgumentValue.new(
291
- value: prepared_value,
292
- definition: self,
293
- default_used: default_used,
294
- )
289
+ def load_and_authorize_value(load_method_owner, coerced_value, context)
290
+ if coerced_value.nil?
291
+ return nil
292
+ end
293
+ arg_load_method = "load_#{keyword}"
294
+ if load_method_owner.respond_to?(arg_load_method)
295
+ custom_loaded_value = if load_method_owner.is_a?(Class)
296
+ load_method_owner.public_send(arg_load_method, coerced_value, context)
297
+ else
298
+ load_method_owner.public_send(arg_load_method, coerced_value)
299
+ end
300
+ context.schema.after_lazy(custom_loaded_value) do |custom_value|
301
+ if loads
302
+ if type.list?
303
+ loaded_values = custom_value.each_with_index.map { |custom_val, idx|
304
+ id = coerced_value[idx]
305
+ load_method_owner.authorize_application_object(self, id, context, custom_val)
306
+ }
307
+ context.schema.after_any_lazies(loaded_values, &:itself)
308
+ else
309
+ load_method_owner.authorize_application_object(self, coerced_value, context, custom_loaded_value)
310
+ end
311
+ else
312
+ custom_value
313
+ end
314
+ end
315
+ elsif loads
316
+ if type.list?
317
+ loaded_values = coerced_value.map { |val| load_method_owner.load_and_authorize_application_object(self, val, context) }
318
+ context.schema.after_any_lazies(loaded_values, &:itself)
319
+ else
320
+ load_method_owner.load_and_authorize_application_object(self, coerced_value, context)
321
+ end
322
+ else
323
+ coerced_value
295
324
  end
296
325
  end
297
326
 
@@ -122,6 +122,9 @@ module GraphQL
122
122
  else
123
123
  kwargs[:type] = type
124
124
  end
125
+ if type.is_a?(Class) && type < GraphQL::Schema::Mutation
126
+ raise ArgumentError, "Use `field #{name.inspect}, mutation: Mutation, ...` to provide a mutation to this field instead"
127
+ end
125
128
  end
126
129
  new(**kwargs, &block)
127
130
  end
@@ -510,6 +513,7 @@ module GraphQL
510
513
  field_defn
511
514
  end
512
515
 
516
+ class MissingReturnTypeError < GraphQL::Error; end
513
517
  attr_writer :type
514
518
 
515
519
  def type
@@ -517,14 +521,21 @@ module GraphQL
517
521
  Member::BuildType.parse_type(@function.type, null: false)
518
522
  elsif @field
519
523
  Member::BuildType.parse_type(@field.type, null: false)
524
+ elsif @return_type_expr.nil?
525
+ # Not enough info to determine type
526
+ message = "Can't determine the return type for #{self.path}"
527
+ if @resolver_class
528
+ message += " (it has `resolver: #{@resolver_class}`, consider configuration a `type ...` for that class)"
529
+ end
530
+ raise MissingReturnTypeError, message
520
531
  else
521
532
  Member::BuildType.parse_type(@return_type_expr, null: @return_type_null)
522
533
  end
523
- rescue GraphQL::Schema::InvalidDocumentError => err
534
+ rescue GraphQL::Schema::InvalidDocumentError, MissingReturnTypeError => err
524
535
  # Let this propagate up
525
536
  raise err
526
537
  rescue StandardError => err
527
- raise ArgumentError, "Failed to build return type for #{@owner.graphql_name}.#{name} from #{@return_type_expr.inspect}: (#{err.class}) #{err.message}", err.backtrace
538
+ raise MissingReturnTypeError, "Failed to build return type for #{@owner.graphql_name}.#{name} from #{@return_type_expr.inspect}: (#{err.class}) #{err.message}", err.backtrace
528
539
  end
529
540
 
530
541
  def visible?(context)
@@ -608,8 +619,7 @@ module GraphQL
608
619
  if is_authorized
609
620
  public_send_field(object, args, ctx)
610
621
  else
611
- err = GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: ctx, field: self)
612
- ctx.schema.unauthorized_field(err)
622
+ raise GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: ctx, field: self)
613
623
  end
614
624
  end
615
625
  rescue GraphQL::UnauthorizedFieldError => err
@@ -40,11 +40,7 @@ module GraphQL
40
40
  # With the interpreter, it's done during `coerce_arguments`
41
41
  if loads && !arg_defn.from_resolver? && !context.interpreter?
42
42
  value = @ruby_style_hash[ruby_kwargs_key]
43
- loaded_value = if arg_defn.type.list?
44
- value.map { |val| load_application_object(arg_defn, loads, val, context) }
45
- else
46
- load_application_object(arg_defn, loads, value, context)
47
- end
43
+ loaded_value = arg_defn.load_and_authorize_value(self, value, context)
48
44
  maybe_lazies << context.schema.after_lazy(loaded_value) do |loaded_value|
49
45
  overwrite_argument(ruby_kwargs_key, loaded_value)
50
46
  end
@@ -37,6 +37,40 @@ module GraphQL
37
37
  end
38
38
  arg_defn = self.argument_class.new(*args, **kwargs, &block)
39
39
  add_argument(arg_defn)
40
+
41
+ if self.is_a?(Class) && !method_defined?(:"load_#{arg_defn.keyword}")
42
+ method_owner = if self < GraphQL::Schema::InputObject || self < GraphQL::Schema::Directive
43
+ "self."
44
+ elsif self < GraphQL::Schema::Resolver
45
+ ""
46
+ else
47
+ raise "Unexpected argument owner: #{self}"
48
+ end
49
+ if loads && arg_defn.type.list?
50
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
51
+ def #{method_owner}load_#{arg_defn.keyword}(values, context = nil)
52
+ argument = get_argument("#{arg_defn.graphql_name}")
53
+ (context || self.context).schema.after_lazy(values) do |values2|
54
+ GraphQL::Execution::Lazy.all(values2.map { |value| load_application_object(argument, value, context || self.context) })
55
+ end
56
+ end
57
+ RUBY
58
+ elsif loads
59
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
60
+ def #{method_owner}load_#{arg_defn.keyword}(value, context = nil)
61
+ argument = get_argument("#{arg_defn.graphql_name}")
62
+ load_application_object(argument, value, context || self.context)
63
+ end
64
+ RUBY
65
+ else
66
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
67
+ def #{method_owner}load_#{arg_defn.keyword}(value, _context = nil)
68
+ value
69
+ end
70
+ RUBY
71
+ end
72
+ end
73
+ arg_defn
40
74
  end
41
75
 
42
76
  # Register this argument with the class.
@@ -94,54 +128,52 @@ module GraphQL
94
128
  arg_defns = self.arguments
95
129
  total_args_count = arg_defns.size
96
130
 
97
- if total_args_count == 0
98
- final_args = GraphQL::Execution::Interpreter::Arguments::EMPTY
99
- if block_given?
100
- block.call(final_args)
101
- nil
131
+ finished_args = nil
132
+ prepare_finished_args = -> {
133
+ if total_args_count == 0
134
+ finished_args = GraphQL::Execution::Interpreter::Arguments::EMPTY
135
+ if block_given?
136
+ block.call(finished_args)
137
+ end
102
138
  else
103
- final_args
104
- end
105
- else
106
- finished_args = nil
107
- argument_values = {}
108
- resolved_args_count = 0
109
- raised_error = false
110
- arg_defns.each do |arg_name, arg_defn|
111
- context.dataloader.append_job do
112
- begin
113
- arg_defn.coerce_into_values(parent_object, values, context, argument_values)
114
- rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
115
- raised_error = true
116
- if block_given?
117
- block.call(err)
118
- else
139
+ argument_values = {}
140
+ resolved_args_count = 0
141
+ raised_error = false
142
+ arg_defns.each do |arg_name, arg_defn|
143
+ context.dataloader.append_job do
144
+ begin
145
+ arg_defn.coerce_into_values(parent_object, values, context, argument_values)
146
+ rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
147
+ raised_error = true
119
148
  finished_args = err
149
+ if block_given?
150
+ block.call(finished_args)
151
+ end
120
152
  end
121
- end
122
-
123
- resolved_args_count += 1
124
- if resolved_args_count == total_args_count && !raised_error
125
- finished_args = context.schema.after_any_lazies(argument_values.values) {
126
- GraphQL::Execution::Interpreter::Arguments.new(
127
- argument_values: argument_values,
128
- )
129
- }
130
153
 
131
- if block_given?
132
- block.call(finished_args)
154
+ resolved_args_count += 1
155
+ if resolved_args_count == total_args_count && !raised_error
156
+ finished_args = context.schema.after_any_lazies(argument_values.values) {
157
+ GraphQL::Execution::Interpreter::Arguments.new(
158
+ argument_values: argument_values,
159
+ )
160
+ }
161
+ if block_given?
162
+ block.call(finished_args)
163
+ end
133
164
  end
134
165
  end
135
166
  end
136
167
  end
168
+ }
137
169
 
138
- if block_given?
139
- nil
140
- else
141
- # This API returns eagerly, gotta run it now
142
- context.dataloader.run
143
- finished_args
144
- end
170
+ if block_given?
171
+ prepare_finished_args.call
172
+ nil
173
+ else
174
+ # This API returns eagerly, gotta run it now
175
+ context.dataloader.run_isolated(&prepare_finished_args)
176
+ finished_args
145
177
  end
146
178
  end
147
179
 
@@ -186,12 +218,20 @@ module GraphQL
186
218
  context.schema.object_from_id(id, context)
187
219
  end
188
220
 
189
- def load_application_object(argument, lookup_as_type, id, context)
221
+ def load_application_object(argument, id, context)
190
222
  # See if any object can be found for this ID
191
223
  if id.nil?
192
224
  return nil
193
225
  end
194
- loaded_application_object = object_from_id(lookup_as_type, id, context)
226
+ object_from_id(argument.loads, id, context)
227
+ end
228
+
229
+ def load_and_authorize_application_object(argument, id, context)
230
+ loaded_application_object = load_application_object(argument, id, context)
231
+ authorize_application_object(argument, id, context, loaded_application_object)
232
+ end
233
+
234
+ def authorize_application_object(argument, id, context, loaded_application_object)
195
235
  context.schema.after_lazy(loaded_application_object) do |application_object|
196
236
  if application_object.nil?
197
237
  err = GraphQL::LoadApplicationObjectFailedError.new(argument: argument, id: id, object: application_object)
@@ -199,9 +239,9 @@ module GraphQL
199
239
  end
200
240
  # Double-check that the located object is actually of this type
201
241
  # (Don't want to allow arbitrary access to objects this way)
202
- resolved_application_object_type = context.schema.resolve_type(lookup_as_type, application_object, context)
242
+ resolved_application_object_type = context.schema.resolve_type(argument.loads, application_object, context)
203
243
  context.schema.after_lazy(resolved_application_object_type) do |application_object_type|
204
- possible_object_types = context.warden.possible_types(lookup_as_type)
244
+ possible_object_types = context.warden.possible_types(argument.loads)
205
245
  if !possible_object_types.include?(application_object_type)
206
246
  err = GraphQL::LoadApplicationObjectFailedError.new(argument: argument, id: id, object: application_object)
207
247
  load_application_object_failed(err)
@@ -28,7 +28,7 @@ module GraphQL
28
28
  include Schema::Member::HasPath
29
29
  extend Schema::Member::HasPath
30
30
 
31
- # @param object [Object] the initialize object, pass to {Query.initialize} as `root_value`
31
+ # @param object [Object] The application object that this field is being resolved on
32
32
  # @param context [GraphQL::Query::Context]
33
33
  # @param field [GraphQL::Schema::Field]
34
34
  def initialize(object:, context:, field:)
@@ -40,7 +40,6 @@ module GraphQL
40
40
  self.class.arguments.each do |name, arg|
41
41
  @arguments_by_keyword[arg.keyword] = arg
42
42
  end
43
- @arguments_loads_as_type = self.class.arguments_loads_as_type
44
43
  @prepared_arguments = nil
45
44
  end
46
45
 
@@ -110,7 +109,7 @@ module GraphQL
110
109
  public_send(self.class.resolve_method)
111
110
  end
112
111
  else
113
- nil
112
+ raise GraphQL::UnauthorizedFieldError.new(context: context, object: object, type: field.owner, field: field)
114
113
  end
115
114
  end
116
115
  end
@@ -170,18 +169,14 @@ module GraphQL
170
169
  args.each do |key, value|
171
170
  arg_defn = @arguments_by_keyword[key]
172
171
  if arg_defn
173
- if value.nil?
174
- prepared_args[key] = value
175
- else
176
- prepped_value = prepared_args[key] = load_argument(key, value)
177
- if context.schema.lazy?(prepped_value)
178
- prepare_lazies << context.schema.after_lazy(prepped_value) do |finished_prepped_value|
179
- prepared_args[key] = finished_prepped_value
180
- end
172
+ prepped_value = prepared_args[key] = arg_defn.load_and_authorize_value(self, value, context)
173
+ if context.schema.lazy?(prepped_value)
174
+ prepare_lazies << context.schema.after_lazy(prepped_value) do |finished_prepped_value|
175
+ prepared_args[key] = finished_prepped_value
181
176
  end
182
177
  end
183
178
  else
184
- # These are `extras: [...]`
179
+ # these are `extras:`
185
180
  prepared_args[key] = value
186
181
  end
187
182
  end
@@ -194,8 +189,8 @@ module GraphQL
194
189
  end
195
190
  end
196
191
 
197
- def load_argument(name, value)
198
- public_send("load_#{name}", value)
192
+ def get_argument(name)
193
+ self.class.get_argument(name)
199
194
  end
200
195
 
201
196
  class << self
@@ -334,47 +329,9 @@ module GraphQL
334
329
  # also add some preparation hook methods which will be used for this argument
335
330
  # @see {GraphQL::Schema::Argument#initialize} for the signature
336
331
  def argument(*args, **kwargs, &block)
337
- loads = kwargs[:loads]
338
332
  # Use `from_resolver: true` to short-circuit the InputObject's own `loads:` implementation
339
333
  # so that we can support `#load_{x}` methods below.
340
- arg_defn = super(*args, from_resolver: true, **kwargs)
341
- own_arguments_loads_as_type[arg_defn.keyword] = loads if loads
342
-
343
- if !method_defined?(:"load_#{arg_defn.keyword}")
344
- if loads && arg_defn.type.list?
345
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
346
- def load_#{arg_defn.keyword}(values)
347
- argument = @arguments_by_keyword[:#{arg_defn.keyword}]
348
- lookup_as_type = @arguments_loads_as_type[:#{arg_defn.keyword}]
349
- context.schema.after_lazy(values) do |values2|
350
- GraphQL::Execution::Lazy.all(values2.map { |value| load_application_object(argument, lookup_as_type, value, context) })
351
- end
352
- end
353
- RUBY
354
- elsif loads
355
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
356
- def load_#{arg_defn.keyword}(value)
357
- argument = @arguments_by_keyword[:#{arg_defn.keyword}]
358
- lookup_as_type = @arguments_loads_as_type[:#{arg_defn.keyword}]
359
- load_application_object(argument, lookup_as_type, value, context)
360
- end
361
- RUBY
362
- else
363
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
364
- def load_#{arg_defn.keyword}(value)
365
- value
366
- end
367
- RUBY
368
- end
369
- end
370
-
371
- arg_defn
372
- end
373
-
374
- # @api private
375
- def arguments_loads_as_type
376
- inherited_lookups = superclass.respond_to?(:arguments_loads_as_type) ? superclass.arguments_loads_as_type : {}
377
- inherited_lookups.merge(own_arguments_loads_as_type)
334
+ super(*args, from_resolver: true, **kwargs)
378
335
  end
379
336
 
380
337
  # Registers new extension
@@ -410,10 +367,6 @@ module GraphQL
410
367
  def own_extensions
411
368
  @own_extensions
412
369
  end
413
-
414
- def own_arguments_loads_as_type
415
- @own_arguments_loads_as_type ||= {}
416
- end
417
370
  end
418
371
  end
419
372
  end
@@ -14,7 +14,7 @@ module GraphQL
14
14
  class Subscription < GraphQL::Schema::Resolver
15
15
  extend GraphQL::Schema::Resolver::HasPayloadType
16
16
  extend GraphQL::Schema::Member::HasFields
17
-
17
+ NO_UPDATE = :no_update
18
18
  # The generated payload type is required; If there's no payload,
19
19
  # propagate null.
20
20
  null false
@@ -68,7 +68,7 @@ module GraphQL
68
68
  # Wrap the user-provided `#update` hook
69
69
  def resolve_update(**args)
70
70
  ret_val = args.any? ? update(**args) : update
71
- if ret_val == :no_update
71
+ if ret_val == NO_UPDATE
72
72
  context.namespace(:subscriptions)[:no_update] = true
73
73
  context.skip
74
74
  else
@@ -77,7 +77,7 @@ module GraphQL
77
77
  end
78
78
 
79
79
  # The default implementation returns the root object.
80
- # Override it to return `:no_update` if you want to
80
+ # Override it to return {NO_UPDATE} if you want to
81
81
  # skip updates sometimes. Or override it to return a different object.
82
82
  def update(args = {})
83
83
  object
@@ -122,7 +122,7 @@ module GraphQL
122
122
  # In that implementation, only `.trigger` calls with _exact matches_ result in updates to subscribers.
123
123
  #
124
124
  # To implement a filtered stream-type subscription flow, override this method to return a string with field name and subscription scope.
125
- # Then, implement {#update} to compare its arguments to the current `object` and return `:no_update` when an
125
+ # Then, implement {#update} to compare its arguments to the current `object` and return {NO_UPDATE} when an
126
126
  # update should be filtered out.
127
127
  #
128
128
  # @see {#update} for how to skip updates when an event comes with a matching topic.
@@ -161,7 +161,7 @@ module GraphQL
161
161
 
162
162
  accepts_definitions \
163
163
  :query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy,
164
- :validate_timeout, :max_depth, :max_complexity, :default_max_page_size,
164
+ :validate_timeout, :validate_max_errors, :max_depth, :max_complexity, :default_max_page_size,
165
165
  :orphan_types, :resolve_type, :type_error, :parse_error,
166
166
  :error_bubbling,
167
167
  :raise_definition_error,
@@ -200,7 +200,7 @@ module GraphQL
200
200
  attr_accessor \
201
201
  :query, :mutation, :subscription,
202
202
  :query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy,
203
- :validate_timeout, :max_depth, :max_complexity, :default_max_page_size,
203
+ :validate_timeout, :validate_max_errors, :max_depth, :max_complexity, :default_max_page_size,
204
204
  :orphan_types, :directives,
205
205
  :query_analyzers, :multiplex_analyzers, :instrumenters, :lazy_methods,
206
206
  :cursor_encoder,
@@ -552,7 +552,7 @@ module GraphQL
552
552
  end
553
553
  end
554
554
 
555
- # @see [GraphQL::Schema::Warden] Resticted access to root types
555
+ # @see [GraphQL::Schema::Warden] Restricted access to root types
556
556
  # @return [GraphQL::ObjectType, nil]
557
557
  def root_type_for_operation(operation)
558
558
  case operation
@@ -934,6 +934,7 @@ module GraphQL
934
934
  schema_defn.mutation = mutation && mutation.graphql_definition
935
935
  schema_defn.subscription = subscription && subscription.graphql_definition
936
936
  schema_defn.validate_timeout = validate_timeout
937
+ schema_defn.validate_max_errors = validate_max_errors
937
938
  schema_defn.max_complexity = max_complexity
938
939
  schema_defn.error_bubbling = error_bubbling
939
940
  schema_defn.max_depth = max_depth
@@ -1073,7 +1074,7 @@ module GraphQL
1073
1074
  end
1074
1075
  end
1075
1076
 
1076
- # @see [GraphQL::Schema::Warden] Resticted access to root types
1077
+ # @see [GraphQL::Schema::Warden] Restricted access to root types
1077
1078
  # @return [GraphQL::ObjectType, nil]
1078
1079
  def root_type_for_operation(operation)
1079
1080
  case operation
@@ -1290,10 +1291,22 @@ module GraphQL
1290
1291
  validator_opts = { schema: self }
1291
1292
  rules && (validator_opts[:rules] = rules)
1292
1293
  validator = GraphQL::StaticValidation::Validator.new(**validator_opts)
1293
- res = validator.validate(query, timeout: validate_timeout)
1294
+ res = validator.validate(query, timeout: validate_timeout, max_errors: validate_max_errors)
1294
1295
  res[:errors]
1295
1296
  end
1296
1297
 
1298
+ attr_writer :validate_max_errors
1299
+
1300
+ def validate_max_errors(new_validate_max_errors = nil)
1301
+ if new_validate_max_errors
1302
+ @validate_max_errors = new_validate_max_errors
1303
+ elsif defined?(@validate_max_errors)
1304
+ @validate_max_errors
1305
+ else
1306
+ find_inherited_value(:validate_max_errors)
1307
+ end
1308
+ end
1309
+
1297
1310
  attr_writer :max_complexity
1298
1311
 
1299
1312
  def max_complexity(max_complexity = nil)
@@ -205,6 +205,9 @@ module GraphQL
205
205
  private
206
206
 
207
207
  def add_error(error, path: nil)
208
+ if @context.too_many_errors?
209
+ throw :too_many_validation_errors
210
+ end
208
211
  error.path ||= (path || @path.dup)
209
212
  context.errors << error
210
213
  end
@@ -193,26 +193,26 @@ module GraphQL
193
193
  if node1.name != node2.name
194
194
  errored_nodes = [node1.name, node2.name].sort.join(" or ")
195
195
  msg = "Field '#{response_key}' has a field conflict: #{errored_nodes}?"
196
- context.errors << GraphQL::StaticValidation::FieldsWillMergeError.new(
196
+ add_error(GraphQL::StaticValidation::FieldsWillMergeError.new(
197
197
  msg,
198
198
  nodes: [node1, node2],
199
199
  path: [],
200
200
  field_name: response_key,
201
201
  conflicts: errored_nodes
202
- )
202
+ ))
203
203
  end
204
204
 
205
205
  if !same_arguments?(node1, node2)
206
206
  args = [serialize_field_args(node1), serialize_field_args(node2)]
207
207
  conflicts = args.map { |arg| GraphQL::Language.serialize(arg) }.join(" or ")
208
208
  msg = "Field '#{response_key}' has an argument conflict: #{conflicts}?"
209
- context.errors << GraphQL::StaticValidation::FieldsWillMergeError.new(
209
+ add_error(GraphQL::StaticValidation::FieldsWillMergeError.new(
210
210
  msg,
211
211
  nodes: [node1, node2],
212
212
  path: [],
213
213
  field_name: response_key,
214
214
  conflicts: conflicts
215
- )
215
+ ))
216
216
  end
217
217
  end
218
218
 
@@ -7,12 +7,12 @@ module GraphQL
7
7
  dependency_map = context.dependencies
8
8
  dependency_map.cyclical_definitions.each do |defn|
9
9
  if defn.node.is_a?(GraphQL::Language::Nodes::FragmentDefinition)
10
- context.errors << GraphQL::StaticValidation::FragmentsAreFiniteError.new(
10
+ add_error(GraphQL::StaticValidation::FragmentsAreFiniteError.new(
11
11
  "Fragment #{defn.name} contains an infinite loop",
12
12
  nodes: defn.node,
13
13
  path: defn.path,
14
14
  name: defn.name
15
- )
15
+ ))
16
16
  end
17
17
  end
18
18
  end
@@ -19,10 +19,11 @@ module GraphQL
19
19
 
20
20
  def_delegators :@query, :schema, :document, :fragments, :operations, :warden
21
21
 
22
- def initialize(query, visitor_class)
22
+ def initialize(query, visitor_class, max_errors)
23
23
  @query = query
24
24
  @literal_validator = LiteralValidator.new(context: query.context)
25
25
  @errors = []
26
+ @max_errors = max_errors || Float::INFINITY
26
27
  @on_dependency_resolve_handlers = []
27
28
  @visitor = visitor_class.new(document, self)
28
29
  end
@@ -38,6 +39,10 @@ module GraphQL
38
39
  def validate_literal(ast_value, type)
39
40
  @literal_validator.validate(ast_value, type)
40
41
  end
42
+
43
+ def too_many_errors?
44
+ @errors.length >= @max_errors
45
+ end
41
46
  end
42
47
  end
43
48
  end
@@ -24,8 +24,9 @@ module GraphQL
24
24
  # @param query [GraphQL::Query]
25
25
  # @param validate [Boolean]
26
26
  # @param timeout [Float] Number of seconds to wait before aborting validation. Any positive number may be used, including Floats to specify fractional seconds.
27
+ # @param max_errors [Integer] Maximum number of errors before aborting validation. Any positive number will limit the number of errors. Defaults to nil for no limit.
27
28
  # @return [Array<Hash>]
28
- def validate(query, validate: true, timeout: nil)
29
+ def validate(query, validate: true, timeout: nil, max_errors: nil)
29
30
  query.trace("validate", { validate: validate, query: query }) do
30
31
  can_skip_rewrite = query.context.interpreter? && query.schema.using_ast_analysis? && query.schema.is_a?(Class)
31
32
  errors = if validate == false && can_skip_rewrite
@@ -34,25 +35,27 @@ module GraphQL
34
35
  rules_to_use = validate ? @rules : []
35
36
  visitor_class = BaseVisitor.including_rules(rules_to_use, rewrite: !can_skip_rewrite)
36
37
 
37
- context = GraphQL::StaticValidation::ValidationContext.new(query, visitor_class)
38
+ context = GraphQL::StaticValidation::ValidationContext.new(query, visitor_class, max_errors)
38
39
 
39
40
  begin
40
41
  # CAUTION: Usage of the timeout module makes the assumption that validation rules are stateless Ruby code that requires no cleanup if process was interrupted. This means no blocking IO calls, native gems, locks, or `rescue` clauses that must be reached.
41
42
  # A timeout value of 0 or nil will execute the block without any timeout.
42
43
  Timeout::timeout(timeout) do
43
- # Attach legacy-style rules.
44
- # Only loop through rules if it has legacy-style rules
45
- unless (legacy_rules = rules_to_use - GraphQL::StaticValidation::ALL_RULES).empty?
46
- legacy_rules.each do |rule_class_or_module|
47
- if rule_class_or_module.method_defined?(:validate)
48
- GraphQL::Deprecation.warn "Legacy validator rules will be removed from GraphQL-Ruby 2.0, use a module instead (see the built-in rules: https://github.com/rmosolgo/graphql-ruby/tree/master/lib/graphql/static_validation/rules)"
49
- GraphQL::Deprecation.warn " -> Legacy validator: #{rule_class_or_module}"
50
- rule_class_or_module.new.validate(context)
44
+ catch(:too_many_validation_errors) do
45
+ # Attach legacy-style rules.
46
+ # Only loop through rules if it has legacy-style rules
47
+ unless (legacy_rules = rules_to_use - GraphQL::StaticValidation::ALL_RULES).empty?
48
+ legacy_rules.each do |rule_class_or_module|
49
+ if rule_class_or_module.method_defined?(:validate)
50
+ GraphQL::Deprecation.warn "Legacy validator rules will be removed from GraphQL-Ruby 2.0, use a module instead (see the built-in rules: https://github.com/rmosolgo/graphql-ruby/tree/master/lib/graphql/static_validation/rules)"
51
+ GraphQL::Deprecation.warn " -> Legacy validator: #{rule_class_or_module}"
52
+ rule_class_or_module.new.validate(context)
53
+ end
51
54
  end
52
55
  end
53
- end
54
56
 
55
- context.visitor.visit
57
+ context.visitor.visit
58
+ end
56
59
  end
57
60
  rescue Timeout::Error
58
61
  handle_timeout(query, context)
@@ -127,7 +127,13 @@ module GraphQL
127
127
  # It will receive notifications when events come in
128
128
  # and re-evaluate the query locally.
129
129
  def write_subscription(query, events)
130
- channel = query.context.fetch(:channel)
130
+ unless (channel = query.context[:channel])
131
+ raise GraphQL::Error, "This GraphQL Subscription client does not support the transport protocol expected"\
132
+ "by the backend Subscription Server implementation (graphql-ruby ActionCableSubscriptions in this case)."\
133
+ "Some official client implementation including Apollo (https://graphql-ruby.org/javascript_client/apollo_subscriptions.html), "\
134
+ "Relay Modern (https://graphql-ruby.org/javascript_client/relay_subscriptions.html#actioncable)."\
135
+ "GraphiQL via `graphiql-rails` may not work out of box (#1051)."
136
+ end
131
137
  subscription_id = query.context[:subscription_id] ||= build_id
132
138
  stream = stream_subscription_name(subscription_id)
133
139
  channel.stream_from(stream)
@@ -54,6 +54,7 @@ module GraphQL
54
54
  class << self
55
55
  private
56
56
  def stringify_args(arg_owner, args)
57
+ arg_owner = arg_owner.respond_to?(:unwrap) ? arg_owner.unwrap : arg_owner # remove list and non-null wrappers
57
58
  case args
58
59
  when Hash
59
60
  next_args = {}
@@ -64,11 +65,11 @@ module GraphQL
64
65
 
65
66
  if arg_defn
66
67
  normalized_arg_name = camelized_arg_name
68
+ next_args[normalized_arg_name] = stringify_args(arg_defn.type, v)
67
69
  else
68
70
  normalized_arg_name = arg_name
69
71
  arg_defn = get_arg_definition(arg_owner, normalized_arg_name)
70
72
  end
71
-
72
73
  next_args[normalized_arg_name] = stringify_args(arg_defn.type, v)
73
74
  end
74
75
  # Make sure they're deeply sorted
@@ -9,7 +9,7 @@ module GraphQL
9
9
  SYMBOL_KEY = "__sym__"
10
10
  SYMBOL_KEYS_KEY = "__sym_keys__"
11
11
  TIMESTAMP_KEY = "__timestamp__"
12
- TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S.%N%Z" # eg '2020-01-01 23:59:59.123456789+05:00'
12
+ TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S.%N%z" # eg '2020-01-01 23:59:59.123456789+05:00'
13
13
  OPEN_STRUCT_KEY = "__ostruct__"
14
14
 
15
15
  module_function
@@ -14,7 +14,22 @@ module GraphQL
14
14
  "execute_query_lazy" => "execute.graphql",
15
15
  }
16
16
 
17
+ # @param set_action_name [Boolean] If true, the GraphQL operation name will be used as the transaction name.
18
+ # This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing.
19
+ # It can also be specified per-query with `context[:set_appsignal_action_name]`.
20
+ def initialize(options = {})
21
+ @set_action_name = options.fetch(:set_action_name, false)
22
+ super
23
+ end
24
+
17
25
  def platform_trace(platform_key, key, data)
26
+ if key == "execute_query"
27
+ set_this_txn_name = data[:query].context[:set_appsignal_action_name]
28
+ if set_this_txn_name == true || (set_this_txn_name.nil? && @set_action_name)
29
+ Appsignal::Transaction.current.set_action(transaction_name(data[:query]))
30
+ end
31
+ end
32
+
18
33
  Appsignal.instrument(platform_key) do
19
34
  yield
20
35
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.12.17"
3
+ VERSION = "1.12.18"
4
4
  end
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.17
4
+ version: 1.12.18
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-15 00:00:00.000000000 Z
11
+ date: 2021-11-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: benchmark-ips
@@ -700,7 +700,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
700
700
  - !ruby/object:Gem::Version
701
701
  version: '0'
702
702
  requirements: []
703
- rubygems_version: 3.2.15
703
+ rubygems_version: 3.2.22
704
704
  signing_key:
705
705
  specification_version: 4
706
706
  summary: A GraphQL language and runtime for Ruby