graphql 2.1.0 → 2.1.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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install/templates/base_mutation.erb +2 -0
  3. data/lib/generators/graphql/install/templates/mutation_type.erb +2 -0
  4. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  5. data/lib/generators/graphql/templates/base_connection.erb +2 -0
  6. data/lib/generators/graphql/templates/base_edge.erb +2 -0
  7. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  8. data/lib/generators/graphql/templates/base_field.erb +2 -0
  9. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  10. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  11. data/lib/generators/graphql/templates/base_object.erb +2 -0
  12. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  13. data/lib/generators/graphql/templates/base_union.erb +2 -0
  14. data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
  15. data/lib/generators/graphql/templates/loader.erb +2 -0
  16. data/lib/generators/graphql/templates/mutation.erb +2 -0
  17. data/lib/generators/graphql/templates/node_type.erb +2 -0
  18. data/lib/generators/graphql/templates/query_type.erb +2 -0
  19. data/lib/generators/graphql/templates/schema.erb +2 -0
  20. data/lib/graphql/analysis/ast/analyzer.rb +7 -0
  21. data/lib/graphql/analysis/ast/query_depth.rb +7 -2
  22. data/lib/graphql/analysis/ast/visitor.rb +2 -2
  23. data/lib/graphql/analysis/ast.rb +15 -11
  24. data/lib/graphql/dataloader/source.rb +7 -0
  25. data/lib/graphql/dataloader.rb +38 -10
  26. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +170 -0
  27. data/lib/graphql/execution/interpreter/runtime.rb +90 -251
  28. data/lib/graphql/execution/interpreter.rb +0 -6
  29. data/lib/graphql/execution/lookahead.rb +1 -1
  30. data/lib/graphql/introspection/dynamic_fields.rb +1 -1
  31. data/lib/graphql/introspection/entry_points.rb +2 -2
  32. data/lib/graphql/language/block_string.rb +28 -16
  33. data/lib/graphql/language/definition_slice.rb +1 -1
  34. data/lib/graphql/language/document_from_schema_definition.rb +30 -19
  35. data/lib/graphql/language/nodes.rb +1 -1
  36. data/lib/graphql/language/printer.rb +88 -27
  37. data/lib/graphql/language/sanitized_printer.rb +6 -1
  38. data/lib/graphql/language/static_visitor.rb +167 -0
  39. data/lib/graphql/language/visitor.rb +2 -0
  40. data/lib/graphql/language.rb +1 -0
  41. data/lib/graphql/query/context/scoped_context.rb +101 -0
  42. data/lib/graphql/query/context.rb +32 -98
  43. data/lib/graphql/schema/directive/specified_by.rb +14 -0
  44. data/lib/graphql/schema/field/connection_extension.rb +1 -15
  45. data/lib/graphql/schema/field.rb +7 -4
  46. data/lib/graphql/schema/has_single_input_argument.rb +156 -0
  47. data/lib/graphql/schema/introspection_system.rb +2 -0
  48. data/lib/graphql/schema/member/base_dsl_methods.rb +2 -1
  49. data/lib/graphql/schema/member/has_arguments.rb +19 -4
  50. data/lib/graphql/schema/member/has_fields.rb +4 -1
  51. data/lib/graphql/schema/member/has_interfaces.rb +21 -7
  52. data/lib/graphql/schema/relay_classic_mutation.rb +6 -128
  53. data/lib/graphql/schema/resolver.rb +4 -0
  54. data/lib/graphql/schema/scalar.rb +3 -3
  55. data/lib/graphql/schema/warden.rb +84 -55
  56. data/lib/graphql/schema.rb +123 -36
  57. data/lib/graphql/static_validation/all_rules.rb +1 -1
  58. data/lib/graphql/static_validation/base_visitor.rb +1 -1
  59. data/lib/graphql/static_validation/literal_validator.rb +1 -1
  60. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
  61. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  62. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +1 -1
  63. data/lib/graphql/static_validation/validation_context.rb +5 -2
  64. data/lib/graphql/tracing/appoptics_trace.rb +2 -2
  65. data/lib/graphql/tracing/appoptics_tracing.rb +2 -2
  66. data/lib/graphql/version.rb +1 -1
  67. data/lib/graphql.rb +1 -1
  68. metadata +9 -4
