graphql 1.4.5 → 1.5.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (139) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/enum_generator.rb +33 -0
  3. data/lib/generators/graphql/function_generator.rb +15 -0
  4. data/lib/generators/graphql/install_generator.rb +118 -0
  5. data/lib/generators/graphql/interface_generator.rb +27 -0
  6. data/lib/generators/graphql/loader_generator.rb +17 -0
  7. data/lib/generators/graphql/mutation_generator.rb +19 -0
  8. data/lib/generators/graphql/object_generator.rb +34 -0
  9. data/lib/generators/graphql/templates/enum.erb +4 -0
  10. data/lib/generators/graphql/templates/function.erb +17 -0
  11. data/lib/generators/graphql/templates/graphql_controller.erb +32 -0
  12. data/lib/generators/graphql/templates/interface.erb +4 -0
  13. data/lib/generators/graphql/templates/loader.erb +15 -0
  14. data/lib/generators/graphql/templates/mutation.erb +12 -0
  15. data/lib/generators/graphql/templates/object.erb +5 -0
  16. data/lib/generators/graphql/templates/query_type.erb +15 -0
  17. data/lib/generators/graphql/templates/schema.erb +34 -0
  18. data/lib/generators/graphql/templates/union.erb +4 -0
  19. data/lib/generators/graphql/type_generator.rb +78 -0
  20. data/lib/generators/graphql/union_generator.rb +33 -0
  21. data/lib/graphql.rb +10 -0
  22. data/lib/graphql/analysis/analyze_query.rb +1 -1
  23. data/lib/graphql/analysis/query_complexity.rb +6 -50
  24. data/lib/graphql/analysis/query_depth.rb +1 -1
  25. data/lib/graphql/argument.rb +21 -0
  26. data/lib/graphql/compatibility/execution_specification/counter_schema.rb +3 -3
  27. data/lib/graphql/define.rb +1 -0
  28. data/lib/graphql/define/assign_argument.rb +3 -19
  29. data/lib/graphql/define/assign_mutation_function.rb +34 -0
  30. data/lib/graphql/define/assign_object_field.rb +26 -14
  31. data/lib/graphql/define/defined_object_proxy.rb +21 -0
  32. data/lib/graphql/define/instance_definable.rb +61 -11
  33. data/lib/graphql/directive.rb +6 -1
  34. data/lib/graphql/execution/directive_checks.rb +1 -0
  35. data/lib/graphql/execution/execute.rb +14 -9
  36. data/lib/graphql/execution/field_result.rb +1 -0
  37. data/lib/graphql/execution/lazy.rb +8 -17
  38. data/lib/graphql/execution/lazy/lazy_method_map.rb +2 -0
  39. data/lib/graphql/execution/lazy/resolve.rb +1 -0
  40. data/lib/graphql/execution/selection_result.rb +1 -0
  41. data/lib/graphql/execution/typecast.rb +39 -26
  42. data/lib/graphql/field.rb +15 -3
  43. data/lib/graphql/field/resolve.rb +3 -3
  44. data/lib/graphql/function.rb +134 -0
  45. data/lib/graphql/id_type.rb +1 -1
  46. data/lib/graphql/input_object_type.rb +1 -1
  47. data/lib/graphql/internal_representation.rb +1 -1
  48. data/lib/graphql/internal_representation/node.rb +35 -107
  49. data/lib/graphql/internal_representation/rewrite.rb +189 -183
  50. data/lib/graphql/internal_representation/visit.rb +38 -0
  51. data/lib/graphql/introspection/input_value_type.rb +10 -1
  52. data/lib/graphql/introspection/schema_type.rb +1 -1
  53. data/lib/graphql/language/lexer.rb +6 -3
  54. data/lib/graphql/language/lexer.rl +6 -3
  55. data/lib/graphql/object_type.rb +53 -13
  56. data/lib/graphql/query.rb +30 -14
  57. data/lib/graphql/query/arguments.rb +2 -0
  58. data/lib/graphql/query/context.rb +2 -2
  59. data/lib/graphql/query/literal_input.rb +9 -0
  60. data/lib/graphql/query/serial_execution/field_resolution.rb +2 -2
  61. data/lib/graphql/query/serial_execution/selection_resolution.rb +1 -1
  62. data/lib/graphql/relay.rb +1 -0
  63. data/lib/graphql/relay/array_connection.rb +1 -1
  64. data/lib/graphql/relay/base_connection.rb +34 -15
  65. data/lib/graphql/relay/connection_resolve.rb +7 -2
  66. data/lib/graphql/relay/mutation.rb +45 -4
  67. data/lib/graphql/relay/node.rb +18 -6
  68. data/lib/graphql/relay/range_add.rb +45 -0
  69. data/lib/graphql/relay/relation_connection.rb +17 -2
  70. data/lib/graphql/runtime_type_error.rb +1 -0
  71. data/lib/graphql/schema.rb +40 -5
  72. data/lib/graphql/schema/base_64_encoder.rb +1 -0
  73. data/lib/graphql/schema/build_from_definition.rb +56 -21
  74. data/lib/graphql/schema/default_parse_error.rb +10 -0
  75. data/lib/graphql/schema/loader.rb +8 -1
  76. data/lib/graphql/schema/null_mask.rb +1 -0
  77. data/lib/graphql/schema/validation.rb +35 -0
  78. data/lib/graphql/static_validation.rb +1 -0
  79. data/lib/graphql/static_validation/all_rules.rb +1 -0
  80. data/lib/graphql/static_validation/arguments_validator.rb +7 -4
  81. data/lib/graphql/static_validation/definition_dependencies.rb +183 -0
  82. data/lib/graphql/static_validation/rules/fields_will_merge.rb +28 -96
  83. data/lib/graphql/static_validation/rules/fragment_names_are_unique.rb +23 -0
  84. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +8 -5
  85. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +6 -31
  86. data/lib/graphql/static_validation/rules/fragments_are_used.rb +11 -41
  87. data/lib/graphql/static_validation/rules/operation_names_are_valid.rb +2 -2
  88. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +19 -7
  89. data/lib/graphql/static_validation/validation_context.rb +22 -1
  90. data/lib/graphql/static_validation/validator.rb +4 -1
  91. data/lib/graphql/string_type.rb +5 -1
  92. data/lib/graphql/version.rb +1 -1
  93. data/readme.md +12 -3
  94. data/spec/generators/graphql/enum_generator_spec.rb +29 -0
  95. data/spec/generators/graphql/function_generator_spec.rb +33 -0
  96. data/spec/generators/graphql/install_generator_spec.rb +185 -0
  97. data/spec/generators/graphql/interface_generator_spec.rb +32 -0
  98. data/spec/generators/graphql/loader_generator_spec.rb +31 -0
  99. data/spec/generators/graphql/mutation_generator_spec.rb +28 -0
  100. data/spec/generators/graphql/object_generator_spec.rb +42 -0
  101. data/spec/generators/graphql/union_generator_spec.rb +50 -0
  102. data/spec/graphql/analysis/query_complexity_spec.rb +2 -1
  103. data/spec/graphql/define/instance_definable_spec.rb +38 -0
  104. data/spec/graphql/directive/skip_directive_spec.rb +1 -0
  105. data/spec/graphql/directive_spec.rb +18 -0
  106. data/spec/graphql/execution/typecast_spec.rb +41 -46
  107. data/spec/graphql/field_spec.rb +1 -1
  108. data/spec/graphql/function_spec.rb +128 -0
  109. data/spec/graphql/internal_representation/rewrite_spec.rb +166 -129
  110. data/spec/graphql/introspection/type_type_spec.rb +1 -1
  111. data/spec/graphql/language/lexer_spec.rb +6 -0
  112. data/spec/graphql/object_type_spec.rb +73 -2
  113. data/spec/graphql/query/arguments_spec.rb +28 -0
  114. data/spec/graphql/query/variables_spec.rb +7 -1
  115. data/spec/graphql/query_spec.rb +30 -0
  116. data/spec/graphql/relay/base_connection_spec.rb +26 -8
  117. data/spec/graphql/relay/connection_resolve_spec.rb +45 -0
  118. data/spec/graphql/relay/connection_type_spec.rb +21 -0
  119. data/spec/graphql/relay/node_spec.rb +30 -2
  120. data/spec/graphql/relay/range_add_spec.rb +113 -0
  121. data/spec/graphql/schema/build_from_definition_spec.rb +114 -0
  122. data/spec/graphql/schema/loader_spec.rb +1 -0
  123. data/spec/graphql/schema/printer_spec.rb +2 -2
  124. data/spec/graphql/schema/validation_spec.rb +80 -11
  125. data/spec/graphql/schema/warden_spec.rb +10 -10
  126. data/spec/graphql/schema_spec.rb +18 -1
  127. data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +16 -0
  128. data/spec/graphql/static_validation/rules/fields_will_merge_spec.rb +50 -3
  129. data/spec/graphql/static_validation/rules/fragment_names_are_unique_spec.rb +27 -0
  130. data/spec/graphql/static_validation/rules/fragments_are_finite_spec.rb +57 -0
  131. data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +1 -1
  132. data/spec/graphql/static_validation/rules/variables_are_input_types_spec.rb +14 -0
  133. data/spec/graphql/string_type_spec.rb +7 -0
  134. data/spec/spec_helper.rb +3 -3
  135. data/spec/support/base_generator_test.rb +7 -0
  136. data/spec/support/dummy/schema.rb +32 -30
  137. data/spec/support/star_wars/schema.rb +81 -23
  138. metadata +98 -20
  139. data/lib/graphql/internal_representation/selection.rb +0 -85
