graphql 1.13.0 → 1.13.4

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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/core.rb +3 -1
  3. data/lib/generators/graphql/install_generator.rb +9 -2
  4. data/lib/graphql/analysis/ast/field_usage.rb +6 -2
  5. data/lib/graphql/base_type.rb +4 -2
  6. data/lib/graphql/boolean_type.rb +1 -1
  7. data/lib/graphql/directive/deprecated_directive.rb +1 -1
  8. data/lib/graphql/directive/include_directive.rb +1 -1
  9. data/lib/graphql/directive/skip_directive.rb +1 -1
  10. data/lib/graphql/execution/interpreter/runtime.rb +11 -7
  11. data/lib/graphql/execution/multiplex.rb +3 -0
  12. data/lib/graphql/float_type.rb +1 -1
  13. data/lib/graphql/id_type.rb +1 -1
  14. data/lib/graphql/int_type.rb +1 -1
  15. data/lib/graphql/language/block_string.rb +2 -2
  16. data/lib/graphql/language/document_from_schema_definition.rb +7 -3
  17. data/lib/graphql/language/nodes.rb +11 -2
  18. data/lib/graphql/pagination/active_record_relation_connection.rb +43 -6
  19. data/lib/graphql/pagination/relation_connection.rb +57 -27
  20. data/lib/graphql/query/context.rb +10 -0
  21. data/lib/graphql/relay/global_id_resolve.rb +1 -1
  22. data/lib/graphql/relay/page_info.rb +1 -1
  23. data/lib/graphql/schema/argument.rb +8 -10
  24. data/lib/graphql/schema/directive.rb +5 -1
  25. data/lib/graphql/schema/enum.rb +3 -1
  26. data/lib/graphql/schema/enum_value.rb +2 -0
  27. data/lib/graphql/schema/field.rb +139 -62
  28. data/lib/graphql/schema/field_extension.rb +89 -2
  29. data/lib/graphql/schema/input_object.rb +18 -1
  30. data/lib/graphql/schema/interface.rb +3 -1
  31. data/lib/graphql/schema/introspection_system.rb +1 -1
  32. data/lib/graphql/schema/list.rb +3 -1
  33. data/lib/graphql/schema/member/accepts_definition.rb +7 -2
  34. data/lib/graphql/schema/member/cached_graphql_definition.rb +29 -2
  35. data/lib/graphql/schema/non_null.rb +7 -1
  36. data/lib/graphql/schema/object.rb +3 -1
  37. data/lib/graphql/schema/relay_classic_mutation.rb +8 -0
  38. data/lib/graphql/schema/resolver.rb +19 -13
  39. data/lib/graphql/schema/scalar.rb +2 -0
  40. data/lib/graphql/schema/traversal.rb +1 -1
  41. data/lib/graphql/schema/union.rb +2 -0
  42. data/lib/graphql/schema/validator/required_validator.rb +29 -15
  43. data/lib/graphql/schema/validator.rb +4 -7
  44. data/lib/graphql/schema.rb +24 -8
  45. data/lib/graphql/static_validation/all_rules.rb +1 -0
  46. data/lib/graphql/static_validation/rules/fields_will_merge.rb +14 -7
  47. data/lib/graphql/static_validation/rules/query_root_exists.rb +17 -0
  48. data/lib/graphql/static_validation/rules/query_root_exists_error.rb +26 -0
  49. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +2 -0
  50. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +6 -0
  51. data/lib/graphql/string_type.rb +1 -1
  52. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +2 -0
  53. data/lib/graphql/subscriptions/serialize.rb +22 -2
  54. data/lib/graphql/tracing/active_support_notifications_tracing.rb +6 -20
  55. data/lib/graphql/tracing/notifications_tracing.rb +59 -0
  56. data/lib/graphql/types/relay/connection_behaviors.rb +26 -9
  57. data/lib/graphql/types/relay/default_relay.rb +5 -1
  58. data/lib/graphql/types/relay/edge_behaviors.rb +13 -2
  59. data/lib/graphql/types/relay/node_field.rb +14 -3
  60. data/lib/graphql/types/relay/nodes_field.rb +13 -3
  61. data/lib/graphql/version.rb +1 -1
  62. data/lib/graphql.rb +1 -1
  63. metadata +5 -2