@@ -21,6 +21,10 @@ module GraphQL
21
21
  # @see {GraphQL::Schema::Mutation} for an example, it's basically the same.
22
22
  #
23
23
  class RelayClassicMutation < GraphQL::Schema::Mutation
24
+ include GraphQL::Schema::HasSingleInputArgument
25
+
26
+ argument :client_mutation_id, String, "A unique identifier for the client performing the mutation.", required: false
27
+
24
28
  # The payload should always include this field
25
29
  field(:client_mutation_id, String, "A unique identifier for the client performing the mutation.")
26
30
  # Relay classic default:
@@ -31,34 +35,14 @@ module GraphQL
31
35
  def resolve_with_support(**inputs)
32
36
  input = inputs[:input].to_kwargs
33
37
 
34
- new_extras = field ? field.extras : []
35
- all_extras = self.class.extras + new_extras
36
-
37
- # Transfer these from the top-level hash to the
38
- # shortcutted `input:` object
39
- all_extras.each do |ext|
40
- # It's possible that the `extra` was not passed along by this point,
41
- # don't re-add it if it wasn't given here.
42
- if inputs.key?(ext)
43
- input[ext] = inputs[ext]
44
- end
45
- end
46
-
47
38
  if input
48
39
  # This is handled by Relay::Mutation::Resolve, a bit hacky, but here we are.
49
40
  input_kwargs = input.to_h
50
41
  client_mutation_id = input_kwargs.delete(:client_mutation_id)
51
- else
52
- # Relay Classic Mutations with no `argument`s
53
- # don't require `input:`
54
- input_kwargs = {}
42
+ inputs[:input] = input_kwargs
55
43
  end
56
44
 
57
- return_value = if input_kwargs.any?
58
- super(**input_kwargs)
59
- else
60
- super()
61
- end
45
+ return_value = super(**inputs)
62
46
 
63
47
  context.query.after_lazy(return_value) do |return_hash|
64
48
  # It might be an error
@@ -68,112 +52,6 @@ module GraphQL
68
52
  return_hash
69
53
  end
70
54
  end
71
-
72
- class << self
73
- def dummy
74
- @dummy ||= begin
75
- d = Class.new(GraphQL::Schema::Resolver)
76
- d.argument_class(self.argument_class)
77
- # TODO make this lazier?
78
- d.argument(:input, input_type, description: "Parameters for #{self.graphql_name}")
79
- d
80
- end
81
- end
82
-
83
- def field_arguments(context = GraphQL::Query::NullContext)
84
- dummy.arguments(context)
85
- end
86
-
87
- def get_field_argument(name, context = GraphQL::Query::NullContext)
88
- dummy.get_argument(name, context)
89
- end
90
-
91
- def own_field_arguments
92
- dummy.own_arguments
93
- end
94
-
95
- def all_field_argument_definitions
96
- dummy.all_argument_definitions
97
- end
98
-
99
- # Also apply this argument to the input type:
100
- def argument(*args, own_argument: false, **kwargs, &block)
101
- it = input_type # make sure any inherited arguments are already added to it
102
- arg = super(*args, **kwargs, &block)
103
-
104
- # This definition might be overriding something inherited;
105
- # if it is, remove the inherited definition so it's not confused at runtime as having multiple definitions
106
- prev_args = it.own_arguments[arg.graphql_name]
107
- case prev_args
108
- when GraphQL::Schema::Argument
109
- if prev_args.owner != self
110
- it.own_arguments.delete(arg.graphql_name)
111
- end
112
- when Array
113
- prev_args.reject! { |a| a.owner != self }
114
- if prev_args.empty?
115
- it.own_arguments.delete(arg.graphql_name)
116
- end
117
- end
118
-
119
- it.add_argument(arg)
120
- arg
121
- end
122
-
123
- # The base class for generated input object types
124
- # @param new_class [Class] The base class to use for generating input object definitions
125
- # @return [Class] The base class for this mutation's generated input object (default is {GraphQL::Schema::InputObject})
126
- def input_object_class(new_class = nil)
127
- if new_class
128
- @input_object_class = new_class
129
- end
130
- @input_object_class || (superclass.respond_to?(:input_object_class) ? superclass.input_object_class : GraphQL::Schema::InputObject)
131
- end
132
-
133
- # @param new_input_type [Class, nil] If provided, it configures this mutation to accept `new_input_type` instead of generating an input type
134
- # @return [Class] The generated {Schema::InputObject} class for this mutation's `input`
135
- def input_type(new_input_type = nil)
136
- if new_input_type
137
- @input_type = new_input_type
138
- end
139
- @input_type ||= generate_input_type
140
- end
141
-
142
- private
143
-
144
- # Generate the input type for the `input:` argument
145
- # To customize how input objects are generated, override this method
146
- # @return [Class] a subclass of {.input_object_class}
147
- def generate_input_type
148
- mutation_args = all_argument_definitions
149
- mutation_class = self
150
- Class.new(input_object_class) do
151
- class << self
152
- def default_graphql_name
153
- "#{self.mutation.graphql_name}Input"
154
- end
155
-
156
- def description(new_desc = nil)
157
- super || "Autogenerated input type of #{self.mutation.graphql_name}"
158
- end
159
- end
160
- mutation(mutation_class)
161
- # these might be inherited:
162
- mutation_args.each do |arg|
163
- add_argument(arg)
164
- end
165
- argument :client_mutation_id, String, "A unique identifier for the client performing the mutation.", required: false
166
- end
167
- end
168
- end
169
-
170
- private
171
-
172
- def authorize_arguments(args, values)
173
- # remove the `input` wrapper to match values
174
- input_args = args["input"].type.unwrap.arguments(context)
175
- super(input_args, values)
176
- end
177
55
  end
