graphql 2.4.2 → 2.4.4

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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bbf9779d0fbc1da481a481d987489b980818c5ea6afb352f4e65f0654ea14163
4
- data.tar.gz: 91528aabb657b8602c7f3143b6a9dc3a770a336f6a31af0f89fce1c27e5e00a0
3
+ metadata.gz: caa82217695402eca9cd3782abf5deb4b27dd61d8aa3c16e7c4a555e1c09634a
4
+ data.tar.gz: a39b5821df1a8656616fd1831878c365c930289c0f94d733b788ab4298f24a96
5
5
  SHA512:
6
- metadata.gz: c73cedf9cd1fc6ab4d91f026dfe627cb1490d5888d1252f591b944359e1f537a479c5d7f7ceed691ddd79883ae1ba027dfb952e969d14617e6a19becee792aa6
7
- data.tar.gz: 4d429bb74350c9fbbaa0e33498ccb193acfa6691f62342b38923965e69bc23bb377e6ade8a56b8f5d5c785ea24a2f6e04a47a8384000a45140d320e7461c6832
6
+ metadata.gz: 1181acff2ccf066def91b1f39644b449fe715d3cfa18437c7edf74dfa5af01a45cbd1e00db2413810917fc290d1d3abc8bcc0620354458721d4df184973beb17
7
+ data.tar.gz: 61bcb56a34d0b0beb61672e3bae868dc5bd7a9caf351d85e0a78afd16c12347a076767e63ce55cc468c6edc5247caa10e2ccace32447b54c9dbc4f864846db5b
@@ -56,7 +56,14 @@ module GraphQL
56
56
  else
57
57
  @arguments = if @field
58
58
  @query.after_lazy(@query.arguments_for(@ast_nodes.first, @field)) do |args|
59
- args.is_a?(Execution::Interpreter::Arguments) ? args.keyword_arguments : args
59
+ case args
60
+ when Execution::Interpreter::Arguments
61
+ args.keyword_arguments
62
+ when GraphQL::ExecutionError
63
+ EmptyObjects::EMPTY_HASH
64
+ else
65
+ args
66
+ end
60
67
  end
61
68
  else
62
69
  nil
@@ -38,38 +38,17 @@ module GraphQL
38
38
  @all_types = {}
39
39
  @all_types_loaded = false
40
40
  @unvisited_types = []
41
- @referenced_types = Hash.new { |h, type_defn| h[type_defn] = [] }.compare_by_identity
42
- @cached_directives = {}
43
41
  @all_directives = nil
44
- @cached_visible = Hash.new { |h, member|
45
- h[member] = @schema.visible?(member, @context)
46
- }.compare_by_identity
42
+ @cached_visible = Hash.new { |h, member| h[member] = @schema.visible?(member, @context) }.compare_by_identity
47
43
 
48
44
  @cached_visible_fields = Hash.new { |h, owner|
49
45
  h[owner] = Hash.new do |h2, field|
50
- h2[field] = if @cached_visible[field] &&
51
- (ret_type = field.type.unwrap) &&
52
- @cached_visible[ret_type] &&
53
- (owner == field.owner || (!owner.kind.object?) || field_on_visible_interface?(field, owner))
54
-
55
- if !field.introspection?
56
- # The problem is that some introspection fields may have references
57
- # to non-custom introspection types.
58
- # If those were added here, they'd cause a DuplicateNamesError.
59
- # This is basically a bug -- those fields _should_ reference the custom types.
60
- add_type(ret_type, field)
61
- end
62
- true
63
- else
64
- false
65
- end
46
+ h2[field] = visible_field_for(owner, field)
66
47
  end.compare_by_identity
67
48
  }.compare_by_identity
68
49
 
69
50
  @cached_visible_arguments = Hash.new do |h, arg|
70
51
  h[arg] = if @cached_visible[arg] && (arg_type = arg.type.unwrap) && @cached_visible[arg_type]
71
- add_type(arg_type, arg)
72
- arg.validate_default_value
73
52
  true
74
53
  else
75
54
  false
@@ -88,39 +67,7 @@ module GraphQL
88
67
  end
89
68
  end.compare_by_identity
90
69
 
