graphql 2.3.10 → 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.
- checksums.yaml +4 -4
- data/lib/generators/graphql/install_generator.rb +46 -0
- data/lib/graphql/current.rb +52 -0
- data/lib/graphql/dataloader.rb +4 -1
- data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
- data/lib/graphql/execution/interpreter/runtime.rb +2 -8
- data/lib/graphql/execution/interpreter.rb +2 -0
- data/lib/graphql/introspection/entry_points.rb +2 -2
- data/lib/graphql/introspection/schema_type.rb +1 -1
- data/lib/graphql/language/nodes.rb +2 -2
- data/lib/graphql/query/context.rb +4 -2
- data/lib/graphql/query/null_context.rb +0 -4
- data/lib/graphql/query.rb +2 -6
- data/lib/graphql/schema/build_from_definition.rb +8 -1
- data/lib/graphql/schema/directive/flagged.rb +1 -1
- data/lib/graphql/schema/field/connection_extension.rb +1 -1
- data/lib/graphql/schema/interface.rb +20 -4
- data/lib/graphql/schema/introspection_system.rb +3 -2
- data/lib/graphql/schema/member/has_arguments.rb +7 -3
- data/lib/graphql/schema/subset.rb +215 -102
- data/lib/graphql/schema/types_migration.rb +185 -0
- data/lib/graphql/schema/warden.rb +4 -7
- data/lib/graphql/schema.rb +30 -22
- data/lib/graphql/testing/helpers.rb +6 -3
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +1 -0
- 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
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
@
|
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
|
-
@
|
51
|
-
|
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
|
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
|
-
|
83
|
-
|
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
|
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
|
-
|
227
|
+
@cached_fields[owner]
|
144
228
|
end
|
145
229
|
|
146
230
|
def arguments(owner)
|
147
|
-
|
231
|
+
@cached_arguments[owner]
|
148
232
|
end
|
149
233
|
|
150
234
|
def argument(owner, arg_name)
|
151
|
-
|
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
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
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
|
-
|
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
|
-
|
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
|
-
@
|
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]
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
341
|
-
|
342
|
-
|
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
|
-
|
351
|
-
|
352
|
-
|
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
|
-
|
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(
|
79
|
-
NullWarden.new(context:
|
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
|
-
|
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
|
-
|
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
|