178
56
  end
179
57
  end
@@ -214,6 +214,10 @@ module GraphQL
214
214
  arguments(context)
215
215
  end
216
216
 
217
+ def any_field_arguments?
218
+ any_arguments?
219
+ end
220
+
217
221
  def get_field_argument(name, context = GraphQL::Query::NullContext)
218
222
  get_argument(name, context)
219
223
  end
@@ -19,9 +19,9 @@ module GraphQL
19
19
 
20
20
  def specified_by_url(new_url = nil)
21
21
  if new_url
22
- @specified_by_url = new_url
23
- elsif defined?(@specified_by_url)
24
- @specified_by_url
22
+ directive(GraphQL::Schema::Directive::SpecifiedBy, url: new_url)
23
+ elsif (directive = directives.find { |dir| dir.graphql_name == "specifiedBy" })
24
+ directive.arguments[:url] # rubocop:disable Development/ContextIsPassedCop
25
25
  elsif superclass.respond_to?(:specified_by_url)
26
26
  superclass.specified_by_url
27
27
  else
@@ -60,6 +60,7 @@ module GraphQL
60
60
  def visible_type_membership?(tm, ctx); tm.visible?(ctx); end
61
61
  def interface_type_memberships(obj_t, ctx); obj_t.interface_type_memberships; end
62
62
  def arguments(owner, ctx); owner.arguments(ctx); end
63
+ def loadable?(type, ctx); type.visible?(ctx); end
63
64
  end
64
65
  end
65
66
 
@@ -84,6 +85,7 @@ module GraphQL
84
85
  def fields(type_defn); type_defn.all_field_definitions; end # rubocop:disable Development/ContextIsPassedCop
85
86
  def get_field(parent_type, field_name); @schema.get_field(parent_type, field_name); end
86
87
  def reachable_type?(type_name); true; end
88
+ def loadable?(type, _ctx); true; end
87
89
  def reachable_types; @schema.types.values; end # rubocop:disable Development/ContextIsPassedCop
88
90
  def possible_types(type_defn); @schema.possible_types(type_defn); end
89
91
  def interfaces(obj_type); obj_type.interfaces; end
@@ -122,6 +124,11 @@ module GraphQL
122
124
  end
123
125
  end
124
126
 
