graphql 1.4.5 → 1.5.3
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/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
|