graphql 2.1.1 → 2.1.5

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/graphql/analysis/ast/query_depth.rb +7 -2
  3. data/lib/graphql/dataloader.rb +29 -10
  4. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +1 -1
  5. data/lib/graphql/execution/interpreter/runtime.rb +7 -26
  6. data/lib/graphql/execution/lookahead.rb +87 -20
  7. data/lib/graphql/load_application_object_failed_error.rb +5 -1
  8. data/lib/graphql/pagination/connection.rb +15 -10
  9. data/lib/graphql/pagination/mongoid_relation_connection.rb +1 -2
  10. data/lib/graphql/query/context.rb +4 -0
  11. data/lib/graphql/query/null_context.rb +2 -10
  12. data/lib/graphql/query.rb +10 -0
  13. data/lib/graphql/schema/build_from_definition.rb +0 -11
  14. data/lib/graphql/schema/directive/one_of.rb +12 -0
  15. data/lib/graphql/schema/directive.rb +1 -1
  16. data/lib/graphql/schema/enum.rb +2 -2
  17. data/lib/graphql/schema/field/scope_extension.rb +4 -3
  18. data/lib/graphql/schema/field.rb +2 -2
  19. data/lib/graphql/schema/has_single_input_argument.rb +2 -2
  20. data/lib/graphql/schema/input_object.rb +1 -1
  21. data/lib/graphql/schema/interface.rb +10 -10
  22. data/lib/graphql/schema/loader.rb +0 -2
  23. data/lib/graphql/schema/member/has_arguments.rb +47 -36
  24. data/lib/graphql/schema/member/has_fields.rb +4 -4
  25. data/lib/graphql/schema/member/has_interfaces.rb +2 -2
  26. data/lib/graphql/schema/member/validates_input.rb +3 -3
  27. data/lib/graphql/schema/resolver.rb +3 -3
  28. data/lib/graphql/schema/union.rb +1 -1
  29. data/lib/graphql/schema/warden.rb +73 -57
  30. data/lib/graphql/schema.rb +154 -43
  31. data/lib/graphql/subscriptions/event.rb +1 -1
  32. data/lib/graphql/subscriptions.rb +2 -2
  33. data/lib/graphql/version.rb +1 -1
  34. metadata +17 -17
@@ -20,6 +20,15 @@ module GraphQL
20
20
  # - Added as class methods to this interface
21
21
  # - Added as class methods to all child interfaces
22
22
  def definition_methods(&block)
23
+ # Use an instance variable to tell whether it's been included previously or not;
24
+ # You can't use constant detection because constants are brought into scope
25
+ # by `include`, which has already happened at this point.
26
+ if !defined?(@_definition_methods)
27
+ defn_methods_module = Module.new
28
+ @_definition_methods = defn_methods_module
29
+ const_set(:DefinitionMethods, defn_methods_module)
30
+ extend(self::DefinitionMethods)
31
+ end
23
32
  self::DefinitionMethods.module_eval(&block)
24
33
  end
25
34
 
@@ -47,20 +56,11 @@ module GraphQL
47
56
 
48
57
  child_class.type_membership_class(self.type_membership_class)
49
58
  child_class.ancestors.reverse_each do |ancestor|
50
- if ancestor.const_defined?(:DefinitionMethods)
59
+ if ancestor.const_defined?(:DefinitionMethods) && ancestor != child_class
51
60
  child_class.extend(ancestor::DefinitionMethods)
52
61
  end
53
62
  end
54
63
 
55
- # Use an instance variable to tell whether it's been included previously or not;
56
- # You can't use constant detection because constants are brought into scope
57
- # by `include`, which has already happened at this point.
58
- if !child_class.instance_variable_defined?(:@_definition_methods)
59
- defn_methods_module = Module.new
60
- child_class.instance_variable_set(:@_definition_methods, defn_methods_module)
61
- child_class.const_set(:DefinitionMethods, defn_methods_module)
62
- child_class.extend(child_class::DefinitionMethods)
63
- end
64
64
  child_class.introspection(introspection)
65
65
  child_class.description(description)
