graphql 2.2.5 → 2.3.2

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 (55) 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/visitor.rb +8 -0
  5. data/lib/graphql/analysis/ast.rb +10 -1
  6. data/lib/graphql/backtrace/inspect_result.rb +0 -12
  7. data/lib/graphql/coercion_error.rb +1 -9
  8. data/lib/graphql/dataloader/request.rb +5 -0
  9. data/lib/graphql/execution/interpreter/argument_value.rb +5 -1
  10. data/lib/graphql/execution/interpreter/runtime.rb +9 -0
  11. data/lib/graphql/execution/interpreter.rb +90 -150
  12. data/lib/graphql/introspection/entry_points.rb +9 -3
  13. data/lib/graphql/introspection/schema_type.rb +3 -1
  14. data/lib/graphql/language/document_from_schema_definition.rb +2 -3
  15. data/lib/graphql/language/lexer.rb +48 -30
  16. data/lib/graphql/language/nodes.rb +2 -1
  17. data/lib/graphql/language/parser.rb +25 -11
  18. data/lib/graphql/language/printer.rb +4 -0
  19. data/lib/graphql/language.rb +60 -0
  20. data/lib/graphql/pagination/array_connection.rb +6 -6
  21. data/lib/graphql/query/context.rb +30 -33
  22. data/lib/graphql/query/validation_pipeline.rb +2 -2
  23. data/lib/graphql/query/variables.rb +3 -3
  24. data/lib/graphql/query.rb +3 -3
  25. data/lib/graphql/schema/argument.rb +18 -2
  26. data/lib/graphql/schema/base_64_encoder.rb +3 -5
  27. data/lib/graphql/schema/build_from_definition.rb +9 -1
  28. data/lib/graphql/schema/field.rb +33 -30
  29. data/lib/graphql/schema/input_object.rb +1 -2
  30. data/lib/graphql/schema/interface.rb +5 -1
  31. data/lib/graphql/schema/loader.rb +2 -1
  32. data/lib/graphql/schema/member/has_arguments.rb +2 -2
  33. data/lib/graphql/schema/mutation.rb +7 -0
  34. data/lib/graphql/schema/resolver.rb +19 -10
  35. data/lib/graphql/schema/unique_within_type.rb +1 -1
  36. data/lib/graphql/schema.rb +119 -28
  37. data/lib/graphql/static_validation/literal_validator.rb +1 -2
  38. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +1 -1
  39. data/lib/graphql/static_validation/validator.rb +3 -0
  40. data/lib/graphql/subscriptions/serialize.rb +2 -0
  41. data/lib/graphql/subscriptions.rb +0 -3
  42. data/lib/graphql/testing/helpers.rb +32 -6
  43. data/lib/graphql/tracing/data_dog_trace.rb +21 -34
  44. data/lib/graphql/tracing/data_dog_tracing.rb +7 -21
  45. data/lib/graphql/tracing/legacy_hooks_trace.rb +74 -0
  46. data/lib/graphql/tracing/platform_tracing.rb +3 -1
  47. data/lib/graphql/tracing/{prometheus_tracing → prometheus_trace}/graphql_collector.rb +3 -1
  48. data/lib/graphql/tracing/prometheus_trace.rb +2 -2
  49. data/lib/graphql/tracing/sentry_trace.rb +112 -0
  50. data/lib/graphql/tracing.rb +3 -1
  51. data/lib/graphql/version.rb +1 -1
  52. data/lib/graphql.rb +10 -2
  53. metadata +38 -23
  54. data/lib/graphql/schema/base_64_bp.rb +0 -26
  55. data/lib/graphql/subscriptions/instrumentation.rb +0 -28
@@ -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
 
@@ -743,6 +777,7 @@ module GraphQL
743
777
 
744
778
  def error_bubbling(new_error_bubbling = nil)
745
779
  if !new_error_bubbling.nil?
780
+ 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
781
  @error_bubbling = new_error_bubbling
747
782
  else
748
783
  @error_bubbling.nil? ? find_inherited_value(:error_bubbling) : @error_bubbling
@@ -814,9 +849,40 @@ module GraphQL
814
849
  end
815
850
  end
816
851
 
