graphql 2.3.14 → 2.3.15

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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/orm_mutations_base.rb +1 -1
  3. data/lib/generators/graphql/templates/base_resolver.erb +2 -0
  4. data/lib/generators/graphql/type_generator.rb +1 -1
  5. data/lib/graphql/analysis.rb +1 -1
  6. data/lib/graphql/execution/interpreter/resolve.rb +10 -6
  7. data/lib/graphql/language/comment.rb +18 -0
  8. data/lib/graphql/language/document_from_schema_definition.rb +38 -4
  9. data/lib/graphql/language/lexer.rb +15 -12
  10. data/lib/graphql/language/nodes.rb +22 -14
  11. data/lib/graphql/language/parser.rb +5 -0
  12. data/lib/graphql/language/printer.rb +23 -7
  13. data/lib/graphql/language.rb +6 -5
  14. data/lib/graphql/query.rb +1 -1
  15. data/lib/graphql/schema/argument.rb +13 -1
  16. data/lib/graphql/schema/enum.rb +1 -0
  17. data/lib/graphql/schema/enum_value.rb +9 -1
  18. data/lib/graphql/schema/field.rb +23 -3
  19. data/lib/graphql/schema/interface.rb +1 -0
  20. data/lib/graphql/schema/member/base_dsl_methods.rb +15 -0
  21. data/lib/graphql/schema/member/has_arguments.rb +2 -2
  22. data/lib/graphql/schema/member/has_fields.rb +2 -2
  23. data/lib/graphql/schema/resolver.rb +3 -4
  24. data/lib/graphql/schema/visibility/migration.rb +188 -0
  25. data/lib/graphql/schema/visibility/subset.rb +509 -0
  26. data/lib/graphql/schema/visibility.rb +30 -0
  27. data/lib/graphql/schema.rb +46 -41
  28. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
  29. data/lib/graphql/testing/helpers.rb +1 -1
  30. data/lib/graphql/tracing/notifications_trace.rb +2 -2
  31. data/lib/graphql/version.rb +1 -1
  32. metadata +6 -4
  33. data/lib/graphql/schema/subset.rb +0 -509
  34. data/lib/graphql/schema/types_migration.rb +0 -187