@@ -6,7 +6,7 @@ GraphQL::ID_TYPE = GraphQL::ScalarType.define do
6
6
  coerce_result ->(value) { value.to_s }
7
7
  coerce_input ->(value) {
8
8
  case value
9
- when String, Fixnum, Bignum
9
+ when String, Integer
10
10
  value.to_s
11
11
  else
12
12
  nil
@@ -31,7 +31,7 @@ module GraphQL
31
31
  )
32
32
 
33
33
  attr_accessor :mutation, :arguments
34
- ensure_defined(:mutation, :arguments)
34
+ ensure_defined(:mutation, :arguments, :input_fields)
35
35
  alias :input_fields :arguments
36
36
 
37
37
  # @!attribute mutation
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  require "graphql/internal_representation/node"
3
3
  require "graphql/internal_representation/rewrite"
4
- require "graphql/internal_representation/selection"
4
+ require "graphql/internal_representation/visit"
@@ -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
- def initialize(parent:, ast_node: nil, return_type: nil, owner_type: nil, name: nil, definition_name: nil, definition: nil, spreads: [], directives: Set.new, included: true, typed_children: Hash.new {|h, k| h[k] = {} }, definitions: {}, children: {})
8
- @ast_node = ast_node
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
- # A _shallow_ cache of type-field pairs for executing & analyzing this node.
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::Field, GraphQL::Directive] the static definition for this field (it might be an interface field definition even though an object field definition will be used at runtime)
60
- attr_reader :definition
11
+ # @return [Hash<GraphQL::ObjectType, Hash<String => Node>>] selections on this node for each type
12
+ attr_reader :typed_children
61
13
 