127
+ # @return [Boolean] True if this type is used for `loads:` but not in the schema otherwise and not _explicitly_ hidden.
128
+ def loadable?(type, _ctx)
129
+ !reachable_type_set.include?(type) && visible_type?(type)
130
+ end
131
+
125
132
  # @return [GraphQL::BaseType, nil] The type named `type_name`, if it exists (else `nil`)
126
133
  def get_type(type_name)
127
134
  @visible_types ||= read_through do |name|
@@ -188,7 +195,16 @@ module GraphQL
188
195
  # @param argument_owner [GraphQL::Field, GraphQL::InputObjectType]
189
196
  # @return [Array<GraphQL::Argument>] Visible arguments on `argument_owner`
190
197
  def arguments(argument_owner, ctx = nil)
191
- @visible_arguments ||= read_through { |o| o.arguments(@context).each_value.select { |a| visible_argument?(a, @context) } }
198
+ @visible_arguments ||= read_through { |o|
199
+ args = o.arguments(@context)
200
+ if args.any?
201
+ args = args.values
202
+ args.select! { |a| visible_argument?(a, @context) }
203
+ args
204
+ else
205
+ EmptyObjects::EMPTY_ARRAY
206
+ end
207
+ }
192
208
  @visible_arguments[argument_owner]
193
209
  end
194
210
 
@@ -211,7 +227,13 @@ module GraphQL
211
227
 
212
228
  # @return [Array<GraphQL::InterfaceType>] Visible interfaces implemented by `obj_type`
213
229
  def interfaces(obj_type)
214
- @visible_interfaces ||= read_through { |t| t.interfaces(@context).select { |i| visible_type?(i) } }
230
+ @visible_interfaces ||= read_through { |t|
231
+ ints = t.interfaces(@context)
232
+ if ints.any?
233
+ ints.select! { |i| visible_type?(i) }
234
+ end
235
+ ints
236
+ }
215
237
  @visible_interfaces[obj_type]
216
238
  end
217
239
 
@@ -267,11 +289,19 @@ module GraphQL
267
289
  next true if root_type?(type_defn) || type_defn.introspection?
268
290
 
269
291
  if type_defn.kind.union?
270
- visible_possible_types?(type_defn) && (referenced?(type_defn) || orphan_type?(type_defn))
292
+ possible_types(type_defn).any? && (referenced?(type_defn) || orphan_type?(type_defn))
271
293
  elsif type_defn.kind.interface?
272
- visible_possible_types?(type_defn)
294
+ possible_types(type_defn).any?
273
295
  else
274
- referenced?(type_defn) || visible_abstract_type?(type_defn)
296
+ if referenced?(type_defn)
297
+ true
298
+ elsif type_defn.kind.object?
299
+ # Show this object if it belongs to ...
300
+ interfaces(type_defn).any? { |t| referenced?(t) } || # an interface which is referenced in the schema
301
+ union_memberships(type_defn).any? { |t| referenced?(t) || orphan_type?(t) } # or a union which is referenced or added via orphan_types
302
+ else
303
+ false
304
+ end
275
305
  end
276
306
  end
277
307
 
@@ -338,23 +368,14 @@ module GraphQL
338
368
  @schema.orphan_types.include?(type_defn)
339
369
  end
340
370
 
341
- def visible_abstract_type?(type_defn)
342
- type_defn.kind.object? && (
343
- interfaces(type_defn).any? ||
344
- union_memberships(type_defn).any?
345
- )
346
- end
347
-
348
- def visible_possible_types?(type_defn)
349
- possible_types(type_defn).any? { |t| visible_and_reachable_type?(t) }
350
- end
351
-
352
371
  def visible?(member)
353
372
  @visibility_cache[member]
354
373
  end
355
374
 
356
375
  def read_through
357
- Hash.new { |h, k| h[k] = yield(k) }
376
+ h = Hash.new { |h, k| h[k] = yield(k) }
377
+ h.compare_by_identity
378
+ h
358
379
  end
359
380
 
360
381
  def reachable_type_set
@@ -385,54 +406,62 @@ module GraphQL
385
406
  end
386
407
  end
387
408
 
409
+ included_interface_possible_types_set = Set.new
410
+
388
411
  until unvisited_types.empty?
389
412
  type = unvisited_types.pop
