graphql 2.3.9 → 2.3.11

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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install_generator.rb +46 -0
  3. data/lib/graphql/current.rb +52 -0
  4. data/lib/graphql/dataloader/source.rb +5 -2
  5. data/lib/graphql/dataloader.rb +4 -1
  6. data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
  7. data/lib/graphql/execution/interpreter/runtime.rb +2 -8
  8. data/lib/graphql/execution/interpreter.rb +2 -0
  9. data/lib/graphql/introspection/entry_points.rb +2 -2
  10. data/lib/graphql/introspection/schema_type.rb +1 -1
  11. data/lib/graphql/language/nodes.rb +2 -2
  12. data/lib/graphql/language/parser.rb +9 -1
  13. data/lib/graphql/query/context.rb +4 -2
  14. data/lib/graphql/query/null_context.rb +0 -4
  15. data/lib/graphql/query.rb +2 -6
  16. data/lib/graphql/schema/build_from_definition.rb +8 -1
  17. data/lib/graphql/schema/directive/flagged.rb +1 -1
  18. data/lib/graphql/schema/enum.rb +5 -1
  19. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  20. data/lib/graphql/schema/interface.rb +20 -4
  21. data/lib/graphql/schema/introspection_system.rb +3 -2
  22. data/lib/graphql/schema/member/has_arguments.rb +7 -3
  23. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  24. data/lib/graphql/schema/subset.rb +215 -102
  25. data/lib/graphql/schema/types_migration.rb +185 -0
  26. data/lib/graphql/schema/warden.rb +4 -7
  27. data/lib/graphql/schema.rb +30 -22
  28. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  29. data/lib/graphql/testing/helpers.rb +6 -3
  30. data/lib/graphql/version.rb +1 -1
  31. data/lib/graphql.rb +1 -0
  32. metadata +4 -2
@@ -2,16 +2,44 @@
2
2
 
3
3
  module GraphQL
4
4
  class Schema
5
+ # This class filters the types, fields, arguments, enum values, and directives in a schema
6
+ # based on the given `context`.
7
+ #
8
+ # It's like {Warden}, but has some differences:
9
+ #
10
+ # - It doesn't use {Schema}'s top-level caches (eg {Schema.references_to}, {Schema.possible_types}, {Schema.types})
11
+ # - It doesn't hide Interface or Union types when all their possible types are hidden. (Instead, those types should implement `.visible?` to hide in that case.)
12
+ # - It checks `.visible?` on root introspection types
13
+ #
14
+ # In the future, {Subset} will support lazy-loading types as needed during execution and multi-request caching of subsets.
15
+ #
16
+ # @see Schema::TypesMigration for a helper class in adopting this filter
5
17
  class Subset
6
- def initialize(query)
7
- @query = query
8
- @context = query.context
9
- @schema = query.schema
18
+ # @return [Schema::Subset]
19
+ def self.from_context(ctx, schema)
20
+ if ctx.respond_to?(:types) && (types = ctx.types).is_a?(self)
21
+ types
22
+ else
23
+ # TODO use a cached instance from the schema
24
+ self.new(context: ctx, schema: schema)
25
+ end
26
+ end
27
+
28
+ def self.pass_thru(context:, schema:)
29
+ subset = self.new(context: context, schema: schema)
30
+ subset.instance_variable_set(:@cached_visible, Hash.new { |h,k| h[k] = true })
31
+ subset
32
+ end
33
+
34
+ def initialize(context:, schema:)
35
+ @context = context
36
+ @schema = schema
10
37
  @all_types = {}
11
38
  @all_types_loaded = false
12
39
  @unvisited_types = []
13
40
  @referenced_types = Hash.new { |h, type_defn| h[type_defn] = [] }.compare_by_identity
14
- @cached_possible_types = nil
41
+ @cached_directives = {}
42
+ @all_directives = nil
15
43
  @cached_visible = Hash.new { |h, member|
16
44
  h[member] = @schema.visible?(member, @context)
17
45
  }.compare_by_identity
@@ -21,7 +49,6 @@ module GraphQL
21
49
  h2[field] = if @cached_visible[field] &&
22
50
  (ret_type = field.type.unwrap) &&