66
66
  # If interfaces are mixed into each other, only define this class once
@@ -176,7 +176,6 @@ module GraphQL
176
176
  while (of_type = unwrapped_field_hash["ofType"])
177
177
  unwrapped_field_hash = of_type
178
178
  end
179
- type_name = unwrapped_field_hash["name"]
180
179
 
181
180
  type_defn.field(
182
181
  field_hash["name"],
@@ -186,7 +185,6 @@ module GraphQL
186
185
  null: true,
187
186
  camelize: false,
188
187
  connection_extension: nil,
189
- connection: type_name.end_with?("Connection"),
190
188
  ) do
191
189
  if field_hash["args"].any?
192
190
  loader.build_arguments(self, field_hash["args"], type_resolver)
@@ -109,7 +109,7 @@ module GraphQL
109
109
  end
110
110
 
111
111
  # @return [Hash<String => GraphQL::Schema::Argument] Arguments defined on this thing, keyed by name. Includes inherited definitions
112
- def arguments(context = GraphQL::Query::NullContext)
112
+ def arguments(context = GraphQL::Query::NullContext.instance)
113
113
  if own_arguments.any?
114
114
  own_arguments_that_apply = {}
115
115
  own_arguments.each do |name, args_entry|
@@ -133,7 +133,7 @@ module GraphQL
133
133
  end
134
134
 
135
135
  module InheritedArguments
136
- def arguments(context = GraphQL::Query::NullContext)
136
+ def arguments(context = GraphQL::Query::NullContext.instance)
137
137
  own_arguments = super
138
138
  inherited_arguments = superclass.arguments(context)
139
139
 
@@ -166,7 +166,7 @@ module GraphQL
166
166
  end
167
167
 
168
168
 
169
- def get_argument(argument_name, context = GraphQL::Query::NullContext)
169
+ def get_argument(argument_name, context = GraphQL::Query::NullContext.instance)
170
170
  warden = Warden.from_context(context)
171
171
  for ancestor in ancestors
172
172
  if ancestor.respond_to?(:own_arguments) &&
@@ -181,7 +181,7 @@ module GraphQL
181
181
  end
182
182
 
183
183
  module FieldConfigured
184
- def arguments(context = GraphQL::Query::NullContext)
184
+ def arguments(context = GraphQL::Query::NullContext.instance)
185
185
  own_arguments = super
186
186
  if @resolver_class
187
187
  inherited_arguments = @resolver_class.field_arguments(context)
@@ -236,7 +236,7 @@ module GraphQL
236
236
  end
237
237
 
238
238
  # @return [GraphQL::Schema::Argument, nil] Argument defined on this thing, fetched by name.
239
- def get_argument(argument_name, context = GraphQL::Query::NullContext)
239
+ def get_argument(argument_name, context = GraphQL::Query::NullContext.instance)
240
240
  warden = Warden.from_context(context)
241
241
  if (arg_config = own_arguments[argument_name]) && (visible_arg = Warden.visible_entry?(:visible_argument?, arg_config, context, warden))
242
242
  visible_arg
@@ -379,41 +379,52 @@ module GraphQL
379
379
  def authorize_application_object(argument, id, context, loaded_application_object)
380
380
  context.query.after_lazy(loaded_application_object) do |application_object|
381
381
  if application_object.nil?
382
- err = GraphQL::LoadApplicationObjectFailedError.new(argument: argument, id: id, object: application_object)
383
- load_application_object_failed(err)
382
+ err = GraphQL::LoadApplicationObjectFailedError.new(context: context, argument: argument, id: id, object: application_object)
383
+ application_object = load_application_object_failed(err)
384
384
  end
385
385
  # Double-check that the located object is actually of this type
386
386
  # (Don't want to allow arbitrary access to objects this way)
