graphql 1.5.3 → 1.5.4

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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/define/assign_enum_value.rb +1 -1
  3. data/lib/graphql/execution/directive_checks.rb +5 -5
  4. data/lib/graphql/internal_representation.rb +1 -0
  5. data/lib/graphql/internal_representation/node.rb +117 -16
  6. data/lib/graphql/internal_representation/rewrite.rb +39 -94
  7. data/lib/graphql/internal_representation/scope.rb +88 -0
  8. data/lib/graphql/introspection/schema_field.rb +5 -10
  9. data/lib/graphql/introspection/type_by_name_field.rb +8 -13
  10. data/lib/graphql/introspection/typename_field.rb +5 -10
  11. data/lib/graphql/query.rb +24 -155
  12. data/lib/graphql/query/arguments_cache.rb +25 -0
  13. data/lib/graphql/query/validation_pipeline.rb +114 -0
  14. data/lib/graphql/query/variables.rb +18 -14
  15. data/lib/graphql/schema.rb +4 -3
  16. data/lib/graphql/schema/mask.rb +55 -0
  17. data/lib/graphql/schema/possible_types.rb +2 -2
  18. data/lib/graphql/schema/type_expression.rb +19 -4
  19. data/lib/graphql/schema/warden.rb +1 -3
  20. data/lib/graphql/static_validation/rules/fields_will_merge.rb +3 -2
  21. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +4 -2
  22. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +3 -1
  23. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +1 -20
  24. data/lib/graphql/static_validation/validation_context.rb +6 -18
  25. data/lib/graphql/version.rb +1 -1
  26. data/spec/graphql/enum_type_spec.rb +12 -0
  27. data/spec/graphql/internal_representation/rewrite_spec.rb +26 -5
  28. data/spec/graphql/query_spec.rb +23 -3
  29. data/spec/graphql/static_validation/rules/fields_will_merge_spec.rb +2 -2
  30. data/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +12 -0
  31. data/spec/graphql/static_validation/rules/variables_are_input_types_spec.rb +14 -0
  32. metadata +6 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f8a07faba9f807e0d1a1cb2d658dbb03aebd5aff
4
- data.tar.gz: 7cc789d4ff25519e433770bab5fbdff0421338c4
3
+ metadata.gz: eacb69bcf39a028dc32debd2a0f266a6f920fd3d
4
+ data.tar.gz: 921436c519b6b6b8aff3e4dc03845f8d6108a275
5
5
  SHA512:
6
- metadata.gz: c4699ee8d911f2a26824b08f938bec2d3b564f28864491d355d242e91dd79b17de8b301d91f8016409cf76da93e811364620bb24637a81a616093df5a16a2035
7
- data.tar.gz: f805fd9332370ceb6876dc9e68178a5f1753edb6993e14881eba5b36bb7935502a7419c7a0d03fd0b405533be943f4cc4db50f430b7f61cafd331a3ab97a7858
6
+ metadata.gz: b8e1d965b82383e8cee0423fa06b7f20ea5fde13bedb8e9cd2316686c7f7e72dd409a55667b81ea9f0e502906bee45e7957cbdd79c44e96618a405bebf81fc98
7
+ data.tar.gz: 56c92576bee82ce626d332df1070079579c02007ad78718831301de4c815a6d61c57a17464e71498d9e6a4c51412e80de50e2090aca40fed8266b282b6c12a58
@@ -5,7 +5,7 @@ module GraphQL
5
5
  module AssignEnumValue
6
6
  def self.call(enum_type, name, desc = nil, deprecation_reason: nil, value: name, &block)
