graphql 2.2.5 → 2.3.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/templates/schema.erb +3 -0
  3. data/lib/graphql/analysis/ast/field_usage.rb +36 -9
  4. data/lib/graphql/analysis/ast/query_complexity.rb +3 -0
  5. data/lib/graphql/analysis/ast/visitor.rb +8 -0
  6. data/lib/graphql/analysis/ast.rb +10 -1
  7. data/lib/graphql/backtrace/inspect_result.rb +0 -12
  8. data/lib/graphql/coercion_error.rb +1 -9
  9. data/lib/graphql/dataloader/request.rb +5 -0
  10. data/lib/graphql/execution/interpreter/argument_value.rb +5 -1
  11. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +6 -4
  12. data/lib/graphql/execution/interpreter/runtime.rb +93 -106
  13. data/lib/graphql/execution/interpreter.rb +90 -150
  14. data/lib/graphql/introspection/entry_points.rb +9 -3
  15. data/lib/graphql/introspection/schema_type.rb +3 -1
  16. data/lib/graphql/language/document_from_schema_definition.rb +2 -3
  17. data/lib/graphql/language/lexer.rb +48 -30
  18. data/lib/graphql/language/nodes.rb +11 -16
  19. data/lib/graphql/language/parser.rb +94 -45
  20. data/lib/graphql/language/printer.rb +4 -0
  21. data/lib/graphql/language.rb +60 -0
  22. data/lib/graphql/pagination/array_connection.rb +6 -6
  23. data/lib/graphql/query/context.rb +30 -33
  24. data/lib/graphql/query/validation_pipeline.rb +2 -2
  25. data/lib/graphql/query/variables.rb +3 -3
  26. data/lib/graphql/query.rb +3 -3
  27. data/lib/graphql/schema/argument.rb +18 -2
  28. data/lib/graphql/schema/base_64_encoder.rb +3 -5
  29. data/lib/graphql/schema/build_from_definition.rb +9 -1
  30. data/lib/graphql/schema/field.rb +33 -30
  31. data/lib/graphql/schema/input_object.rb +1 -2
  32. data/lib/graphql/schema/interface.rb +5 -1
  33. data/lib/graphql/schema/loader.rb +2 -1
  34. data/lib/graphql/schema/member/has_arguments.rb +2 -2
  35. data/lib/graphql/schema/mutation.rb +7 -0
  36. data/lib/graphql/schema/resolver.rb +19 -10
  37. data/lib/graphql/schema/unique_within_type.rb +1 -1
  38. data/lib/graphql/schema.rb +129 -29
  39. data/lib/graphql/static_validation/literal_validator.rb +1 -2
  40. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +1 -1
  41. data/lib/graphql/static_validation/validator.rb +3 -0
  42. data/lib/graphql/subscriptions/serialize.rb +2 -0
  43. data/lib/graphql/subscriptions.rb +0 -3
  44. data/lib/graphql/testing/helpers.rb +32 -6
  45. data/lib/graphql/tracing/data_dog_trace.rb +21 -34
  46. data/lib/graphql/tracing/data_dog_tracing.rb +7 -21
  47. data/lib/graphql/tracing/legacy_hooks_trace.rb +74 -0
  48. data/lib/graphql/tracing/platform_tracing.rb +3 -1
  49. data/lib/graphql/tracing/{prometheus_tracing → prometheus_trace}/graphql_collector.rb +3 -1
  50. data/lib/graphql/tracing/prometheus_trace.rb +2 -2
  51. data/lib/graphql/tracing/sentry_trace.rb +112 -0
  52. data/lib/graphql/tracing.rb +3 -1
  53. data/lib/graphql/version.rb +1 -1
  54. data/lib/graphql.rb +10 -2
  55. metadata +38 -23
  56. data/lib/graphql/schema/base_64_bp.rb +0 -26
  57. data/lib/graphql/subscriptions/instrumentation.rb +0 -28
@@ -206,8 +206,8 @@ module GraphQL
206
206
  # Used by the runtime.
207
207
  # @api private
208
208
  def prepare_value(obj, value, context: nil)
209
- if value.is_a?(GraphQL::Schema::InputObject)
210
- value = value.prepare
209
+ if type.unwrap.kind.input_object?
210
+ value = recursively_prepare_input_object(value, type)
211
211
  end
212
212
 
213
213
  Schema::Validator.validate!(validators, obj, context, value)