@@ -16,6 +16,8 @@ module GraphQL
16
16
  include GraphQL::Schema::Member::HasDirectives
17
17
  include GraphQL::Schema::Member::HasDeprecationReason
18
18
 
19
+ class FieldImplementationFailed < GraphQL::Error; end
20
+
19
21
  # @return [String] the GraphQL name for this field, camelized unless `camelize: false` is provided
20
22
  attr_reader :name
21
23
  alias :graphql_name :name
@@ -290,6 +292,7 @@ module GraphQL
290
292
  @subscription_scope = subscription_scope
291
293
 
292
294
  @extensions = EMPTY_ARRAY
295
+ @call_after_define = false
293
296
  # This should run before connection extension,
294
297
  # but should it run after the definition block?
295
298
  if scoped?
@@ -322,6 +325,9 @@ module GraphQL
322
325
  instance_eval(&definition_block)
323
326
  end
324
327
  end
328
+
329
+ self.extensions.each(&:after_define_apply)
330
+ @call_after_define = true
325
331
  end
326
332
 
327
333
  # If true, subscription updates with this field can be shared between viewers
@@ -354,27 +360,20 @@ module GraphQL
354
360
  # @example adding an extension with options
355
361
  # extensions([MyExtensionClass, { AnotherExtensionClass => { filter: true } }])
356
362
  #
357
- # @param extensions [Array<Class, Hash<Class => Object>>] Add extensions to this field. For hash elements, only the first key/value is used.
363
+ # @param extensions [Array<Class, Hash<Class => Hash>>] Add extensions to this field. For hash elements, only the first key/value is used.
358
364
  # @return [Array<GraphQL::Schema::FieldExtension>] extensions to apply to this field
359
365
  def extensions(new_extensions = nil)
360
- if new_extensions.nil?
361
- # Read the value
362
- @extensions
363
- else
364
- if @extensions.frozen?
365
- @extensions = @extensions.dup
366
- end
367
- new_extensions.each do |extension|
368
- if extension.is_a?(Hash)
369
- extension = extension.to_a[0]
370
- extension_class, options = *extension
371
- @extensions << extension_class.new(field: self, options: options)
366
+ if new_extensions
367
+ new_extensions.each do |extension_config|
368
+ if extension_config.is_a?(Hash)
369
+ extension_class, options = *extension_config.to_a[0]
370
+ self.extension(extension_class, options)
372
371
  else
373
- extension_class = extension
374
- @extensions << extension_class.new(field: self, options: nil)
372
+ self.extension(extension_config)
375
373
  end
376
374
  end
377
375
  end
376
+ @extensions
378
377
  end
379
378
 
380
379
  # Add `extension` to this field, initialized with `options` if provided.
@@ -385,10 +384,19 @@ module GraphQL
385
384
  # @example adding an extension with options
386
385
  # extension(MyExtensionClass, filter: true)
387
386
  #
388
- # @param extension [Class] subclass of {Schema::Fieldextension}
389
- # @param options [Object] if provided, given as `options:` when initializing `extension`.
390
- def extension(extension, options = nil)
391
- extensions([{extension => options}])
387
+ # @param extension_class [Class] subclass of {Schema::FieldExtension}
388
+ # @param options [Hash] if provided, given as `options:` when initializing `extension`.
389
+ # @return [void]
390
+ def extension(extension_class, options = nil)
391
+ extension_inst = extension_class.new(field: self, options: options)
392
+ if @extensions.frozen?
393
+ @extensions = @extensions.dup
394
+ end
395
+ if @call_after_define
396
+ extension_inst.after_define_apply
397
+ end
398
+ @extensions << extension_inst
399
+ nil
392
400
  end
393
401
 
394
402
  # Read extras (as symbols) from this field,
@@ -441,10 +449,15 @@ module GraphQL
441
449
  if lookahead.selects?(:total) || lookahead.selects?(:total_count) || lookahead.selects?(:count)
