graphql 2.6.13 → 2.6.14

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.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- # typed: false
2
+ # typed: true
3
3
  require "graphql/schema/build_from_definition/resolve_map"
4
4
 
5
5
  module GraphQL
@@ -7,18 +7,18 @@ module GraphQL
7
7
  module BuildFromDefinition
8
8
  class << self
9
9
  # See [Schema.from_definition](rdoc-ref:Schema.from_definition)
10
- def from_definition(schema_superclass, definition_string, parser: GraphQL.default_parser, **kwargs)
10
+ def from_definition(schema_superclass, definition_string, default_resolve:, parser: GraphQL.default_parser, using: {}, base_types: {}, relay: false)
11
11
  if defined?(parser::SchemaParser)
12
- parser = parser::SchemaParser
12
+ parser = parser.const_get(:SchemaParser)
13
13
  end
14
- from_document(schema_superclass, parser.parse(definition_string), **kwargs)
14
+ from_document(schema_superclass, parser.parse(definition_string), default_resolve: default_resolve, using: using, base_types: base_types, relay: relay)
15
15
  end
16
16
 
17
- def from_definition_path(schema_superclass, definition_path, parser: GraphQL.default_parser, **kwargs)
17
+ def from_definition_path(schema_superclass, definition_path, default_resolve:, parser: GraphQL.default_parser, using: {}, base_types: {}, relay: false)
18
18
  if defined?(parser::SchemaParser)
19
- parser = parser::SchemaParser
19
+ parser = parser.const_get(:SchemaParser)
20
20
  end
21
- from_document(schema_superclass, parser.parse_file(definition_path), **kwargs)
21
+ from_document(schema_superclass, parser.parse_file(definition_path), default_resolve: default_resolve, using: using, base_types: base_types, relay: relay)
22
22
  end
23
23
 
24
24
  def from_document(schema_superclass, document, default_resolve:, using: {}, base_types: {}, relay: false)
@@ -48,24 +48,61 @@ module GraphQL
48
48
  default_resolve = ResolveMap.new(default_resolve)
49
49
  end
50
50
 
51
- schema_defns = document.definitions.select { |d| d.is_a?(GraphQL::Language::Nodes::SchemaDefinition) }
51
+ definitions = document.definitions #: Array[untyped]
52
+ schema_defns = definitions.select { |d| d.is_a?(GraphQL::Language::Nodes::SchemaDefinition) }
52
53
  if schema_defns.size > 1
53
54
  raise InvalidDocumentError.new('Must provide only one schema definition.')
54
55
  end
55
- schema_definition = schema_defns.first
56
+ schema_definition = schema_defns.first #: GraphQL::Language::Nodes::SchemaDefinition?
56
57
  types = {}
57
58
  directives = schema_superclass.directives.dup
58
59
  # Directive applications are queued while types are built, then applied once every type is complete,
59
60
  # because applying a directive coerces its arguments, which requires fully-built input types.
60
61
  queued_directives = []
61
- type_resolver = TypeResolver.new(types, directives, queued_directives) { |type_name| types[type_name] ||= Schema::LateBoundType.new(type_name) }
62
+ definitions_by_name = {}
63
+ # Classes (or modules, for interfaces) made before any type is built, so that references to them don't need late-bound types
64
+ type_classes = {}
65
+ interface_definitions = {}
66
+ definitions.each do |d|
67
+ if d.respond_to?(:name)
68
+ definitions_by_name[d.name] ||= d
69
+ if !type_classes.key?(d.name)
70
+ base_type = case d
71
+ when GraphQL::Language::Nodes::ObjectTypeDefinition then base_types[:object]
72
+ when GraphQL::Language::Nodes::EnumTypeDefinition then base_types[:enum]
73
+ when GraphQL::Language::Nodes::InputObjectTypeDefinition then base_types[:input_object]
74
+ when GraphQL::Language::Nodes::UnionTypeDefinition then base_types[:union]
75
+ when GraphQL::Language::Nodes::ScalarTypeDefinition then base_types[:scalar]
76
+ when GraphQL::Language::Nodes::InterfaceTypeDefinition
77
+ interface_definitions[d.name] = d
78
+ type_classes[d.name] = new_interface_module(d.name, base_types[:interface])
79
+ nil
80
+ end
81
+ if base_type
82
+ type_classes[d.name] = Class.new(base_type) {
83
+ #: self as singleton(GraphQL::Schema::Member)
84
+ graphql_name(d.name)
85
+ }
86
+ end
87
+ end
88
+ end
89
+ end
90
+ type_resolver = TypeResolver.new(types, directives, queued_directives, type_classes) { |type_name|
91
+ types[type_name] ||= if (type_class = type_classes[type_name])
92
+ type_class
93
+ elsif !definitions_by_name.key?(type_name) && (built_in_type = GraphQL::Schema::BUILT_IN_TYPES[type_name])
94
+ built_in_type
95
+ else
96
+ Schema::LateBoundType.new(type_name)
97
+ end
98
+ }
62
99
  # Make a different type resolver because directive argument types must be real classes (not late-bound)
