graphql 1.13.0 → 1.13.4

Sign up to get free protection for your applications and to get access to all the features.
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