graphql 2.0.0 → 2.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -1
  3. data/lib/graphql/dataloader/null_dataloader.rb +3 -1
  4. data/lib/graphql/execution/errors.rb +12 -74
  5. data/lib/graphql/execution/interpreter/runtime.rb +35 -38
  6. data/lib/graphql/query/context.rb +96 -9
  7. data/lib/graphql/query/input_validation_result.rb +10 -1
  8. data/lib/graphql/query/null_context.rb +0 -3
  9. data/lib/graphql/query.rb +2 -5
  10. data/lib/graphql/relay/range_add.rb +9 -16
  11. data/lib/graphql/schema/addition.rb +5 -0
  12. data/lib/graphql/schema/argument.rb +13 -4
  13. data/lib/graphql/schema/enum.rb +3 -5
  14. data/lib/graphql/schema/field.rb +164 -130
  15. data/lib/graphql/schema/input_object.rb +12 -12
  16. data/lib/graphql/schema/list.rb +2 -1
  17. data/lib/graphql/schema/member/has_arguments.rb +36 -6
  18. data/lib/graphql/schema/member/has_directives.rb +1 -1
  19. data/lib/graphql/schema/member/has_interfaces.rb +1 -1
  20. data/lib/graphql/schema/member/relay_shortcuts.rb +28 -2
  21. data/lib/graphql/schema/member/validates_input.rb +2 -2
  22. data/lib/graphql/schema/object.rb +14 -9
  23. data/lib/graphql/schema/relay_classic_mutation.rb +32 -14
  24. data/lib/graphql/schema/resolver/has_payload_type.rb +11 -1
  25. data/lib/graphql/schema/resolver.rb +23 -45
  26. data/lib/graphql/schema/scalar.rb +7 -7
  27. data/lib/graphql/schema/subscription.rb +0 -7
  28. data/lib/graphql/schema/warden.rb +1 -1
  29. data/lib/graphql/schema.rb +35 -6
  30. data/lib/graphql/subscriptions.rb +10 -3
  31. data/lib/graphql/tracing/data_dog_tracing.rb +3 -1
  32. data/lib/graphql/types/relay/connection_behaviors.rb +0 -16
  33. data/lib/graphql/version.rb +1 -1
  34. metadata +6 -7
  35. data/lib/graphql/query/literal_input.rb +0 -131
@@ -75,7 +75,10 @@ module GraphQL
75
75
  end
76
76
  end
77
77
 
78
- self.validates(validates)
78
+ if validates && !validates.empty?
79
+ self.validates(validates)
80
+ end
81
+
79
82
  if required == :nullable
80
83
  self.owner.validates(required: { argument: arg_name })
81
84
  end
@@ -247,24 +250,30 @@ module GraphQL
247
250
  end
248
251
 
249
252
  loaded_value = nil
250
- coerced_value = context.schema.error_handler.with_error_handling(context) do
253
+ coerced_value = begin
251
254
  type.coerce_input(value, context)
255
+ rescue StandardError => err
256
+ context.schema.handle_or_reraise(context, err)
252
257
  end
253
258
 
254
259
  # If this isn't lazy, then the block returns eagerly and assigns the result here
255
260
  # If it _is_ lazy, then we write the lazy to the hash, then update it later
256
261
  argument_values[arg_key] = context.schema.after_lazy(coerced_value) do |resolved_coerced_value|
257
262
  if loads && !from_resolver?
258
- loaded_value = context.query.with_error_handling do
263
+ loaded_value = begin
259
264
  load_and_authorize_value(owner, coerced_value, context)
265
+ rescue StandardError => err
266
+ context.schema.handle_or_reraise(context, err)
260
267
  end
261
268
  end
262
269
 
263
270
  maybe_loaded_value = loaded_value || resolved_coerced_value
264
271
  context.schema.after_lazy(maybe_loaded_value) do |resolved_loaded_value|
265
272
  owner.validate_directive_argument(self, resolved_loaded_value)
