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.
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