63
100
  # when queued directives are applied. It builds types eagerly. (Each type registers itself in `types`
64
101
  # before building its members, so cycles between input objects terminate.)
65
102
  directive_type_resolver = nil
66
- directive_type_resolver = TypeResolver.new(types, directives, queued_directives) { |type_name|
103
+ directive_type_resolver = TypeResolver.new(types, directives, queued_directives, type_classes) { |type_name|
67
104
  types[type_name] ||= begin
68
- defn = document.definitions.find { |d| d.respond_to?(:name) && d.name == type_name }
105
+ defn = definitions_by_name[type_name]
69
106
  if defn
70
107
  build_definition_from_node(defn, directive_type_resolver, default_resolve, base_types)
71
108
  elsif (built_in_defn = GraphQL::Schema::BUILT_IN_TYPES[type_name])
@@ -76,8 +113,20 @@ module GraphQL
76
113
  end
77
114
  }
78
115
 
116
+ build_interface = ->(interface_name, resolver) {
117
+ if (interface_definition = interface_definitions.delete(interface_name)) && type_classes[interface_name]
118
+ check_transitive_interfaces(interface_definition, definitions_by_name)
119
+ build_interface_type(interface_definition, resolver, base_types[:interface])
120
+ end
121
+ }
122
+ type_resolver.build_interface = build_interface
123
+ directive_type_resolver.build_interface = build_interface
124
+ type_links = TypeLinks.new
125
+ type_resolver.links = type_links
126
+ directive_type_resolver.links = type_links
127
+
79
128
  directives.merge!(GraphQL::Schema.default_directives)
80
- document.definitions.each do |definition|
129
+ definitions.each do |definition|
81
130
  if definition.is_a?(GraphQL::Language::Nodes::DirectiveDefinition)
82
131
  directives[definition.name] = build_directive(definition, directive_type_resolver)
83
132
  end
@@ -86,9 +135,8 @@ module GraphQL
86
135
  # In case any directives referenced built-in types for their arguments:
87
136
  replace_late_bound_types_with_built_in(types)
88
137
 
89
- schema_extensions = nil
90
- definitions_by_name = nil
91
- document.definitions.each do |definition|
138
+ schema_extensions = nil #: Array[untyped]?
139
+ definitions.each do |definition|
92
140
  case definition
93
141
  when GraphQL::Language::Nodes::SchemaDefinition, GraphQL::Language::Nodes::DirectiveDefinition
94
142
  nil # already handled
@@ -104,24 +152,10 @@ module GraphQL
104
152
  else
105
153
  # It's possible that this was already loaded by the directives
106
154
  prev_type = types[definition.name]
107
- if prev_type.nil? || prev_type.is_a?(Schema::LateBoundType)
155
+ if prev_type.nil? || prev_type.is_a?(Schema::LateBoundType) || type_classes[definition.name]
108
156
  if definition.is_a?(GraphQL::Language::Nodes::ObjectTypeDefinition) || definition.is_a?(Language::Nodes::InterfaceTypeDefinition)
109
- interface_names = definition.interfaces.map(&:name)
110
- if !interface_names.empty?
111
- definitions_by_name ||= document.definitions.each_with_object({}) do |d, by_name|
112
- by_name[d.name] ||= d if d.respond_to?(:name)
113
- end
114
- transitive_names = interface_names.map { |n| definitions_by_name[n]&.interfaces&.map(&:name) }
115
- transitive_names.flatten!
116
- transitive_names.compact!
117
- else
118
- transitive_names = interface_names
119
- end
120
- if !(missing_transitive_interfaces = transitive_names - interface_names).empty?
121
- raise GraphQL::Schema::InvalidDocumentError, "type #{definition.name} is missing one or more transitive interface names: #{missing_transitive_interfaces.join(", ")}. Add them to the type's `implements` list and try again."
122
- end
157
+ check_transitive_interfaces(definition, definitions_by_name)
123
158
  end
124
-
125
159
  build_definition_from_node(definition, type_resolver, default_resolve, base_types)
126
160
  end
127
161
  end
@@ -196,13 +230,19 @@ module GraphQL
196
230
  builder = self
197
231
 
198
232
  found_types = types.values
199
- object_types = found_types.select { |t| t.respond_to?(:kind) && t.kind.object? }
233
+ object_types = found_types.select { |t| t.is_a?(Class) && t < GraphQL::Schema::Object }
200
234
  schema_class = Class.new(schema_superclass) do
201
235
  #: self as singleton(GraphQL::Schema)
202
236
  begin
203
237
  # Add these first so that there's some chance of resolving late-bound types
204
- add_type_and_traverse(found_types, root: false)
205
- orphan_types(object_types)
238
+ if !builder.add_linked_types(self, found_types, type_links, directives, base_types)
239
+ add_type_and_traverse(found_types, root: false)
240
+ end
241
+ # Without visibility, this would only re-traverse types that were just added
242
+ # (and `own_orphan_types` is cleared below), so it can be skipped.
243
+ if use_visibility_profile? || visibility
244
+ orphan_types(object_types)
245
+ end
206
246
  query query_root_type
207
247
  mutation mutation_root_type
208
248
  subscription subscription_root_type
@@ -255,7 +295,7 @@ module GraphQL
255
295
  attr_accessor :definition_default_resolve
256
296
  end
257
297
 
258
- self.definition_default_resolve = default_resolve
298
+ @definition_default_resolve = default_resolve
259
299
 
260
300
  def definition_default_resolve
261
301
  self.class.definition_default_resolve
@@ -274,9 +314,131 @@ module GraphQL
274
314
  end
275
315
  apply_queued_directives(queued_directives)
276
316
 
317
+ directives.each_value do |dir_class|
318
+ if dir_class.respond_to?(:coerced_arguments_cache=) && dir_class.coerced_arguments_cache
319
+ dir_class.coerced_arguments_cache = nil
320
+ end
321
+ end
322
+
277
323
  schema_class
278
324
  end
279
325
 
326
+ # References between types in this document, recorded while they're built.
327
+ class TypeLinks
328
+ def initialize
329
+ @references = {}.compare_by_identity
330
+ @arguments = []
331
+ @complete = true
332
+ end
333
+
334
+ # Hash of type => Array of the fields and arguments that use it (no duplicates, since each one is added once)
335
+ attr_reader :references
336
+
337
+ attr_reader :arguments
338
+
339
+ # True if everything was recorded, so the generic traversal isn't needed
340
+ def complete?
341
+ @complete
342
+ end
343
+
344
+ def incomplete!
345
+ @complete = false
346
+ end
347
+
348
+ def add_argument(argument, argument_type)
349
+ @arguments << argument
350
+ add_reference(argument, argument_type)
351
+ end
352
+
353
+ def add_reference(member, member_type)
354
+ member_type = member_type.unwrap
355
+ if member_type.is_a?(Schema::LateBoundType)
356
+ # The generic traversal will raise an error for this
357
+ incomplete!
358
+ else
359
+ (@references[member_type] ||= []) << member
360
+ end
361
+ end
362
+ end
363
+
364
+ # Like `schema_class.add_type_and_traverse(found_types, root: false)`, but it uses the links recorded while types were built
365
+ # instead of a traversal. (Directive classes are added later, with `directives(...)`.)
366
+ # Returns false if the traversal is required, for example, when base types have their own fields or interfaces.
367
+ def add_linked_types(schema_class, found_types, type_links, directives, base_types)
368
+ if !type_links.complete? ||
369
+ schema_class.use_visibility_profile? ||
370
+ # A redefined `@deprecated` isn't the one used by `deprecation_reason:`, so the traversal would find another directive.
371
+ !directives["deprecated"].equal?(GraphQL::Schema::Directive::Deprecated) ||
372
+ !base_types_without_members?(base_types)
373
+ return false
374
+ end
375
+
376
+ own_types = schema_class.send(:own_types)
377
+ found_types.each do |type|
378
+ if !(Module === type)
379
+ return false
380
+ end
381
+ type = type #: as GraphQL::Schema::Member::type_definition
382
+ if (Class === type && GraphQL::Schema::Directive > type) ||
383
+ ((prev_type = own_types[type.graphql_name]) && !prev_type.equal?(type))
384
+ return false
385
+ end
386
+ end
387
+
388
+ # Like the traversal, check each argument's type now (it raises an error if the type is invalid)
389
+ type_links.arguments.each(&:type)
390
+
391
+ possible_types = {}.compare_by_identity
392
+ possible_types_sets = {}.compare_by_identity
393
+ union_memberships = {}
394
+ found_types.each do |type|
395
+ own_types[type.graphql_name] = type
396
+ if Class === type
397
+ if GraphQL::Schema::Object > type
398
+ (possible_types[type] ||= []) << type
399
+ object_type = type #: as singleton(GraphQL::Schema::Object)
400
+ object_type.interface_type_memberships.each do |type_membership|
401
+ interface_type = type_membership.abstract_type
402
+ if (possible_types_sets[interface_type] ||= Set.new).add?(type)
403
+ (possible_types[interface_type] ||= []) << type
404
+ end
405
+ end
406
+ elsif GraphQL::Schema::Union > type
407
+ union_type = type #: as singleton(GraphQL::Schema::Union)
408
+ member_types = possible_types[type] = union_type.all_possible_types
409
+ member_types.each do |member_type|
410
+ (union_memberships[member_type.graphql_name] ||= []) << type
411
+ end
412
+ end
413
+ end
414
+ end
415
+
416
+ schema_class.send(:own_possible_types).merge!(possible_types) { |key, old_val, new_val| old_val + new_val }
417
+ schema_class.send(:own_union_memberships).merge!(union_memberships)
418
+ own_references_to = schema_class.send(:own_references_to)
419
+ type_links.references.each do |thing, new_refs|
420
+ prev_refs = own_references_to[thing]
421
+ own_references_to[thing] = prev_refs ? (prev_refs + (new_refs - prev_refs)) : new_refs
422
+ end
423
+ type_links.arguments.each do |argument|
424
+ if argument.default_value?
425
+ argument.validate_default_value
426
+ end
427
+ end
428
+ true
429
+ end
430
+
431
+ def base_types_without_members?(base_types)
432
+ object = base_types[:object]
433
+ interface = base_types[:interface]
434
+ object.all_field_definitions.empty? && object.interface_type_memberships.empty? && object.directives.empty? &&
435
+ interface.all_field_definitions.empty? && interface.interface_type_memberships.empty? && interface.directives.empty? && interface.orphan_types.empty? &&
436
+ base_types[:input_object].all_argument_definitions.empty? && base_types[:input_object].directives.empty? &&
437
+ base_types[:union].all_possible_types.empty? && base_types[:union].directives.empty? &&
438
+ base_types[:enum].all_enum_value_definitions.empty? && base_types[:enum].directives.empty? &&
439
+ base_types[:scalar].directives.empty?
440
+ end
441
+
280
442
  NullResolveType = ->(type, obj, ctx) {
281
443
  raise(GraphQL::RequiredImplementationMissingError, "Generated Schema cannot use Interface or Union types for execution. Implement resolve_type on your resolver.")
282
444
  }
@@ -326,7 +488,7 @@ module GraphQL
326
488
  def build_directives(definition, ast_node, type_resolver)
327
489
  return if ast_node.directives.empty?
328
490
 
329
- if ast_node.directives.all? { |dir_node| dir_node.name == "deprecated" || type_resolver.directive_class(dir_node.name)&.all_argument_definitions&.empty? }
491
+ if ast_node.directives.all? { |dir_node| dir_node.name == "deprecated" || type_resolver.directive_without_arguments?(dir_node.name) }
330
492
  apply_directives(definition, ast_node, type_resolver)
331
493
  else
332
494
  type_resolver.queued_directives << [definition, ast_node, type_resolver]
@@ -340,45 +502,34 @@ module GraphQL
340
502
  end
341
503
 
342
504
  def apply_directives(definition, ast_node, type_resolver)
343
- dirs = prepare_directives(ast_node, type_resolver)
344
- dirs.each do |(dir_class, options)|
345
- if definition.respond_to?(:schema_directive)
346
- # it's a schema
347
- definition.schema_directive(dir_class, **options)
348
- else
349
- definition.directive(dir_class, **options)
350
- end
351
- end
352
- end
353
-
354
- def prepare_directives(ast_node, type_resolver)
355
- dirs = []
505
+ is_schema = Class === definition && GraphQL::Schema >= definition
356
506
  ast_node.directives.each do |dir_node|
357
- if dir_node.name == "deprecated"
358
- # This is handled using `deprecation_reason`
359
- next
507
+ # This is handled using `deprecation_reason`
508
+ next if dir_node.name == "deprecated"
509
+ dir_class = type_resolver.call(dir_node.name)
510
+ if dir_class.nil?
511
+ raise ArgumentError, "No definition for @#{dir_node.name} #{ast_node.respond_to?(:name) ? "on #{ast_node.name} " : ""}at #{ast_node.line}:#{ast_node.col}"
512
+ end
513
+ options = args_to_kwargs(dir_class, dir_node, type_resolver)
514
+ if is_schema
515
+ schema_class = definition #: as singleton(GraphQL::Schema)
516
+ schema_class.schema_directive(dir_class, **options)
360
517
  else
361
- dir_class = type_resolver.call(dir_node.name)
362
- if dir_class.nil?
363
- raise ArgumentError, "No definition for @#{dir_node.name} #{ast_node.respond_to?(:name) ? "on #{ast_node.name} " : ""}at #{ast_node.line}:#{ast_node.col}"
364
- end
365
- options = args_to_kwargs(dir_class, dir_node)
366
- dirs << [dir_class, options]
518
+ definition.directive(dir_class, **options)
367
519
  end
368
520
  end
369
- dirs
370
521
  end
371
522
 
372
- def args_to_kwargs(arg_owner, node)
523
+ def args_to_kwargs(arg_owner, node, type_resolver)
373
524
  if node.respond_to?(:arguments)
374
525
  kwargs = {}
375
526
  node.arguments.each do |arg_node|
376
- arg_defn = arg_owner.get_argument(arg_node.name)
377
- kwargs[arg_defn.keyword] = args_to_kwargs(arg_defn.type.unwrap, arg_node.value)
527
+ arg_defn = type_resolver.get_argument(arg_owner, arg_node.name)
528
+ kwargs[arg_defn.keyword] = args_to_kwargs(arg_defn.type.unwrap, arg_node.value, type_resolver)
378
529
  end
379
530
  kwargs
380
531
  elsif node.is_a?(Array)
381
- node.map { |n| args_to_kwargs(arg_owner, n) }
532
+ node.map { |n| args_to_kwargs(arg_owner, n, type_resolver) }
382
533
  elsif node.is_a?(Language::Nodes::Enum)
383
534
  node.name
384
535
  else
@@ -388,16 +539,13 @@ module GraphQL
388
539
  end
389
540
 
390
541
  def build_enum_type(enum_type_definition, type_resolver, base_type)
391
- builder = self
392
- Class.new(base_type) do
393
- #: self as singleton(GraphQL::Schema::Enum)
394
- graphql_name(enum_type_definition.name)
395
- type_resolver.register(self)
396
- builder.build_directives(self, enum_type_definition, type_resolver)
397
- description(enum_type_definition.description)
398
- ast_node(enum_type_definition)
399
- builder.build_values(self, enum_type_definition.values, type_resolver)
400
- end
542
+ type_class = type_resolver.type_class(enum_type_definition.name, base_type) #: singleton(GraphQL::Schema::Enum)
543
+ type_resolver.register(type_class)
544
+ build_directives(type_class, enum_type_definition, type_resolver)
545
+ type_class.description(enum_type_definition.description)
546
+ type_class.ast_node(enum_type_definition)
547
+ build_values(type_class, enum_type_definition.values, type_resolver)
548
+ type_class
401
549
  end
402
550
 
403
551
  def build_values(type_class, enum_value_definitions, type_resolver)
@@ -413,32 +561,34 @@ module GraphQL
413
561
  end
414
562
 
415
563
  def build_deprecation_reason(directives)
416
- deprecated_directive = directives.find{ |d| d.name == 'deprecated' }
417
- return unless deprecated_directive
418
-
419
- reason = deprecated_directive.arguments.find{ |a| a.name == 'reason' }
420
- return GraphQL::Schema::Directive::DEFAULT_DEPRECATION_REASON unless reason
421
-
422
- reason.value
564
+ return if directives.empty?
565
+ directives.each do |d|
566
+ if d.name == "deprecated"
567
+ d.arguments.each do |a|
568
+ if a.name == "reason"
569
+ return a.value
570
+ end
571
+ end
572
+ return GraphQL::Schema::Directive::DEFAULT_DEPRECATION_REASON
573
+ end
574
+ end
575
+ nil
423
576
  end
424
577
 
425
578
  def build_scalar_type(scalar_type_definition, type_resolver, base_type, default_resolve:)
426
- builder = self
427
- Class.new(base_type) do
428
- #: self as singleton(GraphQL::Schema::Scalar)
429
- graphql_name(scalar_type_definition.name)
430
- type_resolver.register(self)
431
- description(scalar_type_definition.description)
432
- ast_node(scalar_type_definition)
433
- builder.build_directives(self, scalar_type_definition, type_resolver)
434
-
435
- if default_resolve.respond_to?(:coerce_input)
436
- # Put these method definitions in another method to avoid retaining `type_resolve`
437
- # from this method's bindiing
438
- builder.build_scalar_type_coerce_method(self, :coerce_input, default_resolve)
439
- builder.build_scalar_type_coerce_method(self, :coerce_result, default_resolve)
440
- end
441
- end
579
+ type_class = type_resolver.type_class(scalar_type_definition.name, base_type) #: singleton(GraphQL::Schema::Scalar)
580
+ type_resolver.register(type_class)
581
+ type_class.description(scalar_type_definition.description)
582
+ type_class.ast_node(scalar_type_definition)
583
+ build_directives(type_class, scalar_type_definition, type_resolver)
584
+
585
+ if default_resolve.respond_to?(:coerce_input)
586
+ # Put these method definitions in another method to avoid retaining `type_resolve`
587
+ # from this method's bindiing
588
+ build_scalar_type_coerce_method(type_class, :coerce_input, default_resolve)
589
+ build_scalar_type_coerce_method(type_class, :coerce_result, default_resolve)
590
+ end
591
+ type_class
442
592
  end
443
593
 
444
594
  def build_scalar_type_coerce_method(scalar_class, method_name, default_definition_resolve)
@@ -448,52 +598,70 @@ module GraphQL
448
598
  end
449
599
 
450
600
  def build_union_type(union_type_definition, type_resolver, base_type)
451
- builder = self
452
- Class.new(base_type) do
453
- #: self as singleton(GraphQL::Schema::Union)
454
- graphql_name(union_type_definition.name)
455
- type_resolver.register(self)
456
- description(union_type_definition.description)
457
- possible_types(*union_type_definition.types.map { |type_name| type_resolver.call(type_name) })
458
- ast_node(union_type_definition)
459
- builder.build_directives(self, union_type_definition, type_resolver)
460
- end
601
+ type_class = type_resolver.type_class(union_type_definition.name, base_type) #: singleton(GraphQL::Schema::Union)
602
+ type_resolver.register(type_class)
603
+ type_class.description(union_type_definition.description)
604
+ type_class.possible_types(*union_type_definition.types.map { |type_name| type_resolver.call(type_name) })
605
+ type_class.ast_node(union_type_definition)
606
+ build_directives(type_class, union_type_definition, type_resolver)
607
+ type_class
461
608
  end
462
609
 
463
610
  def build_object_type(object_type_definition, type_resolver, base_type)
464
- builder = self
465
-
466
- Class.new(base_type) do
467
- #: self as singleton(GraphQL::Schema::Object)
468
- graphql_name(object_type_definition.name)
469
- type_resolver.register(self)
470
- description(object_type_definition.description)
471
- ast_node(object_type_definition)
472
- builder.build_directives(self, object_type_definition, type_resolver)
473
- builder.build_interfaces(self, object_type_definition.interfaces, type_resolver)
474
- builder.build_fields(self, object_type_definition.fields, type_resolver, default_resolve: true)
475
- end
611
+ type_class = type_resolver.type_class(object_type_definition.name, base_type) #: singleton(GraphQL::Schema::Object)
612
+ type_resolver.register(type_class)
613
+ type_class.description(object_type_definition.description)
614
+ type_class.ast_node(object_type_definition)
615
+ build_directives(type_class, object_type_definition, type_resolver)
616
+ build_interfaces(type_class, object_type_definition.interfaces, type_resolver)
617
+ build_fields(type_class, object_type_definition.fields, type_resolver, default_resolve: true)
618
+ type_class
476
619
  end
477
620
 
478
621
  def build_interfaces(type_class, interface_names, type_resolver)
479
622
  interface_names.each do |interface_name|
623
+ # Implementing an interface copies some of its configuration, so it must be built first
624
+ type_resolver.build_interface_now(interface_name.name)
480
625
  type_class.implements(type_resolver.call(interface_name))
481
626
  end
482
627
  end
483
628
 
484
- def build_input_object_type(input_object_type_definition, type_resolver, base_type)
485
- builder = self
486
- Class.new(base_type) do
487
- #: self as singleton(GraphQL::Schema::InputObject)
488
- graphql_name(input_object_type_definition.name)
489
- type_resolver.register(self)
490
- description(input_object_type_definition.description)
491
- ast_node(input_object_type_definition)
492
- builder.build_directives(self, input_object_type_definition, type_resolver)
493
- builder.build_arguments(self, input_object_type_definition.fields, type_resolver)
629
+ def check_transitive_interfaces(definition, definitions_by_name)
630
+ interface_names = definition.interfaces.map(&:name)
631
+ if !interface_names.empty?
632
+ transitive_names = interface_names.map { |n|
633
+ defn = definitions_by_name[n]
634
+ # Other kinds of types don't have `interfaces`; `implements` raises a better error for them later.
635
+ defn.is_a?(Language::Nodes::InterfaceTypeDefinition) ? defn.interfaces.map(&:name) : nil
636
+ }
637
+ transitive_names.flatten!
638
+ transitive_names.compact!
639
+ else
640
+ transitive_names = interface_names
641
+ end
642
+ if !(missing_transitive_interfaces = transitive_names - interface_names).empty?
643
+ raise GraphQL::Schema::InvalidDocumentError, "type #{definition.name} is missing one or more transitive interface names: #{missing_transitive_interfaces.join(", ")}. Add them to the type's `implements` list and try again."
494
644
  end
495
645
  end
496
646
 
647
+ def new_interface_module(name, base_type)
648
+ Module.new {
649
+ #: self as GraphQL::Schema::Member::interface_definition
650
+ include base_type
651
+ graphql_name(name)
652
+ }
653
+ end
654
+
655
+ def build_input_object_type(input_object_type_definition, type_resolver, base_type)
656
+ type_class = type_resolver.type_class(input_object_type_definition.name, base_type) #: singleton(GraphQL::Schema::InputObject)
657
+ type_resolver.register(type_class)
658
+ type_class.description(input_object_type_definition.description)
659
+ type_class.ast_node(input_object_type_definition)
660
+ build_directives(type_class, input_object_type_definition, type_resolver)
661
+ build_arguments(type_class, input_object_type_definition.fields, type_resolver)
662
+ type_class
663
+ end
664
+
497
665
  def build_default_value(default_value)
498
666
  case default_value
499
667
  when GraphQL::Language::Nodes::Enum
@@ -511,7 +679,8 @@ module GraphQL
511
679
 
512
680
  def build_arguments(type_class, arguments, type_resolver)
513
681
  builder = self
514
-
682
+ # Directive arguments are linked when directives are added to the schema
683
+ links = (Class === type_class && GraphQL::Schema::Directive > type_class) ? nil : type_resolver.links
515
684
  arguments.each do |argument_defn|
516
685
  default_value_kwargs = if !argument_defn.default_value.nil?
517
686
  { default_value: builder.build_default_value(argument_defn.default_value) }
@@ -519,9 +688,10 @@ module GraphQL
519
688
  EMPTY_HASH
520
689
  end
521
690
 
691
+ argument_type = type_resolver.call(argument_defn.type)
522
692
  argument = type_class.argument(
523
693
  argument_defn.name,
524
- type: type_resolver.call(argument_defn.type),
694
+ type: argument_type,
525
695
  required: false,
526
696
  description: argument_defn.description,
527
697
  deprecation_reason: builder.build_deprecation_reason(argument_defn.directives),
@@ -529,6 +699,7 @@ module GraphQL
529
699
  camelize: false,
530
700
  **default_value_kwargs
531
701
  )
702
+ links&.add_argument(argument, argument_type)
532
703
  builder.build_directives(argument, argument_defn, type_resolver)
533
704
  end
534
705
  end
@@ -542,79 +713,159 @@ module GraphQL
542
713
  locations(*directive_definition.locations.map { |location| location.name.to_sym })
543
714
  ast_node(directive_definition)
544
715
  builder.build_arguments(self, directive_definition.arguments, type_resolver)
716
+ self.coerced_arguments_cache = {}
545
717
  end
546
718
  end
547
719
 
548
720
  def build_interface_type(interface_type_definition, type_resolver, base_type)
549
- builder = self
550
- Module.new do
551
- #: self as GraphQL::Schema::Member::interface_definition
552
- include base_type
553
- graphql_name(interface_type_definition.name)
554
- type_resolver.register(self)
555
- description(interface_type_definition.description)
556
- builder.build_interfaces(self, interface_type_definition.interfaces, type_resolver)
557
- ast_node(interface_type_definition)
558
- builder.build_directives(self, interface_type_definition, type_resolver)
559
-
560
- builder.build_fields(self, interface_type_definition.fields, type_resolver, default_resolve: nil)
561
- end
721
+ type_module = type_resolver.interface_module(interface_type_definition.name, base_type) #: GraphQL::Schema::Member::interface_definition
722
+ type_resolver.register(type_module)
723
+ type_module.description(interface_type_definition.description)
724
+ build_interfaces(type_module, interface_type_definition.interfaces, type_resolver)
725
+ type_module.ast_node(interface_type_definition)
726
+ build_directives(type_module, interface_type_definition, type_resolver)
727
+ build_fields(type_module, interface_type_definition.fields, type_resolver, default_resolve: nil)
728
+ type_module
562
729
  end
563
730
 
564
731
  def build_fields(owner, field_definitions, type_resolver, default_resolve:)
565
732
  builder = self
566
-
733
+ # If `owner.field` isn't customized, make fields directly. It's the same, but faster.
734
+ field_class = owner.method(:field).owner.equal?(GraphQL::Schema::Member::HasFields) ? owner.field_class : nil
735
+ resolve_method_names = type_resolver.field_resolve_methods.method_names
736
+ links = type_resolver.links
737
+ # Custom field classes might add arguments (for example, with extensions), which the links wouldn't include
738
+ if !field_class.equal?(GraphQL::Schema::Field)
739
+ links.incomplete!
740
+ end
567
741
  field_definitions.each do |field_definition|
568
- resolve_method_name = -"resolve_field_#{field_definition.name}"
569
- schema_field_defn = owner.field(
570
- field_definition.name,
571
- description: field_definition.description,
572
- type: type_resolver.call(field_definition.type),
573
- null: true,
574
- connection_extension: nil,
575
- deprecation_reason: build_deprecation_reason(field_definition.directives),
576
- ast_node: field_definition,
577
- method_conflict_warning: false,
578
- camelize: false,
579
- resolver_method: resolve_method_name,
580
- resolve_batch: resolve_method_name,
581
- )
742
+ resolve_method_name = resolve_method_names[field_definition.name]
743
+ if field_class
744
+ schema_field_defn = field_class.new(
745
+ name: field_definition.name,
746
+ owner: owner,
747
+ description: field_definition.description,
748
+ type: (field_type = type_resolver.call(field_definition.type)),
749
+ null: true,
750
+ connection_extension: nil,
751
+ deprecation_reason: build_deprecation_reason(field_definition.directives),
752
+ ast_node: field_definition,
753
+ method_conflict_warning: false,
754
+ camelize: false,
755
+ resolver_method: resolve_method_name,
756
+ resolve_batch: resolve_method_name,
757
+ )
758
+ owner.add_field(schema_field_defn)
759
+ else
760
+ schema_field_defn = owner.field(
761
+ field_definition.name,
762
+ description: field_definition.description,
763
+ type: (field_type = type_resolver.call(field_definition.type)),
764
+ null: true,
765
+ connection_extension: nil,
766
+ deprecation_reason: build_deprecation_reason(field_definition.directives),
767
+ ast_node: field_definition,
768
+ method_conflict_warning: false,
769
+ camelize: false,
770
+ resolver_method: resolve_method_name,
771
+ resolve_batch: resolve_method_name,
772
+ )
773
+ end
582
774
 
775
+ links.add_reference(schema_field_defn, field_type)
583
776
  builder.build_directives(schema_field_defn, field_definition, type_resolver)
584
777
  builder.build_arguments(schema_field_defn, field_definition.arguments, type_resolver)
585
778
 
586
779
  # Don't do this for interfaces
587
780
  if default_resolve
588
- define_field_resolve_method(owner, resolve_method_name, field_definition.name)
781
+ define_field_resolve_method(owner, resolve_method_name, field_definition.name, type_resolver)
589
782
  end
590
783
  end
591
784
  end
592
785
 
593
- def define_field_resolve_method(owner, method_name, field_name)
594
- owner.define_method(method_name) { |**args|
595
- #: self as GraphQL::Schema::Object
596
- field_instance = context.types.field(owner, field_name)
597
- context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context)
598
- }
599
- owner.define_singleton_method(method_name) { |objects, context, **args|
600
- field_instance = context.types.field(owner, field_name)
601
- objects.map do |object|
602
- context.schema.definition_default_resolve.call(self, field_instance, object, args, context)
603
- end
604
- }
786
+ def define_field_resolve_method(owner, method_name, field_name, type_resolver)
787
+ resolve_methods = type_resolver.field_resolve_methods
788
+ resolve_methods.field_names[method_name.to_sym] = field_name
789
+ owner.define_method(method_name, &resolve_methods.instance_method_proc)
790
+ owner.define_singleton_method(method_name, &resolve_methods.singleton_method_proc)
791
+ end
792
+
793
+ # Every field's resolve methods share these procs. They use the method's name to find the field.
794
+ class FieldResolveMethods
795
+ def initialize
796
+ @method_names = Hash.new { |h, field_name| h[field_name] = -"resolve_field_#{field_name}" }
797
+ field_names = @field_names = {}
798
+ @instance_method_proc = proc { |**args|
799
+ #: self as GraphQL::Schema::Object
800
+ owner = self.class
801
+ field_instance = context.types.field(owner, field_names[__method__])
802
+ context.schema.definition_default_resolve.call(owner, field_instance, object, args, context)
803
+ }
804
+ @singleton_method_proc = proc { |objects, context, **args|
805
+ field_instance = context.types.field(self, field_names[__method__])
806
+ objects.map do |object|
807
+ context.schema.definition_default_resolve.call(self, field_instance, object, args, context)
808
+ end
809
+ }
810
+ end
811
+
812
+ # Field names by resolve method name
813
+ attr_reader :field_names
814
+
815
+ # Resolve method names by field name
816
+ attr_reader :method_names
817
+
818
+ attr_reader :instance_method_proc, :singleton_method_proc
605
819
  end