91
- @cached_possible_types = Hash.new do |h, type|
92
- h[type] = case type.kind.name
93
- when "INTERFACE"
94
- load_all_types
95
- pts = []
96
- @unfiltered_interface_type_memberships[type].each { |itm|
97
- if @cached_visible[itm] && (ot = itm.object_type) && @cached_visible[ot] && referenced?(ot)
98
- pts << ot
99
- end
100
- }
101
- pts
102
- when "UNION"
103
- pts = []
104
- type.type_memberships.each { |tm|
105
- if @cached_visible[tm] &&
106
- (ot = tm.object_type) &&
107
- @cached_visible[ot] &&
108
- referenced?(ot)
109
- pts << ot
110
- end
111
- }
112
- pts
113
- when "OBJECT"
114
- load_all_types
115
- if @all_types[type.graphql_name] == type
116
- [type]
117
- else
118
- EmptyObjects::EMPTY_ARRAY
119
- end
120
- else
121
- GraphQL::EmptyObjects::EMPTY_ARRAY
122
- end
123
- end.compare_by_identity
70
+ @cached_possible_types = Hash.new { |h, type| h[type] = possible_types_for(type) }.compare_by_identity
124
71
 
125
72
  @cached_enum_values = Hash.new do |h, enum_t|
126
73
  values = non_duplicate_items(enum_t.enum_values(@context), @cached_visible)
@@ -164,17 +111,12 @@ module GraphQL
164
111
  end
165
112
 
166
113
  def type(type_name)
167
- t = if (loaded_t = @all_types[type_name])
168
- loaded_t
169
- elsif !@all_types_loaded
170
- load_all_types
171
- @all_types[type_name]
172
- end
114
+ t = @schema.visibility.get_type(type_name) # rubocop:disable ContextIsPassedCop
173
115
  if t
174
116
  if t.is_a?(Array)
175
117
  vis_t = nil
176
118
  t.each do |t_defn|
177
- if @cached_visible[t_defn]
119
+ if @cached_visible[t_defn] && referenced?(t_defn)
178
120
  if vis_t.nil?
179
121
  vis_t = t_defn
180
122
  else
@@ -184,7 +126,7 @@ module GraphQL
184
126
  end
185
127
  vis_t
186
128
  else
187
- if t && @cached_visible[t]
129
+ if t && @cached_visible[t] && referenced?(t)
188
130
  t
189
131
  else
190
132
  nil
@@ -267,15 +209,15 @@ module GraphQL
267
209
  end
268
210
 
269
211
  def query_root
270
- add_if_visible(@schema.query)
212
+ ((t = @schema.query) && @cached_visible[t]) ? t : nil
271
213
  end
272
214
 
273
215
  def mutation_root
274
- add_if_visible(@schema.mutation)
216
+ ((t = @schema.mutation) && @cached_visible[t]) ? t : nil
275
217
  end
276
218
 
277
219
  def subscription_root
278
- add_if_visible(@schema.subscription)
220
+ ((t = @schema.subscription) && @cached_visible[t]) ? t : nil
279
221
  end
280
222
 
281
223
  def all_types
@@ -293,28 +235,15 @@ module GraphQL
293
235
  end
294
236
 
295
237
  def directive_exists?(dir_name)
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
238
+ directives.any? { |d| d.graphql_name == dir_name }
302
239
  end
303
240
 
304
241
  def directives
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
242
+ @all_directives ||= @schema.visibility.all_directives.select { |dir| @cached_visible[dir] }
315
243
  end
316
244
 
317
245
  def loadable?(t, _ctx)
246
+ load_all_types
318
247
  !@all_types[t.graphql_name] && @cached_visible[t]
319
248
  end
320
249
 
@@ -322,9 +251,9 @@ module GraphQL
322
251
  @all_types.values
323
252
  end
324
253
 
325
- def reachable_type?(name)
254
+ def reachable_type?(type_name)
326
255
  load_all_types
327
- !!@all_types[name]
256
+ !!@all_types[type_name]
328
257
  end
329
258
 
330
259
  def visible_enum_value?(enum_value, _ctx = nil)
@@ -333,29 +262,6 @@ module GraphQL
333
262
 
334
263
  private
335
264
 
336
- def add_if_visible(t)
337
- (t && @cached_visible[t]) ? (add_type(t, true); t) : nil
338
- end
339
-
340
- def add_type(t, by_member)
341
- if t && @cached_visible[t]
342
- n = t.graphql_name
343
- if (prev_t = @all_types[n])
344
- if !prev_t.equal?(t)
345
- raise_duplicate_definition(prev_t, t)
346
- end
347
- false
348
- else
349
- @referenced_types[t] << by_member
350
- @all_types[n] = t
351
- @unvisited_types << t
352
- true
353
- end
354
- else
355
- false
356
- end
357
- end
358
-
359
265
  def non_duplicate_items(definitions, visibility_cache)