23
51
  @cached_visible[ret_type] &&
24
- reachable_type?(ret_type.graphql_name) &&
25
52
  (owner == field.owner || (!owner.kind.object?) || field_on_visible_interface?(field, owner))
26
53
 
27
54
  if !field.introspection?
@@ -47,8 +74,66 @@ module GraphQL
47
74
  end
48
75
  end.compare_by_identity
49
76
 
50
- @unfiltered_pt = Hash.new do |hash, type|
51
- hash[type] = @schema.possible_types(type)
77
+ @cached_parent_fields = Hash.new do |h, type|
78
+ h[type] = Hash.new do |h2, field_name|
79
+ h2[field_name] = type.get_field(field_name, @context)
80
+ end
81
+ end.compare_by_identity
82
+
83
+ @cached_parent_arguments = Hash.new do |h, arg_owner|
84
+ h[arg_owner] = Hash.new do |h2, arg_name|
85
+ h2[arg_name] = arg_owner.get_argument(arg_name, @context)
86
+ end
87
+ end.compare_by_identity
88
+
89
+ @cached_possible_types = Hash.new do |h, type|
90
+ h[type] = case type.kind.name
91
+ when "INTERFACE"
92
+ load_all_types
93
+ pts = []
94
+ @unfiltered_interface_type_memberships[type].each { |itm|
95
+ if @cached_visible[itm] && (ot = itm.object_type) && @cached_visible[ot] && referenced?(ot)
96
+ pts << ot
97
+ end
98
+ }
99
+ pts
100
+ when "UNION"
101
+ pts = []
102
+ type.type_memberships.each { |tm|
103
+ if @cached_visible[tm] &&
104
+ (ot = tm.object_type) &&
105
+ @cached_visible[ot] &&
106
+ referenced?(ot)
107
+ pts << ot
108
+ end
109
+ }
110
+ pts
111
+ when "OBJECT"
112
+ load_all_types
113
+ if @all_types[type.graphql_name] == type
114
+ [type]
115
+ else
116
+ EmptyObjects::EMPTY_ARRAY
117
+ end
118
+ else
119
+ GraphQL::EmptyObjects::EMPTY_ARRAY
120
+ end
121
+ end.compare_by_identity
122
+
123
+ @cached_enum_values = Hash.new do |h, enum_t|
124
+ values = non_duplicate_items(enum_t.all_enum_value_definitions, @cached_visible)
125
+ if values.size == 0
126
+ raise GraphQL::Schema::Enum::MissingValuesError.new(enum_t)
127
+ end
128
+ h[enum_t] = values
129
+ end.compare_by_identity
130
+
131
+ @cached_fields = Hash.new do |h, owner|
132
+ h[owner] = non_duplicate_items(owner.all_field_definitions, @cached_visible_fields[owner])
133
+ end.compare_by_identity
134
+
135
+ @cached_arguments = Hash.new do |h, owner|
136
+ h[owner] = non_duplicate_items(owner.all_argument_definitions, @cached_visible_arguments)
52
137
  end.compare_by_identity
53
138
  end
54
139
 
@@ -59,7 +144,7 @@ module GraphQL
59
144
  any_interface_has_field = false
60
145
  any_interface_has_visible_field = false
61
146
  ints.each do |int_t|
62
- if (_int_f_defn = int_t.get_field(field_name, @context))
147
+ if (_int_f_defn = @cached_parent_fields[int_t][field_name])
63
148
  any_interface_has_field = true
64
149
 
65
150
  if filtered_ints.include?(int_t) # TODO cycles, or maybe not necessary since previously checked? && @cached_visible_fields[owner][field]
@@ -79,11 +164,10 @@ module GraphQL
79
164
  def type(type_name)
80
165
  t = if (loaded_t = @all_types[type_name])
81
166
  loaded_t
82
- elsif !@all_types_loaded
83
- load_all_types
167
+ elsif !@all_types_loaded
168
+ load_all_types
84
169
  @all_types[type_name]
85
170
  end
86
-
87
171
  if t
88
172
  if t.is_a?(Array)
89
173
  vis_t = nil
@@ -108,7 +192,7 @@ module GraphQL
108
192
  end