@@ -0,0 +1,509 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Visibility
6
+ # This class filters the types, fields, arguments, enum values, and directives in a schema
7
+ # based on the given `context`.
8
+ #
9
+ # It's like {Warden}, but has some differences:
10
+ #
11
+ # - It doesn't use {Schema}'s top-level caches (eg {Schema.references_to}, {Schema.possible_types}, {Schema.types})
12
+ # - 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.)
13
+ # - It checks `.visible?` on root introspection types
14
+ #
15
+ # In the future, {Subset} will support lazy-loading types as needed during execution and multi-request caching of subsets.
16
+ class Subset
17
+ # @return [Schema::Visibility::Subset]
18
+ def self.from_context(ctx, schema)
19
+ if ctx.respond_to?(:types) && (types = ctx.types).is_a?(self)
20
+ types
21
+ else
22
+ # TODO use a cached instance from the schema
23
+ self.new(context: ctx, schema: schema)
24
+ end
25
+ end
26
+
27
+ def self.pass_thru(context:, schema:)
28
+ subset = self.new(context: context, schema: schema)
29
+ subset.instance_variable_set(:@cached_visible, Hash.new { |h,k| h[k] = true })
30
+ subset
31
+ end
32
+
33
+ def initialize(context:, schema:)
34
+ @context = context
35
+ @schema = schema
36
+ @all_types = {}
37
+ @all_types_loaded = false
38
+ @unvisited_types = []
39
+ @referenced_types = Hash.new { |h, type_defn| h[type_defn] = [] }.compare_by_identity
40
+ @cached_directives = {}
41
+ @all_directives = nil
42
+ @cached_visible = Hash.new { |h, member|
43
+ h[member] = @schema.visible?(member, @context)
44
+ }.compare_by_identity
45
+
46
+ @cached_visible_fields = Hash.new { |h, owner|
47
+ h[owner] = Hash.new do |h2, field|
48
+ h2[field] = if @cached_visible[field] &&
49
+ (ret_type = field.type.unwrap) &&
50
+ @cached_visible[ret_type] &&
51
+ (owner == field.owner || (!owner.kind.object?) || field_on_visible_interface?(field, owner))
52
+
53
+ if !field.introspection?
54
+ # The problem is that some introspection fields may have references
55
+ # to non-custom introspection types.
56
+ # If those were added here, they'd cause a DuplicateNamesError.
57
+ # This is basically a bug -- those fields _should_ reference the custom types.
58
+ add_type(ret_type, field)
59
+ end
60
+ true
61
+ else
62
+ false
63
+ end
64
+ end.compare_by_identity
65
+ }.compare_by_identity
66
+
67
+ @cached_visible_arguments = Hash.new do |h, arg|
68
+ h[arg] = if @cached_visible[arg] && (arg_type = arg.type.unwrap) && @cached_visible[arg_type]
69
+ add_type(arg_type, arg)
70
+ true
71
+ else
72
+ false
73
+ end
74
+ end.compare_by_identity
75
+
76
+ @cached_parent_fields = Hash.new do |h, type|
77
+ h[type] = Hash.new do |h2, field_name|
78
+ h2[field_name] = type.get_field(field_name, @context)
79
+ end
80
+ end.compare_by_identity
81
+
82
+ @cached_parent_arguments = Hash.new do |h, arg_owner|
83
+ h[arg_owner] = Hash.new do |h2, arg_name|
84
+ h2[arg_name] = arg_owner.get_argument(arg_name, @context)
85
+ end
86
+ end.compare_by_identity
87
+
88
+ @cached_possible_types = Hash.new do |h, type|
89
+ h[type] = case type.kind.name
90
+ when "INTERFACE"
91
+ load_all_types
92
+ pts = []
93
+ @unfiltered_interface_type_memberships[type].each { |itm|
94
+ if @cached_visible[itm] && (ot = itm.object_type) && @cached_visible[ot] && referenced?(ot)
95
+ pts << ot
96
+ end
97
+ }
98
+ pts
99
+ when "UNION"
100
+ pts = []
101
+ type.type_memberships.each { |tm|
102
+ if @cached_visible[tm] &&
103
+ (ot = tm.object_type) &&
104
+ @cached_visible[ot] &&
105
+ referenced?(ot)
106
+ pts << ot
107
+ end
108
+ }
109
+ pts
110
+ when "OBJECT"
111
+ load_all_types
112
+ if @all_types[type.graphql_name] == type
113
+ [type]
114
+ else
115
+ EmptyObjects::EMPTY_ARRAY
116
+ end
117
+ else
118
+ GraphQL::EmptyObjects::EMPTY_ARRAY
119
+ end
120
+ end.compare_by_identity
121
+
122
+ @cached_enum_values = Hash.new do |h, enum_t|
123
+ values = non_duplicate_items(enum_t.all_enum_value_definitions, @cached_visible)
124
+ if values.size == 0
125
+ raise GraphQL::Schema::Enum::MissingValuesError.new(enum_t)
126
+ end
127
+ h[enum_t] = values
128
+ end.compare_by_identity
129
+
130
+ @cached_fields = Hash.new do |h, owner|
131
+ h[owner] = non_duplicate_items(owner.all_field_definitions.each(&:ensure_loaded), @cached_visible_fields[owner])
132
+ end.compare_by_identity
133
+
134
+ @cached_arguments = Hash.new do |h, owner|
135
+ h[owner] = non_duplicate_items(owner.all_argument_definitions, @cached_visible_arguments)
136
+ end.compare_by_identity
137
+ end
138
+
139
+ def field_on_visible_interface?(field, owner)
140
+ ints = owner.interface_type_memberships.map(&:abstract_type)
141
+ field_name = field.graphql_name
142
+ filtered_ints = interfaces(owner)
143
+ any_interface_has_field = false
144
+ any_interface_has_visible_field = false
145
+ ints.each do |int_t|
146
+ if (_int_f_defn = @cached_parent_fields[int_t][field_name])
147
+ any_interface_has_field = true
148
+
149
+ if filtered_ints.include?(int_t) # TODO cycles, or maybe not necessary since previously checked? && @cached_visible_fields[owner][field]
150
+ any_interface_has_visible_field = true
151
+ break
152
+ end
153
+ end
154
+ end
155
+
156
+ if any_interface_has_field
157
+ any_interface_has_visible_field
158
+ else
159
+ true
160
+ end
161
+ end
162
+
163
+ def type(type_name)
164
+ t = if (loaded_t = @all_types[type_name])
165
+ loaded_t
166
+ elsif !@all_types_loaded
167
+ load_all_types
168
+ @all_types[type_name]
169
+ end
170
+ if t
171
+ if t.is_a?(Array)
172
+ vis_t = nil
173
+ t.each do |t_defn|
174
+ if @cached_visible[t_defn]
175
+ if vis_t.nil?
176
+ vis_t = t_defn
177
+ else
178
+ raise_duplicate_definition(vis_t, t_defn)
179
+ end
180
+ end
181
+ end
182
+ vis_t
183
+ else
184
+ if t && @cached_visible[t]
185
+ t
186
+ else
187
+ nil
188
+ end
189
+ end
190
+ end
191
+ end
192
+
193
+ def field(owner, field_name)
194
+ f = if owner.kind.fields? && (field = @cached_parent_fields[owner][field_name])
195
+ field
196
+ elsif owner == query_root && (entry_point_field = @schema.introspection_system.entry_point(name: field_name))
197
+ entry_point_field
198
+ elsif (dynamic_field = @schema.introspection_system.dynamic_field(name: field_name))
199
+ dynamic_field
200
+ else
201
+ nil
202
+ end
203
+ if f.is_a?(Array)
204
+ visible_f = nil
205
+ f.each do |f_defn|
206
+ if @cached_visible_fields[owner][f_defn]
207
+
208
+ if visible_f.nil?
209
+ visible_f = f_defn
210
+ else
211
+ raise_duplicate_definition(visible_f, f_defn)
212
+ end
213
+ end
214
+ end
215
+ visible_f.ensure_loaded
216
+ elsif f && @cached_visible_fields[owner][f.ensure_loaded]
217
+ f
218
+ else
219
+ nil
220
+ end
221
+ end
222
+
223
+ def fields(owner)
224
+ @cached_fields[owner]
225
+ end
226
+
227
+ def arguments(owner)
228
+ @cached_arguments[owner]
229
+ end
230
+
231
+ def argument(owner, arg_name)
232
+ arg = @cached_parent_arguments[owner][arg_name]
233
+ if arg.is_a?(Array)
234
+ visible_arg = nil
235
+ arg.each do |arg_defn|
236
+ if @cached_visible_arguments[arg_defn]
237
+ if visible_arg.nil?
238
+ visible_arg = arg_defn
239
+ else
240
+ raise_duplicate_definition(visible_arg, arg_defn)
241
+ end
242
+ end
243
+ end
244
+ visible_arg
245
+ else
246
+ if arg && @cached_visible_arguments[arg]
247
+ arg
248
+ else
249
+ nil
250
+ end
251
+ end
252
+ end
253
+
254
+ def possible_types(type)
255
+ @cached_possible_types[type]
256
+ end
257
+
258
+ def interfaces(obj_or_int_type)
259
+ ints = obj_or_int_type.interface_type_memberships
260
+ .select { |itm| @cached_visible[itm] && @cached_visible[itm.abstract_type] }
261
+ .map!(&:abstract_type)
262
+ ints.uniq! # Remove any duplicate interfaces implemented via other interfaces
263
+ ints
264
+ end
265
+
266
+ def query_root
267
+ add_if_visible(@schema.query)
268
+ end
269
+
270
+ def mutation_root
271
+ add_if_visible(@schema.mutation)
272
+ end
273
+
274
+ def subscription_root
275
+ add_if_visible(@schema.subscription)
276
+ end
277
+
278
+ def all_types
279
+ load_all_types
280
+ @all_types.values
281
+ end
282
+
283
+ def all_types_h
284
+ load_all_types
285
+ @all_types
286
+ end
287
+
288
+ def enum_values(owner)
289
+ @cached_enum_values[owner]
290
+ end
291
+
292
+ def directive_exists?(dir_name)
293
+ if (dir = @schema.directives[dir_name]) && @cached_visible[dir]
294
+ !!dir
295
+ else
296
+ load_all_types
297
+ !!@cached_directives[dir_name]
298
+ end
299
+ end
300
+
301
+ def directives
302
+ @all_directives ||= begin
303
+ load_all_types
304
+ dirs = []
305
+ @schema.directives.each do |name, dir_defn|
306
+ if !@cached_directives[name] && @cached_visible[dir_defn]
307
+ dirs << dir_defn
308
+ end
309
+ end
310
+ dirs.concat(@cached_directives.values)
311
+ end
312
+ end
313
+
314
+ def loadable?(t, _ctx)
315
+ !@all_types[t.graphql_name] && @cached_visible[t]
316
+ end
317
+
318
+ def loaded_types
319
+ @all_types.values
320
+ end
321
+
322
+ def reachable_type?(name)
323
+ load_all_types
324
+ !!@all_types[name]
325
+ end
326
+
327
+ private
328
+
329
+ def add_if_visible(t)
330
+ (t && @cached_visible[t]) ? (add_type(t, true); t) : nil
331
+ end
332
+
333
+ def add_type(t, by_member)
334
+ if t && @cached_visible[t]
335
+ n = t.graphql_name
336
+ if (prev_t = @all_types[n])
337
+ if !prev_t.equal?(t)
338
+ raise_duplicate_definition(prev_t, t)
339
+ end
340
+ false
341
+ else
342
+ @referenced_types[t] << by_member
343
+ @all_types[n] = t
344
+ @unvisited_types << t
345
+ true
346
+ end
347
+ else
348
+ false
349
+ end
350
+ end
351
+
352
+ def non_duplicate_items(definitions, visibility_cache)
353
+ non_dups = []
354
+ definitions.each do |defn|
355
+ if visibility_cache[defn]
356
+ if (dup_defn = non_dups.find { |d| d.graphql_name == defn.graphql_name })
357
+ raise_duplicate_definition(dup_defn, defn)
358
+ end
359
+ non_dups << defn
360
+ end
361
+ end
362
+ non_dups
363
+ end
364
+
365
+ def raise_duplicate_definition(first_defn, second_defn)
366
+ raise DuplicateNamesError.new(duplicated_name: first_defn.path, duplicated_definition_1: first_defn.inspect, duplicated_definition_2: second_defn.inspect)
367
+ end
368
+
369
+ def referenced?(t)
370
+ load_all_types
371
+ @referenced_types[t].any? { |reference| (reference == true) || @cached_visible[reference] }
372
+ end
373
+
374
+ def load_all_types
375
+ return if @all_types_loaded
376
+ @all_types_loaded = true
377
+ entry_point_types = [
378
+ query_root,
379
+ mutation_root,
380
+ subscription_root,
381
+ *@schema.introspection_system.types.values,
382
+ ]
383
+
384
+ # Don't include any orphan_types whose interfaces aren't visible.
385
+ @schema.orphan_types.each do |orphan_type|
386
+ if @cached_visible[orphan_type] &&
387
+ orphan_type.interface_type_memberships.any? { |tm| @cached_visible[tm] && @cached_visible[tm.abstract_type] }
388
+ entry_point_types << orphan_type
389
+ end
390
+ end
391
+
392
+ @schema.directives.each do |_dir_name, dir_class|
393
+ if @cached_visible[dir_class]
394
+ arguments(dir_class).each do |arg|
395
+ entry_point_types << arg.type.unwrap
396
+ end
397
+ end
398
+ end
399
+
400
+ entry_point_types.compact! # TODO why is this necessary?!
401
+ entry_point_types.flatten! # handle multiple defns
402
+ entry_point_types.each { |t| add_type(t, true) }
403
+
404
+ @unfiltered_interface_type_memberships = Hash.new { |h, k| h[k] = [] }.compare_by_identity
405
+ @add_possible_types = Set.new
406
+
407
+ while @unvisited_types.any?
408
+ while t = @unvisited_types.pop
409
+ # These have already been checked for `.visible?`
410
+ visit_type(t)
411
+ end
412
+ @add_possible_types.each do |int_t|
413
+ itms = @unfiltered_interface_type_memberships[int_t]
414
+ itms.each do |itm|
415
+ if @cached_visible[itm] && (obj_type = itm.object_type) && @cached_visible[obj_type]
416
+ add_type(obj_type, itm)
417
+ end
418
+ end
419
+ end
420
+ @add_possible_types.clear
421
+ end
422
+
423
+ @all_types.delete_if { |type_name, type_defn| !referenced?(type_defn) }
424
+ nil
425
+ end
426
+
427
+ def visit_type(type)
428
+ visit_directives(type)
429
+ case type.kind.name
430
+ when "OBJECT", "INTERFACE"
431
+ if type.kind.object?
432
+ type.interface_type_memberships.each do |itm|
433
+ @unfiltered_interface_type_memberships[itm.abstract_type] << itm
434
+ end
435
+ # recurse into visible implemented interfaces
436
+ interfaces(type).each do |interface|
437
+ add_type(interface, type)
438
+ end
439
+ else
440
+ type.orphan_types.each { |t| add_type(t, type)}
441
+ end
442
+
443
+ # recurse into visible fields
444
+ t_f = type.all_field_definitions
445
+ t_f.each do |field|
446
+ field.ensure_loaded
447
+ if @cached_visible[field]
448
+ visit_directives(field)
449
+ field_type = field.type.unwrap
450
+ if field_type.kind.interface?
451
+ @add_possible_types.add(field_type)
452
+ end
453
+ add_type(field_type, field)
454
+
455
+ # recurse into visible arguments
456
+ arguments(field).each do |argument|
457
+ visit_directives(argument)
458
+ add_type(argument.type.unwrap, argument)
459
+ end
460
+ end
461
+ end
462
+ when "INPUT_OBJECT"
463
+ # recurse into visible arguments
464
+ arguments(type).each do |argument|
465
+ visit_directives(argument)
466
+ add_type(argument.type.unwrap, argument)
467
+ end
468
+ when "UNION"
469
+ # recurse into visible possible types
470
+ type.type_memberships.each do |tm|
471
+ if @cached_visible[tm]
472
+ obj_t = tm.object_type
473
+ if obj_t.is_a?(String)
474
+ obj_t = Member::BuildType.constantize(obj_t)
475
+ tm.object_type = obj_t
476
+ end
477
+ if @cached_visible[obj_t]
478
+ add_type(obj_t, tm)
479
+ end
480
+ end
481
+ end
482
+ when "ENUM"
483
+ enum_values(type).each do |val|
484
+ visit_directives(val)
485
+ end
486
+ when "SCALAR"
487
+ # pass
488
+ end
489
+ end
490
+
491
+ def visit_directives(member)
492
+ member.directives.each { |dir|
493
+ dir_class = dir.class
494
+ if @cached_visible[dir_class]
495
+ dir_name = dir_class.graphql_name
496
+ if (existing_dir = @cached_directives[dir_name])
497
+ if existing_dir != dir_class
498
+ raise ArgumentError, "Two directives for `@#{dir_name}`: #{existing_dir}, #{dir.class}"
499
+ end
500
+ else
501
+ @cached_directives[dir.graphql_name] = dir_class
502
+ end
503
+ end
504
+ }
505
+ end
506
+ end
507
+ end
508
+ end
509
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ require "graphql/schema/visibility/subset"
3
+ require "graphql/schema/visibility/migration"
4
+
5
+ module GraphQL
6
+ class Schema
7
+ class Visibility
8
+ def self.use(schema, preload: nil, migration_errors: false)
9
+ schema.visibility = self.new(schema, preload: preload)
10
+ schema.use_schema_visibility = true
11
+ if migration_errors
12
+ schema.subset_class = Migration
13
+ end
14
+ end
15
+
16
+ def initialize(schema, preload:)
17
+ @schema = schema
18
+ @cached_subsets = {}
19
+
20
+ if preload.nil? && defined?(Rails) && Rails.env.production?
21
+ preload = true
22
+ end
23
+
24
+ if preload
25
+
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end