387
- maybe_lazy_resolve_type = context.schema.resolve_type(argument.loads, application_object, context)
388
- context.query.after_lazy(maybe_lazy_resolve_type) do |resolve_type_result|
389
- if resolve_type_result.is_a?(Array) && resolve_type_result.size == 2
390
- application_object_type, application_object = resolve_type_result
391
- else
392
- application_object_type = resolve_type_result
393
- # application_object is already assigned
394
- end
395
- possible_object_types = context.warden.possible_types(argument.loads)
396
- if !possible_object_types.include?(application_object_type)
397
- err = GraphQL::LoadApplicationObjectFailedError.new(argument: argument, id: id, object: application_object)
398
- load_application_object_failed(err)
399
- else
400
- # This object was loaded successfully
401
- # and resolved to the right type,
402
- # now apply the `.authorized?` class method if there is one
403
- context.query.after_lazy(application_object_type.authorized?(application_object, context)) do |authed|
404
- if authed
405
- application_object
406
- else
407
- err = GraphQL::UnauthorizedError.new(
408
- object: application_object,
409
- type: application_object_type,
410
- context: context,
411
- )
412
- if self.respond_to?(:unauthorized_object)
413
- err.set_backtrace(caller)
414
- unauthorized_object(err)
387
+ if application_object.nil?
388
+ nil
389
+ else
390
+ maybe_lazy_resolve_type = context.schema.resolve_type(argument.loads, application_object, context)
391
+ context.query.after_lazy(maybe_lazy_resolve_type) do |resolve_type_result|
392
+ if resolve_type_result.is_a?(Array) && resolve_type_result.size == 2
393
+ application_object_type, application_object = resolve_type_result
394
+ else
395
+ application_object_type = resolve_type_result
396
+ # application_object is already assigned
397
+ end
398
+
399
+ if !(
400
+ context.warden.possible_types(argument.loads).include?(application_object_type) ||
401
+ context.warden.loadable?(argument.loads, context)
402
+ )
403
+ err = GraphQL::LoadApplicationObjectFailedError.new(context: context, argument: argument, id: id, object: application_object)
404
+ application_object = load_application_object_failed(err)
405
+ end
406
+
407
+ if application_object.nil?
408
+ nil
409
+ else
410
+ # This object was loaded successfully
411
+ # and resolved to the right type,
412
+ # now apply the `.authorized?` class method if there is one
413
+ context.query.after_lazy(application_object_type.authorized?(application_object, context)) do |authed|
414
+ if authed
415
+ application_object
415
416
  else
416
- raise err
417
+ err = GraphQL::UnauthorizedError.new(
418
+ object: application_object,
419
+ type: application_object_type,
420
+ context: context,
421
+ )
422
+ if self.respond_to?(:unauthorized_object)
423
+ err.set_backtrace(caller)
424
+ unauthorized_object(err)
425
+ else
426
+ raise err
427
+ end
417
428
  end
418
429
  end
419
430
  end
@@ -97,7 +97,7 @@ module GraphQL
97
97
  end
98
98
 
99
99
  module InterfaceMethods
100
- def get_field(field_name, context = GraphQL::Query::NullContext)
100
+ def get_field(field_name, context = GraphQL::Query::NullContext.instance)
101
101
  warden = Warden.from_context(context)
102
102
  for ancestor in ancestors
103
103
  if ancestor.respond_to?(:own_fields) &&
@@ -110,7 +110,7 @@ module GraphQL
110
110
  end
111
111
 
112
112
  # @return [Hash<String => GraphQL::Schema::Field>] Fields on this object, keyed by name, including inherited fields
113
- def fields(context = GraphQL::Query::NullContext)
113
+ def fields(context = GraphQL::Query::NullContext.instance)
114
114
  warden = Warden.from_context(context)
115
115
  # Local overrides take precedence over inherited fields
116
116
  visible_fields = {}
@@ -130,7 +130,7 @@ module GraphQL
130
130
  end
131
131
 
132
132
  module ObjectMethods
133
- def get_field(field_name, context = GraphQL::Query::NullContext)
133
+ def get_field(field_name, context = GraphQL::Query::NullContext.instance)
134
134
  # Objects need to check that the interface implementation is visible, too
135
135
  warden = Warden.from_context(context)
136
136
  ancs = ancestors
@@ -148,7 +148,7 @@ module GraphQL
148
148
  end
149
149
 