109
193
 
110
194
  def field(owner, field_name)
111
- f = if owner.kind.fields? && (field = owner.get_field(field_name, @context))
195
+ f = if owner.kind.fields? && (field = @cached_parent_fields[owner][field_name])
112
196
  field
113
197
  elsif owner == query_root && (entry_point_field = @schema.introspection_system.entry_point(name: field_name))
114
198
  entry_point_field
@@ -140,24 +224,19 @@ module GraphQL
140
224
  end
141
225
 
142
226
  def fields(owner)
143
- non_duplicate_items(owner.all_field_definitions, @cached_visible_fields[owner])
227
+ @cached_fields[owner]
144
228
  end
145
229
 
146
230
  def arguments(owner)
147
- non_duplicate_items(owner.all_argument_definitions, @cached_visible_arguments)
231
+ @cached_arguments[owner]
148
232
  end
149
233
 
150
234
  def argument(owner, arg_name)
151
- # TODO this makes a Warden.visible_entry call down the stack
152
- # I need a non-Warden implementation
153
- arg = owner.get_argument(arg_name, @context)
235
+ arg = @cached_parent_arguments[owner][arg_name]
154
236
  if arg.is_a?(Array)
155
237
  visible_arg = nil
156
238
  arg.each do |arg_defn|
157
239
  if @cached_visible_arguments[arg_defn]
158
- if arg_defn&.loads
159
- add_type(arg_defn.loads, arg_defn)
160
- end
161
240
  if visible_arg.nil?
162
241
  visible_arg = arg_defn
163
242
  else
@@ -168,9 +247,6 @@ module GraphQL
168
247
  visible_arg
169
248
  else
170
249
  if arg && @cached_visible_arguments[arg]
171
- if arg&.loads
172
- add_type(arg.loads, arg)
173
- end
174
250
  arg
175
251
  else
176
252
  nil
@@ -179,22 +255,6 @@ module GraphQL
179
255
  end
180
256
 
181
257
  def possible_types(type)
182
- @cached_possible_types ||= Hash.new do |h, type|
183
- pt = case type.kind.name
184
- when "INTERFACE"
185
- # TODO this requires the global map
186
- @unfiltered_pt[type]
187
- when "UNION"
188
- type.type_memberships.select { |tm| @cached_visible[tm] && @cached_visible[tm.object_type] }.map!(&:object_type)
189
- else
190
- [type]
191
- end
192
-
193
- # TODO use `select!` when possible, skip it for `[type]`
194
- h[type] = pt.select { |t|
195
- @cached_visible[t] && referenced?(t)
196
- }
197
- end.compare_by_identity
198
258
  @cached_possible_types[type]
199
259
  end
200
260
 
@@ -219,49 +279,54 @@ module GraphQL
219
279
  end
220
280
 
221
281
  def all_types
222
- @all_types_filtered ||= begin
223
- load_all_types
224
- at = []
225
- @all_types.each do |_name, type_defn|
226
- if possible_types(type_defn).any? || referenced?(type_defn)
227
- at << type_defn
228
- end
229
- end
230
- at
231
- end
282
+ load_all_types
283
+ @all_types.values
284
+ end
285
+
286
+ def all_types_h
287
+ load_all_types
288
+ @all_types
232
289
  end
233
290
 
234
291
  def enum_values(owner)
235
- values = non_duplicate_items(owner.all_enum_value_definitions, @cached_visible)
236
- if values.size == 0
237
- raise GraphQL::Schema::Enum::MissingValuesError.new(owner)
238
- end
239
- values
292
+ @cached_enum_values[owner]
240
293
  end
241
294
 
242
295
  def directive_exists?(dir_name)
243
- dir = @schema.directives[dir_name]
244
- dir && @cached_visible[dir]
296
+ if (dir = @schema.directives[dir_name]) && @cached_visible[dir]
297
+ !!dir
298
+ else
299
+ load_all_types
300
+ !!@cached_directives[dir_name]
301
+ end
245
302
  end
246
303
 
247
304
  def directives