@@ -290,6 +290,7 @@ module GraphQL
290
290
  # TODO code smell to access such a deeply-nested constant in a distant module
291
291
  argument_values[arg_key] = GraphQL::Execution::Interpreter::ArgumentValue.new(
292
292
  value: resolved_loaded_value,
293
+ original_value: resolved_coerced_value,
293
294
  definition: self,
294
295
  default_used: default_used,
295
296
  )
@@ -372,6 +373,21 @@ module GraphQL
372
373
 
373
374
  private
374
375
 
376
+ def recursively_prepare_input_object(value, type)
377
+ if type.non_null?
378
+ type = type.of_type
379
+ end
380
+
381
+ if type.list? && !value.nil?
382
+ inner_type = type.of_type
383
+ value.map { |v| recursively_prepare_input_object(v, inner_type) }
384
+ elsif value.is_a?(GraphQL::Schema::InputObject)
385
+ value.prepare
386
+ else
387
+ value
388
+ end
389
+ end
390
+
375
391
  def validate_input_type(input_type)
376
392
  if input_type.is_a?(String) || input_type.is_a?(GraphQL::Schema::LateBoundType)
377
393
  # Do nothing; assume this will be validated later
@@ -1,18 +1,16 @@
1
1
  # frozen_string_literal: true
2
-
3
- require 'graphql/schema/base_64_bp'
4
-
2
+ require "base64"
5
3
  module GraphQL
6
4
  class Schema
7
5
  # @api private
8
6
  module Base64Encoder
9
7
  def self.encode(unencoded_text, nonce: false)
10
- Base64Bp.urlsafe_encode64(unencoded_text, padding: false)
8
+ Base64.urlsafe_encode64(unencoded_text, padding: false)
11
9
  end
12
10
 
13
11
  def self.decode(encoded_text, nonce: false)
14
12
  # urlsafe_decode64 is for forward compatibility
15
- Base64Bp.urlsafe_decode64(encoded_text)
13
+ Base64.urlsafe_decode64(encoded_text)
16
14
  rescue ArgumentError
17
15
  raise GraphQL::ExecutionError, "Invalid input: #{encoded_text.inspect}"
18
16
  end
@@ -7,10 +7,16 @@ module GraphQL
7
7
  class << self
8
8
  # @see {Schema.from_definition}
9
9
  def from_definition(schema_superclass, definition_string, parser: GraphQL.default_parser, **kwargs)
10
+ if defined?(parser::SchemaParser)
11
+ parser = parser::SchemaParser
12
+ end
10
13
  from_document(schema_superclass, parser.parse(definition_string), **kwargs)
11
14
  end
12
15
 
13
16
  def from_definition_path(schema_superclass, definition_path, parser: GraphQL.default_parser, **kwargs)
17
+ if defined?(parser::SchemaParser)
18
+ parser = parser::SchemaParser
19
+ end
14
20
  from_document(schema_superclass, parser.parse_file(definition_path), **kwargs)
15
21
  end
16
22
 
@@ -120,10 +126,12 @@ module GraphQL
120
126
 
121
127
  builder = self
122
128
 
129
+ found_types = types.values
123
130
  schema_class = Class.new(schema_superclass) do
124
131
  begin
125
132
  # Add these first so that there's some chance of resolving late-bound types
126
- orphan_types types.values
133
+ add_type_and_traverse(found_types, root: false)
134
+ orphan_types(found_types.select { |t| t.respond_to?(:kind) && t.kind.object? })
127
135
  query query_root_type
128
136
  mutation mutation_root_type
129
137
  subscription subscription_root_type
@@ -471,6 +471,8 @@ module GraphQL
471
471
  if arguments[:last] && (max_possible_page_size.nil? || arguments[:last] > max_possible_page_size)
472
472
  max_possible_page_size = arguments[:last]
473
473
  end
474
+ elsif arguments.is_a?(GraphQL::UnauthorizedError)
475
+ raise arguments
474
476
  end
475
477
 
476
478
  if max_possible_page_size.nil?
@@ -483,41 +485,24 @@ module GraphQL
483
485
  metadata_complexity = 0
484
486
  lookahead = GraphQL::Execution::Lookahead.new(query: query, field: self, ast_nodes: nodes, owner_type: owner)
485
487
 