150
150
  # @return [Hash<String => GraphQL::Schema::Field>] Fields on this object, keyed by name, including inherited fields
151
- def fields(context = GraphQL::Query::NullContext)
151
+ def fields(context = GraphQL::Query::NullContext.instance)
152
152
  # Objects need to check that the interface implementation is visible, too
153
153
  warden = Warden.from_context(context)
154
154
  # Local overrides take precedence over inherited fields
@@ -70,7 +70,7 @@ module GraphQL
70
70
  end
71
71
 
72
72
  module InheritedInterfaces
73
- def interfaces(context = GraphQL::Query::NullContext)
73
+ def interfaces(context = GraphQL::Query::NullContext.instance)
74
74
  visible_interfaces = super
75
75
  inherited_interfaces = superclass.interfaces(context)
76
76
  if visible_interfaces.any?
@@ -99,7 +99,7 @@ module GraphQL
99
99
  end
100
100
 
101
101
  # param context [Query::Context] If omitted, skip filtering.
102
- def interfaces(context = GraphQL::Query::NullContext)
102
+ def interfaces(context = GraphQL::Query::NullContext.instance)
103
103
  warden = Warden.from_context(context)
104
104
  visible_interfaces = nil
105
105
  own_interface_type_memberships.each do |type_membership|
@@ -17,15 +17,15 @@ module GraphQL
17
17
  end
18
18
 
19
19
  def valid_isolated_input?(v)
20
- valid_input?(v, GraphQL::Query::NullContext)
20
+ valid_input?(v, GraphQL::Query::NullContext.instance)
21
21
  end
22
22
 
23
23
  def coerce_isolated_input(v)
24
- coerce_input(v, GraphQL::Query::NullContext)
24
+ coerce_input(v, GraphQL::Query::NullContext.instance)
25
25
  end
26
26
 
27
27
  def coerce_isolated_result(v)
28
- coerce_result(v, GraphQL::Query::NullContext)
28
+ coerce_result(v, GraphQL::Query::NullContext.instance)
29
29
  end
30
30
  end
31
31
  end
@@ -205,12 +205,12 @@ module GraphQL
205
205
  end
206
206
  end
207
207
 
208
- def get_argument(name, context = GraphQL::Query::NullContext)
208
+ def get_argument(name, context = GraphQL::Query::NullContext.instance)
209
209
  self.class.get_argument(name, context)
210
210
  end
211
211
 
212
212
  class << self
213
- def field_arguments(context = GraphQL::Query::NullContext)
213
+ def field_arguments(context = GraphQL::Query::NullContext.instance)
214
214
  arguments(context)
215
215
  end
216
216
 
@@ -218,7 +218,7 @@ module GraphQL
218
218
  any_arguments?
219
219
  end
220
220
 
221
- def get_field_argument(name, context = GraphQL::Query::NullContext)
221
+ def get_field_argument(name, context = GraphQL::Query::NullContext.instance)
222
222
  get_argument(name, context)
223
223
  end
224
224
 
@@ -10,7 +10,7 @@ module GraphQL
10
10
  super
11
11
  end
12
12
 
13
- def possible_types(*types, context: GraphQL::Query::NullContext, **options)
13
+ def possible_types(*types, context: GraphQL::Query::NullContext.instance, **options)
14
14
  if types.any?
15
15
  types.each do |t|
16
16
  assert_valid_union_member(t)
@@ -60,6 +60,7 @@ module GraphQL
60
60
  def visible_type_membership?(tm, ctx); tm.visible?(ctx); end
61
61
  def interface_type_memberships(obj_t, ctx); obj_t.interface_type_memberships; end
62
62
  def arguments(owner, ctx); owner.arguments(ctx); end
63
+ def loadable?(type, ctx); type.visible?(ctx); end
63
64
  end
64
65
  end
65
66
 
@@ -84,6 +85,7 @@ module GraphQL
84
85
  def fields(type_defn); type_defn.all_field_definitions; end # rubocop:disable Development/ContextIsPassedCop
85
86
  def get_field(parent_type, field_name); @schema.get_field(parent_type, field_name); end