360
266
  non_dups = []
361
267
  definitions.each do |defn|
@@ -373,153 +279,71 @@ module GraphQL
373
279
  raise DuplicateNamesError.new(duplicated_name: first_defn.path, duplicated_definition_1: first_defn.inspect, duplicated_definition_2: second_defn.inspect)
374
280
  end
375
281
 
376
- def referenced?(t)
377
- load_all_types
378
- @referenced_types[t].any? { |reference| (reference == true) || @cached_visible[reference] }
379
- end
380
-
381
282
  def load_all_types
382
283
  return if @all_types_loaded
383
284
  @all_types_loaded = true
384
- entry_point_types = [
385
- query_root,
386
- mutation_root,
387
- subscription_root,
388
- *@schema.introspection_system.types.values,
389
- ]
390
-
391
- # Don't include any orphan_types whose interfaces aren't visible.
392
- @schema.orphan_types.each do |orphan_type|
393
- if @cached_visible[orphan_type] &&
394
- orphan_type.interface_type_memberships.any? { |tm| @cached_visible[tm] && @cached_visible[tm.abstract_type] }
395
- entry_point_types << orphan_type
396
- end
397
- end
398
-
399
- @schema.directives.each do |_dir_name, dir_class|
400
- if @cached_visible[dir_class]
401
- arguments(dir_class).each do |arg|
402
- entry_point_types << arg.type.unwrap
403
- end
404
- end
405
- end
406
-
407
- entry_point_types.compact! # Root types might be nil
408
- entry_point_types.flatten! # handle multiple defns
409
- entry_point_types.each { |t| add_type(t, true) }
410
-
411
- @unfiltered_interface_type_memberships = Hash.new { |h, k| h[k] = [] }.compare_by_identity
412
- @add_possible_types = Set.new
413
- @late_types = []
414
-
415
- while @unvisited_types.any? || @late_types.any?
416
- while t = @unvisited_types.pop
417
- # These have already been checked for `.visible?`
418
- visit_type(t)
419
- end
420
- @add_possible_types.each do |int_t|
421
- itms = @unfiltered_interface_type_memberships[int_t]
422
- itms.each do |itm|
423
- if @cached_visible[itm] && (obj_type = itm.object_type) && @cached_visible[obj_type]
424
- add_type(obj_type, itm)
285
+ visit = Visibility::Visit.new(@schema) do |member|
286
+ if member.is_a?(Module) && member.respond_to?(:kind)
287
+ if @cached_visible[member]
288
+ type_name = member.graphql_name
289
+ if (prev_t = @all_types[type_name]) && !prev_t.equal?(member)
290
+ raise_duplicate_definition(member, prev_t)
425
291
  end
292
+ @all_types[type_name] = member
293
+ true
294
+ else
295
+ false
426
296
  end
427
- end
428
- @add_possible_types.clear
429
-
430
- while (union_tm = @late_types.shift)
431
- late_obj_t = union_tm.object_type
432
- obj_t = @all_types[late_obj_t.graphql_name] || raise("Failed to resolve #{late_obj_t.graphql_name.inspect} from #{union_tm.inspect}")
433
- union_tm.abstract_type.assign_type_membership_object_type(obj_t)
297
+ else
298
+ @cached_visible[member]
434
299
  end
435
300
  end
436
-
301
+ visit.visit_each
437
302
  @all_types.delete_if { |type_name, type_defn| !referenced?(type_defn) }
438
303
  nil
439
304
  end
440
305
 
441
- def visit_type(type)
442
- visit_directives(type)
443
- case type.kind.name
444
- when "OBJECT", "INTERFACE"
445
- if type.kind.object?
446
- type.interface_type_memberships.each do |itm|
447
- @unfiltered_interface_type_memberships[itm.abstract_type] << itm
448
- end
449
- # recurse into visible implemented interfaces
450
- interfaces(type).each do |interface|
451
- add_type(interface, type)
452
- end
453
- else
454
- type.orphan_types.each { |t| add_type(t, type)}
455
- end
456
-
457
- # recurse into visible fields
458
- t_f = type.all_field_definitions
459
- t_f.each do |field|
460
- field.ensure_loaded
461
- if @cached_visible[field]
462
- visit_directives(field)
463
- field_type = field.type.unwrap
464
- if field_type.kind.interface?
465
- @add_possible_types.add(field_type)
466
- end
467
- add_type(field_type, field)
306
+ def referenced?(type_defn)
307
+ @schema.visibility.all_references[type_defn].any? { |r| r == true || @cached_visible[r] }
308
+ end
468
309
 