62
- # @return [String] the name to use for the result in the response hash
63
- attr_reader :name
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::Language::Nodes::AbstractNode] The AST node (or one of the nodes) where this was derived from
66
- attr_reader :ast_node
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
- # @return [GraphQL::BaseType]
72
- attr_reader :owner_type
73
-
74
- # Returns leaf selections on this node.
75
- # Known to be buggy: deeply nested selections are not handled properly
76
- # @deprecated use {#typed_children} instead
77
- # @return [Array<Node>]
78
- attr_reader :children
79
-
80
- # @return [Boolean] false if every field for this node included `@skip(if: true)`
81
- attr_accessor :included
82
- alias :included? :included
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
- # @return [GraphQL::InternalRepresentation::Node] The node which this node is a child of
89
- attr_reader :parent
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 path
103
- warn("InternalRepresentation::Node#path is deprecated, use Query::Context#path instead")
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
- attr_writer :index
49
+ def ast_node
50
+ @ast_node ||= ast_nodes.first
51
+ end
115
52
 
116
- def inspect(indent = 0)
117
- own_indent = " " * indent
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
- # Convert an AST into a tree of {InternalRepresentation::Node}s
4
+ # While visiting an AST, build a normalized, flattened tree of {InternalRepresentation::Node}s.
5
5
  #