7
7
  enum_value = GraphQL::EnumType::EnumValue.define(
8
- name: name,
8
+ name: name.to_s,
9
9
  description: desc,
10
10
  deprecation_reason: deprecation_reason,
11
11
  value: value,
@@ -11,18 +11,18 @@ module GraphQL
11
11
  module_function
12
12
 
13
13
  # @return [Boolean] Should this node be included in the query?
14
- def include?(directive_irep_nodes, query)
15
- directive_irep_nodes.each do |directive_irep_node|
16
- name = directive_irep_node.name
14
+ def include?(directive_ast_nodes, query)
15
+ directive_ast_nodes.each do |directive_ast_node|
16
+ name = directive_ast_node.name
17
17
  directive_defn = query.schema.directives[name]
18
18
  case name
19
19
  when SKIP
20
- args = query.arguments_for(directive_irep_node, directive_defn)
20
+ args = query.arguments_for(directive_ast_node, directive_defn)
21
21
  if args['if'] == true
22
22
  return false
23
23
  end
24
24
  when INCLUDE
25
- args = query.arguments_for(directive_irep_node, directive_defn)
25
+ args = query.arguments_for(directive_ast_node, directive_defn)
26
26
  if args['if'] == false
27
27
  return false
28
28
  end
@@ -1,4 +1,5 @@
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/scope"
4
5
  require "graphql/internal_representation/visit"
@@ -8,42 +8,92 @@ module GraphQL
8
8
  # @return [GraphQL::ObjectType]
9
9
  attr_reader :owner_type
10
10
 
11
- # @return [Hash<GraphQL::ObjectType, Hash<String => Node>>] selections on this node for each type
12
- attr_reader :typed_children
13
-
14
- # @return [Set<Language::Nodes::AbstractNode>] AST nodes which are represented by this node
15
- def ast_nodes
16
- @ast_nodes ||= Set.new
11
+ # Each key is a {GraphQL::ObjectType} which this selection _may_ be made on.
12
+ # The values for that key are selections which apply to that type.
13
+ #
14
+ # This value is derived from {#scoped_children} after the rewrite is finished.
15
+ # @return [Hash<GraphQL::ObjectType, Hash<String => Node>>]
16
+ def typed_children
17
+ @typed_childen ||= begin
18
+ new_tc = Hash.new { |h, k| h[k] = {} }
19
+ if @scoped_children.any?
20
+ all_object_types = Set.new
21
+ scoped_children.each_key { |t| all_object_types.merge(@query.possible_types(t)) }
22
+ all_object_types.each do |t|
23
+ new_tc[t] = get_typed_children(t)
24
+ end
25
+ end
26
+ new_tc
27
+ end
17
28
  end
18
29
 
19
- # @return [Set<GraphQL::Field>] Field definitions for this node (there should only be one!)
20
- def definitions
21
- @definitions ||= Set.new
22
- end
30
+ # These children correspond closely to scopes in the AST.
31
+ # Keys _may_ be abstract types. They're assumed to be read-only after rewrite is finished
32
+ # because {#typed_children} is derived from them.
33
+ #
34
+ # Using {#scoped_children} during the rewrite step reduces the overhead of reifying
35
+ # abstract types because they're only reified _after_ the rewrite.
36
+ # @return [Hash<GraphQL::BaseType, Hash<String => Node>>]
37
+ attr_reader :scoped_children
38
+
39
+ # @return [Array<Language::Nodes::AbstractNode>] AST nodes which are represented by this node
40
+ attr_reader :ast_nodes
41
+
42
+ # @return [Array<GraphQL::Field>] Field definitions for this node (there should only be one!)
43
+ attr_reader :definitions
23
44
 
24
45
  # @return [GraphQL::BaseType]
25
46
  attr_reader :return_type
26
47
 
48
+ # @return [InternalRepresentation::Node, nil]
49
+ attr_reader :parent
50
+
27
51
  def initialize(
28
- name:, owner_type:, query:, return_type:,
29
- ast_nodes: nil,
30
- definitions: nil, typed_children: nil
52
+ name:, owner_type:, query:, return_type:, parent:,
53
+ ast_nodes: [],
54
+ definitions: []
31
55
  )
32
56
  @name = name
33
57
  @query = query
34
58
  @owner_type = owner_type
35
- @typed_children = typed_children || Hash.new { |h1, k1| h1[k1] = {} }
59
+ @parent = parent
60
+ @typed_children = nil
61
+ @scoped_children = Hash.new { |h1, k1| h1[k1] = {} }
36
62
  @ast_nodes = ast_nodes
37
63
  @definitions = definitions
38
64
  @return_type = return_type
39
65
  end
40
66
 
67
+ def initialize_copy(other_node)
68
+ super
69
+ # Bust some caches:
70
+ @typed_children = nil
71
+ @definition = nil
72
+ @definition_name = nil
73
+ @ast_node = nil
74
+ # Shallow-copy some state:
75
+ @scoped_children = other_node.scoped_children.dup
76
+ @ast_nodes = other_node.ast_nodes.dup
77
+ @definitions = other_node.definitions.dup
78
+ end
79
+
80
+ def ==(other)
81
+ other.is_a?(self.class) &&
82
+ other.name == name &&
83
+ other.parent == parent &&
84
+ other.return_type == return_type &&
85
+ other.owner_type == owner_type &&
86
+ other.scoped_children == scoped_children &&
87
+ other.definitions == definitions &&
88
+ other.ast_nodes == ast_nodes
89
+ end
90
+
41
91
  def definition_name
42
92
  @definition_name ||= definition.name
43
93
  end
44
94
 
45
95
  def definition
46
- @definition ||= definitions.first
96
+ @definition ||= @query.get_field(@owner_type, @definitions.first.name)
47
97
  end
48
98
 
49
99
  def ast_node
@@ -51,7 +101,58 @@ module GraphQL
51
101
  end
52
102
 
53
103
  def inspect
54
- "#<Node #{@owner_type}.#{@name} -> #{@return_type}>"
104
+ all_children_names = scoped_children.values.map(&:keys).flatten.uniq.join(", ")
105
+ all_locations = ast_nodes.map {|n| "#{n.line}:#{n.col}" }.join(", ")
106
+ "#<Node #{@owner_type}.#{@name} -> #{@return_type} {#{all_children_names}} @ [#{all_locations}] #{object_id}>"
107
+ end
108
+
109
+ # Merge selections from `new_parent` into `self`.
110
+ # Selections are merged in place, not copied.
111
+ def deep_merge_node(new_parent, merge_self: true)
112
+ if merge_self
113
+ @ast_nodes.concat(new_parent.ast_nodes)
114
+ @definitions.concat(new_parent.definitions)
115
+ end
116
+ new_parent.scoped_children.each do |obj_type, new_fields|
117
+ prev_fields = @scoped_children[obj_type]
118
+ new_fields.each do |name, new_node|
119
+ prev_node = prev_fields[name]
120
+ if prev_node
121
+ prev_node.deep_merge_node(new_node)
122
+ else
123
+ prev_fields[name] = new_node
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ protected
130
+
131
+ attr_writer :owner_type, :parent
132
+
133
+ private
134
+
135
+ # Get applicable children from {#scoped_children}
136
+ # @param obj_type [GraphQL::ObjectType]
137
+ # @return [Hash<String => Node>]
138
+ def get_typed_children(obj_type)
139
+ new_tc = {}
140
+ @scoped_children.each do |scope_type, scope_nodes|
141
+ if GraphQL::Execution::Typecast.subtype?(scope_type, obj_type)
142
+ scope_nodes.each do |name, new_node|
143
+ prev_node = new_tc[name]
144
+ if prev_node
145
+ prev_node.deep_merge_node(new_node)
146
+ else
147
+ copied_node = new_node.dup
148
+ copied_node.owner_type = obj_type
149
+ copied_node.parent = self
150
+ new_tc[name] = copied_node
151
+ end
152
+ end
153
+ end
154
+ end
155
+ new_tc
55
156
  end
56
157
  end
57
158
  end
@@ -34,17 +34,17 @@ module GraphQL
34
34
  # Array<Set<InternalRepresentation::Node>>
35
35
  # The current point of the irep_tree during visitation
36
36
  nodes_stack = []
37
- # Array<Set<GraphQL::ObjectType>>
38
- # Object types that the current point of the irep_tree applies to
39
- scope_stack = []
37
+ # Array<Scope>
38
+ scopes_stack = []
39
+
40
40
  fragment_definitions = {}
41
41
  skip_nodes = Set.new
42
42
 
43
- visit_op = VisitDefinition.new(context, @operations, nodes_stack, scope_stack)
43
+ visit_op = VisitDefinition.new(context, @operations, nodes_stack, scopes_stack)
44
44
  visitor[Nodes::OperationDefinition].enter << visit_op.method(:enter)
45
45
  visitor[Nodes::OperationDefinition].leave << visit_op.method(:leave)
46
46
 
47
- visit_frag = VisitDefinition.new(context, fragment_definitions, nodes_stack, scope_stack)
47
+ visit_frag = VisitDefinition.new(context, fragment_definitions, nodes_stack, scopes_stack)
48
48
  visitor[Nodes::FragmentDefinition].enter << visit_frag.method(:enter)
49
49
  visitor[Nodes::FragmentDefinition].leave << visit_frag.method(:leave)
50
50
 
@@ -52,28 +52,18 @@ module GraphQL
52
52
  # Inline fragments provide two things to the rewritten tree:
53
53
  # - They _may_ narrow the scope by their type condition
54
54
  # - They _may_ apply their directives to their children
55
-
56
55
  if skip?(ast_node, query)
57
56
  skip_nodes.add(ast_node)
58
57
  end
59
58
 
60
59
  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)