442
450
  metadata_complexity += 1
443
451
  end
452
+
453
+ nodes_edges_complexity = 0
454
+ nodes_edges_complexity += 1 if lookahead.selects?(:edges)
455
+ nodes_edges_complexity += 1 if lookahead.selects?(:nodes)
456
+
444
457
  # Possible bug: selections on `edges` and `nodes` are _both_ multiplied here. Should they be?
445
- items_complexity = child_complexity - metadata_complexity
458
+ items_complexity = child_complexity - metadata_complexity - nodes_edges_complexity
446
459
  # Add 1 for _this_ field
447
- 1 + (max_possible_page_size * items_complexity) + metadata_complexity
460
+ 1 + (max_possible_page_size * items_complexity) + metadata_complexity + nodes_edges_complexity
448
461
  end
449
462
  else
450
463
  defined_complexity = complexity
@@ -488,6 +501,8 @@ module GraphQL
488
501
  # @return [Integer, nil] Applied to connections if {#has_max_page_size?}
489
502
  attr_reader :max_page_size
490
503
 
504
+ prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
505
+
491
506
  # @return [GraphQL::Field]
492
507
  def to_graphql
493
508
  field_defn = if @field
@@ -543,7 +558,7 @@ module GraphQL
543
558
  field_defn.ast_node = ast_node
544
559
 
545
560
  all_argument_definitions.each do |defn|
546
- arg_graphql = defn.to_graphql
561
+ arg_graphql = defn.deprecated_to_graphql
547
562
  field_defn.arguments[arg_graphql.name] = arg_graphql # rubocop:disable Development/ContextIsPassedCop -- legacy-related
548
563
  end
549
564
 
@@ -605,7 +620,7 @@ module GraphQL
605
620
 
606
621
  def authorized?(object, args, context)
607
622
  if @resolver_class
608
- # The resolver will check itself during `resolve()`
623
+ # The resolver _instance_ will check itself during `resolve()`
609
624
  @resolver_class.authorized?(object, context)
610
625
  else
611
626
  if (arg_values = context[:current_arguments])
@@ -779,51 +794,103 @@ module GraphQL
779
794
 
780
795
  def public_send_field(unextended_obj, unextended_ruby_kwargs, query_ctx)
781
796
  with_extensions(unextended_obj, unextended_ruby_kwargs, query_ctx) do |obj, ruby_kwargs|
782
- if @resolver_class
783
- if obj.is_a?(GraphQL::Schema::Object)
784
- obj = obj.object
785
- end
786
- obj = @resolver_class.new(object: obj, context: query_ctx, field: self)
787
- end
788
-
789
- # Find a way to resolve this field, checking:
790
- #
791
- # - A method on the type instance;
792
- # - Hash keys, if the wrapped object is a hash;
793
- # - A method on the wrapped object;
794
- # - Or, raise not implemented.
795
- #
796
- if obj.respond_to?(@resolver_method)
797
- # Call the method with kwargs, if there are any
798
- if ruby_kwargs.any?
799
- obj.public_send(@resolver_method, **ruby_kwargs)
800
- else
801
- obj.public_send(@resolver_method)
797
+ begin
798
+ method_receiver = nil
799
+ method_to_call = nil
800
+ if @resolver_class
801
+ if obj.is_a?(GraphQL::Schema::Object)
802
+ obj = obj.object
803
+ end
804
+ obj = @resolver_class.new(object: obj, context: query_ctx, field: self)
802
805
  end