852
+ # @param new_extra_types [Module] Type definitions to include in printing and introspection, even though they aren't referenced in the schema
853
+ # @return [Array<Module>] Type definitions added to this schema
854
+ def extra_types(*new_extra_types)
855
+ if new_extra_types.any?
856
+ new_extra_types = new_extra_types.flatten
857
+ @own_extra_types ||= []
858
+ @own_extra_types.concat(new_extra_types)
859
+ end
860
+ inherited_et = find_inherited_value(:extra_types, nil)
861
+ if inherited_et
862
+ if @own_extra_types
863
+ inherited_et + @own_extra_types
864
+ else
865
+ inherited_et
866
+ end
867
+ else
868
+ @own_extra_types || EMPTY_ARRAY
869
+ end
870
+ end
871
+
817
872
  def orphan_types(*new_orphan_types)
818
873
  if new_orphan_types.any?
819
874
  new_orphan_types = new_orphan_types.flatten
875
+ non_object_types = new_orphan_types.reject { |ot| ot.is_a?(Class) && ot < GraphQL::Schema::Object }
876
+ if non_object_types.any?
877
+ raise ArgumentError, <<~ERR
878
+ Only object type classes should be added as `orphan_types(...)`.
879
+
880
+ - Remove these no-op types from `orphan_types`: #{non_object_types.map { |t| "#{t.inspect} (#{t.kind.name})"}.join(", ")}
881
+ - See https://graphql-ruby.org/type_definitions/interfaces.html#orphan-types
882
+
883
+ To add other types to your schema, you might want `extra_types`: https://graphql-ruby.org/schema/definition.html#extra-types
884
+ ERR
885
+ end
820
886
  add_type_and_traverse(new_orphan_types, root: false)
821
887
  own_orphan_types.concat(new_orphan_types.flatten)
822
888
  end
@@ -1044,6 +1110,12 @@ module GraphQL
1044
1110
  end
1045
1111
 
1046
1112
  def instrument(instrument_step, instrumenter, options = {})
1113
+ warn <<~WARN
1114
+ Schema.instrument is deprecated, use `trace_with` instead: https://graphql-ruby.org/queries/tracing.html"
1115
+ (From `#{self}.instrument(#{instrument_step}, #{instrumenter})` at #{caller(1, 1).first})
1116
+
1117
+ WARN
1118
+ trace_with(Tracing::LegacyHooksTrace)
1047
1119
  own_instrumenters[instrument_step] << instrumenter
1048
1120
  end
1049
1121
 
@@ -1079,8 +1151,12 @@ module GraphQL
1079
1151
  }.freeze
1080
1152
  end
1081
1153
 
1082
- def tracer(new_tracer)
1083
- default_trace = trace_class_for(:default)
1154
+ def tracer(new_tracer, silence_deprecation_warning: false)
1155
+ if !silence_deprecation_warning
1156
+ warn("`Schema.tracer(#{new_tracer.inspect})` is deprecated; use module-based `trace_with` instead. See: https://graphql-ruby.org/queries/tracing.html")
1157
+ warn " #{caller(1, 1).first}"
1158
+ end
1159
+ default_trace = trace_class_for(:default, build: true)
1084
1160
  if default_trace.nil? || !(default_trace < GraphQL::Tracing::CallLegacyTracers)
1085
1161
  trace_with(GraphQL::Tracing::CallLegacyTracers)
1086
1162
  end
@@ -1106,20 +1182,23 @@ module GraphQL
1106
1182
  tc = own_trace_modes[mode] ||= build_trace_mode(mode)
1107
1183
  tc.include(trace_mod)
1108
1184
  own_trace_modules[mode] << trace_mod
1109
-
1185
+ add_trace_options_for(mode, options)
1110
1186
  if mode == :default
1111
1187
  # This module is being added as a default tracer. If any other mode classes
1112
1188
  # have already been created, but get their default behavior from a superclass,
1113
1189
  # Then mix this into this schema's subclass.
1114
1190
  # (But don't mix it into mode classes that aren't default-based.)
1115
1191
  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)
1192
+ if other_mode_class < DefaultTraceClass
1193
+ # Don't add it back to the inheritance tree if it's already there
1194
+ if !(other_mode_class < trace_mod)
1195
+ other_mode_class.include(trace_mod)
1196
+ end
1197
+ # Add any options so they'll be available
1198
+ add_trace_options_for(other_mode_name, options)
1118
1199
  end