266
- prepared_value = context.schema.error_handler.with_error_handling(context) do
273
+ prepared_value = begin
267
274
  prepare_value(parent_object, resolved_loaded_value, context: context)
275
+ rescue StandardError => err
276
+ context.schema.handle_or_reraise(context, err)
268
277
  end
269
278
 
270
279
  # TODO code smell to access such a deeply-nested constant in a distant module
@@ -123,16 +123,14 @@ module GraphQL
123
123
  end
124
124
 
125
125
  def validate_non_null_input(value_name, ctx)
126
- result = GraphQL::Query::InputValidationResult.new
127
-
128
126
  allowed_values = ctx.warden.enum_values(self)
129
127
  matching_value = allowed_values.find { |v| v.graphql_name == value_name }
130
128
 
131
129
  if matching_value.nil?
132
- result.add_problem("Expected #{GraphQL::Language.serialize(value_name)} to be one of: #{allowed_values.map(&:graphql_name).join(', ')}")
130
+ GraphQL::Query::InputValidationResult.from_problem("Expected #{GraphQL::Language.serialize(value_name)} to be one of: #{allowed_values.map(&:graphql_name).join(', ')}")
131
+ else
132
+ nil
133
133
  end
134
-
135
- result
136
134
  end
137
135
 
138
136
  def coerce_result(value, ctx)
@@ -29,7 +29,13 @@ module GraphQL
29
29
  attr_reader :method_str
30
30
 
31
31
  # @return [Symbol] The method on the type to look up
32
- attr_reader :resolver_method
32
+ def resolver_method
33
+ if @resolver_class
34
+ @resolver_class.resolver_method
35
+ else
36
+ @resolver_method
37
+ end
38
+ end
33
39
 
34
40
  # @return [Class] The thing this field was defined on (type, mutation, resolver)
35
41
  attr_accessor :owner
@@ -68,7 +74,10 @@ module GraphQL
68
74
  attr_reader :trace
69
75
 
70
76
  # @return [String, nil]
71
- attr_accessor :subscription_scope
77
+ def subscription_scope
78
+ @subscription_scope || (@resolver_class.respond_to?(:subscription_scope) ? @resolver_class.subscription_scope : nil)
79
+ end
80
+ attr_writer :subscription_scope
72
81
 
73
82
  # Create a field instance from a list of arguments, keyword arguments, and a block.
74
83
  #
@@ -82,11 +91,9 @@ module GraphQL
82
91
  # @return [GraphQL::Schema:Field] an instance of `self
83
92
  # @see {.initialize} for other options
84
93
  def self.from_options(name = nil, type = nil, desc = nil, resolver: nil, mutation: nil, subscription: nil,**kwargs, &block)
85
- if (parent_config = resolver || mutation || subscription)
86
- # Get the parent config, merge in local overrides
87
- kwargs = parent_config.field_options.merge(kwargs)
94
+ if (resolver_class = resolver || mutation || subscription)
88
95
  # Add a reference to that parent class
89
- kwargs[:resolver_class] = parent_config
96
+ kwargs[:resolver_class] = resolver_class
90
97
  end
91
98
 
92
99
  if name
@@ -101,8 +108,8 @@ module GraphQL
101
108
 
102
109
  kwargs[:description] = desc
103
110
  kwargs[:type] = type
104
- elsif (kwargs[:field] || kwargs[:function] || resolver || mutation) && type.is_a?(String)
105
- # The return type should be copied from `field` or `function`, and the second positional argument is the description
111
+ elsif (resolver || mutation) && type.is_a?(String)
112
+ # The return type should be copied from the resolver, and the second positional argument is the description
106
113
  kwargs[:description] = type
107
114
  else
108
115
  kwargs[:type] = type
@@ -119,8 +126,8 @@ module GraphQL
119
126
  def connection?
120
127
  if @connection.nil?
121
128
  # Provide default based on type name