248
- @schema.directives.each_value.select { |d| @cached_visible[d] }
305
+ @all_directives ||= begin
306
+ load_all_types
307
+ dirs = []
308
+ @schema.directives.each do |name, dir_defn|
309
+ if !@cached_directives[name] && @cached_visible[dir_defn]
310
+ dirs << dir_defn
311
+ end
312
+ end
313
+ dirs.concat(@cached_directives.values)
314
+ end
249
315
  end
250
316
 
251
317
  def loadable?(t, _ctx)
252
- !@all_types[t.graphql_name] # TODO make sure t is not reachable but t is visible
253
- end
254
-
255
- # TODO rename this to indicate that it is called with a typename
256
- def reachable_type?(type_name)
257
- load_all_types
258
- !!((t = @all_types[type_name]) && referenced?(t))
318
+ !@all_types[t.graphql_name] && @cached_visible[t]
259
319
  end
260
320
 
261
321
  def loaded_types
262
322
  @all_types.values
263
323
  end
264
324
 
325
+ def reachable_type?(name)
326
+ load_all_types
327
+ !!@all_types[name]
328
+ end
329
+
265
330
  private
266
331
 
267
332
  def add_if_visible(t)
@@ -306,20 +371,13 @@ module GraphQL
306
371
 
307
372
  def referenced?(t)
308
373
  load_all_types
309
- res = if @referenced_types[t].any? { |member| (member == true) || @cached_visible[member] }
310
- if t.kind.abstract?
311
- possible_types(t).any?
312
- else
313
- true
314
- end
315
- end
316
- res
374
+ @referenced_types[t].any? { |reference| (reference == true) || @cached_visible[reference] }
317
375
  end
318
376
 
319
377
  def load_all_types
320
378
  return if @all_types_loaded
321
379
  @all_types_loaded = true