1119
1200
  end
1120
1201
  end
1121
- t_opts = trace_options_for(mode)
1122
- t_opts.merge!(options)
1123
1202
  end
1124
1203
  nil
1125
1204
  end
@@ -1129,10 +1208,14 @@ module GraphQL
1129
1208
  def trace_options_for(mode)
1130
1209
  @trace_options_for_mode ||= {}
1131
1210
  @trace_options_for_mode[mode] ||= begin
1211
+ # It may be time to create an options hash for a mode that wasn't registered yet.
1212
+ # Mix in the default options in that case.
1213
+ default_options = mode == :default ? EMPTY_HASH : trace_options_for(:default)
1214
+ # Make sure this returns a new object so that other hashes aren't modified later
1132
1215
  if superclass.respond_to?(:trace_options_for)
1133
- superclass.trace_options_for(mode).dup
1216
+ superclass.trace_options_for(mode).merge(default_options)
1134
1217
  else
1135
- {}
1218
+ default_options.dup
1136
1219
  end
1137
1220
  end
1138
1221
  end
@@ -1155,15 +1238,17 @@ module GraphQL
1155
1238
  raise ArgumentError, "Can't use `context[:backtrace]` with a custom default trace mode (`#{dm.inspect}`)"
1156
1239
  else
1157
1240
  own_trace_modes[:default_backtrace] ||= build_trace_mode(:default_backtrace)
1241
+ options_trace_mode = :default
1158
1242
  :default_backtrace
1159
1243
  end
1160
1244
  else
1161
1245
  default_trace_mode
1162
1246
  end
1163
1247
 
1164
- base_trace_options = trace_options_for(trace_mode)
1248
+ options_trace_mode ||= trace_mode
1249
+ base_trace_options = trace_options_for(options_trace_mode)
1165
1250
  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}")
1251
+ trace_class_for_mode = trace_class_for(trace_mode, build: true)
1167
1252
  trace_class_for_mode.new(**trace_options)
1168
1253
  end
1169
1254
 
@@ -1317,6 +1402,12 @@ module GraphQL
1317
1402
 
1318
1403
  private
1319
1404
 
1405
+ def add_trace_options_for(mode, new_options)
1406
+ t_opts = trace_options_for(mode)
1407
+ t_opts.merge!(new_options)
1408
+ nil
1409
+ end
1410
+
1320
1411
  # @param t [Module, Array<Module>]
1321
1412
  # @return [void]
1322
1413
  def add_type_and_traverse(t, root:)
@@ -1378,7 +1469,7 @@ module GraphQL
1378
1469
  else
1379
1470
  @lazy_methods = GraphQL::Execution::Lazy::LazyMethodMap.new
1380
1471
  @lazy_methods.set(GraphQL::Execution::Lazy, :value)
1381
- @lazy_methods.set(GraphQL::Dataloader::Request, :load)
1472
+ @lazy_methods.set(GraphQL::Dataloader::Request, :load_with_deprecation_warning)
1382
1473
  end
1383
1474
  end
1384
1475
  @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)
@@ -28,6 +28,7 @@ module GraphQL
28
28
  # @return [Array<Hash>]
29
29
  def validate(query, validate: true, timeout: nil, max_errors: nil)
30
30
  query.current_trace.validate(validate: validate, query: query) do
31
+ begin_t = Time.now
31
32
  errors = if validate == false
32
33
  []
33
34
  else
@@ -52,11 +53,13 @@ module GraphQL
52
53
  end
53
54
 
54
55
  {
56
+ remaining_timeout: timeout ? (timeout - (Time.now - begin_t)) : nil,
55
57
  errors: errors,
56
58
  }
57
59
  end
58
60
  rescue GraphQL::ExecutionError => e
59
61
  {
62
+ remaining_timeout: nil,
60
63
  errors: [e],
61
64
  }
62
65
  end
@@ -148,6 +148,8 @@ module GraphQL
148
148
  { TIMESTAMP_KEY => [obj.class.name, obj.strftime(TIMESTAMP_FORMAT)] }
149
149
  elsif obj.is_a?(OpenStruct)
150
150
  { OPEN_STRUCT_KEY => dump_value(obj.to_h) }