486
- if (page_info_lookahead = lookahead.selection(:page_info)).selected?
487
- metadata_complexity += 1 # pageInfo
488
- metadata_complexity += page_info_lookahead.selections.size # subfields
489
- end
490
-
491
- if lookahead.selects?(:total) || lookahead.selects?(:total_count) || lookahead.selects?(:count)
492
- metadata_complexity += 1
488
+ lookahead.selections.each do |next_lookahead|
489
+ # this includes `pageInfo`, `nodes` and `edges` and any custom fields
490
+ # TODO this doesn't support procs yet -- unlikely to need it.
491
+ metadata_complexity += next_lookahead.field.complexity
492
+ if next_lookahead.name != :nodes && next_lookahead.name != :edges
493
+ # subfields, eg, for pageInfo -- assumes no subselections
494
+ metadata_complexity += next_lookahead.selections.size
495
+ end
493
496
  end
494
497
 
495
- nodes_edges_complexity = 0
496
- nodes_edges_complexity += 1 if lookahead.selects?(:edges)
497
- nodes_edges_complexity += 1 if lookahead.selects?(:nodes)
498
-
499
498
  # Possible bug: selections on `edges` and `nodes` are _both_ multiplied here. Should they be?
500
- items_complexity = child_complexity - metadata_complexity - nodes_edges_complexity
501
- # Add 1 for _this_ field
502
- 1 + (max_possible_page_size * items_complexity) + metadata_complexity + nodes_edges_complexity
499
+ items_complexity = child_complexity - metadata_complexity
500
+ subfields_complexity = (max_possible_page_size * items_complexity) + metadata_complexity
501
+ # Apply this field's own complexity
502
+ apply_own_complexity_to(subfields_complexity, query, nodes)
503
503
  end
504
504
  else
505
- defined_complexity = complexity
506
- case defined_complexity
507
- when Proc
508
- arguments = query.arguments_for(nodes.first, self)
509
- if arguments.is_a?(GraphQL::ExecutionError)
510
- return child_complexity
511
- elsif arguments.respond_to?(:keyword_arguments)
512
- arguments = arguments.keyword_arguments
513
- end
514
-
515
- defined_complexity.call(query.context, arguments, child_complexity)
516
- when Numeric
517
- defined_complexity + child_complexity
518
- else
519
- raise("Invalid complexity: #{defined_complexity.inspect} on #{path} (#{inspect})")
520
- end
505
+ apply_own_complexity_to(child_complexity, query, nodes)
521
506
  end
522
507
  end
523
508
 
@@ -882,6 +867,24 @@ ERR
882
867
  yield(obj, args)
883
868
  end
884
869
  end
870
+
871
+ def apply_own_complexity_to(child_complexity, query, nodes)
872
+ case (own_complexity = complexity)
873
+ when Numeric
874
+ own_complexity + child_complexity
875
+ when Proc
876
+ arguments = query.arguments_for(nodes.first, self)
877
+ if arguments.is_a?(GraphQL::ExecutionError)
878
+ return child_complexity
879
+ elsif arguments.respond_to?(:keyword_arguments)
880
+ arguments = arguments.keyword_arguments
881
+ end
882
+
883
+ own_complexity.call(query.context, arguments, child_complexity)
884
+ else
885
+ raise ArgumentError, "Invalid complexity for #{self.path}: #{own_complexity.inspect}"
886
+ end
887
+ end
885
888
  end
886
889
  end
887
890
  end
@@ -215,8 +215,7 @@ module GraphQL
215
215
  if resolved_arguments.is_a?(GraphQL::Error)
216
216
  raise resolved_arguments
217
217
  else
218
- input_obj_instance = self.new(resolved_arguments, ruby_kwargs: resolved_arguments.keyword_arguments, context: ctx, defaults_used: nil)
219
- input_obj_instance.prepare
218
+ self.new(resolved_arguments, ruby_kwargs: resolved_arguments.keyword_arguments, context: ctx, defaults_used: nil)
220
219
  end
221
220
  end
222
221
  end
@@ -69,7 +69,11 @@ module GraphQL
69
69
  end
70
70
  elsif child_class < GraphQL::Schema::Object
71
71
  # This is being included into an object type, make sure it's using `implements(...)`
72
- backtrace_line = caller(0, 10).find { |line| line.include?("schema/member/has_interfaces.rb") && line.include?("in `implements'")}
72
+ backtrace_line = caller_locations(0, 10).find do |location|
73
+ location.base_label == "implements" &&
74
+ location.path.end_with?("schema/member/has_interfaces.rb")
75
+ end
76
+
73
77
  if !backtrace_line
74
78
  raise "Attach interfaces using `implements(#{self})`, not `include(#{self})`"