803
- elsif obj.object.is_a?(Hash)
804
- inner_object = obj.object
805
- if inner_object.key?(@method_sym)
806
- inner_object[@method_sym]
806
+
807
+ # Find a way to resolve this field, checking:
808
+ #
809
+ # - A method on the type instance;
810
+ # - Hash keys, if the wrapped object is a hash;
811
+ # - A method on the wrapped object;
812
+ # - Or, raise not implemented.
813
+ #
814
+ if obj.respond_to?(@resolver_method)
815
+ method_to_call = @resolver_method
816
+ method_receiver = obj
817
+ # Call the method with kwargs, if there are any
818
+ if ruby_kwargs.any?
819
+ obj.public_send(@resolver_method, **ruby_kwargs)
820
+ else
821
+ obj.public_send(@resolver_method)
822
+ end
823
+ elsif obj.object.is_a?(Hash)
824
+ inner_object = obj.object
825
+ if inner_object.key?(@method_sym)
826
+ inner_object[@method_sym]
827
+ else
828
+ inner_object[@method_str]
829
+ end
830
+ elsif obj.object.respond_to?(@method_sym)
831
+ method_to_call = @method_sym
832
+ method_receiver = obj.object
833
+ if ruby_kwargs.any?
834
+ obj.object.public_send(@method_sym, **ruby_kwargs)
835
+ else
836
+ obj.object.public_send(@method_sym)
837
+ end
807
838
  else
808
- inner_object[@method_str]
839
+ raise <<-ERR
840
+ Failed to implement #{@owner.graphql_name}.#{@name}, tried:
841
+
842
+ - `#{obj.class}##{@resolver_method}`, which did not exist
843
+ - `#{obj.object.class}##{@method_sym}`, which did not exist
844
+ - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
845
+
846
+ To implement this field, define one of the methods above (and check for typos)
847
+ ERR
809
848
  end
810
- elsif obj.object.respond_to?(@method_sym)
811
- if ruby_kwargs.any?
812
- obj.object.public_send(@method_sym, **ruby_kwargs)
849
+ rescue ArgumentError
850
+ assert_satisfactory_implementation(method_receiver, method_to_call, ruby_kwargs)
851
+ # if the line above doesn't raise, re-raise
852
+ raise
853
+ end
854
+ end
855
+ end
856
+
857
+ def assert_satisfactory_implementation(receiver, method_name, ruby_kwargs)
858
+ method_defn = receiver.method(method_name)
859
+ unsatisfied_ruby_kwargs = ruby_kwargs.dup
860
+ unsatisfied_method_params = []
861
+ encountered_keyrest = false
862
+ method_defn.parameters.each do |(param_type, param_name)|
863
+ case param_type
864
+ when :key
865
+ unsatisfied_ruby_kwargs.delete(param_name)
866
+ when :keyreq
867
+ if unsatisfied_ruby_kwargs.key?(param_name)
868
+ unsatisfied_ruby_kwargs.delete(param_name)
813
869
  else
814
- obj.object.public_send(@method_sym)
870
+ unsatisfied_method_params << "- `#{param_name}:` is required by Ruby, but not by GraphQL. Consider `#{param_name}: nil` instead, or making this argument required in GraphQL."
815
871
  end
816
- else
817
- raise <<-ERR
818
- Failed to implement #{@owner.graphql_name}.#{@name}, tried:
872
+ when :keyrest
873
+ encountered_keyrest = true
874
+ when :req
875
+ unsatisfied_method_params << "- `#{param_name}` is required by Ruby, but GraphQL doesn't pass positional arguments. If it's meant to be a GraphQL argument, use `#{param_name}:` instead. Otherwise, remove it."
876
+ when :opt, :rest
877
+ # This is fine, although it will never be present
878
+ end
879
+ end
880
+
881
+ if encountered_keyrest
882
+ unsatisfied_ruby_kwargs.clear
883
+ end
819
884
 
820
- - `#{obj.class}##{@resolver_method}`, which did not exist
821
- - `#{obj.object.class}##{@method_sym}`, which did not exist
822
- - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
885
+ if unsatisfied_ruby_kwargs.any? || unsatisfied_method_params.any?
886
+ raise FieldImplementationFailed.new, <<-ERR
887
+ Failed to call #{method_name} on #{receiver.inspect} because the Ruby method params were incompatible with the GraphQL arguments:
823
888
 
824
- To implement this field, define one of the methods above (and check for typos)
825
- ERR
826
- end
889
+ #{ unsatisfied_ruby_kwargs
890
+ .map { |key, value| "- `#{key}: #{value}` was given by GraphQL but not defined in the Ruby method. Add `#{key}:` to the method parameters." }
891
+ .concat(unsatisfied_method_params)
892
+ .join("\n") }
893
+ ERR
827
894
  end