86
87
  def reachable_type?(type_name); true; end
88
+ def loadable?(type, _ctx); true; end
87
89
  def reachable_types; @schema.types.values; end # rubocop:disable Development/ContextIsPassedCop
88
90
  def possible_types(type_defn); @schema.possible_types(type_defn); end
89
91
  def interfaces(obj_type); obj_type.interfaces; end
@@ -104,7 +106,7 @@ module GraphQL
104
106
  @types = @visible_types = @reachable_types = @visible_parent_fields =
105
107
  @visible_possible_types = @visible_fields = @visible_arguments = @visible_enum_arrays =
106
108
  @visible_enum_values = @visible_interfaces = @type_visibility = @type_memberships =
107
- @visible_and_reachable_type = @unions = @unfiltered_interfaces = @references_to =
109
+ @visible_and_reachable_type = @unions = @unfiltered_interfaces =
108
110
  @reachable_type_set =
109
111
  nil
110
112
  end
@@ -122,6 +124,11 @@ module GraphQL
122
124
  end
123
125
  end
124
126
 
127
+ # @return [Boolean] True if this type is used for `loads:` but not in the schema otherwise and not _explicitly_ hidden.
128
+ def loadable?(type, _ctx)
129
+ !reachable_type_set.include?(type) && visible_type?(type)
130
+ end
131
+
125
132
  # @return [GraphQL::BaseType, nil] The type named `type_name`, if it exists (else `nil`)
126
133
  def get_type(type_name)
127
134
  @visible_types ||= read_through do |name|
@@ -282,11 +289,26 @@ module GraphQL
282
289
  next true if root_type?(type_defn) || type_defn.introspection?
283
290
 
284
291
  if type_defn.kind.union?
285
- visible_possible_types?(type_defn) && (referenced?(type_defn) || orphan_type?(type_defn))
292
+ possible_types(type_defn).any? && (referenced?(type_defn) || orphan_type?(type_defn))
286
293
  elsif type_defn.kind.interface?
287
- visible_possible_types?(type_defn)
294
+ if possible_types(type_defn).any?
295
+ true
296
+ else
297
+ if @context.respond_to?(:logger) && (logger = @context.logger)
298
+ logger.debug { "Interface `#{type_defn.graphql_name}` hidden because it has no visible implementors" }
299
+ end
300
+ false
301
+ end
288
302
  else
289
- referenced?(type_defn) || visible_abstract_type?(type_defn)
303
+ if referenced?(type_defn)
304
+ true
305
+ elsif type_defn.kind.object?
306
+ # Show this object if it belongs to ...
307
+ interfaces(type_defn).any? { |t| referenced?(t) } || # an interface which is referenced in the schema
308
+ union_memberships(type_defn).any? { |t| referenced?(t) || orphan_type?(t) } # or a union which is referenced or added via orphan_types
309
+ else
310
+ false
311
+ end
290
312
  end
291
313
  end
292
314
 
@@ -341,29 +363,15 @@ module GraphQL
341
363
  end
342
364
 
343
365
  def referenced?(type_defn)
344
- @references_to ||= @schema.references_to
345
366
  graphql_name = type_defn.unwrap.graphql_name
346
- members = @references_to[graphql_name] || NO_REFERENCES
367
+ members = @schema.references_to(graphql_name)
347
368
  members.any? { |m| visible?(m) }
348
369
  end
349
370
 
350
- NO_REFERENCES = [].freeze
351
-
352
371
  def orphan_type?(type_defn)
353
372
  @schema.orphan_types.include?(type_defn)
354
373
  end
355
374
 
356
- def visible_abstract_type?(type_defn)
357
- type_defn.kind.object? && (
358
- interfaces(type_defn).any? ||
359
- union_memberships(type_defn).any?
360
- )
361
- end
362
-
363
- def visible_possible_types?(type_defn)
364
- possible_types(type_defn).any? { |t| visible_and_reachable_type?(t) }
365
- end
366
-
367
375
  def visible?(member)
368
376
  @visibility_cache[member]
369
377
  end