390
- if @reachable_type_set.add?(type)
391
- type_by_name = rt_hash[type.graphql_name] ||= type
392
- if type_by_name != type
393
- raise DuplicateNamesError.new(
394
- duplicated_name: type.graphql_name, duplicated_definition_1: type.inspect, duplicated_definition_2: type_by_name.inspect
395
- )
413
+ visit_type(type, unvisited_types, @reachable_type_set, rt_hash, included_interface_possible_types_set, include_interface_possible_types: false)
414
+ end
415
+
416
+ @reachable_type_set
417
+ end
418
+
419
+ def visit_type(type, unvisited_types, visited_type_set, type_by_name_hash, included_interface_possible_types_set, include_interface_possible_types:)
420
+ if visited_type_set.add?(type) || (include_interface_possible_types && type.kind.interface? && included_interface_possible_types_set.add?(type))
421
+ type_by_name = type_by_name_hash[type.graphql_name] ||= type
422
+ if type_by_name != type
423
+ name_1, name_2 = [type.inspect, type_by_name.inspect].sort
424
+ raise DuplicateNamesError.new(
425
+ duplicated_name: type.graphql_name, duplicated_definition_1: name_1, duplicated_definition_2: name_2
426
+ )
427
+ end
428
+ if type.kind.input_object?
429
+ # recurse into visible arguments
430
+ arguments(type).each do |argument|
431
+ argument_type = argument.type.unwrap
432
+ unvisited_types << argument_type
396
433
  end
397
- if type.kind.input_object?
398
- # recurse into visible arguments
399
- arguments(type).each do |argument|
400
- argument_type = argument.type.unwrap
401
- unvisited_types << argument_type
402
- end
403
- elsif type.kind.union?
404
- # recurse into visible possible types
405
- possible_types(type).each do |possible_type|
406
- unvisited_types << possible_type
434
+ elsif type.kind.union?
435
+ # recurse into visible possible types
436
+ possible_types(type).each do |possible_type|
437
+ unvisited_types << possible_type
438
+ end
439
+ elsif type.kind.fields?
440
+ if type.kind.object?
441
+ # recurse into visible implemented interfaces
442
+ interfaces(type).each do |interface|
443
+ unvisited_types << interface
407
444
  end
408
- elsif type.kind.fields?
409
- if type.kind.interface?
410
- # recurse into visible possible types
411
- possible_types(type).each do |possible_type|
412
- unvisited_types << possible_type
413
- end
414
- elsif type.kind.object?
415
- # recurse into visible implemented interfaces
416
- interfaces(type).each do |interface|
417
- unvisited_types << interface
418
- end
445
+ elsif include_interface_possible_types
446
+ possible_types(type).each do |pt|
447
+ unvisited_types << pt
419
448
  end
449
+ end
450
+ # Don't visit interface possible types -- it's not enough to justify visibility
420
451
 
421
- # recurse into visible fields
422
- fields(type).each do |field|
423
- field_type = field.type.unwrap
424
- unvisited_types << field_type
425
- # recurse into visible arguments
426
- arguments(field).each do |argument|
427
- argument_type = argument.type.unwrap
428
- unvisited_types << argument_type
429
- end
452
+ # recurse into visible fields
453
+ fields(type).each do |field|
454
+ field_type = field.type.unwrap
455
+ # In this case, if it's an interface, we want to include
456
+ visit_type(field_type, unvisited_types, visited_type_set, type_by_name_hash, included_interface_possible_types_set, include_interface_possible_types: true)
457
+ # recurse into visible arguments
458
+ arguments(field).each do |argument|
459
+ argument_type = argument.type.unwrap
460
+ unvisited_types << argument_type
430
461
  end
431
462
  end
432
463
  end
433
464
  end
434
-
435
- @reachable_type_set
436
465
  end
437
466
  end
438
467
  end
@@ -37,10 +37,12 @@ require "graphql/schema/directive/skip"
37
37
  require "graphql/schema/directive/feature"
38
38
  require "graphql/schema/directive/flagged"
39
39
  require "graphql/schema/directive/transform"
40
+ require "graphql/schema/directive/specified_by"
40
41
  require "graphql/schema/type_membership"