828
895
  end
829
896
 
@@ -837,8 +904,12 @@ module GraphQL
837
904
  # This is a hack to get the _last_ value for extended obj and args,
838
905
  # in case one of the extensions doesn't `yield`.
839
906
  # (There's another implementation that uses multiple-return, but I'm wary of the perf cost of the extra arrays)
840
- extended = { args: args, obj: obj, memos: nil }
907
+ extended = { args: args, obj: obj, memos: nil, added_extras: nil }
841
908
  value = run_extensions_before_resolve(obj, args, ctx, extended) do |obj, args|
909
+ if (added_extras = extended[:added_extras])
910
+ args = args.dup
911
+ added_extras.each { |e| args.delete(e) }
912
+ end
842
913
  yield(obj, args)
843
914
  end
844
915
 
@@ -867,6 +938,12 @@ module GraphQL
867
938
  memos = extended[:memos] ||= {}
868
939
  memos[idx] = memo
869
940
  end
941
+
942
+ if (extras = extension.added_extras)
943
+ ae = extended[:added_extras] ||= []
944
+ ae.concat(extras)
945
+ end
946
+
870
947
  extended[:obj] = extended_obj
871
948
  extended[:args] = extended_args
872
949
  run_extensions_before_resolve(extended_obj, extended_args, ctx, extended, idx: idx + 1) { |o, a| yield(o, a) }
@@ -15,23 +15,110 @@ module GraphQL
15
15
  # @return [Object]
16
16
  attr_reader :options
17
17
 
18
+ # @return [Array<Symbol>, nil] `default_argument`s added, if any were added (otherwise, `nil`)
19
+ attr_reader :added_default_arguments
20
+
18
21
  # Called when the extension is mounted with `extension(name, options)`.
19
- # The instance is frozen to avoid improper use of state during execution.
22
+ # The instance will be frozen to avoid improper use of state during execution.
20
23
  # @param field [GraphQL::Schema::Field] The field where this extension was mounted
21
24
  # @param options [Object] The second argument to `extension`, or `{}` if nothing was passed.
22
25
  def initialize(field:, options:)
23
26
  @field = field
24
27
  @options = options || {}
28
+ @added_default_arguments = nil
25
29
  apply
26
- freeze
27
30
  end
28
31
 
32
+ class << self
33
+ # @return [Array(Array, Hash), nil] A list of default argument configs, or `nil` if there aren't any
34
+ def default_argument_configurations
35
+ args = superclass.respond_to?(:default_argument_configurations) ? superclass.default_argument_configurations : nil
36
+ if @own_default_argument_configurations
37
+ if args
38
+ args.concat(@own_default_argument_configurations)
39
+ else
40
+ args = @own_default_argument_configurations.dup
41
+ end
42
+ end
43
+ args
44
+ end
45
+
46
+ # @see Argument#initialize
47
+ # @see HasArguments#argument
48
+ def default_argument(*argument_args, **argument_kwargs)
49
+ configs = @own_default_argument_configurations ||= []
50
+ configs << [argument_args, argument_kwargs]
51
+ end
52
+
53
+ # If configured, these `extras` will be added to the field if they aren't already present,
54
+ # but removed by from `arguments` before the field's `resolve` is called.
55
+ # (The extras _will_ be present for other extensions, though.)
56
+ #
57
+ # @param new_extras [Array<Symbol>] If provided, assign extras used by this extension
58
+ # @return [Array<Symbol>] any extras assigned to this extension
59
+ def extras(new_extras = nil)
60
+ if new_extras
61
+ @own_extras = new_extras
62
+ end
63
+
64
+ inherited_extras = self.superclass.respond_to?(:extras) ? superclass.extras : nil
65
+ if @own_extras
66
+ if inherited_extras
67
+ inherited_extras + @own_extras
68
+ else
69
+ @own_extras
70
+ end
71
+ elsif inherited_extras
72
+ inherited_extras
73
+ else
74
+ NO_EXTRAS
75
+ end
76
+ end
77
+ end
78
+
79
+ NO_EXTRAS = [].freeze
80
+ private_constant :NO_EXTRAS
81
+
29
82
  # Called when this extension is attached to a field.