122
- return_type_name = if (contains_type = @field || @function)
123
- Member::BuildType.to_type_name(contains_type.type)
129
+ return_type_name = if @resolver_class && @resolver_class.type
130
+ Member::BuildType.to_type_name(@resolver_class.type)
124
131
  elsif @return_type_expr
125
132
  Member::BuildType.to_type_name(@return_type_expr)
126
133
  else
@@ -138,8 +145,18 @@ module GraphQL
138
145
  if !@scope.nil?
139
146
  # The default was overridden
140
147
  @scope
148
+ elsif @return_type_expr
149
+ # Detect a list return type, but don't call `type` since that may eager-load an otherwise lazy-loaded type
150
+ @return_type_expr.is_a?(Array) ||
151
+ (@return_type_expr.is_a?(String) && @return_type_expr.include?("[")) ||
152
+ connection?
153
+ elsif @resolver_class
154
+ resolver_type = @resolver_class.type_expr
155
+ resolver_type.is_a?(Array) ||
156
+ (resolver_type.is_a?(String) && resolver_type.include?("[")) ||
157
+ connection?
141
158
  else
142
- @return_type_expr && (@return_type_expr.is_a?(Array) || (@return_type_expr.is_a?(String) && @return_type_expr.include?("[")) || connection?)
159
+ false
143
160
  end
144
161
  end
145
162
 
@@ -181,9 +198,6 @@ module GraphQL
181
198
  # @param connection_extension [Class] The extension to add, to implement connections. If `nil`, no extension is added.
182
199
  # @param max_page_size [Integer, nil] For connections, the maximum number of items to return from this field, or `nil` to allow unlimited results.
183
200
  # @param introspection [Boolean] If true, this field will be marked as `#introspection?` and the name may begin with `__`
184
- # @param resolve [<#call(obj, args, ctx)>] **deprecated** for compatibility with <1.8.0
185
- # @param field [GraphQL::Field, GraphQL::Schema::Field] **deprecated** for compatibility with <1.8.0
186
- # @param function [GraphQL::Function] **deprecated** for compatibility with <1.8.0
187
201
  # @param resolver_class [Class] (Private) A {Schema::Resolver} which this field was derived from. Use `resolver:` to create a field with a resolver.
188
202
  # @param arguments [{String=>GraphQL::Schema::Argument, Hash}] Arguments for this field (may be added in the block, also)
189
203
  # @param camelize [Boolean] If true, the field name will be camelized when building the schema
@@ -197,30 +211,22 @@ module GraphQL
197
211
  # @param ast_node [Language::Nodes::FieldDefinition, nil] If this schema was parsed from definition, this AST node defined the field
198
212
  # @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
199
213
  # @param validates [Array<Hash>] Configurations for validating this field
200
- def initialize(type: nil, name: nil, owner: nil, null: true, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, resolve: nil, connection: nil, max_page_size: :not_given, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: nil, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, &definition_block)
214
+ def initialize(type: nil, name: nil, owner: nil, null: true, description: :not_given, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: :not_given, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: nil, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, &definition_block)
201
215
  if name.nil?
202
216
  raise ArgumentError, "missing first `name` argument or keyword `name:`"
203
217
  end
204
- if !(field || function || resolver_class)
218
+ if !(resolver_class)
205
219
  if type.nil?
206
220
  raise ArgumentError, "missing second `type` argument or keyword `type:`"
207
221
  end
208
222
  end
209
- if (field || function || resolve) && extras.any?
210
- raise ArgumentError, "keyword `extras:` may only be used with method-based resolve and class-based field such as mutation class, please remove `field:`, `function:` or `resolve:`"
211
- end
212
223
  @original_name = name
213
224
  name_s = -name.to_s
214
225
  @underscored_name = -Member::BuildType.underscore(name_s)
215
226
  @name = -(camelize ? Member::BuildType.camelize(name_s) : name_s)
216
- @description = description
217
- if field.is_a?(GraphQL::Schema::Field)
218
- raise ArgumentError, "Instead of passing a field as `field:`, use `add_field(field)` to add an already-defined field."
219
- else
220
- @field = field
227
+ if description != :not_given
228
+ @description = description
221
229
  end
