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.
- checksums.yaml +4 -4
- data/lib/graphql/analysis/max_query_complexity.rb +1 -1
- data/lib/graphql/execution/field_resolve_step.rb +243 -157
- data/lib/graphql/execution/input_values.rb +3 -1
- data/lib/graphql/execution/prepare_object_step.rb +37 -13
- data/lib/graphql/language/lexer.rb +1 -1
- data/lib/graphql/language/parser.rb +5 -1
- data/lib/graphql/pagination/relation_connection.rb +21 -10
- data/lib/graphql/query/partial.rb +1 -2
- data/lib/graphql/schema/addition.rb +55 -48
- data/lib/graphql/schema/argument.rb +14 -3
- data/lib/graphql/schema/build_from_definition.rb +435 -173
- data/lib/graphql/schema/directive.rb +16 -0
- data/lib/graphql/schema/field/connection_extension.rb +1 -1
- data/lib/graphql/schema/field.rb +1 -1
- data/lib/graphql/schema/member/build_type.rb +5 -2
- data/lib/graphql/schema/member/has_arguments.rb +0 -4
- data/lib/graphql/schema/member/has_directives.rb +7 -1
- data/lib/graphql/schema/member/has_interfaces.rb +0 -2
- data/lib/graphql/schema/object.rb +11 -3
- data/lib/graphql/schema.rb +4 -2
- data/lib/graphql/version.rb +1 -1
- metadata +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
# typed:
|
|
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,
|
|
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
|
|
12
|
+
parser = parser.const_get(:SchemaParser)
|
|
13
13
|
end
|
|
14
|
-
from_document(schema_superclass, parser.parse(definition_string),
|
|
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,
|
|
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
|
|
19
|
+
parser = parser.const_get(:SchemaParser)
|
|
20
20
|
end
|
|
21
|
-
from_document(schema_superclass, parser.parse_file(definition_path),
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
205
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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:
|
|
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
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
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 =
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
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
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
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
|