30
83
  # The field definition may be extended during this method.
31
84
  # @return [void]
32
85
  def apply
33
86
  end
34
87
 
88
+ # Called after the field's definition block has been executed.
89
+ # (Any arguments from the block are present on `field`)
90
+ # @return [void]
91
+ def after_define
92
+ end
93
+
94
+ # @api private
95
+ def after_define_apply
96
+ after_define
97
+ if (configs = self.class.default_argument_configurations)
98
+ existing_keywords = field.all_argument_definitions.map(&:keyword)
99
+ existing_keywords.uniq!
100
+ @added_default_arguments = []
101
+ configs.each do |config|
102
+ argument_args, argument_kwargs = config
103
+ arg_name = argument_args[0]
104
+ if !existing_keywords.include?(arg_name)
105
+ @added_default_arguments << arg_name
106
+ field.argument(*argument_args, **argument_kwargs)
107
+ end
108
+ end
109
+ end
110
+ if (extras = self.class.extras).any?
111
+ @added_extras = extras - field.extras
112
+ field.extras(@added_extras)
113
+ else
114
+ @added_extras = nil
115
+ end
116
+ freeze
117
+ end
118
+
119
+ # @api private
120
+ attr_reader :added_extras
121
+
35
122
  # Called before resolving {#field}. It should either:
36
123
  #
37
124
  # - `yield` values to continue execution; OR
@@ -79,6 +79,21 @@ module GraphQL
79
79
  end
80
80
  end
81
81
 
82
+ def self.authorized?(obj, value, ctx)
83
+ # Authorize each argument (but this doesn't apply if `prepare` is implemented):
84
+ if value.is_a?(InputObject)
85
+ arguments(ctx).each do |_name, input_obj_arg|
86
+ input_obj_arg = input_obj_arg.type_class
87
+ if value.key?(input_obj_arg.keyword) &&
88
+ !input_obj_arg.authorized?(obj, value[input_obj_arg.keyword], ctx)
89
+ return false
90
+ end
91
+ end
92
+ end
93
+ # It didn't early-return false:
94
+ true
95
+ end
96
+
82
97
  def unwrap_value(value)
83
98
  case value
84
99
  when Array
@@ -132,6 +147,8 @@ module GraphQL
132
147
  argument_defn
133
148
  end
134
149
 
150
+ prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
151
+
135
152
  def to_graphql
136
153
  type_defn = GraphQL::InputObjectType.new
137
154
  type_defn.name = graphql_name
@@ -140,7 +157,7 @@ module GraphQL
140
157
  type_defn.mutation = mutation
141
158
  type_defn.ast_node = ast_node
142
159
  all_argument_definitions.each do |arg|
143
- type_defn.arguments[arg.graphql_definition.name] = arg.graphql_definition # rubocop:disable Development/ContextIsPassedCop -- legacy-related
160
+ type_defn.arguments[arg.graphql_definition(silence_deprecation_warning: true).name] = arg.graphql_definition(silence_deprecation_warning: true) # rubocop:disable Development/ContextIsPassedCop -- legacy-related
144
161
  end
145
162
  # Make a reference to a classic-style Arguments class
146
163
  self.arguments_class = GraphQL::Query::Arguments.construct_arguments_class(type_defn)
@@ -100,6 +100,8 @@ module GraphQL
100
100
  end
101
101
  end
102
102
 
103
+ prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
104
+
103
105
  def to_graphql
104
106
  type_defn = GraphQL::InterfaceType.new
105
107
  type_defn.name = graphql_name
@@ -108,7 +110,7 @@ module GraphQL
108
110
  type_defn.type_membership_class = self.type_membership_class
109
111
  type_defn.ast_node = ast_node
110
112
  fields.each do |field_name, field_inst| # rubocop:disable Development/ContextIsPassedCop -- legacy-related