222
- @function = function
223
- @resolve = resolve
224
230
  self.deprecation_reason = deprecation_reason
225
231
 
226
232
  if method && hash_key && dig
@@ -253,7 +259,9 @@ module GraphQL
253
259
  @max_page_size = max_page_size == :not_given ? nil : max_page_size
254
260
  @introspection = introspection
255
261
  @extras = extras
256
- @broadcastable = broadcastable
262
+ if !broadcastable.nil?
263
+ @broadcastable = broadcastable
264
+ end
257
265
  @resolver_class = resolver_class
258
266
  @scope = scope
259
267
  @trace = trace
@@ -297,13 +305,19 @@ module GraphQL
297
305
  self.extensions(extensions)
298
306
  end
299
307
 
308
+ if resolver_class && resolver_class.extensions.any?
309
+ self.extensions(resolver_class.extensions)
310
+ end
311
+
300
312
  if directives.any?
301
313
  directives.each do |(dir_class, options)|
302
314
  self.directive(dir_class, **options)
303
315
  end
304
316
  end
305
317
 
306
- self.validates(validates)
318
+ if !validates.empty?
319
+ self.validates(validates)
320
+ end
307
321
 
308
322
  if definition_block
309
323
  if definition_block.arity == 1
@@ -321,7 +335,13 @@ module GraphQL
321
335
  # @return [Boolean, nil]
322
336
  # @see GraphQL::Subscriptions::BroadcastAnalyzer
323
337
  def broadcastable?
324
- @broadcastable
338
+ if defined?(@broadcastable)
339
+ @broadcastable
340
+ elsif @resolver_class
341
+ @resolver_class.broadcastable?
342
+ else
343
+ nil
344
+ end
325
345
  end
326
346
 
327
347
  # @param text [String]
@@ -329,8 +349,12 @@ module GraphQL
329
349
  def description(text = nil)
330
350
  if text
331
351
  @description = text
332
- else
352
+ elsif defined?(@description)
333
353
  @description
354
+ elsif @resolver_class
355
+ @description || @resolver_class.description
356
+ else
357
+ nil
334
358
  end
335
359
  end
336
360
 
@@ -394,7 +418,12 @@ module GraphQL
394
418
  def extras(new_extras = nil)
395
419
  if new_extras.nil?
396
420
  # Read the value
397
- @extras
421
+ field_extras = @extras
422
+ if @resolver_class && @resolver_class.extras.any?
423
+ field_extras + @resolver_class.extras
424
+ else
425
+ field_extras
426
+ end
398
427
  else
399
428
  if @extras.frozen?
400
429
  @extras = @extras.dup
@@ -477,7 +506,11 @@ module GraphQL
477
506
  when Numeric
478
507
  @complexity = new_complexity
479
508
  when nil
480
- @complexity
509
+ if @resolver_class
510
+ @complexity || @resolver_class.complexity || 1
511
+ else
512
+ @complexity || 1
513
+ end
481
514
  else
482
515
  raise("Invalid complexity: #{new_complexity.inspect} on #{@name}")
483
516
  end
@@ -485,29 +518,31 @@ module GraphQL
485
518
 
486
519
  # @return [Boolean] True if this field's {#max_page_size} should override the schema default.
487
520
  def has_max_page_size?
488
- @has_max_page_size
521
+ @has_max_page_size || (@resolver_class && @resolver_class.has_max_page_size?)
489
522
  end
490
523
 
491
524
  # @return [Integer, nil] Applied to connections if {#has_max_page_size?}
492
- attr_reader :max_page_size
525
+ def max_page_size
526
+ @max_page_size || (@resolver_class && @resolver_class.max_page_size)
527
+ end
493
528
 
494
529
  class MissingReturnTypeError < GraphQL::Error; end
495
530
  attr_writer :type
496
531
 
497
532
  def type