469
- # recurse into visible arguments
470
- arguments(field).each do |argument|
471
- visit_directives(argument)
472
- add_type(argument.type.unwrap, argument)
473
- end
310
+ def possible_types_for(type)
311
+ case type.kind.name
312
+ when "INTERFACE"
313
+ pts = []
314
+ @schema.visibility.all_interface_type_memberships[type].each do |itm|
315
+ if @cached_visible[itm] && (ot = itm.object_type) && @cached_visible[ot] && referenced?(ot)
316
+ pts << ot
474
317
  end
475
318
  end
476
- when "INPUT_OBJECT"
477
- # recurse into visible arguments
478
- arguments(type).each do |argument|
479
- visit_directives(argument)
480
- add_type(argument.type.unwrap, argument)
481
- end
319
+ pts
482
320
  when "UNION"
483
- # recurse into visible possible types
484
- type.type_memberships.each do |tm|
485
- if @cached_visible[tm]
486
- obj_t = tm.object_type
487
- if obj_t.is_a?(GraphQL::Schema::LateBoundType)
488
- @late_types << tm
489
- else
490
- if obj_t.is_a?(String)
491
- obj_t = Member::BuildType.constantize(obj_t)
492
- tm.object_type = obj_t
493
- end
494
- if @cached_visible[obj_t]
495
- add_type(obj_t, tm)
496
- end
497
- end
321
+ pts = []
322
+ type.type_memberships.each { |tm|
323
+ if @cached_visible[tm] &&
324
+ (ot = tm.object_type) &&
325
+ @cached_visible[ot] &&
326
+ referenced?(ot)
327
+ pts << ot
498
328
  end
329
+ }
330
+ pts
331
+ when "OBJECT"
332
+ if @cached_visible[type]
333
+ [type]
334
+ else
335
+ EmptyObjects::EMPTY_ARRAY
499
336
  end
500
- when "ENUM"
501
- enum_values(type).each do |val|
502
- visit_directives(val)
503
- end
504
- when "SCALAR"
505
- # pass
337
+ else
338
+ GraphQL::EmptyObjects::EMPTY_ARRAY
506
339
  end
507
340
  end
508
341
 
509
- def visit_directives(member)
510
- member.directives.each { |dir|
511
- dir_class = dir.class
512
- if @cached_visible[dir_class]
513
- dir_name = dir_class.graphql_name
514
- if (existing_dir = @cached_directives[dir_name])
515
- if existing_dir != dir_class
516
- raise ArgumentError, "Two directives for `@#{dir_name}`: #{existing_dir}, #{dir.class}"
517
- end
518
- else
519
- @cached_directives[dir.graphql_name] = dir_class
520
- end
521
- end
522
- }
342
+ def visible_field_for(owner, field)
343
+ @cached_visible[field] &&
344
+ (ret_type = field.type.unwrap) &&
345
+ @cached_visible[ret_type] &&
346
+ (owner == field.owner || (!owner.kind.object?) || field_on_visible_interface?(field, owner))
523
347
  end
524
348
  end