41
42
 
42
43
  require "graphql/schema/resolver"
43
44
  require "graphql/schema/mutation"
45
+ require "graphql/schema/has_single_input_argument"
44
46
  require "graphql/schema/relay_classic_mutation"
45
47
  require "graphql/schema/subscription"
46
48
 
@@ -144,6 +146,19 @@ module GraphQL
144
146
  @subscriptions = new_implementation
145
147
  end
146
148
 
149
+ # @param new_mode [Symbol] If configured, this will be used when `context: { trace_mode: ... }` isn't set.
150
+ def default_trace_mode(new_mode = nil)
151
+ if new_mode
152
+ @default_trace_mode = new_mode
153
+ elsif defined?(@default_trace_mode)
154
+ @default_trace_mode
155
+ elsif superclass.respond_to?(:default_trace_mode)
156
+ superclass.default_trace_mode
157
+ else
158
+ :default
159
+ end
160
+ end
161
+
147
162
  def trace_class(new_class = nil)
148
163
  if new_class
149
164
  trace_mode(:default, new_class)
@@ -155,42 +170,66 @@ module GraphQL
155
170
  end
156
171
 
157
172
  # @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.
158
- def trace_class_for(mode)
159
- @trace_modes ||= {}
160
- @trace_modes[mode] ||= begin
161
- case mode
162
- when :default
163
- superclass_base_class = if superclass.respond_to?(:trace_class_for)
164
- superclass.trace_class_for(mode)
165
- else
166
- GraphQL::Tracing::Trace
167
- end
168
- Class.new(superclass_base_class)
169
- when :default_backtrace
170
- schema_base_class = trace_class_for(:default)
171
- Class.new(schema_base_class) do
172
- include(GraphQL::Backtrace::Trace)
173
- end
174
- else
175
- mods = trace_modules_for(mode)
176
- Class.new(trace_class_for(:default)) do
177
- mods.any? && include(*mods)
178
- end
179
- end
180
- end
173
+ def trace_class_for(mode, build: false)
174
+ own_trace_modes[mode] ||
175
+ (superclass.respond_to?(:trace_class_for) ? superclass.trace_class_for(mode) : nil)
181
176
  end
182
177
 
183
178
  # Configure `trace_class` to be used whenever `context: { trace_mode: mode_name }` is requested.
184
- # `:default` is used when no `trace_mode: ...` is requested.
179
+ # {default_trace_mode} is used when no `trace_mode: ...` is requested.
180
+ #
181
+ # When a `trace_class` is added this way, it will _not_ receive other modules added with `trace_with(...)`
182
+ # unless `trace_mode` is explicitly given. (This class will not recieve any default trace modules.)
183
+ #
184
+ # Subclasses of the schema will use `trace_class` as a base class for this mode and those
185
+ # subclass also will _not_ receive default tracing modules.
186
+ #
185
187
  # @param mode_name [Symbol]
186
188
  # @param trace_class [Class] subclass of GraphQL::Tracing::Trace
187
189
  # @return void
188
190
  def trace_mode(mode_name, trace_class)
189
- @trace_modes ||= {}
190
- @trace_modes[mode_name] = trace_class
191
+ own_trace_modes[mode_name] = trace_class
191
192
  nil
192
193
  end
193
194
 
195
+ def own_trace_modes
196
+ @own_trace_modes ||= {}
197
+ end
198
+
199
+ module DefaultTraceClass
200
+ end
201
+
202
+ private_constant :DefaultTraceClass
203
+
204
+ def build_trace_mode(mode)
205
+ case mode
206
+ when :default
207
+ # Use the superclass's default mode if it has one, or else start an inheritance chain at the built-in base class.
208
+ base_class = (superclass.respond_to?(:trace_class_for) && superclass.trace_class_for(mode)) || GraphQL::Tracing::Trace
209
+ Class.new(base_class) do
210
+ include DefaultTraceClass
211
+ end
212
+ when :default_backtrace
213
+ schema_base_class = trace_class_for(:default)
214
+ Class.new(schema_base_class) do
215
+ include(GraphQL::Backtrace::Trace)
216
+ end
217
+ else
218
+ # First, see if the superclass has a custom-defined class for this.
219
+ # Then, if it doesn't, use this class's default trace
220
+ base_class = (superclass.respond_to?(:trace_class_for) && superclass.trace_class_for(mode)) || trace_class_for(:default)
221
+ # Prepare the default trace class if it hasn't been initialized yet
222
+ base_class ||= (own_trace_modes[:default] = build_trace_mode(:default))
223
+ mods = trace_modules_for(mode)
224
+ if base_class < DefaultTraceClass
225
+ mods = trace_modules_for(:default) + mods
226
+ end
227
+ Class.new(base_class) do
228
+ mods.any? && include(*mods)
229
+ end
230
+ end
231
+ end
232
+
194
233
  def own_trace_modules
