graphql 2.1.0 → 2.1.2

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