75
79
  end
@@ -32,7 +32,8 @@ module GraphQL
32
32
  end
33
33
 
34
34
  Class.new(GraphQL::Schema) do
35
- orphan_types(types.values)
35
+ add_type_and_traverse(types.values, root: false)
36
+ orphan_types(types.values.select { |t| t.kind.object? })
36
37
  directives(directives)
37
38
  description(schema["description"])
38
39
 
@@ -50,7 +50,7 @@ module GraphQL
50
50
  if loads && arg_defn.type.list?
51
51
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
52
52
  def #{method_owner}load_#{arg_defn.keyword}(values, context = nil)
53
- argument = get_argument("#{arg_defn.graphql_name}")
53
+ argument = get_argument("#{arg_defn.graphql_name}", context || self.context)
54
54
  (context || self.context).query.after_lazy(values) do |values2|
55
55
  GraphQL::Execution::Lazy.all(values2.map { |value| load_application_object(argument, value, context || self.context) })
56
56
  end
@@ -59,7 +59,7 @@ module GraphQL
59
59
  elsif loads
60
60
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
61
61
  def #{method_owner}load_#{arg_defn.keyword}(value, context = nil)
62
- argument = get_argument("#{arg_defn.graphql_name}")
62
+ argument = get_argument("#{arg_defn.graphql_name}", context || self.context)
63
63
  load_application_object(argument, value, context || self.context)
64
64
  end
65
65
  RUBY
@@ -62,6 +62,13 @@ module GraphQL
62
62
  extend GraphQL::Schema::Member::HasFields
63
63
  extend GraphQL::Schema::Resolver::HasPayloadType
64
64
 
65
+ # @api private
66
+ def call_resolve(_args_hash)
67
+ # Clear any cached values from `loads` or authorization:
68
+ dataloader.clear_cache
69
+ super
70
+ end
71
+
65
72
  class << self
66
73
  def visible?(context)
67
74
  true
@@ -103,11 +103,7 @@ module GraphQL
103
103
  end
104
104
  elsif authorized_val
105
105
  # Finally, all the hooks have passed, so resolve it
106
- if loaded_args.any?
107
- public_send(self.class.resolve_method, **loaded_args)
108
- else
109
- public_send(self.class.resolve_method)
110
- end
106
+ call_resolve(loaded_args)
111
107
  else
112
108
  raise GraphQL::UnauthorizedFieldError.new(context: context, object: object, type: field.owner, field: field)
113
109
  end
@@ -117,6 +113,15 @@ module GraphQL
117
113
  end
118
114
  end
119
115
 
116
+ # @api private {GraphQL::Schema::Mutation} uses this to clear the dataloader cache
117
+ def call_resolve(args_hash)
118
+ if args_hash.any?
119
+ public_send(self.class.resolve_method, **args_hash)
120
+ else
121
+ public_send(self.class.resolve_method)
122
+ end
123
+ end
124
+
120
125
  # Do the work. Everything happens here.
121
126
  # @return [Object] An object corresponding to the return type
122
127
  def resolve(**args)
@@ -166,11 +171,15 @@ module GraphQL
166
171
  args.each_value do |argument|
167
172
  arg_keyword = argument.keyword
168
173
  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
+ auth_result = argument.authorized?(self, arg_value, context)
175
+ if auth_result.is_a?(Array)
176
+ # only return this second value if the application returned a second value
177
+ arg_auth, err = auth_result
178
+ if !arg_auth
179
+ return arg_auth, err
180
+ end
181
+ elsif auth_result == false
182
+ return auth_result
174
183
  end
175
184
  else
176
185
  true
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- require 'graphql/schema/base_64_bp'
2
+ require "base64"
3
3
 
4
4
  module GraphQL
5
5
  class Schema
@@ -63,10 +63,6 @@ module GraphQL
63
63
  # Schemas can restrict large incoming queries with `max_depth` and `max_complexity` configurations.
64
64
  # (These configurations can be overridden by specific calls to {Schema#execute})
65
65
  #
66
- # Schemas can specify how queries should be executed against them.
67
- # `query_execution_strategy`, `mutation_execution_strategy` and `subscription_execution_strategy`
68
- # each apply to corresponding root types.
69
- #
70
66
  # @example defining a schema
71
67
  # class MySchema < GraphQL::Schema
72
68
  # query QueryType
@@ -162,18 +158,29 @@ module GraphQL
162
158
 
163
159
  def trace_class(new_class = nil)