195
234
  @own_trace_modules ||= Hash.new { |h, k| h[k] = [] }
196
235
  end
@@ -701,9 +740,10 @@ module GraphQL
701
740
 
702
741
  attr_writer :max_depth
703
742
 
704
- def max_depth(new_max_depth = nil)
743
+ def max_depth(new_max_depth = nil, count_introspection_fields: true)
705
744
  if new_max_depth
706
745
  @max_depth = new_max_depth
746
+ @count_introspection_fields = count_introspection_fields
707
747
  elsif defined?(@max_depth)
708
748
  @max_depth
709
749
  else
@@ -711,6 +751,14 @@ module GraphQL
711
751
  end
712
752
  end
713
753
 
754
+ def count_introspection_fields
755
+ if defined?(@count_introspection_fields)
756
+ @count_introspection_fields
757
+ else
758
+ find_inherited_value(:count_introspection_fields, true)
759
+ end
760
+ end
761
+
714
762
  def disable_introspection_entry_points
715
763
  @disable_introspection_entry_points = true
716
764
  # TODO: this clears the cache made in `def types`. But this is not a great solution.
@@ -760,7 +808,16 @@ module GraphQL
760
808
  own_orphan_types.concat(new_orphan_types.flatten)
761
809
  end
762
810
 
763
- find_inherited_value(:orphan_types, EMPTY_ARRAY) + own_orphan_types
811
+ inherited_ot = find_inherited_value(:orphan_types, nil)
812
+ if inherited_ot
813
+ if own_orphan_types.any?
814
+ inherited_ot + own_orphan_types
815
+ else
816
+ inherited_ot
817
+ end
818
+ else
819
+ own_orphan_types
820
+ end
764
821
  end
765
822
 
766
823
  def default_execution_strategy
@@ -864,8 +921,14 @@ module GraphQL
864
921
 
865
922
  def inherited(child_class)
866
923
  if self == GraphQL::Schema
924
+ child_class.own_trace_modes[:default] = child_class.build_trace_mode(:default)
867
925
  child_class.directives(default_directives.values)
868
926
  end
927
+ # Make sure the child class has these built out, so that
928
+ # subclasses can be modified by later calls to `trace_with`
929
+ own_trace_modes.each do |name, _class|
930
+ child_class.own_trace_modes[name] = child_class.build_trace_mode(name)
931
+ end
869
932
  child_class.singleton_class.prepend(ResolveTypeWithType)
870
933
  super
871
934
  end
@@ -962,7 +1025,12 @@ module GraphQL
962
1025
  new_directives.flatten.each { |d| directive(d) }
963
1026
  end
964
1027
 
965
- find_inherited_value(:directives, default_directives).merge(own_directives)
1028
+ inherited_dirs = find_inherited_value(:directives, default_directives)
1029
+ if own_directives.any?
1030
+ inherited_dirs.merge(own_directives)
1031
+ else
1032
+ inherited_dirs
1033
+ end
966
1034
  end
967
1035
 
968
1036
  # Attach a single directive to this schema
@@ -978,11 +1046,13 @@ module GraphQL
978
1046
  "skip" => GraphQL::Schema::Directive::Skip,
979
1047
  "deprecated" => GraphQL::Schema::Directive::Deprecated,
980
1048
  "oneOf" => GraphQL::Schema::Directive::OneOf,