6
- # This rides along with {StaticValidation}, building a tree of nodes.
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
- # @return [Hash<String => InternalRepresentation::Node>] internal representation of each query root (operation, fragment)
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
- visitor[Nodes::OperationDefinition].enter << ->(ast_node, prev_ast_node) {
36
- node = Node.new(
37
- return_type: context.type_definition && context.type_definition.unwrap,
38
- ast_node: ast_node,
39
- name: ast_node.name,
40
- parent: nil,
41
- )
42
- @nodes.push(node)
43
- @operations[ast_node.name] = node
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::FragmentSpread].enter << ->(ast_node, prev_ast_node) {
88
- parent_node = @nodes.last
89
- # Record _both sides_ of the dependency
90
- spread_node = Node.new(
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
- visitor[Nodes::FragmentDefinition].enter << ->(ast_node, prev_ast_node) {
105
- node = Node.new(
106
- parent: nil,
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::InlineFragment].leave << ->(ast_node, prev_ast_node) {
116
- @parent_directives.pop
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
- visitor[Nodes::FragmentSpread].leave << ->(ast_node, prev_ast_node) {
120
- # Capture any directives that apply to this spread
121
- # so that they can be applied to fields when
122
- # the fragment is merged in later
123
- spread_node = @nodes.pop
124
- applicable_directives = pop_applicable_directives(@parent_directives)
125
- spread_node.included ||= GraphQL::Execution::DirectiveChecks.include?(applicable_directives, context.query)
126
- spread_node.directives.merge(applicable_directives)
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::FragmentDefinition].leave << ->(ast_node, prev_ast_node) {
130
- # This fragment doesn't depend on any others,
131
- # we should save it as the starting point for dependency resolution
132
- frag_node = @nodes.pop
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
- visitor[Nodes::OperationDefinition].leave << ->(ast_node, prev_ast_node) {
139
- @nodes.pop
131
+ if skip_nodes.include?(ast_node)
132
+ skip_nodes.delete(ast_node)
133
+ end
140
134
  }
141
135
 
142
- visitor[Nodes::Field].leave << ->(ast_node, prev_ast_node) {
143
- # Pop this field's node
144
- # and record any directives that were visited
145
- # during this field & before it (eg, inline fragments)
146
- field_node = @nodes.pop
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
- visitor[Nodes::Document].leave << ->(ast_node, prev_ast_node) {
153
- # Resolve fragment dependencies. Start with fragments with no
154
- # dependencies and work along the spreads.
155
- while fragment_node = @independent_fragments.pop
156
- fragment_usages = @fragment_spreads[fragment_node.name]
157
- while dependent_node = fragment_usages.pop
158
- # Find the spreads for this reference
159
- resolved_spread_nodes = dependent_node.spreads.select { |spr| spr.name == fragment_node.name }
160
- spread_is_included = resolved_spread_nodes.any?(&:included?)
161
- # Since we're going to resolve them, remove them from the dependcies
162
- resolved_spread_nodes.each { |r_node| dependent_node.spreads.delete(r_node) }
163
-
164
- # resolve the dependency (merge into dependent node)
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
- private
176
-
177
- # Merge the children from `fragment_node` into `parent_node`.
178
- # This is an implementation of "fragment inlining"
179
- def deep_merge(parent_node, fragment_node, included)
180
- fragment_node.typed_children.each do |type_defn, children|
181
- children.each do |name, child_node|
182
- deep_merge_child(parent_node, type_defn, name, child_node, included)
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
- # Merge `node` into `parent_node`'s children, as `name`, applying `extra_included`
188
- # `extra_included` comes from the spread node:
189
- # - If the spread was included, first-level children should be included if _either_ node was included
190
- # - If the spread was _not_ included, first-level children should be included if _a pre-existing_ node was included
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
- node.typed_children.each do |type_defn, children|
204
- children.each do |merge_child_name, merge_child_node|
205
- deep_merge_child(child_node, type_defn, merge_child_name, merge_child_node, node.included)
206
- end
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
- # return true if node or _any_ children have a fragment spread
211
- def any_fragment_spreads?(node)
212
- node.spreads.any? || node.typed_children.any? { |type_defn, children| children.any? { |name, node| any_fragment_spreads?(node) } }
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
- # pop off own directives,
216
- # then check the last one to see if it's directives
217
- # from an inline fragment. If it is, add them in
218
- # @return [Array<Node>]
219
- def pop_applicable_directives(directive_stack)
220
- own_directives = directive_stack.pop
221
- if directive_stack.last.is_a?(InlineFragmentDirectives)
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
- # It's an array, but can be identified with `is_a?`
229
- class InlineFragmentDirectives
230
- extend Forwardable
231
- def initialize
232
- @storage = []
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
- def_delegators :@storage, :push, :+
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