606
820
 
607
821
  class TypeResolver
608
- def initialize(lookup_hash, directives, queued_directives, &missing_type_handler)
822
+ def initialize(lookup_hash, directives, queued_directives, type_classes, &missing_type_handler)
609
823
  @lookup_hash = lookup_hash
610
824
  @directives = directives
611
825
  @queued_directives = queued_directives
826
+ @type_classes = type_classes
612
827
  @missing_type_handler = missing_type_handler
613
828
  end
614
829
 
830
+ # The class made ahead of time for `type_name` (returned once, to be built), or a new subclass of `base_type`
831
+ def type_class(type_name, base_type)
832
+ @type_classes.delete(type_name) || Class.new(base_type) {
833
+ #: self as singleton(GraphQL::Schema::Member)
834
+ graphql_name(type_name)
835
+ }
836
+ end
837
+
838
+ def interface_module(type_name, base_type)
839
+ @type_classes.delete(type_name) || Builder.new_interface_module(type_name, base_type)
840
+ end
841
+
842
+ attr_writer :build_interface
843
+
844
+ attr_accessor :links
845
+
846
+ def build_interface_now(interface_name)
847
+ @build_interface&.call(interface_name, self)
848
+ end
849
+
615
850
  # Directive applications waiting for {Builder#apply_queued_directives}