60
+ scopes_stack.push(scopes_stack.last.enter(context.type_definition))
71
61
  end
72
62
  }
73
63
 
74
64
  visitor[Nodes::InlineFragment].leave << ->(ast_node, ast_parent) {
75
65
  if skip_nodes.none?
76
- scope_stack.pop
66
+ scopes_stack.pop
77
67
  end
78
68
 
79
69
  if skip_nodes.include?(ast_node)
@@ -90,42 +80,39 @@ module GraphQL
90
80
  node_name = ast_node.alias || ast_node.name
91
81
  parent_nodes = nodes_stack.last
92
82
  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
83
+
84
+ field_defn = context.field_definition
85
+ if field_defn.nil?
86
+ # It's a non-existent field
87
+ new_scope = nil
88
+ else
89
+ field_return_type = field_defn.type.unwrap
90
+ scopes_stack.last.each do |scope_type|
107
91
  parent_nodes.each do |parent_node|
108
- node = parent_node.typed_children[obj_type][node_name] ||= Node.new(
92
+ node = parent_node.scoped_children[scope_type][node_name] ||= Node.new(
93
+ parent: parent_node,
109
94
  name: node_name,
110
- owner_type: obj_type,
95
+ owner_type: scope_type,
111
96
  query: query,
112
97
  return_type: field_return_type,
113
98
  )
114
- node.ast_nodes.add(ast_node)
115
- node.definitions.add(field_defn)
99
+ node.ast_nodes << ast_node
100
+ node.definitions << field_defn
116
101
  next_nodes << node
117
102
  end
118
103
  end
104
+ new_scope = Scope.new(query, field_return_type)
119
105
  end
106
+
120
107
  nodes_stack.push(next_nodes)
121
- scope_stack.push(next_scope)
108
+ scopes_stack.push(new_scope)
122
109
  end
123
110
  }
124
111
 
125
112
  visitor[Nodes::Field].leave << ->(ast_node, ast_parent) {
126
113
  if skip_nodes.none?
127
114
  nodes_stack.pop
128
- scope_stack.pop
115
+ scopes_stack.pop
129
116
  end
130
117
 
131
118
  if skip_nodes.include?(ast_node)
@@ -147,97 +134,55 @@ module GraphQL
147
134
  # can be shared between its usages.
148
135
  context.on_dependency_resolve do |defn_ast_node, spread_ast_nodes, frag_ast_node|
149
136
  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)
156
- end
157
- end
158
- end
159
- end
160
- end
137
+ fragment_node = fragment_definitions[frag_name]
161
138
 
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
139
+ if fragment_node
140
+ spread_ast_nodes.each do |spread_ast_node|
141
+ parent_nodes = spread_parents[spread_ast_node]
142
+ parent_nodes.each do |parent_node|
143
+ parent_node.deep_merge_node(fragment_node, merge_self: false)
144
+ end
176
145
  end
177
146
  end
178
147
  end
179
148
  end
180
149
 
181
- # @see {.each_type}
182
- def each_type(query, owner_type, &block)
183
- self.class.each_type(query, owner_type, &block)
184
- end
185
-
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}"
197
- end
198
- end
199
-
200
150
  def skip?(ast_node, query)