322
- schema_types = [
380
+ entry_point_types = [
323
381
  query_root,
324
382
  mutation_root,
325
383
  subscription_root,
@@ -330,16 +388,39 @@ module GraphQL
330
388
  @schema.orphan_types.each do |orphan_type|
331
389
  if @cached_visible[orphan_type] &&
332
390
  orphan_type.interface_type_memberships.any? { |tm| @cached_visible[tm] && @cached_visible[tm.abstract_type] }
333
- schema_types << orphan_type
391
+ entry_point_types << orphan_type
334
392
  end
335
393
  end
336
- schema_types.compact! # TODO why is this necessary?!
337
- schema_types.flatten! # handle multiple defns
338
- schema_types.each { |t| add_type(t, true) }
339
394
 
340
- while t = @unvisited_types.pop
341
- # These have already been checked for `.visible?`
342
- visit_type(t)
395
+ @schema.directives.each do |_dir_name, dir_class|
396
+ if @cached_visible[dir_class]
397
+ arguments(dir_class).each do |arg|
398
+ entry_point_types << arg.type.unwrap
399
+ end
400
+ end
401
+ end
402
+
403
+ entry_point_types.compact! # TODO why is this necessary?!
404
+ entry_point_types.flatten! # handle multiple defns
405
+ entry_point_types.each { |t| add_type(t, true) }
406
+
407
+ @unfiltered_interface_type_memberships = Hash.new { |h, k| h[k] = [] }.compare_by_identity
408
+ @add_possible_types = Set.new
409
+
410
+ while @unvisited_types.any?
411
+ while t = @unvisited_types.pop
412
+ # These have already been checked for `.visible?`
413
+ visit_type(t)
414
+ end
415
+ @add_possible_types.each do |int_t|
416
+ itms = @unfiltered_interface_type_memberships[int_t]
417
+ itms.each do |itm|
418
+ if @cached_visible[itm] && (obj_type = itm.object_type) && @cached_visible[obj_type]
419
+ add_type(obj_type, itm)
420
+ end
421
+ end
422
+ end
423
+ @add_possible_types.clear
343
424
  end
344
425
 
345
426
  @all_types.delete_if { |type_name, type_defn| !referenced?(type_defn) }
@@ -347,51 +428,83 @@ module GraphQL
347
428
  end
348
429
 
349
430
  def visit_type(type)
350
- if type.kind.input_object?
351
- # recurse into visible arguments
352
- arguments(type).each do |argument|
353
- add_type(argument.type.unwrap, argument)
354
- end
355
- elsif type.kind.union?
356
- # recurse into visible possible types
357
- type.type_memberships.each do |tm|
358
- if @cached_visible[tm] && @cached_visible[tm.object_type]
359
- add_type(tm.object_type, tm)
360
- end
361
- end
362
- elsif type.kind.fields?
431
+ visit_directives(type)
432
+ case type.kind.name
433
+ when "OBJECT", "INTERFACE"
363
434
  if type.kind.object?
435
+ type.interface_type_memberships.each do |itm|
436
+ @unfiltered_interface_type_memberships[itm.abstract_type] << itm
437
+ end
364
438
  # recurse into visible implemented interfaces
365
439
  interfaces(type).each do |interface|
366
440
  add_type(interface, type)
367
441
  end
442
+ else
443
+ type.orphan_types.each { |t| add_type(t, type)}
368
444
  end
369
445
 
370
446
  # recurse into visible fields
371
447
  t_f = type.all_field_definitions
372
448
  t_f.each do |field|
373
449
  if @cached_visible[field]
450
+ visit_directives(field)
374
451
  field_type = field.type.unwrap
375
452
  if field_type.kind.interface?
376
- pt = @unfiltered_pt[field_type]
377
- pt.each do |obj_type|
378
- if @cached_visible[obj_type] &&
379
- (tm = obj_type.interface_type_memberships.find { |tm| tm.abstract_type == field_type }) &&
380
- @cached_visible[tm]
381
- add_type(obj_type, tm)
382
- end
383
- end
453
+ @add_possible_types.add(field_type)
384
454
  end
385
455
  add_type(field_type, field)
386
456
 
387
457
  # recurse into visible arguments
388
458
  arguments(field).each do |argument|
459
+ visit_directives(argument)
389
460
  add_type(argument.type.unwrap, argument)
390
461
  end
391
462
  end
392
463
  end
464
+ when "INPUT_OBJECT"
465
+ # recurse into visible arguments
466
+ arguments(type).each do |argument|
467
+ visit_directives(argument)
468
+ add_type(argument.type.unwrap, argument)
469
+ end
470
+ when "UNION"
471
+ # recurse into visible possible types
472
+ type.type_memberships.each do |tm|
473
+ if @cached_visible[tm]
474
+ obj_t = tm.object_type
475
+ if obj_t.is_a?(String)
476
+ obj_t = Member::BuildType.constantize(obj_t)
477
+ tm.object_type = obj_t
478
+ end
479
+ if @cached_visible[obj_t]
480
+ add_type(obj_t, tm)
481
+ end
482
+ end
483
+ end
484
+ when "ENUM"
485
+ enum_values(type).each do |val|
486
+ visit_directives(val)
487
+ end
488
+ when "SCALAR"
489
+ # pass
393
490
  end
394
491
  end
492
+
493
+ def visit_directives(member)
494
+ member.directives.each { |dir|
495
+ dir_class = dir.class
496
+ if @cached_visible[dir_class]
497
+ dir_name = dir_class.graphql_name
498
+ if (existing_dir = @cached_directives[dir_name])
499
+ if existing_dir != dir_class
500
+ raise ArgumentError, "Two directives for `@#{dir_name}`: #{existing_dir}, #{dir.class}"
501
+ end
502
+ else
503
+ @cached_directives[dir.graphql_name] = dir_class
504
+ end
505
+ end
506
+ }
507
+ end
395
508
  end
396
509
  end
397
510
  end
@@ -0,0 +1,185 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Schema
4
+ # You can add this plugin to your schema to see how {GraphQL::Schema::Warden} and {GraphQL::Schema::Subset}
5
+ # handle `.visible?` differently in your schema.
6
+ #
7
+ # This plugin runs the same method on both implementations and raises an error when the results diverge.
8
+ #
9
+ # To fix the error, modify your schema so that both implementations return the same thing.
10
+ # Or, open an issue on GitHub to discuss the difference.
11
+ #
12
+ # This plugin adds overhead to runtime and may cause unexpected crashes -- **don't** use it in production!
13
+ #
14
+ # This plugin adds two keys to `context` when running:
15
+ #
16
+ # - `types_migration_running: true`
17
+ # - For the {Warden} which it instantiates, it adds `types_migration_warden_running: true`.
18
+ #
19
+ # Use those keys to modify your `visible?` behavior as needed.
20
+ #
21
+ # Also, in a pinch, you can set `skip_types_migration_error: true` in context to turn off this plugin's behavior per-query.
22
+ # (In that case, it uses {Subset} directly.)
23
+ #
24
+ # @example Adding this plugin
25
+ #
26
+ # if !Rails.env.production?
27
+ # use GraphQL::Schema::TypesMigration
28
+ # end
29
+ class TypesMigration < GraphQL::Schema::Subset
30
+ def self.use(schema)
31
+ schema.subset_class = self
32
+ end
33
+
34
+ class RuntimeTypesMismatchError < GraphQL::Error
35
+ def initialize(method_called, warden_result, subset_result, method_args)
36
+ super(<<~ERR)
37
+ Mismatch in types for `##{method_called}(#{method_args.map(&:inspect).join(", ")})`:
38
+
39
+ #{compare_results(warden_result, subset_result)}
40
+
41
+ Update your `.visible?` implementation to make these implementations return the same value.
42
+
43
+ See: https://graphql-ruby.org/authorization/visibility_migration.html
44
+ ERR
45
+ end
46
+
47
+ private
48
+ def compare_results(warden_result, subset_result)
49
+ if warden_result.is_a?(Array) && subset_result.is_a?(Array)
50
+ all_results = warden_result | subset_result
51
+ all_results.sort_by!(&:graphql_name)
52
+
53
+ entries_text = all_results.map { |entry| "#{entry.graphql_name} (#{entry})"}
54
+ width = entries_text.map(&:size).max
55
+ yes = " ✔ "
56
+ no = " "
57
+ res = "".dup
58
+ res << "#{"Result".center(width)} Warden Subset \n"
59
+ all_results.each_with_index do |entry, idx|
60
+ res << "#{entries_text[idx].ljust(width)}#{warden_result.include?(entry) ? yes : no}#{subset_result.include?(entry) ? yes : no}\n"
61
+ end
62
+ res << "\n"
63
+ else
64
+ "- Warden returned: #{humanize(warden_result)}\n\n- Subset returned: #{humanize(subset_result)}"
65
+ end
66
+ end
67
+ def humanize(val)
68
+ case val
69
+ when Array
70
+ "#{val.size}: #{val.map { |v| humanize(v) }.sort.inspect}"
71
+ when Module
72
+ if val.respond_to?(:graphql_name)
73
+ "#{val.graphql_name} (#{val.inspect})"
74
+ else
75
+ val.inspect
76
+ end
77
+ else
78
+ val.inspect
79
+ end
80
+ end
81
+ end
82
+
83
+ def initialize(context:, schema:)
84
+ @skip_error = context[:skip_types_migration_error]
85
+ context[:types_migration_running] = true
86
+ @subset_types = GraphQL::Schema::Subset.new(context: context, schema: schema)
87
+ if !@skip_error
88
+ warden_ctx_vals = context.to_h.dup
89
+ warden_ctx_vals[:types_migration_warden_running] = true
90
+ if defined?(schema::WardenCompatSchema)
91
+ warden_schema = schema::WardenCompatSchema
92
+ else
93
+ warden_schema = Class.new(schema)
94
+ warden_schema.use_schema_subset = false
95
+ # TODO public API
96
+ warden_schema.send(:add_type_and_traverse, [warden_schema.query, warden_schema.mutation, warden_schema.subscription].compact, root: true)
97
+ warden_schema.send(:add_type_and_traverse, warden_schema.directives.values + warden_schema.orphan_types, root: false)
98
+ end
99
+ warden_ctx = GraphQL::Query::Context.new(query: context.query, values: warden_ctx_vals)
100
+ example_warden = GraphQL::Schema::Warden.new(schema: warden_schema, context: warden_ctx)
101
+ @warden_types = example_warden.schema_subset
102
+ warden_ctx.warden = example_warden
103
+ warden_ctx.types = @warden_types
104
+ end
105
+ end
106
+
107
+ def loaded_types
108
+ @subset_types.loaded_types
109
+ end
110
+
111
+ PUBLIC_SUBSET_METHODS = [
112
+ :enum_values,
113
+ :interfaces,
114
+ :all_types,
115
+ :fields,
116
+ :loadable?,
117
+ :type,
118
+ :arguments,
119
+ :argument,
120
+ :directive_exists?,
121
+ :directives,
122
+ :field,
123
+ :query_root,
124
+ :mutation_root,
125
+ :possible_types,
126
+ :subscription_root,
127
+ :reachable_type?
128
+ ]
129
+
130
+ PUBLIC_SUBSET_METHODS.each do |subset_method|
131
+ define_method(subset_method) do |*args|
132
+ call_method_and_compare(subset_method, args)
133
+ end
134
+ end
135
+
136
+ def call_method_and_compare(method, args)
137
+ res_1 = @subset_types.public_send(method, *args)
138
+ if @skip_error
139
+ return res_1
140
+ end
141
+
142
+ res_2 = @warden_types.public_send(method, *args)
143
+ normalized_res_1 = res_1.is_a?(Array) ? Set.new(res_1) : res_1
144
+ normalized_res_2 = res_2.is_a?(Array) ? Set.new(res_2) : res_2
145
+ if !equivalent_schema_members?(normalized_res_1, normalized_res_2)
146
+ # Raise the errors with the orignally returned values:
147
+ err = RuntimeTypesMismatchError.new(method, res_2, res_1, args)
148
+ raise err
149
+ else
150
+ res_1
151
+ end
152
+ end
153
+
154
+ def equivalent_schema_members?(member1, member2)
155
+ if member1.class != member2.class
156
+ return false
157
+ end
158
+
159
+ case member1
160
+ when Set
161
+ member1_array = member1.to_a.sort_by(&:graphql_name)
162
+ member2_array = member2.to_a.sort_by(&:graphql_name)
163
+ member1_array.each_with_index do |inner_member1, idx|
164
+ inner_member2 = member2_array[idx]
165
+ equivalent_schema_members?(inner_member1, inner_member2)
166
+ end
167
+ when GraphQL::Schema::Field
168
+ if member1.introspection? && member2.introspection?
169
+ member1.inspect == member2.inspect
170
+ else
171
+ member1 == member2
172
+ end
173
+ when Module
174
+ if member1.introspection? && member2.introspection?
175
+ member1.graphql_name == member2.graphql_name
176
+ else
177
+ member1 == member2
178
+ end
179
+ else
180
+ member1 == member2
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
@@ -75,8 +75,8 @@ module GraphQL
75
75
 
76
76
  # @api private
77
77
  module NullSubset
78
- def self.new(query)
79
- NullWarden.new(context: query.context, schema: query.schema).schema_subset
78
+ def self.new(context:, schema:)
79
+ NullWarden.new(context: context, schema: schema).schema_subset
80
80
  end
81
81
  end
82
82
 
@@ -174,7 +174,7 @@ module GraphQL
174
174
  end
175
175
 
176
176
  def reachable_type?(type_name)
177
- @warden.reachable_type?(type_name)
177
+ !!@warden.reachable_type?(type_name)
178
178
  end
179
179
  end
180
180
 
@@ -188,7 +188,6 @@ module GraphQL
188
188
  @subscription = @schema.subscription
189
189
  @context = context
190
190
  @visibility_cache = read_through { |m| schema.visible?(m, context) }
191
- @visibility_cache.compare_by_identity
192
191
  # Initialize all ivars to improve object shape consistency:
193
192
  @types = @visible_types = @reachable_types = @visible_parent_fields =
194
193
  @visible_possible_types = @visible_fields = @visible_arguments = @visible_enum_arrays =
@@ -463,9 +462,7 @@ module GraphQL
463
462
  end
464
463
 
465
464
  def read_through
466
- h = Hash.new { |h, k| h[k] = yield(k) }
467
- h.compare_by_identity
468
- h
465
+ Hash.new { |h, k| h[k] = yield(k) }.compare_by_identity
469
466
  end
470
467
 
471
468
  def reachable_type_set