498
- @type ||= if @function
499
- Member::BuildType.parse_type(@function.type, null: false)
500
- elsif @field
501
- Member::BuildType.parse_type(@field.type, null: false)
502
- elsif @return_type_expr.nil?
503
- # Not enough info to determine type
504
- message = "Can't determine the return type for #{self.path}"
505
- if @resolver_class
506
- message += " (it has `resolver: #{@resolver_class}`, consider configuration a `type ...` for that class)"
533
+ if @return_type_expr.nil?
534
+ if @resolver_class && (t = @resolver_class.type)
535
+ t
536
+ else
537
+ # Not enough info to determine type
538
+ message = "Can't determine the return type for #{self.path}"
539
+ if @resolver_class
540
+ message += " (it has `resolver: #{@resolver_class}`, perhaps that class is missing a `type ...` declaration, or perhaps its type causes a cyclical loading issue)"
541
+ end
542
+ raise MissingReturnTypeError, message
507
543
  end
508
- raise MissingReturnTypeError, message
509
544
  else
510
- Member::BuildType.parse_type(@return_type_expr, null: @return_type_null)
545
+ @type ||= Member::BuildType.parse_type(@return_type_expr, null: @return_type_null)
511
546
  end
512
547
  rescue GraphQL::Schema::InvalidDocumentError, MissingReturnTypeError => err
513
548
  # Let this propagate up
@@ -578,31 +613,94 @@ module GraphQL
578
613
  # @param object [GraphQL::Schema::Object] An instance of some type class, wrapping an application object
579
614
  # @param args [Hash] A symbol-keyed hash of Ruby keyword arguments. (Empty if no args)
580
615
  # @param ctx [GraphQL::Query::Context]
581
- def resolve(object, args, ctx)
582
- if @resolve_proc
583
- raise "Can't run resolve proc for #{path} when using GraphQL::Execution::Interpreter"
584
- end
585
- begin
586
- # Unwrap the GraphQL object to get the application object.
587
- application_object = object.object
616
+ def resolve(object, args, query_ctx)
617
+ # Unwrap the GraphQL object to get the application object.
618
+ application_object = object.object
619
+ method_receiver = nil
620
+ method_to_call = nil
621
+ method_args = nil
622
+
623
+ Schema::Validator.validate!(validators, application_object, query_ctx, args)
624
+
625
+ query_ctx.schema.after_lazy(self.authorized?(application_object, args, query_ctx)) do |is_authorized|
626
+ if is_authorized
627
+ with_extensions(object, args, query_ctx) do |obj, ruby_kwargs|
628
+ method_args = ruby_kwargs
629
+ if @resolver_class
630
+ if obj.is_a?(GraphQL::Schema::Object)
631
+ obj = obj.object
632
+ end
633
+ obj = @resolver_class.new(object: obj, context: query_ctx, field: self)
634
+ end
635
+
636
+ # Find a way to resolve this field, checking:
637
+ #
638
+ # - A method on the type instance;
639
+ # - Hash keys, if the wrapped object is a hash;
640
+ # - A method on the wrapped object;
641
+ # - Or, raise not implemented.
642
+ #
643
+ if obj.respond_to?(resolver_method)
644
+ method_to_call = resolver_method
645
+ method_receiver = obj
646
+ # Call the method with kwargs, if there are any
647
+ if ruby_kwargs.any?
648
+ obj.public_send(resolver_method, **ruby_kwargs)
649
+ else
650
+ obj.public_send(resolver_method)
651
+ end
652
+ elsif obj.object.is_a?(Hash)
653
+ inner_object = obj.object
654
+ if @dig_keys
655
+ inner_object.dig(*@dig_keys)
656
+ elsif inner_object.key?(@method_sym)
657
+ inner_object[@method_sym]
658
+ else
659
+ inner_object[@method_str]
660
+ end
661
+ elsif obj.object.respond_to?(@method_sym)
662
+ method_to_call = @method_sym
663
+ method_receiver = obj.object
664
+ if ruby_kwargs.any?
665
+ obj.object.public_send(@method_sym, **ruby_kwargs)
666
+ else
667
+ obj.object.public_send(@method_sym)
668
+ end
669
+ else
670
+ raise <<-ERR
671
+ Failed to implement #{@owner.graphql_name}.#{@name}, tried:
588
672
 
