graphql 1.4.5 → 1.5.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/generators/graphql/enum_generator.rb +33 -0
- data/lib/generators/graphql/function_generator.rb +15 -0
- data/lib/generators/graphql/install_generator.rb +118 -0
- data/lib/generators/graphql/interface_generator.rb +27 -0
- data/lib/generators/graphql/loader_generator.rb +17 -0
- data/lib/generators/graphql/mutation_generator.rb +19 -0
- data/lib/generators/graphql/object_generator.rb +34 -0
- data/lib/generators/graphql/templates/enum.erb +4 -0
- data/lib/generators/graphql/templates/function.erb +17 -0
- data/lib/generators/graphql/templates/graphql_controller.erb +32 -0
- data/lib/generators/graphql/templates/interface.erb +4 -0
- data/lib/generators/graphql/templates/loader.erb +15 -0
- data/lib/generators/graphql/templates/mutation.erb +12 -0
- data/lib/generators/graphql/templates/object.erb +5 -0
- data/lib/generators/graphql/templates/query_type.erb +15 -0
- data/lib/generators/graphql/templates/schema.erb +34 -0
- data/lib/generators/graphql/templates/union.erb +4 -0
- data/lib/generators/graphql/type_generator.rb +78 -0
- data/lib/generators/graphql/union_generator.rb +33 -0
- data/lib/graphql.rb +10 -0
- data/lib/graphql/analysis/analyze_query.rb +1 -1
- data/lib/graphql/analysis/query_complexity.rb +6 -50
- data/lib/graphql/analysis/query_depth.rb +1 -1
- data/lib/graphql/argument.rb +21 -0
- data/lib/graphql/compatibility/execution_specification/counter_schema.rb +3 -3
- data/lib/graphql/define.rb +1 -0
- data/lib/graphql/define/assign_argument.rb +3 -19
- data/lib/graphql/define/assign_mutation_function.rb +34 -0
- data/lib/graphql/define/assign_object_field.rb +26 -14
- data/lib/graphql/define/defined_object_proxy.rb +21 -0
- data/lib/graphql/define/instance_definable.rb +61 -11
- data/lib/graphql/directive.rb +6 -1
- data/lib/graphql/execution/directive_checks.rb +1 -0
- data/lib/graphql/execution/execute.rb +14 -9
- data/lib/graphql/execution/field_result.rb +1 -0
- data/lib/graphql/execution/lazy.rb +8 -17
- data/lib/graphql/execution/lazy/lazy_method_map.rb +2 -0
- data/lib/graphql/execution/lazy/resolve.rb +1 -0
- data/lib/graphql/execution/selection_result.rb +1 -0
- data/lib/graphql/execution/typecast.rb +39 -26
- data/lib/graphql/field.rb +15 -3
- data/lib/graphql/field/resolve.rb +3 -3
- data/lib/graphql/function.rb +134 -0
- data/lib/graphql/id_type.rb +1 -1
- data/lib/graphql/input_object_type.rb +1 -1
- data/lib/graphql/internal_representation.rb +1 -1
- data/lib/graphql/internal_representation/node.rb +35 -107
- data/lib/graphql/internal_representation/rewrite.rb +189 -183
- data/lib/graphql/internal_representation/visit.rb +38 -0
- data/lib/graphql/introspection/input_value_type.rb +10 -1
- data/lib/graphql/introspection/schema_type.rb +1 -1
- data/lib/graphql/language/lexer.rb +6 -3
- data/lib/graphql/language/lexer.rl +6 -3
- data/lib/graphql/object_type.rb +53 -13
- data/lib/graphql/query.rb +30 -14
- data/lib/graphql/query/arguments.rb +2 -0
- data/lib/graphql/query/context.rb +2 -2
- data/lib/graphql/query/literal_input.rb +9 -0
- data/lib/graphql/query/serial_execution/field_resolution.rb +2 -2
- data/lib/graphql/query/serial_execution/selection_resolution.rb +1 -1
- data/lib/graphql/relay.rb +1 -0
- data/lib/graphql/relay/array_connection.rb +1 -1
- data/lib/graphql/relay/base_connection.rb +34 -15
- data/lib/graphql/relay/connection_resolve.rb +7 -2
- data/lib/graphql/relay/mutation.rb +45 -4
- data/lib/graphql/relay/node.rb +18 -6
- data/lib/graphql/relay/range_add.rb +45 -0
- data/lib/graphql/relay/relation_connection.rb +17 -2
- data/lib/graphql/runtime_type_error.rb +1 -0
- data/lib/graphql/schema.rb +40 -5
- data/lib/graphql/schema/base_64_encoder.rb +1 -0
- data/lib/graphql/schema/build_from_definition.rb +56 -21
- data/lib/graphql/schema/default_parse_error.rb +10 -0
- data/lib/graphql/schema/loader.rb +8 -1
- data/lib/graphql/schema/null_mask.rb +1 -0
- data/lib/graphql/schema/validation.rb +35 -0
- data/lib/graphql/static_validation.rb +1 -0
- data/lib/graphql/static_validation/all_rules.rb +1 -0
- data/lib/graphql/static_validation/arguments_validator.rb +7 -4
- data/lib/graphql/static_validation/definition_dependencies.rb +183 -0
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +28 -96
- data/lib/graphql/static_validation/rules/fragment_names_are_unique.rb +23 -0
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +8 -5
- data/lib/graphql/static_validation/rules/fragments_are_finite.rb +6 -31
- data/lib/graphql/static_validation/rules/fragments_are_used.rb +11 -41
- data/lib/graphql/static_validation/rules/operation_names_are_valid.rb +2 -2
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +19 -7
- data/lib/graphql/static_validation/validation_context.rb +22 -1
- data/lib/graphql/static_validation/validator.rb +4 -1
- data/lib/graphql/string_type.rb +5 -1
- data/lib/graphql/version.rb +1 -1
- data/readme.md +12 -3
- data/spec/generators/graphql/enum_generator_spec.rb +29 -0
- data/spec/generators/graphql/function_generator_spec.rb +33 -0
- data/spec/generators/graphql/install_generator_spec.rb +185 -0
- data/spec/generators/graphql/interface_generator_spec.rb +32 -0
- data/spec/generators/graphql/loader_generator_spec.rb +31 -0
- data/spec/generators/graphql/mutation_generator_spec.rb +28 -0
- data/spec/generators/graphql/object_generator_spec.rb +42 -0
- data/spec/generators/graphql/union_generator_spec.rb +50 -0
- data/spec/graphql/analysis/query_complexity_spec.rb +2 -1
- data/spec/graphql/define/instance_definable_spec.rb +38 -0
- data/spec/graphql/directive/skip_directive_spec.rb +1 -0
- data/spec/graphql/directive_spec.rb +18 -0
- data/spec/graphql/execution/typecast_spec.rb +41 -46
- data/spec/graphql/field_spec.rb +1 -1
- data/spec/graphql/function_spec.rb +128 -0
- data/spec/graphql/internal_representation/rewrite_spec.rb +166 -129
- data/spec/graphql/introspection/type_type_spec.rb +1 -1
- data/spec/graphql/language/lexer_spec.rb +6 -0
- data/spec/graphql/object_type_spec.rb +73 -2
- data/spec/graphql/query/arguments_spec.rb +28 -0
- data/spec/graphql/query/variables_spec.rb +7 -1
- data/spec/graphql/query_spec.rb +30 -0
- data/spec/graphql/relay/base_connection_spec.rb +26 -8
- data/spec/graphql/relay/connection_resolve_spec.rb +45 -0
- data/spec/graphql/relay/connection_type_spec.rb +21 -0
- data/spec/graphql/relay/node_spec.rb +30 -2
- data/spec/graphql/relay/range_add_spec.rb +113 -0
- data/spec/graphql/schema/build_from_definition_spec.rb +114 -0
- data/spec/graphql/schema/loader_spec.rb +1 -0
- data/spec/graphql/schema/printer_spec.rb +2 -2
- data/spec/graphql/schema/validation_spec.rb +80 -11
- data/spec/graphql/schema/warden_spec.rb +10 -10
- data/spec/graphql/schema_spec.rb +18 -1
- data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +16 -0
- data/spec/graphql/static_validation/rules/fields_will_merge_spec.rb +50 -3
- data/spec/graphql/static_validation/rules/fragment_names_are_unique_spec.rb +27 -0
- data/spec/graphql/static_validation/rules/fragments_are_finite_spec.rb +57 -0
- data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/variables_are_input_types_spec.rb +14 -0
- data/spec/graphql/string_type_spec.rb +7 -0
- data/spec/spec_helper.rb +3 -3
- data/spec/support/base_generator_test.rb +7 -0
- data/spec/support/dummy/schema.rb +32 -30
- data/spec/support/star_wars/schema.rb +81 -23
- metadata +98 -20
- data/lib/graphql/internal_representation/selection.rb +0 -85
data/lib/graphql/id_type.rb
CHANGED
@@ -1,129 +1,57 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
require "set"
|
3
|
-
|
4
2
|
module GraphQL
|
5
3
|
module InternalRepresentation
|
6
4
|
class Node
|
7
|
-
|
8
|
-
|
9
|
-
@return_type = return_type
|
10
|
-
@owner_type = owner_type
|
11
|
-
@name = name
|
12
|
-
@definition_name = definition_name
|
13
|
-
@definition = definition
|
14
|
-
@parent = parent
|
15
|
-
@spreads = spreads
|
16
|
-
@directives = directives
|
17
|
-
@included = included
|
18
|
-
@typed_children = typed_children
|
19
|
-
@children = children
|
20
|
-
@definitions = definitions
|
21
|
-
end
|
22
|
-
|
23
|
-
# @return [Hash{GraphQL::BaseType => Hash{String => Node}] Children for each type condition
|
24
|
-
attr_reader :typed_children
|
25
|
-
|
26
|
-
# Note: by the time this gets out of the Rewrite phase, this will be empty -- it's emptied out when fragments are merged back in
|
27
|
-
# @return [Array<GraphQL::InternalRepresentation::Node>] Fragment names that were spread in this node
|
28
|
-
attr_reader :spreads
|
29
|
-
|
30
|
-
# These are the compiled directives from fragment spreads, inline fragments, and the field itself
|
31
|
-
# @return [Set<GraphQL::Language::Nodes::Directive>]
|
32
|
-
attr_reader :directives
|
33
|
-
|
34
|
-
# @return [String] the name for this node's definition ({#name} may be a field's alias, this is always the name)
|
35
|
-
attr_reader :definition_name
|
5
|
+
# @return [String] the name this node has in the response
|
6
|
+
attr_reader :name
|
36
7
|
|
37
|
-
#
|
38
|
-
|
39
|
-
# Known to be buggy: some fields are deeply merged when they shouldn't be.
|
40
|
-
#
|
41
|
-
# @example On-type from previous return value
|
42
|
-
# {
|
43
|
-
# person(id: 1) {
|
44
|
-
# firstName # => defined type is person
|
45
|
-
# }
|
46
|
-
# }
|
47
|
-
# @example On-type from explicit type condition
|
48
|
-
# {
|
49
|
-
# node(id: $nodeId) {
|
50
|
-
# ... on Nameable {
|
51
|
-
# firstName # => defined type is Nameable
|
52
|
-
# }
|
53
|
-
# }
|
54
|
-
# }
|
55
|
-
# @deprecated use {#typed_children} to find matching children, the use the node's {#definition}
|
56
|
-
# @return [Hash<GraphQL::BaseType => GraphQL::Field>] definitions to use for each possible type
|
57
|
-
attr_reader :definitions
|
8
|
+
# @return [GraphQL::ObjectType]
|
9
|
+
attr_reader :owner_type
|
58
10
|
|
59
|
-
# @return [GraphQL::
|
60
|
-
attr_reader :
|
11
|
+
# @return [Hash<GraphQL::ObjectType, Hash<String => Node>>] selections on this node for each type
|
12
|
+
attr_reader :typed_children
|
61
13
|
|
62
|
-
# @return [
|
63
|
-
|
14
|
+
# @return [Set<Language::Nodes::AbstractNode>] AST nodes which are represented by this node
|
15
|
+
def ast_nodes
|
16
|
+
@ast_nodes ||= Set.new
|
17
|
+
end
|
64
18
|
|
65
|
-
# @return [GraphQL::
|
66
|
-
|
19
|
+
# @return [Set<GraphQL::Field>] Field definitions for this node (there should only be one!)
|
20
|
+
def definitions
|
21
|
+
@definitions ||= Set.new
|
22
|
+
end
|
67
23
|
|
68
24
|
# @return [GraphQL::BaseType]
|
69
25
|
attr_reader :return_type
|
70
26
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
def skipped?
|
85
|
-
!@included
|
27
|
+
def initialize(
|
28
|
+
name:, owner_type:, query:, return_type:,
|
29
|
+
ast_nodes: nil,
|
30
|
+
definitions: nil, typed_children: nil
|
31
|
+
)
|
32
|
+
@name = name
|
33
|
+
@query = query
|
34
|
+
@owner_type = owner_type
|
35
|
+
@typed_children = typed_children || Hash.new { |h1, k1| h1[k1] = {} }
|
36
|
+
@ast_nodes = ast_nodes
|
37
|
+
@definitions = definitions
|
38
|
+
@return_type = return_type
|
86
39
|
end
|
87
40
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
# @return [GraphQL::InternalRepresentation::Node] The root node which this node is a (perhaps-distant) child of, or `self` if this is a root node
|
92
|
-
def owner
|
93
|
-
@owner ||= begin
|
94
|
-
if parent.nil?
|
95
|
-
self
|
96
|
-
else
|
97
|
-
parent.owner
|
98
|
-
end
|
99
|
-
end
|
41
|
+
def definition_name
|
42
|
+
@definition_name ||= definition.name
|
100
43
|
end
|
101
44
|
|
102
|
-
def
|
103
|
-
|
104
|
-
if parent
|
105
|
-
path = parent.path
|
106
|
-
path << name
|
107
|
-
path << @index if @index
|
108
|
-
path
|
109
|
-
else
|
110
|
-
[]
|
111
|
-
end
|
45
|
+
def definition
|
46
|
+
@definition ||= definitions.first
|
112
47
|
end
|
113
48
|
|
114
|
-
|
49
|
+
def ast_node
|
50
|
+
@ast_node ||= ast_nodes.first
|
51
|
+
end
|
115
52
|
|
116
|
-
def inspect
|
117
|
-
|
118
|
-
self_inspect = "#{own_indent}<Node #{name} #{skipped? ? "(skipped)" : ""}(#{definition_name} -> #{return_type})>".dup
|
119
|
-
if typed_children.any?
|
120
|
-
self_inspect << " {"
|
121
|
-
typed_children.each do |type_defn, children|
|
122
|
-
self_inspect << "\n#{own_indent} #{type_defn} => (#{children.keys.join(",")})"
|
123
|
-
end
|
124
|
-
self_inspect << "\n#{own_indent}}"
|
125
|
-
end
|
126
|
-
self_inspect
|
53
|
+
def inspect
|
54
|
+
"#<Node #{@owner_type}.#{@name} -> #{@return_type}>"
|
127
55
|
end
|
128
56
|
end
|
129
57
|
end
|
@@ -1,238 +1,244 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module GraphQL
|
3
3
|
module InternalRepresentation
|
4
|
-
#
|
4
|
+
# While visiting an AST, build a normalized, flattened tree of {InternalRepresentation::Node}s.
|
5
5
|
#
|
6
|
-
#
|
6
|
+
# No unions or interfaces are present in this tree, only object types.
|
7
|
+
#
|
8
|
+
# Selections from the AST are attached to the object types they apply to.
|
9
|
+
#
|
10
|
+
# Inline fragments and fragment spreads are preserved in {InternalRepresentation::Node#ast_spreads},
|
11
|
+
# where they can be used to check for the presence of directives. This might not be sufficient
|
12
|
+
# for future directives, since the selections' grouping is lost.
|
13
|
+
#
|
14
|
+
# The rewritten query tree serves as the basis for the `FieldsWillMerge` validation.
|
7
15
|
#
|
8
|
-
# However, if any errors occurred during validation, the resulting tree is bogus.
|
9
|
-
# (For example, `nil` could have been pushed instead of a type.)
|
10
16
|
class Rewrite
|
11
17
|
include GraphQL::Language
|
12
18
|
|
13
|
-
|
19
|
+
NO_DIRECTIVES = [].freeze
|
20
|
+
|
21
|
+
# @return [Hash<String, Node>] Roots of this query
|
14
22
|
attr_reader :operations
|
15
23
|
|
16
24
|
def initialize
|
17
|
-
# { String => Node } Tracks the roots of the query
|
18
25
|
@operations = {}
|
19
|
-
@fragments = {}
|
20
|
-
# [String...] fragments which don't have fragments inside them
|
21
|
-
@independent_fragments = []
|
22
|
-
# Tracks the current node during traversal
|
23
|
-
# Stack<InternalRepresentation::Node>
|
24
|
-
@nodes = []
|
25
|
-
# This tracks dependencies from fragment to Node where it was used
|
26
|
-
# { frag_name => [dependent_node, dependent_node]}
|
27
|
-
@fragment_spreads = Hash.new { |h, k| h[k] = []}
|
28
|
-
# [[Nodes::Directive ...]] directive affecting the current scope
|
29
|
-
@parent_directives = []
|
30
26
|
end
|
31
27
|
|
32
28
|
def validate(context)
|
33
29
|
visitor = context.visitor
|
30
|
+
query = context.query
|
31
|
+
# Hash<Nodes::FragmentSpread => Set<InternalRepresentation::Node>>
|
32
|
+
# A record of fragment spreads and the irep nodes that used them
|
33
|
+
spread_parents = Hash.new { |h, k| h[k] = Set.new }
|
34
|
+
# Array<Set<InternalRepresentation::Node>>
|
35
|
+
# The current point of the irep_tree during visitation
|
36
|
+
nodes_stack = []
|
37
|
+
# Array<Set<GraphQL::ObjectType>>
|
38
|
+
# Object types that the current point of the irep_tree applies to
|
39
|
+
scope_stack = []
|
40
|
+
fragment_definitions = {}
|
41
|
+
skip_nodes = Set.new
|
42
|
+
|
43
|
+
visit_op = VisitDefinition.new(context, @operations, nodes_stack, scope_stack)
|
44
|
+
visitor[Nodes::OperationDefinition].enter << visit_op.method(:enter)
|
45
|
+
visitor[Nodes::OperationDefinition].leave << visit_op.method(:leave)
|
46
|
+
|
47
|
+
visit_frag = VisitDefinition.new(context, fragment_definitions, nodes_stack, scope_stack)
|
48
|
+
visitor[Nodes::FragmentDefinition].enter << visit_frag.method(:enter)
|
49
|
+
visitor[Nodes::FragmentDefinition].leave << visit_frag.method(:leave)
|
50
|
+
|
51
|
+
visitor[Nodes::InlineFragment].enter << ->(ast_node, ast_parent) {
|
52
|
+
# Inline fragments provide two things to the rewritten tree:
|
53
|
+
# - They _may_ narrow the scope by their type condition
|
54
|
+
# - They _may_ apply their directives to their children
|
55
|
+
|
56
|
+
if skip?(ast_node, query)
|
57
|
+
skip_nodes.add(ast_node)
|
58
|
+
end
|
34
59
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
visitor[Nodes::Field].enter << ->(ast_node, prev_ast_node) {
|
47
|
-
parent_node = @nodes.last
|
48
|
-
node_name = ast_node.alias || ast_node.name
|
49
|
-
owner_type = context.parent_type_definition.unwrap
|
50
|
-
# This node might not be novel, eg inside an inline fragment
|
51
|
-
node = parent_node.typed_children[owner_type][node_name] ||= Node.new(
|
52
|
-
return_type: context.type_definition && context.type_definition.unwrap,
|
53
|
-
ast_node: ast_node,
|
54
|
-
name: node_name,
|
55
|
-
definition_name: ast_node.name,
|
56
|
-
definition: context.field_definition,
|
57
|
-
parent: parent_node,
|
58
|
-
owner_type: owner_type,
|
59
|
-
included: false, # may be set to true on leaving the node
|
60
|
-
)
|
61
|
-
parent_node.children[node_name] ||= node
|
62
|
-
node.definitions[owner_type] = context.field_definition
|
63
|
-
@nodes.push(node)
|
64
|
-
@parent_directives.push([])
|
65
|
-
}
|
66
|
-
|
67
|
-
visitor[Nodes::InlineFragment].enter << ->(ast_node, prev_ast_node) {
|
68
|
-
@parent_directives.push(InlineFragmentDirectives.new)
|
69
|
-
}
|
70
|
-
|
71
|
-
visitor[Nodes::Directive].enter << ->(ast_node, prev_ast_node) {
|
72
|
-
# It could be a query error where a directive is somewhere it shouldn't be
|
73
|
-
if @parent_directives.any?
|
74
|
-
directive_irep_node = Node.new(
|
75
|
-
name: ast_node.name,
|
76
|
-
definition_name: ast_node.name,
|
77
|
-
ast_node: ast_node,
|
78
|
-
definition: context.directive_definition,
|
79
|
-
definitions: {context.directive_definition => context.directive_definition},
|
80
|
-
# This isn't used, the directive may have many parents in the case of inline fragment
|
81
|
-
parent: nil,
|
82
|
-
)
|
83
|
-
@parent_directives.last.push(directive_irep_node)
|
60
|
+
if skip_nodes.none?
|
61
|
+
next_scope = Set.new
|
62
|
+
prev_scope = scope_stack.last
|
63
|
+
each_type(query, context.type_definition) do |obj_type|
|
64
|
+
# What this fragment can apply to is also determined by
|
65
|
+
# the scope around it (it can't widen the scope)
|
66
|
+
if prev_scope.include?(obj_type)
|
67
|
+
next_scope.add(obj_type)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
scope_stack.push(next_scope)
|
84
71
|
end
|
85
72
|
}
|
86
73
|
|
87
|
-
visitor[Nodes::
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
parent: parent_node,
|
92
|
-
name: ast_node.name,
|
93
|
-
ast_node: ast_node,
|
94
|
-
included: false, # this may be set to true on leaving the node
|
95
|
-
)
|
96
|
-
# The parent node has a reference to the fragment
|
97
|
-
parent_node.spreads.push(spread_node)
|
98
|
-
# And keep a reference from the fragment to the parent node
|
99
|
-
@fragment_spreads[ast_node.name].push(parent_node)
|
100
|
-
@nodes.push(spread_node)
|
101
|
-
@parent_directives.push([])
|
102
|
-
}
|
74
|
+
visitor[Nodes::InlineFragment].leave << ->(ast_node, ast_parent) {
|
75
|
+
if skip_nodes.none?
|
76
|
+
scope_stack.pop
|
77
|
+
end
|
103
78
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
name: ast_node.name,
|
108
|
-
return_type: context.type_definition,
|
109
|
-
ast_node: ast_node,
|
110
|
-
)
|
111
|
-
@nodes.push(node)
|
112
|
-
@fragments[ast_node.name] = node
|
79
|
+
if skip_nodes.include?(ast_node)
|
80
|
+
skip_nodes.delete(ast_node)
|
81
|
+
end
|
113
82
|
}
|
114
83
|
|
115
|
-
visitor[Nodes::
|
116
|
-
|
117
|
-
|
84
|
+
visitor[Nodes::Field].enter << ->(ast_node, ast_parent) {
|
85
|
+
if skip?(ast_node, query)
|
86
|
+
skip_nodes.add(ast_node)
|
87
|
+
end
|
118
88
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
89
|
+
if skip_nodes.none?
|
90
|
+
node_name = ast_node.alias || ast_node.name
|
91
|
+
parent_nodes = nodes_stack.last
|
92
|
+
next_nodes = []
|
93
|
+
next_scope = Set.new
|
94
|
+
applicable_scope = scope_stack.last
|
95
|
+
|
96
|
+
applicable_scope.each do |obj_type|
|
97
|
+
# Can't use context.field_definition because that might be
|
98
|
+
# a definition on an interface type
|
99
|
+
field_defn = query.get_field(obj_type, ast_node.name)
|
100
|
+
if field_defn.nil?
|
101
|
+
# It's a non-existent field
|
102
|
+
else
|
103
|
+
field_return_type = field_defn.type.unwrap
|
104
|
+
each_type(query, field_return_type) do |obj_type|
|
105
|
+
next_scope.add(obj_type)
|
106
|
+
end
|
107
|
+
parent_nodes.each do |parent_node|
|
108
|
+
node = parent_node.typed_children[obj_type][node_name] ||= Node.new(
|
109
|
+
name: node_name,
|
110
|
+
owner_type: obj_type,
|
111
|
+
query: query,
|
112
|
+
return_type: field_return_type,
|
113
|
+
)
|
114
|
+
node.ast_nodes.add(ast_node)
|
115
|
+
node.definitions.add(field_defn)
|
116
|
+
next_nodes << node
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
nodes_stack.push(next_nodes)
|
121
|
+
scope_stack.push(next_scope)
|
122
|
+
end
|
127
123
|
}
|
128
124
|
|
129
|
-
visitor[Nodes::
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
if !any_fragment_spreads?(frag_node)
|
134
|
-
@independent_fragments << frag_node
|
125
|
+
visitor[Nodes::Field].leave << ->(ast_node, ast_parent) {
|
126
|
+
if skip_nodes.none?
|
127
|
+
nodes_stack.pop
|
128
|
+
scope_stack.pop
|
135
129
|
end
|
136
|
-
}
|
137
130
|
|
138
|
-
|
139
|
-
|
131
|
+
if skip_nodes.include?(ast_node)
|
132
|
+
skip_nodes.delete(ast_node)
|
133
|
+
end
|
140
134
|
}
|
141
135
|
|
142
|
-
visitor[Nodes::
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
applicable_directives = pop_applicable_directives(@parent_directives)
|
148
|
-
field_node.directives.merge(applicable_directives)
|
149
|
-
field_node.included ||= GraphQL::Execution::DirectiveChecks.include?(applicable_directives, context.query)
|
136
|
+
visitor[Nodes::FragmentSpread].enter << ->(ast_node, ast_parent) {
|
137
|
+
if skip_nodes.none? && !skip?(ast_node, query)
|
138
|
+
# Register the irep nodes that depend on this AST node:
|
139
|
+
spread_parents[ast_node].merge(nodes_stack.last)
|
140
|
+
end
|
150
141
|
}
|
151
142
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
deep_merge(dependent_node, fragment_node, spread_is_included)
|
166
|
-
owner = dependent_node.owner
|
167
|
-
if owner.ast_node.is_a?(Nodes::FragmentDefinition) && !any_fragment_spreads?(owner)
|
168
|
-
@independent_fragments.push(owner)
|
143
|
+
# Resolve fragment spreads.
|
144
|
+
# Fragment definitions got their own irep trees during visitation.
|
145
|
+
# Those nodes are spliced in verbatim (not copied), but this is OK
|
146
|
+
# because fragments are resolved from the "bottom up", each fragment
|
147
|
+
# can be shared between its usages.
|
148
|
+
context.on_dependency_resolve do |defn_ast_node, spread_ast_nodes, frag_ast_node|
|
149
|
+
frag_name = frag_ast_node.name
|
150
|
+
spread_ast_nodes.each do |spread_ast_node|
|
151
|
+
parent_nodes = spread_parents[spread_ast_node]
|
152
|
+
parent_nodes.each do |parent_node|
|
153
|
+
fragment_node = fragment_definitions[frag_name]
|
154
|
+
if fragment_node
|
155
|
+
deep_merge_selections(query, parent_node, fragment_node)
|
169
156
|
end
|
170
157
|
end
|
171
158
|
end
|
172
|
-
|
159
|
+
end
|
173
160
|
end
|
174
161
|
|
175
|
-
|
176
|
-
|
177
|
-
#
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
162
|
+
# Merge selections from `new_parent` into `prev_parent`.
|
163
|
+
# If `new_parent` came from a spread in the AST, it's present as `spread`.
|
164
|
+
# Selections are merged in place, not copied.
|
165
|
+
def deep_merge_selections(query, prev_parent, new_parent)
|
166
|
+
new_parent.typed_children.each do |obj_type, new_fields|
|
167
|
+
prev_fields = prev_parent.typed_children[obj_type]
|
168
|
+
new_fields.each do |name, new_node|
|
169
|
+
prev_node = prev_fields[name]
|
170
|
+
if prev_node
|
171
|
+
prev_node.ast_nodes.merge(new_node.ast_nodes)
|
172
|
+
prev_node.definitions.merge(new_node.definitions)
|
173
|
+
deep_merge_selections(query, prev_node, new_node)
|
174
|
+
else
|
175
|
+
prev_fields[name] = new_node
|
176
|
+
end
|
183
177
|
end
|
184
178
|
end
|
185
179
|
end
|
186
180
|
|
187
|
-
#
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
# (A copied node should be excluded)
|
192
|
-
def deep_merge_child(parent_node, type_defn, name, node, extra_included)
|
193
|
-
child_node = parent_node.typed_children[type_defn][name]
|
194
|
-
previously_included = child_node.nil? ? false : child_node.included?
|
195
|
-
next_included = extra_included ? (previously_included || node.included?) : previously_included
|
196
|
-
|
197
|
-
if child_node.nil?
|
198
|
-
child_node = parent_node.typed_children[type_defn][name] = node.dup
|
199
|
-
end
|
200
|
-
|
201
|
-
child_node.included = next_included
|
181
|
+
# @see {.each_type}
|
182
|
+
def each_type(query, owner_type, &block)
|
183
|
+
self.class.each_type(query, owner_type, &block)
|
184
|
+
end
|
202
185
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
186
|
+
# Call the block for each of `owner_type`'s possible types
|
187
|
+
def self.each_type(query, owner_type)
|
188
|
+
case owner_type
|
189
|
+
when GraphQL::ObjectType, GraphQL::ScalarType, GraphQL::EnumType
|
190
|
+
yield(owner_type)
|
191
|
+
when GraphQL::UnionType, GraphQL::InterfaceType
|
192
|
+
query.possible_types(owner_type).each(&Proc.new)
|
193
|
+
when GraphQL::InputObjectType, nil
|
194
|
+
# this is an error, don't give 'em nothin
|
195
|
+
else
|
196
|
+
raise "Unexpected owner type: #{owner_type.inspect}"
|
207
197
|
end
|
208
198
|
end
|
209
199
|
|
210
|
-
|
211
|
-
|
212
|
-
|
200
|
+
def skip?(ast_node, query)
|
201
|
+
dir = ast_node.directives
|
202
|
+
dir.any? && !GraphQL::Execution::DirectiveChecks.include?(dir, query)
|
213
203
|
end
|
214
204
|
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
own_directives = directive_stack.last + own_directives
|
205
|
+
class VisitDefinition
|
206
|
+
def initialize(context, definitions, nodes_stack, scope_stack)
|
207
|
+
@context = context
|
208
|
+
@query = context.query
|
209
|
+
@definitions = definitions
|
210
|
+
@nodes_stack = nodes_stack
|
211
|
+
@scope_stack = scope_stack
|
223
212
|
end
|
224
|
-
own_directives
|
225
|
-
end
|
226
213
|
|
214
|
+
def enter(ast_node, ast_parent)
|
215
|
+
# Either QueryType or the fragment type condition
|
216
|
+
owner_type = @context.type_definition && @context.type_definition.unwrap
|
217
|
+
next_nodes = []
|
218
|
+
next_scope = Set.new
|
219
|
+
defn_name = ast_node.name
|
220
|
+
Rewrite.each_type(@query, owner_type) do |obj_type|
|
221
|
+
next_scope.add(obj_type)
|
222
|
+
end
|
227
223
|
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
224
|
+
node = Node.new(
|
225
|
+
name: defn_name,
|
226
|
+
owner_type: owner_type,
|
227
|
+
query: @query,
|
228
|
+
ast_nodes: Set.new([ast_node]),
|
229
|
+
return_type: owner_type,
|
230
|
+
)
|
231
|
+
@definitions[defn_name] = node
|
232
|
+
next_nodes << node
|
233
|
+
|
234
|
+
@nodes_stack.push(next_nodes)
|
235
|
+
@scope_stack.push(next_scope)
|
233
236
|
end
|
234
237
|
|
235
|
-
|
238
|
+
def leave(ast_node, ast_parent)
|
239
|
+
@nodes_stack.pop
|
240
|
+
@scope_stack.pop
|
241
|
+
end
|
236
242
|
end
|
237
243
|
end
|
238
244
|
end
|