164
160
  if new_class
161
+ # If any modules were already added for `:default`,
162
+ # re-apply them here
163
+ mods = trace_modules_for(:default)
164
+ mods.each { |mod| new_class.include(mod) }
165
165
  trace_mode(:default, new_class)
166
166
  backtrace_class = Class.new(new_class)
167
167
  backtrace_class.include(GraphQL::Backtrace::Trace)
168
168
  trace_mode(:default_backtrace, backtrace_class)
169
169
  end
170
- trace_class_for(:default)
170
+ trace_class_for(:default, build: true)
171
171
  end
172
172
 
173
173
  # @return [Class] Return the trace class to use for this mode, looking one up on the superclass if this Schema doesn't have one defined.
174
- def trace_class_for(mode, build: true)
175
- own_trace_modes[mode] ||
176
- (superclass.respond_to?(:trace_class_for) ? superclass.trace_class_for(mode, build: build) : (build ? (own_trace_modes[mode] = build_trace_mode(mode)) : nil))
174
+ def trace_class_for(mode, build: false)
175
+ if (trace_class = own_trace_modes[mode])
176
+ trace_class
177
+ elsif superclass.respond_to?(:trace_class_for) && (trace_class = superclass.trace_class_for(mode, build: false))
178
+ trace_class
179
+ elsif build
180
+ own_trace_modes[mode] = build_trace_mode(mode)
181
+ else
182
+ nil
183
+ end
177
184
  end
178
185
 
179
186
  # Configure `trace_class` to be used whenever `context: { trace_mode: mode_name }` is requested.
@@ -211,20 +218,24 @@ module GraphQL
211
218
  include DefaultTraceClass
212
219
  end
213
220
  when :default_backtrace
214
- schema_base_class = trace_class_for(:default)
221
+ schema_base_class = trace_class_for(:default, build: true)
215
222
  Class.new(schema_base_class) do
216
223
  include(GraphQL::Backtrace::Trace)
217
224
  end
218
225
  else
219
226
  # First, see if the superclass has a custom-defined class for this.
220
227
  # Then, if it doesn't, use this class's default trace
221
- base_class = (superclass.respond_to?(:trace_class_for) && superclass.trace_class_for(mode, build: false)) || trace_class_for(:default)
228
+ base_class = (superclass.respond_to?(:trace_class_for) && superclass.trace_class_for(mode)) || trace_class_for(:default, build: true)
222
229
  # Prepare the default trace class if it hasn't been initialized yet
223
230
  base_class ||= (own_trace_modes[:default] = build_trace_mode(:default))
224
231
  mods = trace_modules_for(mode)
225
232
  if base_class < DefaultTraceClass
226
233
  mods = trace_modules_for(:default) + mods
227
234
  end
235
+ # Copy the existing default options into this mode's options
236
+ default_options = trace_options_for(:default)
237
+ add_trace_options_for(mode, default_options)
238
+
228
239
  Class.new(base_class) do
229
240
  mods.any? && include(*mods)
230
241
  end
@@ -632,6 +643,17 @@ module GraphQL
632
643
  end
633
644
  end
634
645
 
646
+ # A limit on the number of tokens to accept on incoming query strings.
647
+ # Use this to prevent parsing maliciously-large query strings.
648
+ # @return [nil, Integer]
649
+ def max_query_string_tokens(new_max_tokens = NOT_CONFIGURED)
650
+ if NOT_CONFIGURED.equal?(new_max_tokens)
651
+ defined?(@max_query_string_tokens) ? @max_query_string_tokens : find_inherited_value(:max_query_string_tokens)
652
+ else
653
+ @max_query_string_tokens = new_max_tokens
654
+ end
655
+ end
656
+
635
657
  def default_page_size(new_default_page_size = nil)
636
658
  if new_default_page_size
637
659
  @default_page_size = new_default_page_size
@@ -640,27 +662,39 @@ module GraphQL
640
662
  end
641
663
  end
642
664
 
643
- def query_execution_strategy(new_query_execution_strategy = nil)
665
+ def query_execution_strategy(new_query_execution_strategy = nil, deprecation_warning: true)
666
+ if deprecation_warning
667
+ warn "GraphQL::Schema.query_execution_strategy is deprecated without replacement. Use `GraphQL::Query.new` directly to create and execute a custom query instead."
668
+ warn " #{caller(1, 1).first}"
669
+ end
644
670
  if new_query_execution_strategy
645
671
  @query_execution_strategy = new_query_execution_strategy