111
- field_defn = field_inst.graphql_definition
113
+ field_defn = field_inst.graphql_definition(silence_deprecation_warning: true)
112
114
  type_defn.fields[field_defn.name] = field_defn # rubocop:disable Development/ContextIsPassedCop -- legacy-related
113
115
  end
114
116
  type_defn.metadata[:type_class] = self
@@ -107,7 +107,7 @@ module GraphQL
107
107
  dup_type_class(const)
108
108
  else
109
109
  # Use `.to_graphql` to get a freshly-made version, not shared between schemas
110
- const.to_graphql
110
+ const.deprecated_to_graphql
111
111
  end
112
112
  rescue NameError
113
113
  # Dup the built-in so that the cached fields aren't shared
@@ -8,8 +8,10 @@ module GraphQL
8
8
  class List < GraphQL::Schema::Wrapper
9
9
  include Schema::Member::ValidatesInput
10
10
 
11
+ prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
12
+
11
13
  def to_graphql
12
- @of_type.graphql_definition.to_list_type
14
+ @of_type.graphql_definition(silence_deprecation_warning: true).to_list_type
13
15
  end
14
16
 
15
17
  # @return [GraphQL::TypeKinds::LIST]
@@ -123,8 +123,13 @@ module GraphQL
123
123
  end
124
124
 
125
125
  module ToGraphQLExtension
126
- def to_graphql
127
- defn = super
126
+ def to_graphql(*args, **kwargs)
127
+
128
+ defn = if args.empty? && kwargs.empty?
129
+ super()
130
+ else
131
+ super
132
+ end
128
133
  accepts_definition_methods.each do |method_name|
129
134
  value = public_send(method_name)
130
135
  if !value.nil?
@@ -11,8 +11,24 @@ module GraphQL
11
11
  # A cached result of {.to_graphql}.
12
12
  # It's cached here so that user-overridden {.to_graphql} implementations
13
13
  # are also cached
14
- def graphql_definition
15
- @graphql_definition ||= to_graphql
14
+ def graphql_definition(silence_deprecation_warning: false)
15
+ @graphql_definition ||= begin
16
+ unless silence_deprecation_warning
17
+ message = "Legacy `.graphql_definition` objects are deprecated and will be removed in GraphQL-Ruby 2.0. Remove `.graphql_definition` to use a class-based definition instead."
18
+ caller_message = "\n\nCalled on #{self.inspect} from:\n #{caller(1, 25).map { |l| " #{l}" }.join("\n")}"
19
+ GraphQL::Deprecation.warn(message + caller_message)
20
+ end
21
+ deprecated_to_graphql
22
+ end
23
+ end
24
+
25
+ def deprecated_to_graphql
26
+ case method(:to_graphql).arity
27
+ when 0
28
+ to_graphql
29
+ else
30
+ to_graphql(silence_deprecation_warning: true)
31
+ end
16
32
  end
17
33
 
18
34
  # This is for a common interface with .define-based types
@@ -25,6 +41,17 @@ module GraphQL
25
41
  super
26
42
  @graphql_definition = nil
27
43
  end
44
+
45
+ module DeprecatedToGraphQL
46
+ def to_graphql(silence_deprecation_warning: false)
47
+ unless silence_deprecation_warning
48
+ message = "Legacy `.to_graphql` objects are deprecated and will be removed in GraphQL-Ruby 2.0. Remove `.to_graphql` to use a class-based definition instead."
49
+ caller_message = "\n\nCalled on #{self.inspect} from:\n #{caller(1, 25).map { |l| " #{l}" }.join("\n")}"
50
+ GraphQL::Deprecation.warn(message + caller_message)
51
+ end
52
+ super()
53
+ end
54
+ end
28
55
  end
29
56
  end
30
57
  end
@@ -8,8 +8,10 @@ module GraphQL
8
8
  class NonNull < GraphQL::Schema::Wrapper
9
9
  include Schema::Member::ValidatesInput
10
10
 
11
+ prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
12
+
11
13
  def to_graphql