525
349
  end
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Schema
4
+ class Visibility
5
+ class Visit
6
+ def initialize(schema, &visit_block)
7
+ @schema = schema
8
+ @late_bound_types = nil
9
+ @unvisited_types = nil
10
+ # These accumulate between calls to prevent re-visiting the same types
11
+ @visited_types = Set.new.compare_by_identity
12
+ @visited_directives = Set.new.compare_by_identity
13
+ @visit_block = visit_block
14
+ end
15
+
16
+ def entry_point_types
17
+ ept = [
18
+ @schema.query,
19
+ @schema.mutation,
20
+ @schema.subscription,
21
+ *@schema.introspection_system.types.values,
22
+ *@schema.orphan_types,
23
+ ]
24
+ ept.compact!
25
+ ept
26
+ end
27
+
28
+ def entry_point_directives
29
+ @schema.directives.values
30
+ end
31
+
32
+ def visit_each(types: entry_point_types, directives: entry_point_directives)
33
+ @unvisited_types && raise("Can't re-enter `visit_each` on this Visit (another visit is already in progress)")
34
+ @unvisited_types = types
35
+ @late_bound_types = []
36
+ directives_to_visit = directives
37
+
38
+ while @unvisited_types.any? || @late_bound_types.any?
39
+ while (type = @unvisited_types.pop)
40
+ if @visited_types.add?(type) && @visit_block.call(type)
41
+ directives_to_visit.concat(type.directives)
42
+ case type.kind.name
43
+ when "OBJECT", "INTERFACE"
44
+ type.interface_type_memberships.each do |itm|
45
+ append_unvisited_type(type, itm.abstract_type)
46
+ end
47
+ if type.kind.interface?
48
+ type.orphan_types.each do |orphan_type|
49
+ append_unvisited_type(type, orphan_type)
50
+ end
51
+ end
52
+
53
+ type.all_field_definitions.each do |field|
54
+ field.ensure_loaded
55
+ if @visit_block.call(field)
56
+ directives_to_visit.concat(field.directives)
57
+ append_unvisited_type(type, field.type.unwrap)
58
+ field.all_argument_definitions.each do |argument|
59
+ if @visit_block.call(argument)
60
+ directives_to_visit.concat(argument.directives)
61
+ append_unvisited_type(field, argument.type.unwrap)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ when "INPUT_OBJECT"
67
+ type.all_argument_definitions.each do |argument|
68
+ if @visit_block.call(argument)
69
+ directives_to_visit.concat(argument.directives)
70
+ append_unvisited_type(type, argument.type.unwrap)
71
+ end
72
+ end
73
+ when "UNION"
74
+ type.type_memberships.each do |tm|
75
+ append_unvisited_type(type, tm.object_type)
76
+ end
77
+ when "ENUM"
78
+ type.all_enum_value_definitions.each do |val|
79
+ if @visit_block.call(val)
80
+ directives_to_visit.concat(val.directives)
81
+ end
82
+ end
83
+ when "SCALAR"
84
+ # pass -- nothing else to visit
85
+ else
86
+ raise "Invariant: unhandled type kind: #{type.kind.inspect}"
87
+ end
88
+ end
89
+ end
90
+
91
+ directives_to_visit.each do |dir|
92
+ dir_class = dir.is_a?(Class) ? dir : dir.class
93
+ if @visited_directives.add?(dir_class) && @visit_block.call(dir_class)
94
+ dir_class.all_argument_definitions.each do |arg_defn|
95
+ if @visit_block.call(arg_defn)
96
+ directives_to_visit.concat(arg_defn.directives)
97
+ append_unvisited_type(dir_class, arg_defn.type.unwrap)
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ missed_late_types_streak = 0
104
+ while (owner, late_type = @late_bound_types.shift)
105
+ if (late_type.is_a?(String) && (type = Member::BuildType.constantize(type))) ||
106
+ (late_type.is_a?(LateBoundType) && (type = @visited_types.find { |t| t.graphql_name == late_type.graphql_name }))
107
+ missed_late_types_streak = 0 # might succeed next round
108
+ update_type_owner(owner, type)
109
+ append_unvisited_type(owner, type)
110
+ else
111
+ # Didn't find it -- keep trying
112
+ missed_late_types_streak += 1
113
+ @late_bound_types << [owner, late_type]
114
+ if missed_late_types_streak == @late_bound_types.size
115
+ raise UnresolvedLateBoundTypeError.new(type: late_type)
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ @unvisited_types = nil
122
+ nil
123
+ end
124
+
125
+ private
126
+
127
+ def append_unvisited_type(owner, type)
128
+ if type.is_a?(LateBoundType) || type.is_a?(String)
129
+ @late_bound_types << [owner, type]
130
+ else
131
+ @unvisited_types << type
132
+ end
133
+ end
134
+
135
+ def update_type_owner(owner, type)
136
+ case owner
137
+ when Module
138
+ if owner.kind.union?
139
+ owner.assign_type_membership_object_type(type)
140
+ elsif type.kind.interface?
141
+ new_interfaces = []
142
+ owner.interfaces.each do |int_t|
143
+ if int_t.is_a?(String) && int_t == type.graphql_name
144
+ new_interfaces << type
145
+ elsif int_t.is_a?(LateBoundType) && int_t.graphql_name == type.graphql_name
146
+ new_interfaces << type
147
+ else
148
+ # Don't re-add proper interface definitions,
149
+ # they were probably already added, maybe with options.
150
+ end
151
+ end
152
+ owner.implements(*new_interfaces)
153
+ new_interfaces.each do |int|
154
+ pt = @possible_types[int] ||= []
155
+ if !pt.include?(owner) && owner.is_a?(Class)
156
+ pt << owner
157
+ end
158
+ int.interfaces.each do |indirect_int|
159
+ if indirect_int.is_a?(LateBoundType) && (indirect_int_type = get_type(indirect_int.graphql_name)) # rubocop:disable ContextIsPassedCop
160
+ update_type_owner(owner, indirect_int_type)
161
+ end
162
+ end
163
+ end
164
+ end
165
+ when GraphQL::Schema::Argument, GraphQL::Schema::Field
166
+ orig_type = owner.type
167
+ # Apply list/non-null wrapper as needed
168
+ if orig_type.respond_to?(:of_type)
169
+ transforms = []
170
+ while (orig_type.respond_to?(:of_type))
171
+ if orig_type.kind.non_null?
172
+ transforms << :to_non_null_type
173
+ elsif orig_type.kind.list?
174
+ transforms << :to_list_type
175
+ else
176
+ raise "Invariant: :of_type isn't non-null or list"
177
+ end
178
+ orig_type = orig_type.of_type
179
+ end
180
+ transforms.reverse_each { |t| type = type.public_send(t) }
181
+ end
182
+ owner.type = type
183
+ else
184
+ raise "Unexpected update: #{owner.inspect} #{type.inspect}"
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  require "graphql/schema/visibility/profile"
3
3
  require "graphql/schema/visibility/migration"