646
672
  else
647
- @query_execution_strategy || find_inherited_value(:query_execution_strategy, self.default_execution_strategy)
673
+ @query_execution_strategy || (superclass.respond_to?(:query_execution_strategy) ? superclass.query_execution_strategy(deprecation_warning: false) : self.default_execution_strategy)
648
674
  end
649
675
  end
650
676
 
651
- def mutation_execution_strategy(new_mutation_execution_strategy = nil)
677
+ def mutation_execution_strategy(new_mutation_execution_strategy = nil, deprecation_warning: true)
678
+ if deprecation_warning
679
+ warn "GraphQL::Schema.mutation_execution_strategy is deprecated without replacement. Use `GraphQL::Query.new` directly to create and execute a custom query instead."
680
+ warn " #{caller(1, 1).first}"
681
+ end
652
682
  if new_mutation_execution_strategy
653
683
  @mutation_execution_strategy = new_mutation_execution_strategy
654
684
  else
655
- @mutation_execution_strategy || find_inherited_value(:mutation_execution_strategy, self.default_execution_strategy)
685
+ @mutation_execution_strategy || (superclass.respond_to?(:mutation_execution_strategy) ? superclass.mutation_execution_strategy(deprecation_warning: false) : self.default_execution_strategy)
656
686
  end
657
687
  end
658
688
 
659
- def subscription_execution_strategy(new_subscription_execution_strategy = nil)
689
+ def subscription_execution_strategy(new_subscription_execution_strategy = nil, deprecation_warning: true)
690
+ if deprecation_warning
691
+ warn "GraphQL::Schema.subscription_execution_strategy is deprecated without replacement. Use `GraphQL::Query.new` directly to create and execute a custom query instead."
692
+ warn " #{caller(1, 1).first}"
693
+ end
660
694
  if new_subscription_execution_strategy
661
695
  @subscription_execution_strategy = new_subscription_execution_strategy
662
696
  else
663
- @subscription_execution_strategy || find_inherited_value(:subscription_execution_strategy, self.default_execution_strategy)
697
+ @subscription_execution_strategy || (superclass.respond_to?(:subscription_execution_strategy) ? superclass.subscription_execution_strategy(deprecation_warning: false) : self.default_execution_strategy)
664
698
  end
665
699
  end
666
700
 
@@ -715,9 +749,10 @@ module GraphQL
715
749
 
716
750
  attr_writer :max_complexity
717
751
 
718
- def max_complexity(max_complexity = nil)
752
+ def max_complexity(max_complexity = nil, count_introspection_fields: true)
719
753
  if max_complexity
720
754
  @max_complexity = max_complexity
755
+ @max_complexity_count_introspection_fields = count_introspection_fields
721
756
  elsif defined?(@max_complexity)
722
757
  @max_complexity
723
758
  else
@@ -725,6 +760,14 @@ module GraphQL
725
760
  end
726
761
  end
727
762
 
763
+ def max_complexity_count_introspection_fields
764
+ if defined?(@max_complexity_count_introspection_fields)
765
+ @max_complexity_count_introspection_fields
766
+ else
767
+ find_inherited_value(:max_complexity_count_introspection_fields, true)
768
+ end
769
+ end
770
+
728
771
  attr_writer :analysis_engine
729
772
 
730
773
  def analysis_engine
@@ -743,6 +786,7 @@ module GraphQL
743
786
 
744
787
  def error_bubbling(new_error_bubbling = nil)
745
788
  if !new_error_bubbling.nil?
789
+ warn("error_bubbling(#{new_error_bubbling.inspect}) is deprecated; the default value of `false` will be the only option in GraphQL-Ruby 3.0")
746
790
  @error_bubbling = new_error_bubbling
747
791
  else
748
792
  @error_bubbling.nil? ? find_inherited_value(:error_bubbling) : @error_bubbling
@@ -814,9 +858,40 @@ module GraphQL
814
858
  end
815
859
  end
816
860
 
861
+ # @param new_extra_types [Module] Type definitions to include in printing and introspection, even though they aren't referenced in the schema
862
+ # @return [Array<Module>] Type definitions added to this schema
863
+ def extra_types(*new_extra_types)
864
+ if new_extra_types.any?
865
+ new_extra_types = new_extra_types.flatten
866
+ @own_extra_types ||= []
867
+ @own_extra_types.concat(new_extra_types)
868
+ end
869
+ inherited_et = find_inherited_value(:extra_types, nil)
870
+ if inherited_et
871
+ if @own_extra_types
872
+ inherited_et + @own_extra_types
873
+ else
874
+ inherited_et
875
+ end
876
+ else
877
+ @own_extra_types || EMPTY_ARRAY
878
+ end
879
+ end
880
+
817
881
  def orphan_types(*new_orphan_types)