589
- Schema::Validator.validate!(validators, application_object, ctx, args)
673
+ - `#{obj.class}##{resolver_method}`, which did not exist
674
+ - `#{obj.object.class}##{@method_sym}`, which did not exist
675
+ - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
590
676
 
591
- ctx.schema.after_lazy(self.authorized?(application_object, args, ctx)) do |is_authorized|
592
- if is_authorized
593
- public_send_field(object, args, ctx)
594
- else
595
- raise GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: ctx, field: self)
677
+ To implement this field, define one of the methods above (and check for typos)
678
+ ERR
679
+ end
596
680
  end
681
+ else
682
+ raise GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: query_ctx, field: self)
597
683
  end
598
- rescue GraphQL::UnauthorizedFieldError => err
599
- err.field ||= self
600
- ctx.schema.unauthorized_field(err)
601
- rescue GraphQL::UnauthorizedError => err
602
- ctx.schema.unauthorized_object(err)
603
- end
604
- rescue GraphQL::ExecutionError => err
605
- err
684
+ end
685
+ rescue GraphQL::UnauthorizedFieldError => err
686
+ err.field ||= self
687
+ begin
688
+ query_ctx.schema.unauthorized_field(err)
689
+ rescue GraphQL::ExecutionError => err
690
+ err
691
+ end
692
+ rescue GraphQL::UnauthorizedError => err
693
+ begin
694
+ query_ctx.schema.unauthorized_object(err)
695
+ rescue GraphQL::ExecutionError => err
696
+ err
697
+ end
698
+ rescue ArgumentError
699
+ if method_receiver && method_to_call
700
+ assert_satisfactory_implementation(method_receiver, method_to_call, method_args)
701
+ end
702
+ # if the line above doesn't raise, re-raise
703
+ raise
606
704
  end
607
705
 
608
706
  # @param ctx [GraphQL::Query::Context]
@@ -618,70 +716,6 @@ module GraphQL
618
716
 
619
717
  private
620
718
 
621
- def public_send_field(unextended_obj, unextended_ruby_kwargs, query_ctx)
622
- with_extensions(unextended_obj, unextended_ruby_kwargs, query_ctx) do |obj, ruby_kwargs|
623
- begin
624
- method_receiver = nil
625
- method_to_call = nil
626
- if @resolver_class
627
- if obj.is_a?(GraphQL::Schema::Object)
628
- obj = obj.object
629
- end
630
- obj = @resolver_class.new(object: obj, context: query_ctx, field: self)
631
- end
632
-
633
- # Find a way to resolve this field, checking:
634
- #
635
- # - A method on the type instance;
636
- # - Hash keys, if the wrapped object is a hash;
637
- # - A method on the wrapped object;
638
- # - Or, raise not implemented.
639
- #
640
- if obj.respond_to?(@resolver_method)
641
- method_to_call = @resolver_method
642
- method_receiver = obj
643
- # Call the method with kwargs, if there are any
644
- if ruby_kwargs.any?
645
- obj.public_send(@resolver_method, **ruby_kwargs)
646
- else
647
- obj.public_send(@resolver_method)
648
- end
649
- elsif obj.object.is_a?(Hash)
650
- inner_object = obj.object
651
- if @dig_keys
652
- inner_object.dig(*@dig_keys)
653
- elsif inner_object.key?(@method_sym)
654
- inner_object[@method_sym]
655
- else
656
- inner_object[@method_str]
657
- end
658
- elsif obj.object.respond_to?(@method_sym)
659
- method_to_call = @method_sym
660
- method_receiver = obj.object
661
- if ruby_kwargs.any?
662
- obj.object.public_send(@method_sym, **ruby_kwargs)
663
- else
664
- obj.object.public_send(@method_sym)
665
- end
666
- else
667
- raise <<-ERR
668
- Failed to implement #{@owner.graphql_name}.#{@name}, tried:
669
-
670
- - `#{obj.class}##{@resolver_method}`, which did not exist
671
- - `#{obj.object.class}##{@method_sym}`, which did not exist
672
- - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
673
-
674
- To implement this field, define one of the methods above (and check for typos)
675
- ERR
676
- end
677
- rescue ArgumentError
678
- assert_satisfactory_implementation(method_receiver, method_to_call, ruby_kwargs)
679
- # if the line above doesn't raise, re-raise
680
- raise
681
- end
682
- end
683
- end
684
-
685
719
  def assert_satisfactory_implementation(receiver, method_name, ruby_kwargs)