@@ -402,54 +410,62 @@ module GraphQL
402
410
  end
403
411
  end
404
412
 
413
+ included_interface_possible_types_set = Set.new
414
+
405
415
  until unvisited_types.empty?
406
416
  type = unvisited_types.pop
407
- if @reachable_type_set.add?(type)
408
- type_by_name = rt_hash[type.graphql_name] ||= type
409
- if type_by_name != type
410
- raise DuplicateNamesError.new(
411
- duplicated_name: type.graphql_name, duplicated_definition_1: type.inspect, duplicated_definition_2: type_by_name.inspect
412
- )
417
+ visit_type(type, unvisited_types, @reachable_type_set, rt_hash, included_interface_possible_types_set, include_interface_possible_types: false)
418
+ end
419
+
420
+ @reachable_type_set
421
+ end
422
+
423
+ def visit_type(type, unvisited_types, visited_type_set, type_by_name_hash, included_interface_possible_types_set, include_interface_possible_types:)
424
+ if visited_type_set.add?(type) || (include_interface_possible_types && type.kind.interface? && included_interface_possible_types_set.add?(type))
425
+ type_by_name = type_by_name_hash[type.graphql_name] ||= type
426
+ if type_by_name != type
427
+ name_1, name_2 = [type.inspect, type_by_name.inspect].sort
428
+ raise DuplicateNamesError.new(
429
+ duplicated_name: type.graphql_name, duplicated_definition_1: name_1, duplicated_definition_2: name_2
430
+ )
431
+ end
432
+ if type.kind.input_object?
433
+ # recurse into visible arguments
434
+ arguments(type).each do |argument|
435
+ argument_type = argument.type.unwrap
436
+ unvisited_types << argument_type
413
437
  end
414
- if type.kind.input_object?
415
- # recurse into visible arguments
416
- arguments(type).each do |argument|
417
- argument_type = argument.type.unwrap
418
- unvisited_types << argument_type
419
- end
420
- elsif type.kind.union?
421
- # recurse into visible possible types
422
- possible_types(type).each do |possible_type|
423
- unvisited_types << possible_type
438
+ elsif type.kind.union?
439
+ # recurse into visible possible types
440
+ possible_types(type).each do |possible_type|
441
+ unvisited_types << possible_type
442
+ end
443
+ elsif type.kind.fields?
444
+ if type.kind.object?
445
+ # recurse into visible implemented interfaces
446
+ interfaces(type).each do |interface|
447
+ unvisited_types << interface
424
448
  end
425
- elsif type.kind.fields?
426
- if type.kind.interface?
427
- # recurse into visible possible types
428
- possible_types(type).each do |possible_type|
429
- unvisited_types << possible_type
430
- end
431
- elsif type.kind.object?
432
- # recurse into visible implemented interfaces
433
- interfaces(type).each do |interface|
434
- unvisited_types << interface
435
- end
449
+ elsif include_interface_possible_types
450
+ possible_types(type).each do |pt|
451
+ unvisited_types << pt
436
452
  end
453
+ end
454
+ # Don't visit interface possible types -- it's not enough to justify visibility
437
455
 
438
- # recurse into visible fields
439
- fields(type).each do |field|
440
- field_type = field.type.unwrap
441
- unvisited_types << field_type
442
- # recurse into visible arguments
443
- arguments(field).each do |argument|
444
- argument_type = argument.type.unwrap
445
- unvisited_types << argument_type
446
- end
456
+ # recurse into visible fields
457
+ fields(type).each do |field|
458
+ field_type = field.type.unwrap
459
+ # In this case, if it's an interface, we want to include
460
+ visit_type(field_type, unvisited_types, visited_type_set, type_by_name_hash, included_interface_possible_types_set, include_interface_possible_types: true)
461
+ # recurse into visible arguments
462
+ arguments(field).each do |argument|
463
+ argument_type = argument.type.unwrap
464
+ unvisited_types << argument_type
447
465
  end
448
466
  end
449
467
  end
450
468
  end
451
-
452
- @reachable_type_set
453
469
  end
454
470
  end
455
471
  end