818
882
  if new_orphan_types.any?
819
883
  new_orphan_types = new_orphan_types.flatten
884
+ non_object_types = new_orphan_types.reject { |ot| ot.is_a?(Class) && ot < GraphQL::Schema::Object }
885
+ if non_object_types.any?
886
+ raise ArgumentError, <<~ERR
887
+ Only object type classes should be added as `orphan_types(...)`.
888
+
889
+ - Remove these no-op types from `orphan_types`: #{non_object_types.map { |t| "#{t.inspect} (#{t.kind.name})"}.join(", ")}
890
+ - See https://graphql-ruby.org/type_definitions/interfaces.html#orphan-types
891
+
892
+ To add other types to your schema, you might want `extra_types`: https://graphql-ruby.org/schema/definition.html#extra-types
893
+ ERR
894
+ end
820
895
  add_type_and_traverse(new_orphan_types, root: false)
821
896
  own_orphan_types.concat(new_orphan_types.flatten)
822
897
  end
@@ -1044,6 +1119,12 @@ module GraphQL
1044
1119
  end
1045
1120
 
1046
1121
  def instrument(instrument_step, instrumenter, options = {})
1122
+ warn <<~WARN
1123
+ Schema.instrument is deprecated, use `trace_with` instead: https://graphql-ruby.org/queries/tracing.html"
1124
+ (From `#{self}.instrument(#{instrument_step}, #{instrumenter})` at #{caller(1, 1).first})
1125
+
1126
+ WARN
1127
+ trace_with(Tracing::LegacyHooksTrace)
1047
1128
  own_instrumenters[instrument_step] << instrumenter
1048
1129
  end
1049
1130
 
@@ -1079,8 +1160,12 @@ module GraphQL
1079
1160
  }.freeze
1080
1161
  end
1081
1162
 
1082
- def tracer(new_tracer)
1083
- default_trace = trace_class_for(:default)
1163
+ def tracer(new_tracer, silence_deprecation_warning: false)
1164
+ if !silence_deprecation_warning
1165
+ warn("`Schema.tracer(#{new_tracer.inspect})` is deprecated; use module-based `trace_with` instead. See: https://graphql-ruby.org/queries/tracing.html")
1166
+ warn " #{caller(1, 1).first}"
1167
+ end
1168
+ default_trace = trace_class_for(:default, build: true)
1084
1169
  if default_trace.nil? || !(default_trace < GraphQL::Tracing::CallLegacyTracers)
1085
1170
  trace_with(GraphQL::Tracing::CallLegacyTracers)
1086
1171
  end
@@ -1106,20 +1191,23 @@ module GraphQL
1106
1191
  tc = own_trace_modes[mode] ||= build_trace_mode(mode)
1107
1192
  tc.include(trace_mod)
1108
1193
  own_trace_modules[mode] << trace_mod
1109
-
1194
+ add_trace_options_for(mode, options)
1110
1195
  if mode == :default
1111
1196
  # This module is being added as a default tracer. If any other mode classes
1112
1197
  # have already been created, but get their default behavior from a superclass,
1113
1198
  # Then mix this into this schema's subclass.
1114
1199
  # (But don't mix it into mode classes that aren't default-based.)
1115
1200
  own_trace_modes.each do |other_mode_name, other_mode_class|
1116
- if other_mode_class < DefaultTraceClass && !(other_mode_class < trace_mod)
1117
- other_mode_class.include(trace_mod)
1201
+ if other_mode_class < DefaultTraceClass
1202
+ # Don't add it back to the inheritance tree if it's already there
1203
+ if !(other_mode_class < trace_mod)
1204
+ other_mode_class.include(trace_mod)
1205
+ end
1206
+ # Add any options so they'll be available
1207
+ add_trace_options_for(other_mode_name, options)
1118
1208
  end
1119
1209
  end
1120
1210
  end
1121
- t_opts = trace_options_for(mode)
1122
- t_opts.merge!(options)
1123
1211
  end
1124
1212
  nil
1125
1213
  end
@@ -1129,10 +1217,14 @@ module GraphQL
1129
1217
  def trace_options_for(mode)
