graphql 2.2.5 → 2.3.2

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