4
+ require "graphql/schema/visibility/visit"
4
5
 
5
6
  module GraphQL
6
7
  class Schema
@@ -13,40 +14,76 @@ module GraphQL
13
14
  # @param migration_errors [Boolean] if `true`, raise an error when `Visibility` and `Warden` return different results
14
15
  def self.use(schema, dynamic: false, profiles: EmptyObjects::EMPTY_HASH, preload: (defined?(Rails) ? Rails.env.production? : nil), migration_errors: false)
15
16
  schema.visibility = self.new(schema, dynamic: dynamic, preload: preload, profiles: profiles, migration_errors: migration_errors)
17
+ if preload
18
+ schema.visibility.preload
19
+ end
16
20
  end
17
21
 
18
22
  def initialize(schema, dynamic:, preload:, profiles:, migration_errors:)
19
23
  @schema = schema
20
24
  schema.use_visibility_profile = true
21
- if migration_errors
22
- schema.visibility_profile_class = Migration
25
+ schema.visibility_profile_class = if migration_errors
26
+ Visibility::Migration
27
+ else
28
+ Visibility::Profile
23
29
  end
24
30
  @preload = preload
25
31
  @profiles = profiles
26
32
  @cached_profiles = {}
27
33
  @dynamic = dynamic
28
34
  @migration_errors = migration_errors
29
- if preload
30
- # Traverse the schema now (and in the *_configured hooks below)
31
- # To make sure things are loaded during boot
32
- @preloaded_types = Set.new
33
- types_to_visit = [
34
- @schema.query,
35
- @schema.mutation,
36
- @schema.subscription,
37
- *@schema.introspection_system.types.values,
38
- *@schema.introspection_system.entry_points.map { |ep| ep.type.unwrap },
39
- *@schema.orphan_types,
40
- ]
41
- # Root types may have been nil:
42
- types_to_visit.compact!
43
- ensure_all_loaded(types_to_visit)
44
-
45
- profiles.each do |profile_name, example_ctx|
46
- example_ctx[:visibility_profile] = profile_name
47
- prof = profile_for(example_ctx, profile_name)
48
- prof.all_types # force loading
49
- end
35
+ # Top-level type caches:
36
+ @visit = nil
37
+ @interface_type_memberships = nil
38
+ @directives = nil
39
+ @types = nil
40
+ @references = nil
41
+ @loaded_all = false
42
+ end
43
+
44
+ def all_directives
45
+ load_all
46
+ @directives
47
+ end
48
+
49
+ def all_interface_type_memberships
50
+ load_all
51
+ @interface_type_memberships
52
+ end
53
+
54
+ def all_references
55
+ load_all
56
+ @references
57
+ end
58
+
59
+ def get_type(type_name)
60
+ load_all
61
+ @types[type_name]
62
+ end
63
+
64
+ def preload?
65
+ @preload
66
+ end
67
+
68
+ def preload
69
+ # Traverse the schema now (and in the *_configured hooks below)
70
+ # To make sure things are loaded during boot
71
+ @preloaded_types = Set.new
72
+ types_to_visit = [
73
+ @schema.query,
74
+ @schema.mutation,
75
+ @schema.subscription,
76
+ *@schema.introspection_system.types.values,
77
+ *@schema.introspection_system.entry_points.map { |ep| ep.type.unwrap },
78
+ *@schema.orphan_types,
79
+ ]
80
+ # Root types may have been nil:
81
+ types_to_visit.compact!
82
+ ensure_all_loaded(types_to_visit)
83
+ @profiles.each do |profile_name, example_ctx|
84
+ example_ctx[:visibility_profile] = profile_name
85
+ prof = profile_for(example_ctx, profile_name)
86
+ prof.all_types # force loading
50
87
  end
