graphql 1.5.3 → 1.5.4

Sign up to get free protection for your applications and to get access to all the features.
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