686
720
  method_defn = receiver.method(method_name)
687
721
  unsatisfied_ruby_kwargs = ruby_kwargs.dup
@@ -127,19 +127,15 @@ module GraphQL
127
127
  INVALID_OBJECT_MESSAGE = "Expected %{object} to be a key-value object responding to `to_h` or `to_unsafe_h`."
128
128
 
129
129
  def validate_non_null_input(input, ctx)
130
- result = GraphQL::Query::InputValidationResult.new
131
-
132
130
  warden = ctx.warden
133
131
 
134
132
  if input.is_a?(Array)
135
- result.add_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) })
136
- return result
133
+ return GraphQL::Query::InputValidationResult.from_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) })
137
134
  end
138
135
 
139
136
  if !(input.respond_to?(:to_h) || input.respond_to?(:to_unsafe_h))
140
137
  # We're not sure it'll act like a hash, so reject it:
141
- result.add_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) })
142
- return result
138
+ return GraphQL::Query::InputValidationResult.from_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) })
143
139
  end
144
140
 
145
141
  # Inject missing required arguments
@@ -151,18 +147,22 @@ module GraphQL
151
147
  m
152
148
  end
153
149
 
154
-
150
+ result = nil
155
151
  [input, missing_required_inputs].each do |args_to_validate|
156
152
  args_to_validate.each do |argument_name, value|
157
153
  argument = warden.get_argument(self, argument_name)
158
154
  # Items in the input that are unexpected
159
- unless argument
155
+ if argument.nil?
156
+ result ||= Query::InputValidationResult.new
160
157
  result.add_problem("Field is not defined on #{self.graphql_name}", [argument_name])
161
- next
158
+ else
159
+ # Items in the input that are expected, but have invalid values
160
+ argument_result = argument.type.validate_input(value, ctx)
161
+ result ||= Query::InputValidationResult.new
162
+ if !argument_result.valid?
163
+ result.merge_result!(argument_name, argument_result)
164
+ end
162
165
  end
163
- # Items in the input that are expected, but have invalid values
164
- argument_result = argument.type.validate_input(value, ctx)
165
- result.merge_result!(argument_name, argument_result) unless argument_result.valid?
166
166
  end
167
167
  end
168
168
 
@@ -46,10 +46,11 @@ module GraphQL
46
46
  end
47
47
 
48
48
  def validate_non_null_input(value, ctx)
49
- result = GraphQL::Query::InputValidationResult.new
49
+ result = nil
50
50
  ensure_array(value).each_with_index do |item, index|
51
51
  item_result = of_type.validate_input(item, ctx)
52
52
  if !item_result.valid?
53
+ result ||= GraphQL::Query::InputValidationResult.new
53
54
  result.merge_result!(index, item_result)
54
55
  end
55
56
  end
@@ -78,23 +78,44 @@ module GraphQL
78
78
  # @return [GraphQL::Schema::Argument]
79
79
  def add_argument(arg_defn)
80
80
  @own_arguments ||= {}
81
- prev_defn = own_arguments[arg_defn.name]
81
+ prev_defn = @own_arguments[arg_defn.name]
82
82
  case prev_defn