201
151
  dir = ast_node.directives
202
152
  dir.any? && !GraphQL::Execution::DirectiveChecks.include?(dir, query)
203
153
  end
204
154
 
205
155
  class VisitDefinition
206
- def initialize(context, definitions, nodes_stack, scope_stack)
156
+ def initialize(context, definitions, nodes_stack, scopes_stack)
207
157
  @context = context
208
158
  @query = context.query
209
159
  @definitions = definitions
210
160
  @nodes_stack = nodes_stack
211
- @scope_stack = scope_stack
161
+ @scopes_stack = scopes_stack
212
162
  end
213
163
 
214
164
  def enter(ast_node, ast_parent)
215
165
  # Either QueryType or the fragment type condition
216
166
  owner_type = @context.type_definition && @context.type_definition.unwrap
217
- next_nodes = []
218
- next_scope = Set.new
219
167
  defn_name = ast_node.name
220
- Rewrite.each_type(@query, owner_type) do |obj_type|
221
- next_scope.add(obj_type)
222
- end
223
168
 
224
169
  node = Node.new(
170
+ parent: nil,
225
171
  name: defn_name,
226
172
  owner_type: owner_type,
227
173
  query: @query,
228
- ast_nodes: Set.new([ast_node]),
174
+ ast_nodes: [ast_node],
229
175
  return_type: owner_type,
230
176
  )
