graphql 2.2.5 → 2.3.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 (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)