12
- @of_type.graphql_definition.to_non_null_type
14
+ @of_type.graphql_definition(silence_deprecation_warning: true).to_non_null_type
13
15
  end
14
16
 
15
17
  # @return [GraphQL::TypeKinds::NON_NULL]
@@ -51,6 +53,10 @@ module GraphQL
51
53
  end
52
54
 
53
55
  def coerce_input(value, ctx)
56
+ # `.validate_input` above is used for variables, but this method is used for arguments
57
+ if value.nil?
58
+ raise GraphQL::ExecutionError, "`null` is not a valid input for `#{to_type_signature}`, please provide a value for this argument."
59
+ end
54
60
  of_type.coerce_input(value, ctx)
55
61
  end
56
62
 
@@ -122,6 +122,8 @@ module GraphQL
122
122
  all_fields
123
123
  end
124
124
 
125
+ prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
126
+
125
127
  # @return [GraphQL::ObjectType]
126
128
  def to_graphql
127
129
  obj_type = GraphQL::ObjectType.new
@@ -132,7 +134,7 @@ module GraphQL
132
134
  obj_type.mutation = mutation
133
135
  obj_type.ast_node = ast_node
134
136
  fields.each do |field_name, field_inst| # rubocop:disable Development/ContextIsPassedCop -- legacy-related
135
- field_defn = field_inst.to_graphql
137
+ field_defn = field_inst.to_graphql(silence_deprecation_warning: true)
136
138
  obj_type.fields[field_defn.name] = field_defn # rubocop:disable Development/ContextIsPassedCop -- legacy-related
137
139
  end
138
140
 
@@ -155,6 +155,14 @@ module GraphQL
155
155
  end
156
156
  end
157
157
  end
158
+
159
+ private
160
+
161
+ def authorize_arguments(args, values)
162
+ # remove the `input` wrapper to match values
163
+ input_args = args["input"].type.unwrap.arguments(context)
164
+ super(input_args, values)
165
+ end
158
166
  end
159
167
  end
160
168
  end
@@ -145,19 +145,9 @@ module GraphQL
145
145
  # @raise [GraphQL::UnauthorizedError] To signal an authorization failure
146
146
  # @return [Boolean, early_return_data] If `false`, execution will stop (and `early_return_data` will be returned instead, if present.)
147
147
  def authorized?(**inputs)
148
- self.class.arguments(context).each_value do |argument|
149
- arg_keyword = argument.keyword
150
- if inputs.key?(arg_keyword) && !(arg_value = inputs[arg_keyword]).nil? && (arg_value != argument.default_value)
151
- arg_auth, err = argument.authorized?(self, arg_value, context)
152
- if !arg_auth
153
- return arg_auth, err
154
- else
155
- true
156
- end
157
- else
158
- true
159
- end
160
- end
148
+ arg_owner = @field # || self.class
149
+ args = arg_owner.arguments(context)
150
+ authorize_arguments(args, inputs)
161
151
  end
162
152
 
163
153
  # Called when an object loaded by `loads:` fails the `.authorized?` check for its resolved GraphQL object type.
@@ -172,6 +162,22 @@ module GraphQL
172
162
 
173
163
  private
174
164
 
165
+ def authorize_arguments(args, inputs)
166
+ args.each_value do |argument|
167
+ arg_keyword = argument.keyword
168
+ if inputs.key?(arg_keyword) && !(arg_value = inputs[arg_keyword]).nil? && (arg_value != argument.default_value)
169
+ arg_auth, err = argument.authorized?(self, arg_value, context)
170
+ if !arg_auth
171
+ return arg_auth, err
172
+ else
173
+ true
174
+ end
175
+ else
176
+ true
177
+ end
178
+ end
179
+ end
180
+
175
181
  def load_arguments(args)
176
182
  prepared_args = {}
177
183
  prepare_lazies = []
@@ -14,6 +14,8 @@ module GraphQL
14
14
  val
15
15
  end
16
16
 
17
+ prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
18
+
17
19
  def to_graphql
18
20
  type_defn = GraphQL::ScalarType.new
19
21
  type_defn.name = graphql_name