51
88
  end
52
89
 
@@ -112,7 +149,11 @@ module GraphQL
112
149
  if @profiles.any?
113
150
  if visibility_profile.nil?
114
151
  if @dynamic
115
- @schema.visibility_profile_class.new(context: context, schema: @schema)
152
+ if context.is_a?(Query::NullContext)
153
+ top_level_profile
154
+ else
155
+ @schema.visibility_profile_class.new(context: context, schema: @schema)
156
+ end
116
157
  elsif @profiles.any?
117
158
  raise ArgumentError, "#{@schema} expects a visibility profile, but `visibility_profile:` wasn't passed. Provide a `visibility_profile:` value or add `dynamic: true` to your visibility configuration."
118
159
  end
@@ -121,11 +162,25 @@ module GraphQL
121
162
  else
122
163
  @cached_profiles[visibility_profile] ||= @schema.visibility_profile_class.new(name: visibility_profile, context: context, schema: @schema)
123
164
  end
165
+ elsif context.is_a?(Query::NullContext)
166
+ top_level_profile
124
167
  else
125
168
  @schema.visibility_profile_class.new(context: context, schema: @schema)
126
169
  end
127
170
  end
128
171
 
172
+ attr_reader :top_level
173
+
174
+ # @api private
175
+ attr_reader :unfiltered_interface_type_memberships
176
+
177
+ def top_level_profile(refresh: false)
178
+ if refresh
179
+ @top_level_profile = nil
180
+ end
181
+ @top_level_profile ||= @schema.visibility_profile_class.new(context: Query::NullContext.instance, schema: @schema)
182
+ end
183
+
129
184
  private
130
185
 
131
186
  def ensure_all_loaded(types_to_visit)
@@ -137,6 +192,88 @@ module GraphQL
137
192
  end
138
193
  end
139
194
  end
195
+ top_level_profile(refresh: true)
196
+ nil
197
+ end
198
+
199
+ def load_all(types: nil)
200
+ if @visit.nil?
201
+ # Set up the visit system
202
+ @interface_type_memberships = Hash.new { |h, interface_type| h[interface_type] = [] }.compare_by_identity
203
+ @directives = []
204
+ @types = {} # String => Module
205
+ @references = Hash.new { |h, member| h[member] = [] }.compare_by_identity
206
+ @unions_for_references = Set.new
207
+ @visit = Visibility::Visit.new(@schema) do |member|
208
+ if member.is_a?(Module)
209
+ type_name = member.graphql_name
210
+ if (prev_t = @types[type_name])
211
+ if prev_t.is_a?(Array)
212
+ prev_t << member
213
+ else
214
+ @types[type_name] = [member, prev_t]
215
+ end
216
+ else
217
+ @types[member.graphql_name] = member
218
+ end
219
+ if member < GraphQL::Schema::Directive
220
+ @directives << member
221
+ elsif member.respond_to?(:interface_type_memberships)
222
+ member.interface_type_memberships.each do |itm|
223
+ @references[itm.abstract_type] << member
224
+ @interface_type_memberships[itm.abstract_type] << itm
225
+ end
226
+ elsif member < GraphQL::Schema::Union
227
+ @unions_for_references << member
228
+ end
229
+ elsif member.is_a?(GraphQL::Schema::Argument)
230
+ member.validate_default_value
231
+ @references[member.type.unwrap] << member
232
+ elsif member.is_a?(GraphQL::Schema::Field)
233
+ @references[member.type.unwrap] << member
234
+ end
235
+ true
236
+ end
237
+
238
+ @schema.root_types.each do |t|
239
+ @references[t] << true
240
+ end
241
+
242
+ @schema.introspection_system.types.each_value do |t|
243
+ @references[t] << true
244
+ end
245
+ @visit.visit_each(types: []) # visit default directives
246
+ end
247
+
248
+ if types
249
+ @visit.visit_each(types: types, directives: [])
250
+ elsif @loaded_all == false
251
+ @loaded_all = true
252
+ @visit.visit_each
253
+ else
254
+ # already loaded all
255
+ return
256
+ end
257
+
258
+ # TODO: somehow don't iterate over all these,
259
+ # only the ones that may have been modified
260
+ @interface_type_memberships.each do |int_type, type_memberships|
261
+ referers = @references[int_type].select { |r| r.is_a?(GraphQL::Schema::Field) }
262
+ if referers.any?
263
+ type_memberships.each do |type_membership|
264
+ implementor_type = type_membership.object_type
265
+ # Add new items only:
266
+ @references[implementor_type] |= referers
267
+ end
268
+ end
269
+ end
270
+
271
+ @unions_for_references.each do |union_type|
272
+ refs = @references[union_type]
273
+ union_type.all_possible_types.each do |object_type|
274
+ @references[object_type] |= refs # Add new items
275
+ end
276
+ end
140
277
  end