1049
+ "specifiedBy" => GraphQL::Schema::Directive::SpecifiedBy,
981
1050
  }.freeze
982
1051
  end
983
1052
 
984
1053
  def tracer(new_tracer)
985
- if !(trace_class_for(:default) < GraphQL::Tracing::CallLegacyTracers)
1054
+ default_trace = trace_class_for(:default)
1055
+ if default_trace.nil? || !(default_trace < GraphQL::Tracing::CallLegacyTracers)
986
1056
  trace_with(GraphQL::Tracing::CallLegacyTracers)
987
1057
  end
988
1058
 
@@ -1004,10 +1074,20 @@ module GraphQL
1004
1074
  if mode.is_a?(Array)
1005
1075
  mode.each { |m| trace_with(trace_mod, mode: m, **options) }
1006
1076
  else
1007
- tc = trace_class_for(mode)
1077
+ tc = own_trace_modes[mode] ||= build_trace_mode(mode)
1008
1078
  tc.include(trace_mod)
1009
- if mode != :default
1010
- own_trace_modules[mode] << trace_mod
1079
+ own_trace_modules[mode] << trace_mod
1080
+
1081
+ if mode == :default
1082
+ # This module is being added as a default tracer. If any other mode classes
1083
+ # have already been created, but get their default behavior from a superclass,
1084
+ # Then mix this into this schema's subclass.
1085
+ # (But don't mix it into mode classes that aren't default-based.)
1086
+ own_trace_modes.each do |other_mode_name, other_mode_class|
1087
+ if other_mode_class < DefaultTraceClass && !(other_mode_class < trace_mod)
1088
+ other_mode_class.include(trace_mod)
1089
+ end
1090
+ end
1011
1091
  end
1012
1092
  t_opts = trace_options_for(mode)
1013
1093
  t_opts.merge!(options)
@@ -1030,6 +1110,8 @@ module GraphQL
1030
1110
 
1031
1111
  # Create a trace instance which will include the trace modules specified for the optional mode.
1032
1112
  #
1113
+ # If no `mode:` is given, then {default_trace_mode} will be used.
1114
+ #
1033
1115
  # @param mode [Symbol] Trace modules for this trade mode will be included
1034
1116
  # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
1035
1117
  # @return [Tracing::Trace]
@@ -1040,14 +1122,19 @@ module GraphQL
1040
1122
  trace_mode = if mode
1041
1123
  mode
1042
1124
  elsif target && target.context[:backtrace]
1043
- :default_backtrace
1125
+ if default_trace_mode != :default
1126
+ raise ArgumentError, "Can't use `context[:backtrace]` with a custom default trace mode (`#{dm.inspect}`)"
1127
+ else
1128
+ own_trace_modes[:default_backtrace] ||= build_trace_mode(:default_backtrace)
1129
+ :default_backtrace
1130
+ end
1044
1131
  else
1045
- :default
1132
+ default_trace_mode
1046
1133
  end
1047
1134
 
1048
1135
  base_trace_options = trace_options_for(trace_mode)
1049
1136
  trace_options = base_trace_options.merge(options)
1050
- trace_class_for_mode = trace_class_for(trace_mode)
1137
+ trace_class_for_mode = trace_class_for(trace_mode) || raise(ArgumentError, "#{self} has no trace class for mode: #{trace_mode.inspect}")
1051
1138
  trace_class_for_mode.new(**trace_options)
1052
1139
  end
1053
1140
 
@@ -3,7 +3,7 @@ module GraphQL
3
3
  module StaticValidation
4
4
  # Default rules for {GraphQL::StaticValidation::Validator}
5
5
  #
6
- # Order is important here. Some validators return {GraphQL::Language::Visitor::SKIP}
6
+ # Order is important here. Some validators skip later hooks.
7
7
  # which stops the visit on that node. That way it doesn't try to find fields on types that
8
8
  # don't exist, etc.
9
9
  ALL_RULES = [
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
3
  module StaticValidation
4
- class BaseVisitor < GraphQL::Language::Visitor
4
+ class BaseVisitor < GraphQL::Language::StaticVisitor
5
5
  def initialize(document, context)
6
6
  @path = []
7
7
  @object_types = []