616
851
  attr_reader :queued_directives
617
852
 
853
+ # Like `owner.get_argument(argument_name)`, but cached. Directives are applied after types are built,
854
+ # so their arguments don't change while this cache is used.
855
+ def get_argument(owner, argument_name)
856
+ @arguments_by_owner ||= {}.compare_by_identity
857
+ owner_arguments = @arguments_by_owner[owner] ||= {}
858
+ if owner_arguments.key?(argument_name)
859
+ owner_arguments[argument_name]
860
+ else
861
+ owner_arguments[argument_name] = owner.get_argument(argument_name)
862
+ end
863
+ end
864
+
865
+ def field_resolve_methods
866
+ @field_resolve_methods ||= FieldResolveMethods.new
867
+ end
868
+
618
869
  # Add `type` to the lookup hash. Types call this before building their members, so that references back to them (including cycles) find this type.
619
870
  def register(type)
620
871
  @lookup_hash[type.graphql_name] = type
@@ -625,6 +876,17 @@ module GraphQL
625
876
  @directives[name]
626
877
  end
627
878
 
879
+ def directive_without_arguments?(name)
880
+ @directives_without_arguments ||= {}
881
+ if @directives_without_arguments.key?(name)
882
+ @directives_without_arguments[name]
883
+ else
884
+ dir_class = @directives[name]
885
+ # Don't cache unknown directives, they might be defined later
886
+ dir_class ? (@directives_without_arguments[name] = dir_class.all_argument_definitions.empty?) : false
887
+ end
888
+ end
889
+
628
890
  def call(ast_node)
629
891
  case ast_node
630
892
  when GraphQL::Language::Nodes::TypeName