141
278
  end
142
279
  end
@@ -446,7 +446,12 @@ module GraphQL
446
446
  raise GraphQL::Error, "Second definition of `query(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@query_object.inspect}"
447
447
  elsif use_visibility_profile?
448
448
  if block_given?
449
- @query_object = lazy_load_block
449
+ if visibility.preload?
450
+ @query_object = lazy_load_block.call
451
+ self.visibility.query_configured(@query_object)
452
+ else
453
+ @query_object = lazy_load_block
454
+ end
450
455
  else
451
456
  @query_object = new_query_object
452
457
  self.visibility.query_configured(@query_object)
@@ -480,7 +485,12 @@ module GraphQL
480
485
  raise GraphQL::Error, "Second definition of `mutation(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@mutation_object.inspect}"
481
486
  elsif use_visibility_profile?
482
487
  if block_given?
483
- @mutation_object = lazy_load_block
488
+ if visibility.preload?
489
+ @mutation_object = lazy_load_block.call
490
+ self.visibility.mutation_configured(@mutation_object)
491
+ else
492
+ @mutation_object = lazy_load_block
493
+ end
484
494
  else
485
495
  @mutation_object = new_mutation_object
486
496
  self.visibility.mutation_configured(@mutation_object)
@@ -492,7 +502,7 @@ module GraphQL
492
502
  nil
493
503
  elsif @mutation_object.is_a?(Proc)
494
504
  @mutation_object = @mutation_object.call
495
- self.visibility&.mutation_configured(@query_object)
505
+ self.visibility&.mutation_configured(@mutation_object)
496
506
  @mutation_object
497
507
  else
498
508
  @mutation_object || find_inherited_value(:mutation)
@@ -514,7 +524,12 @@ module GraphQL
514
524
  raise GraphQL::Error, "Second definition of `subscription(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@subscription_object.inspect}"
515
525
  elsif use_visibility_profile?
516
526
  if block_given?
517
- @subscription_object = lazy_load_block
527
+ if visibility.preload?
528
+ @subscription_object = lazy_load_block.call
529
+ visibility.subscription_configured(@subscription_object)
530
+ else
531
+ @subscription_object = lazy_load_block
532
+ end
518
533
  else
519
534
  @subscription_object = new_subscription_object
520
535
  self.visibility.subscription_configured(@subscription_object)
@@ -29,6 +29,7 @@ module GraphQL
29
29
  @visitor = visitor_class.new(document, self)
30
30
  end
31
31
 
32
+ # TODO stop using def_delegators because of Array allocations
32
33
  def_delegators :@visitor,
33
34
  :path, :type_definition, :field_definition, :argument_definition,
34
35
  :parent_type_definition, :directive_definition, :object_types, :dependencies
@@ -92,7 +92,7 @@ module GraphQL
92
92
  end
93
93
  graphql_result
94
94
  else
95
- unfiltered_type = Schema::Visibility::Profile.null_profile(schema: schema, context: context).type(type_name)
95
+ unfiltered_type = schema.use_visibility_profile? ? schema.visibility.get_type(type_name) : schema.get_type(type_name) # rubocop:disable ContextIsPassedCop
96
96
  if unfiltered_type
97
97
  raise TypeNotVisibleError.new(type_name: type_name)
98
98
  else
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "2.4.2"
3
+ VERSION = "2.4.4"
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.2
4
+ version: 2.4.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-11-07 00:00:00.000000000 Z
11
+ date: 2024-11-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -504,6 +504,7 @@ files:
504
504
  - lib/graphql/schema/visibility.rb
505
505
  - lib/graphql/schema/visibility/migration.rb
506
506
  - lib/graphql/schema/visibility/profile.rb
507
+ - lib/graphql/schema/visibility/visit.rb
507
508
  - lib/graphql/schema/warden.rb
508
509
  - lib/graphql/schema/wrapper.rb
509
510
  - lib/graphql/static_validation.rb