231
- @definitions[defn_name] = node
232
- next_nodes << node
233
177
 
234
- @nodes_stack.push(next_nodes)
235
- @scope_stack.push(next_scope)
178
+ @definitions[defn_name] = node
179
+ @scopes_stack.push(Scope.new(@query, owner_type))
180
+ @nodes_stack.push([node])
236
181
  end
237
182
 
238
183
  def leave(ast_node, ast_parent)
239
184
  @nodes_stack.pop
240
- @scope_stack.pop
185
+ @scopes_stack.pop
241
186
  end
242
187
  end
243
188
  end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module InternalRepresentation
4
+ # At a point in the AST, selections may apply to one or more types.
5
+ # {Scope} represents those types which selections may apply to.
6
+ #
7
+ # Scopes can be defined by:
8
+ #
9
+ # - A single concrete or abstract type
10
+ # - An array of types
11
+ # - `nil`
12
+ #
13
+ # The AST may be scoped to an array of types when two abstractly-typed
14
+ # fragments occur in inside one another.
15
+ class Scope
16
+ NO_TYPES = [].freeze
17
+
18
+ # @param query [GraphQL::Query]
19
+ # @param type_defn [GraphQL::BaseType, Array<GraphQL::BaseType>, nil]
20
+ def initialize(query, type_defn)
21
+ @query = query
22
+ @type = type_defn
23
+ @abstract_type = false
24
+ @types = case type_defn
25
+ when Array
26
+ type_defn
27
+ when GraphQL::BaseType
28
+ @abstract_type = true
29
+ nil
30
+ when nil
31
+ NO_TYPES
32
+ else
33
+ raise "Unexpected scope type: #{type_defn}"
34
+ end
35
+ end
36
+
37
+ # From a starting point of `self`, create a new scope by condition `other_type_defn`.
38
+ # @param other_type_defn [GraphQL::BaseType, nil]
39
+ # @return [Scope]
40
+ def enter(other_type_defn)
41
+ case other_type_defn
42
+ when nil
43
+ # The type wasn't found, who cares
44
+ Scope.new(@query, nil)
45
+ when @type
46
+ # The condition is the same as current, so reuse self
47
+ self
48
+ when GraphQL::UnionType, GraphQL::InterfaceType
49
+ # Make a new scope of the intersection between the previous & next conditions
50
+ new_types = @query.possible_types(other_type_defn) & concrete_types
51
+ Scope.new(@query, new_types)
52
+ when GraphQL::BaseType
53
+ # If this type is valid within the current scope,
54
+ # return a new scope of _exactly_ this type.
55
+ # Otherwise, this type is out-of-scope so the scope is null.
56
+ if concrete_types.include?(other_type_defn)
57
+ Scope.new(@query, other_type_defn)
58
+ else
59
+ Scope.new(@query, nil)
60
+ end
61
+ else
62
+ raise "Unexpected scope: #{other_type_defn.inspect}"
63
+ end
64
+ end
65
+
66
+ # Call the block for each type in `self`.
67
+ # This uses the simplest possible expression of `self`,
68
+ # so if this scope is defined by an abstract type, it gets yielded.
69
+ def each
70
+ if @abstract_type
71
+ yield(@type)
72
+ else
73
+ @types.each { |t| yield(t) }
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ def concrete_types
80
+ @concrete_types ||= if @abstract_type
81
+ @query.possible_types(@type)
82
+ else
83
+ @types
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end