83
83
  when nil
84
- own_arguments[arg_defn.name] = arg_defn
84
+ @own_arguments[arg_defn.name] = arg_defn
85
85
  when Array
86
86
  prev_defn << arg_defn
87
87
  when GraphQL::Schema::Argument
88
- own_arguments[arg_defn.name] = [prev_defn, arg_defn]
88
+ @own_arguments[arg_defn.name] = [prev_defn, arg_defn]
89
89
  else
90
90
  raise "Invariant: unexpected `@own_arguments[#{arg_defn.name.inspect}]`: #{prev_defn.inspect}"
91
91
  end
92
92
  arg_defn
93
93
  end
94
94
 
95
+ def remove_argument(arg_defn)
96
+ prev_defn = @own_arguments[arg_defn.name]
97
+ case prev_defn
98
+ when nil
99
+ # done
100
+ when Array
101
+ prev_defn.delete(arg_defn)
102
+ when GraphQL::Schema::Argument
103
+ @own_arguments.delete(arg_defn.name)
104
+ else
105
+ raise "Invariant: unexpected `@own_arguments[#{arg_defn.name.inspect}]`: #{prev_defn.inspect}"
106
+ end
107
+ nil
108
+ end
109
+
95
110
  # @return [Hash<String => GraphQL::Schema::Argument] Arguments defined on this thing, keyed by name. Includes inherited definitions
96
111
  def arguments(context = GraphQL::Query::NullContext)
97
- inherited_arguments = ((self.is_a?(Class) && superclass.respond_to?(:arguments)) ? superclass.arguments(context) : nil)
112
+ inherited_arguments = if self.is_a?(Class) && superclass.respond_to?(:arguments)
113
+ superclass.arguments(context)
114
+ elsif defined?(@resolver_class) && @resolver_class
115
+ @resolver_class.field_arguments(context)
116
+ else
117
+ nil
118
+ end
98
119
  # Local definitions override inherited ones
99
120
  if own_arguments.any?
100
121
  own_arguments_that_apply = {}
@@ -125,6 +146,10 @@ module GraphQL
125
146
  all_defns.merge!(ancestor.own_arguments)
126
147
  end
127
148
  end
149
+ elsif defined?(@resolver_class) && @resolver_class
150
+ all_defns = {}
151
+ all_defns.merge!(@resolver_class.own_field_arguments)
152
+ all_defns.merge!(own_arguments)
128
153
  else
129
154
  all_defns = own_arguments
130
155
  end
@@ -137,8 +162,13 @@ module GraphQL
137
162
  def get_argument(argument_name, context = GraphQL::Query::NullContext)
138
163
  warden = Warden.from_context(context)
139
164
  if !self.is_a?(Class)
140
- a = own_arguments[argument_name]
141
- a && Warden.visible_entry?(:visible_argument?, a, context, warden)
165
+ if (arg_config = own_arguments[argument_name]) && (visible_arg = Warden.visible_entry?(:visible_argument?, arg_config, context, warden))
166
+ visible_arg
167
+ elsif defined?(@resolver_class) && @resolver_class
168
+ @resolver_class.get_field_argument(argument_name, context)
169
+ else
170
+ nil
171
+ end
142
172
  else
143
173
  for ancestor in ancestors
144
174
  if ancestor.respond_to?(:own_arguments) &&
@@ -11,7 +11,7 @@ module GraphQL
11
11
  # @return [void]
12
12
  def directive(dir_class, **options)
13
13
  @own_directives ||= []
14
- remove_directive(dir_class)
14
+ remove_directive(dir_class) unless dir_class.repeatable?
15
15
  @own_directives << dir_class.new(self, **options)
16
16
  nil
17
17
  end
@@ -80,7 +80,7 @@ module GraphQL
80
80
  visible_interfaces.concat(superclass.interfaces(context))
81
81
  end
82
82
 
83
- visible_interfaces
83
+ visible_interfaces.uniq
84
84
  end
85
85
  end
86
86
  end