graphql 2.0.0 → 2.0.3

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.

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