151
+ elsif defined?(ActiveRecord::Relation) && obj.is_a?(ActiveRecord::Relation)
152
+ dump_value(obj.to_a)
151
153
  else
152
154
  obj
153
155
  end
@@ -2,7 +2,6 @@
2
2
  require "securerandom"
3
3
  require "graphql/subscriptions/broadcast_analyzer"
4
4
  require "graphql/subscriptions/event"
5
- require "graphql/subscriptions/instrumentation"
6
5
  require "graphql/subscriptions/serialize"
7
6
  require "graphql/subscriptions/action_cable_subscriptions"
8
7
  require "graphql/subscriptions/default_subscription_resolve_extension"
@@ -30,8 +29,6 @@ module GraphQL
30
29
  raise ArgumentError, "Can't reinstall subscriptions. #{schema} is using #{schema.subscriptions}, can't also add #{self}"
31
30
  end
32
31
 
33
- instrumentation = Subscriptions::Instrumentation.new(schema: schema)
34
- defn.instrument(:query, instrumentation)
35
32
  options[:schema] = schema
36
33
  schema.subscriptions = self.new(**options)
37
34
  schema.add_subscription_extension_if_necessary
@@ -5,10 +5,7 @@ module GraphQL
5
5
  # @param schema_class [Class<GraphQL::Schema>]
6
6
  # @return [Module] A helpers module which always uses the given schema
7
7
  def self.for(schema_class)
8
- Module.new do
9
- include SchemaHelpers
10
- @@schema_class_for_helpers = schema_class
11
- end
8
+ SchemaHelpers.for(schema_class)
12
9
  end
13
10
 
14
11
  class Error < GraphQL::Error
@@ -42,9 +39,9 @@ module GraphQL
42
39
  end
43
40
  end
44
41
 
45
- def run_graphql_field(schema, field_path, object, arguments: {}, context: {})
42
+ def run_graphql_field(schema, field_path, object, arguments: {}, context: {}, ast_node: nil, lookahead: nil)
46
43
  type_name, *field_names = field_path.split(".")
47
- dummy_query = GraphQL::Query.new(schema, context: context)
44
+ dummy_query = GraphQL::Query.new(schema, "{ __typename }", context: context)
48
45
  query_context = dummy_query.context
49
46
  object_type = dummy_query.get_type(type_name) # rubocop:disable Development/ContextIsPassedCop
50
47
  if object_type
@@ -60,6 +57,28 @@ module GraphQL
60
57
  dummy_query.context.dataloader.run_isolated {
61
58
  field_args = visible_field.coerce_arguments(graphql_result, arguments, query_context)
62
59
  field_args = schema.sync_lazy(field_args)
60
+ if visible_field.extras.any?
61
+ extra_args = {}
62
+ visible_field.extras.each do |extra|
63
+ extra_args[extra] = case extra
64
+ when :ast_node
65
+ ast_node ||= GraphQL::Language::Nodes::Field.new(name: visible_field.graphql_name)
66
+ when :lookahead
67
+ lookahead ||= begin
68
+ ast_node ||= GraphQL::Language::Nodes::Field.new(name: visible_field.graphql_name)
69
+ Execution::Lookahead.new(
70
+ query: dummy_query,
71
+ ast_nodes: [ast_node],
72
+ field: visible_field,
73
+ )
74
+ end
75
+ else
76
+ raise ArgumentError, "This extra isn't supported in `run_graphql_field` yet: `#{extra.inspect}`. Open an issue on GitHub to request it: https://github.com/rmosolgo/graphql-ruby/issues/new"
77
+ end
78
+ end
79
+
80
+ field_args = field_args.merge_extras(extra_args)
81
+ end
63
82
  graphql_result = visible_field.resolve(graphql_result, field_args.keyword_arguments, query_context)
64
83
  graphql_result = schema.sync_lazy(graphql_result)
65
84
  }
@@ -119,6 +138,13 @@ module GraphQL
119
138
  # schema will be added later
120
139
  super(nil, *args, **kwargs, &block)
121
140
  end
141
+
142
+ def self.for(schema_class)
143
+ Module.new do
144
+ include SchemaHelpers
145
+ @@schema_class_for_helpers = schema_class
146
+ end
147
+ end
122
148
  end
123
149
  end
124
150
  end