1130
1218
  @trace_options_for_mode ||= {}
1131
1219
  @trace_options_for_mode[mode] ||= begin
1220
+ # It may be time to create an options hash for a mode that wasn't registered yet.
1221
+ # Mix in the default options in that case.
1222
+ default_options = mode == :default ? EMPTY_HASH : trace_options_for(:default)
1223
+ # Make sure this returns a new object so that other hashes aren't modified later
1132
1224
  if superclass.respond_to?(:trace_options_for)
1133
- superclass.trace_options_for(mode).dup
1225
+ superclass.trace_options_for(mode).merge(default_options)
1134
1226
  else
1135
- {}
1227
+ default_options.dup
1136
1228
  end
1137
1229
  end
1138
1230
  end
@@ -1155,15 +1247,17 @@ module GraphQL
1155
1247
  raise ArgumentError, "Can't use `context[:backtrace]` with a custom default trace mode (`#{dm.inspect}`)"
1156
1248
  else
1157
1249
  own_trace_modes[:default_backtrace] ||= build_trace_mode(:default_backtrace)
1250
+ options_trace_mode = :default
1158
1251
  :default_backtrace
1159
1252
  end
1160
1253
  else
1161
1254
  default_trace_mode
1162
1255
  end
1163
1256
 
1164
- base_trace_options = trace_options_for(trace_mode)
1257
+ options_trace_mode ||= trace_mode
1258
+ base_trace_options = trace_options_for(options_trace_mode)
1165
1259
  trace_options = base_trace_options.merge(options)
1166
- trace_class_for_mode = trace_class_for(trace_mode) || raise(ArgumentError, "#{self} has no trace class for mode: #{trace_mode.inspect}")
1260
+ trace_class_for_mode = trace_class_for(trace_mode, build: true)
1167
1261
  trace_class_for_mode.new(**trace_options)
1168
1262
  end
1169
1263
 
@@ -1317,6 +1411,12 @@ module GraphQL
1317
1411
 
1318
1412
  private
1319
1413
 
1414
+ def add_trace_options_for(mode, new_options)
1415
+ t_opts = trace_options_for(mode)
1416
+ t_opts.merge!(new_options)
1417
+ nil
1418
+ end
1419
+
1320
1420
  # @param t [Module, Array<Module>]
1321
1421
  # @return [void]
1322
1422
  def add_type_and_traverse(t, root:)
@@ -1378,7 +1478,7 @@ module GraphQL
1378
1478
  else
1379
1479
  @lazy_methods = GraphQL::Execution::Lazy::LazyMethodMap.new
1380
1480
  @lazy_methods.set(GraphQL::Execution::Lazy, :value)
1381
- @lazy_methods.set(GraphQL::Dataloader::Request, :load)
1481
+ @lazy_methods.set(GraphQL::Dataloader::Request, :load_with_deprecation_warning)
1382
1482
  end
1383
1483
  end
1384
1484
  @lazy_methods
@@ -110,7 +110,7 @@ module GraphQL
110
110
  # TODO - would be nice to use these to create an error message so the caller knows
111
111
  # that required fields are missing
112
112
  required_field_names = @warden.arguments(type)
113
- .select { |argument| argument.type.kind.non_null? && @warden.get_argument(type, argument.name) }
113
+ .select { |argument| argument.type.kind.non_null? && !argument.default_value? }
114
114
  .map!(&:name)
115
115
 
116
116
  present_field_names = ast_node.arguments.map(&:name)
@@ -122,7 +122,6 @@ module GraphQL
122
122
  arg_type = @warden.get_argument(type, name).type
123
123
  recursively_validate(GraphQL::Language::Nodes::NullValue.new(name: name), arg_type)
124
124
  end
125
-
126
125
  if type.one_of? && ast_node.arguments.size != 1
127
126
  results << Query::InputValidationResult.from_problem("`#{type.graphql_name}` is a OneOf type, so only one argument may be given (instead of #{ast_node.arguments.size})")
128
127
  end
@@ -35,7 +35,7 @@ module GraphQL
35
35
  return unless parent_type && parent_type.kind.input_object?
36
36
 
37
37
  required_fields = context.warden.arguments(parent_type)
38
- .select{|arg| arg.type.kind.non_null?}
38
+ .select{ |arg| arg.type.kind.non_null? && !arg.default_value? }
39
39
  .map!(&:graphql_name)
40
40
 
41
41
  present_fields = ast_node.arguments.map(&:name)