graphql 1.11.3 → 1.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (180) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/core.rb +8 -0
  3. data/lib/generators/graphql/install_generator.rb +5 -5
  4. data/lib/generators/graphql/object_generator.rb +2 -0
  5. data/lib/generators/graphql/relay_generator.rb +63 -0
  6. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  7. data/lib/generators/graphql/templates/base_connection.erb +8 -0
  8. data/lib/generators/graphql/templates/base_edge.erb +8 -0
  9. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  10. data/lib/generators/graphql/templates/base_field.erb +2 -0
  11. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  12. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  13. data/lib/generators/graphql/templates/base_mutation.erb +2 -0
  14. data/lib/generators/graphql/templates/base_object.erb +2 -0
  15. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  16. data/lib/generators/graphql/templates/base_union.erb +2 -0
  17. data/lib/generators/graphql/templates/enum.erb +2 -0
  18. data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
  19. data/lib/generators/graphql/templates/interface.erb +2 -0
  20. data/lib/generators/graphql/templates/loader.erb +2 -0
  21. data/lib/generators/graphql/templates/mutation.erb +2 -0
  22. data/lib/generators/graphql/templates/mutation_type.erb +2 -0
  23. data/lib/generators/graphql/templates/node_type.erb +9 -0
  24. data/lib/generators/graphql/templates/object.erb +3 -1
  25. data/lib/generators/graphql/templates/query_type.erb +3 -3
  26. data/lib/generators/graphql/templates/scalar.erb +2 -0
  27. data/lib/generators/graphql/templates/schema.erb +10 -35
  28. data/lib/generators/graphql/templates/union.erb +3 -1
  29. data/lib/graphql.rb +55 -4
  30. data/lib/graphql/analysis/analyze_query.rb +7 -0
  31. data/lib/graphql/analysis/ast.rb +11 -2
  32. data/lib/graphql/analysis/ast/visitor.rb +9 -1
  33. data/lib/graphql/argument.rb +3 -3
  34. data/lib/graphql/backtrace.rb +28 -19
  35. data/lib/graphql/backtrace/legacy_tracer.rb +56 -0
  36. data/lib/graphql/backtrace/table.rb +22 -2
  37. data/lib/graphql/backtrace/tracer.rb +40 -8
  38. data/lib/graphql/backwards_compatibility.rb +1 -0
  39. data/lib/graphql/compatibility/execution_specification.rb +1 -0
  40. data/lib/graphql/compatibility/lazy_execution_specification.rb +2 -0
  41. data/lib/graphql/compatibility/query_parser_specification.rb +2 -0
  42. data/lib/graphql/compatibility/schema_parser_specification.rb +2 -0
  43. data/lib/graphql/dataloader.rb +197 -0
  44. data/lib/graphql/dataloader/null_dataloader.rb +21 -0
  45. data/lib/graphql/dataloader/request.rb +24 -0
  46. data/lib/graphql/dataloader/request_all.rb +22 -0
  47. data/lib/graphql/dataloader/source.rb +93 -0
  48. data/lib/graphql/define/assign_global_id_field.rb +2 -2
  49. data/lib/graphql/define/instance_definable.rb +32 -2
  50. data/lib/graphql/define/type_definer.rb +5 -5
  51. data/lib/graphql/deprecated_dsl.rb +5 -0
  52. data/lib/graphql/enum_type.rb +2 -0
  53. data/lib/graphql/execution/errors.rb +4 -0
  54. data/lib/graphql/execution/execute.rb +7 -0
  55. data/lib/graphql/execution/interpreter.rb +20 -6
  56. data/lib/graphql/execution/interpreter/arguments.rb +57 -5
  57. data/lib/graphql/execution/interpreter/arguments_cache.rb +8 -0
  58. data/lib/graphql/execution/interpreter/handles_raw_value.rb +0 -7
  59. data/lib/graphql/execution/interpreter/runtime.rb +251 -138
  60. data/lib/graphql/execution/multiplex.rb +20 -6
  61. data/lib/graphql/function.rb +4 -0
  62. data/lib/graphql/input_object_type.rb +2 -0
  63. data/lib/graphql/integer_decoding_error.rb +17 -0
  64. data/lib/graphql/interface_type.rb +3 -1
  65. data/lib/graphql/introspection.rb +96 -0
  66. data/lib/graphql/introspection/field_type.rb +7 -3
  67. data/lib/graphql/introspection/input_value_type.rb +6 -0
  68. data/lib/graphql/introspection/introspection_query.rb +6 -92
  69. data/lib/graphql/introspection/type_type.rb +7 -3
  70. data/lib/graphql/invalid_null_error.rb +1 -1
  71. data/lib/graphql/language/block_string.rb +24 -5
  72. data/lib/graphql/language/document_from_schema_definition.rb +50 -23
  73. data/lib/graphql/language/lexer.rb +7 -3
  74. data/lib/graphql/language/lexer.rl +7 -3
  75. data/lib/graphql/language/nodes.rb +1 -1
  76. data/lib/graphql/language/parser.rb +107 -103
  77. data/lib/graphql/language/parser.y +4 -0
  78. data/lib/graphql/language/sanitized_printer.rb +59 -26
  79. data/lib/graphql/name_validator.rb +6 -7
  80. data/lib/graphql/object_type.rb +2 -0
  81. data/lib/graphql/pagination/connection.rb +5 -1
  82. data/lib/graphql/pagination/connections.rb +15 -17
  83. data/lib/graphql/query.rb +8 -3
  84. data/lib/graphql/query/context.rb +38 -4
  85. data/lib/graphql/query/fingerprint.rb +2 -0
  86. data/lib/graphql/query/serial_execution.rb +1 -0
  87. data/lib/graphql/query/validation_pipeline.rb +4 -1
  88. data/lib/graphql/relay/array_connection.rb +2 -2
  89. data/lib/graphql/relay/base_connection.rb +7 -0
  90. data/lib/graphql/relay/connection_instrumentation.rb +4 -4
  91. data/lib/graphql/relay/connection_type.rb +1 -1
  92. data/lib/graphql/relay/mutation.rb +1 -0
  93. data/lib/graphql/relay/node.rb +3 -0
  94. data/lib/graphql/relay/range_add.rb +14 -5
  95. data/lib/graphql/relay/type_extensions.rb +2 -0
  96. data/lib/graphql/scalar_type.rb +2 -0
  97. data/lib/graphql/schema.rb +107 -38
  98. data/lib/graphql/schema/argument.rb +74 -5
  99. data/lib/graphql/schema/build_from_definition.rb +203 -86
  100. data/lib/graphql/schema/default_type_error.rb +2 -0
  101. data/lib/graphql/schema/directive.rb +76 -0
  102. data/lib/graphql/schema/directive/deprecated.rb +1 -1
  103. data/lib/graphql/schema/directive/flagged.rb +57 -0
  104. data/lib/graphql/schema/enum.rb +3 -0
  105. data/lib/graphql/schema/enum_value.rb +12 -6
  106. data/lib/graphql/schema/field.rb +59 -24
  107. data/lib/graphql/schema/field/connection_extension.rb +11 -9
  108. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  109. data/lib/graphql/schema/input_object.rb +38 -25
  110. data/lib/graphql/schema/interface.rb +2 -1
  111. data/lib/graphql/schema/late_bound_type.rb +2 -2
  112. data/lib/graphql/schema/loader.rb +1 -0
  113. data/lib/graphql/schema/member.rb +4 -0
  114. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -0
  115. data/lib/graphql/schema/member/build_type.rb +17 -7
  116. data/lib/graphql/schema/member/has_arguments.rb +70 -51
  117. data/lib/graphql/schema/member/has_deprecation_reason.rb +25 -0
  118. data/lib/graphql/schema/member/has_directives.rb +98 -0
  119. data/lib/graphql/schema/member/has_fields.rb +2 -2
  120. data/lib/graphql/schema/member/has_validators.rb +31 -0
  121. data/lib/graphql/schema/member/type_system_helpers.rb +3 -3
  122. data/lib/graphql/schema/object.rb +11 -0
  123. data/lib/graphql/schema/printer.rb +5 -4
  124. data/lib/graphql/schema/relay_classic_mutation.rb +4 -2
  125. data/lib/graphql/schema/resolver.rb +7 -0
  126. data/lib/graphql/schema/resolver/has_payload_type.rb +2 -0
  127. data/lib/graphql/schema/subscription.rb +20 -12
  128. data/lib/graphql/schema/timeout.rb +29 -15
  129. data/lib/graphql/schema/timeout_middleware.rb +2 -0
  130. data/lib/graphql/schema/unique_within_type.rb +1 -2
  131. data/lib/graphql/schema/validation.rb +10 -0
  132. data/lib/graphql/schema/validator.rb +163 -0
  133. data/lib/graphql/schema/validator/exclusion_validator.rb +31 -0
  134. data/lib/graphql/schema/validator/format_validator.rb +49 -0
  135. data/lib/graphql/schema/validator/inclusion_validator.rb +33 -0
  136. data/lib/graphql/schema/validator/length_validator.rb +57 -0
  137. data/lib/graphql/schema/validator/numericality_validator.rb +71 -0
  138. data/lib/graphql/schema/validator/required_validator.rb +68 -0
  139. data/lib/graphql/schema/warden.rb +2 -3
  140. data/lib/graphql/static_validation.rb +1 -0
  141. data/lib/graphql/static_validation/all_rules.rb +1 -0
  142. data/lib/graphql/static_validation/rules/fields_will_merge.rb +25 -17
  143. data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +30 -0
  144. data/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb +30 -0
  145. data/lib/graphql/static_validation/validation_timeout_error.rb +25 -0
  146. data/lib/graphql/static_validation/validator.rb +31 -7
  147. data/lib/graphql/subscriptions.rb +23 -16
  148. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +21 -7
  149. data/lib/graphql/tracing.rb +2 -2
  150. data/lib/graphql/tracing/appoptics_tracing.rb +12 -2
  151. data/lib/graphql/tracing/platform_tracing.rb +4 -2
  152. data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +4 -1
  153. data/lib/graphql/tracing/skylight_tracing.rb +1 -1
  154. data/lib/graphql/types/int.rb +9 -2
  155. data/lib/graphql/types/iso_8601_date_time.rb +2 -1
  156. data/lib/graphql/types/relay.rb +11 -3
  157. data/lib/graphql/types/relay/base_connection.rb +2 -90
  158. data/lib/graphql/types/relay/base_edge.rb +2 -34
  159. data/lib/graphql/types/relay/connection_behaviors.rb +123 -0
  160. data/lib/graphql/types/relay/default_relay.rb +27 -0
  161. data/lib/graphql/types/relay/edge_behaviors.rb +42 -0
  162. data/lib/graphql/types/relay/has_node_field.rb +41 -0
  163. data/lib/graphql/types/relay/has_nodes_field.rb +41 -0
  164. data/lib/graphql/types/relay/node.rb +2 -4
  165. data/lib/graphql/types/relay/node_behaviors.rb +15 -0
  166. data/lib/graphql/types/relay/node_field.rb +1 -19
  167. data/lib/graphql/types/relay/nodes_field.rb +1 -19
  168. data/lib/graphql/types/relay/page_info.rb +2 -14
  169. data/lib/graphql/types/relay/page_info_behaviors.rb +25 -0
  170. data/lib/graphql/types/string.rb +7 -1
  171. data/lib/graphql/unauthorized_error.rb +1 -1
  172. data/lib/graphql/union_type.rb +2 -0
  173. data/lib/graphql/upgrader/member.rb +1 -0
  174. data/lib/graphql/upgrader/schema.rb +1 -0
  175. data/lib/graphql/version.rb +1 -1
  176. data/readme.md +1 -1
  177. metadata +38 -9
  178. data/lib/graphql/types/relay/base_field.rb +0 -22
  179. data/lib/graphql/types/relay/base_interface.rb +0 -29
  180. data/lib/graphql/types/relay/base_object.rb +0 -26
@@ -29,11 +29,16 @@ module GraphQL
29
29
 
30
30
  private
31
31
 
32
+ NO_ARGUMENTS = {}.freeze
33
+
32
34
  NO_VALUE_GIVEN = Object.new
33
35
 
34
36
  def prepare_args_hash(ast_arg_or_hash_or_value)
35
37
  case ast_arg_or_hash_or_value
36
38
  when Hash
39
+ if ast_arg_or_hash_or_value.empty?
40
+ return NO_ARGUMENTS
41
+ end
37
42
  args_hash = {}
38
43
  ast_arg_or_hash_or_value.each do |k, v|
39
44
  args_hash[k] = prepare_args_hash(v)
@@ -42,6 +47,9 @@ module GraphQL
42
47
  when Array
43
48
  ast_arg_or_hash_or_value.map { |v| prepare_args_hash(v) }
44
49
  when GraphQL::Language::Nodes::Field, GraphQL::Language::Nodes::InputObject, GraphQL::Language::Nodes::Directive
50
+ if ast_arg_or_hash_or_value.arguments.empty?
51
+ return NO_ARGUMENTS
52
+ end
45
53
  args_hash = {}
46
54
  ast_arg_or_hash_or_value.arguments.each do |arg|
47
55
  v = prepare_args_hash(arg.value)
@@ -13,13 +13,6 @@ module GraphQL
13
13
  @object
14
14
  end
15
15
  end
16
-
17
- # Allows to return "raw" value from the resolver
18
- module HandlesRawValue
19
- def raw_value(obj)
20
- RawValue.new(obj)
21
- end
22
- end
23
16
  end
24
17
  end
25
18
  end
@@ -19,8 +19,10 @@ module GraphQL
19
19
 
20
20
  def initialize(query:, response:)
21
21
  @query = query
22
+ @dataloader = query.multiplex.dataloader
22
23
  @schema = query.schema
23
24
  @context = query.context
25
+ @multiplex_context = query.multiplex.context
24
26
  @interpreter_context = @context.namespace(:interpreter)
25
27
  @response = response
26
28
  @dead_paths = {}
@@ -43,26 +45,63 @@ module GraphQL
43
45
  # might be stored up in lazies.
44
46
  # @return [void]
45
47
  def run_eager
46
-
47
48
  root_operation = query.selected_operation
48
49
  root_op_type = root_operation.operation_type || "query"
49
50
  root_type = schema.root_type_for_operation(root_op_type)
50
51
  path = []
51
- @interpreter_context[:current_object] = query.root_value
52
- @interpreter_context[:current_path] = path
52
+ set_all_interpreter_context(query.root_value, nil, nil, path)
53
53
  object_proxy = authorized_new(root_type, query.root_value, context, path)
54
54
  object_proxy = schema.sync_lazy(object_proxy)
55
55
  if object_proxy.nil?
56
56
  # Root .authorized? returned false.
57
57
  write_in_response(path, nil)
58
- nil
59
58
  else
60
- evaluate_selections(path, context.scoped_context, object_proxy, root_type, root_operation.selections, root_operation_type: root_op_type)
61
- nil
59
+ # Prepare this runtime state to be encapsulated in a Fiber
60
+ @progress_path = path
61
+ @progress_scoped_context = context.scoped_context
62
+ @progress_object = object_proxy
63
+ @progress_object_type = root_type
64
+ @progress_index = nil
65
+ @progress_is_eager_selection = root_op_type == "mutation"
66
+ @progress_selections = gather_selections(object_proxy, root_type, root_operation.selections)
67
+
68
+ # Make the first fiber which will begin execution
69
+ enqueue_selections_fiber
62
70
  end
71
+ delete_interpreter_context(:current_path)
72
+ delete_interpreter_context(:current_field)
73
+ delete_interpreter_context(:current_object)
74
+ delete_interpreter_context(:current_arguments)
75
+ nil
76
+ end
77
+
78
+ # Use `@dataloader` to enqueue a fiber that will pick up from the current point.
79
+ # @return [void]
80
+ def enqueue_selections_fiber
81
+ # Read these into local variables so that later assignments don't affect the block below.
82
+ path = @progress_path
83
+ scoped_context = @progress_scoped_context
84
+ owner_object = @progress_object
85
+ owner_type = @progress_object_type
86
+ idx = @progress_index
87
+ is_eager_selection = @progress_is_eager_selection
88
+ gathered_selections = @progress_selections
89
+
90
+ @dataloader.enqueue {
91
+ evaluate_selections(
92
+ path,
93
+ scoped_context,
94
+ owner_object,
95
+ owner_type,
96
+ is_eager_selection: is_eager_selection,
97
+ after: idx,
98
+ gathered_selections: gathered_selections,
99
+ )
100
+ }
101
+ nil
63
102
  end
64
103
 
65
- def gather_selections(owner_object, owner_type, selections, selections_by_name)
104
+ def gather_selections(owner_object, owner_type, selections, selections_by_name = {})
66
105
  selections.each do |node|
67
106
  # Skip gathering this if the directive says so
68
107
  if !directives_include?(node, owner_object, owner_type)
@@ -114,147 +153,191 @@ module GraphQL
114
153
  raise "Invariant: unexpected selection class: #{node.class}"
115
154
  end
116
155
  end
156
+ selections_by_name
117
157
  end
118
158
 
119
- def evaluate_selections(path, scoped_context, owner_object, owner_type, selections, root_operation_type: nil)
120
- @interpreter_context[:current_object] = owner_object
121
- @interpreter_context[:current_path] = path
122
- selections_by_name = {}
123
- gather_selections(owner_object, owner_type, selections, selections_by_name)
124
- selections_by_name.each do |result_name, field_ast_nodes_or_ast_node|
125
- # As a performance optimization, the hash key will be a `Node` if
126
- # there's only one selection of the field. But if there are multiple
127
- # selections of the field, it will be an Array of nodes
128
- if field_ast_nodes_or_ast_node.is_a?(Array)
129
- field_ast_nodes = field_ast_nodes_or_ast_node
130
- ast_node = field_ast_nodes.first
131
- else
132
- field_ast_nodes = nil
133
- ast_node = field_ast_nodes_or_ast_node
159
+ NO_ARGS = {}.freeze
160
+
161
+ # @return [void]
162
+ def evaluate_selections(path, scoped_context, owner_object, owner_type, is_eager_selection:, gathered_selections:, after:)
163
+ set_all_interpreter_context(owner_object, nil, nil, path)
164
+
165
+ @progress_path = path
166
+ @progress_scoped_context = scoped_context
167
+ @progress_object = owner_object
168
+ @progress_object_type = owner_type
169
+ @progress_index = nil
170
+ @progress_is_eager_selection = is_eager_selection
171
+ @progress_selections = gathered_selections
172
+
173
+ # Track `idx` manually to avoid an allocation on this hot path
174
+ idx = 0
175
+ gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
176
+ prev_idx = idx
177
+ idx += 1
178
+ # TODO: this is how a `progress` resumes where this left off.
179
+ # Is there a better way to seek in the hash?
180
+ # I think we could also use the array of keys; it supports seeking just fine.
181
+ if after && prev_idx <= after
182
+ next
134
183
  end
135
- field_name = ast_node.name
136
- field_defn = @fields_cache[owner_type][field_name] ||= owner_type.get_field(field_name)
137
- is_introspection = false
138
- if field_defn.nil?
139
- field_defn = if owner_type == schema.query && (entry_point_field = schema.introspection_system.entry_point(name: field_name))
140
- is_introspection = true
141
- entry_point_field
142
- elsif (dynamic_field = schema.introspection_system.dynamic_field(name: field_name))
143
- is_introspection = true
144
- dynamic_field
145
- else
146
- raise "Invariant: no field for #{owner_type}.#{field_name}"
147
- end
184
+ @progress_index = prev_idx
185
+ # This is how the current runtime gives itself to `dataloader`
186
+ # so that the dataloader can enqueue another fiber to resume if needed.
187
+ @dataloader.current_runtime = self
188
+ evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_selection)
189
+ # The dataloader knows if ^^ that selection halted and later selections were executed in another fiber.
190
+ # If that's the case, then don't continue execution here.
191
+ if @dataloader.yielded?
192
+ break
148
193
  end
149
- return_type = field_defn.type
194
+ end
195
+ nil
196
+ end
150
197
 
151
- next_path = path.dup
152
- next_path << result_name
153
- next_path.freeze
198
+ # @return [void]
199
+ def evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_field)
200
+ # As a performance optimization, the hash key will be a `Node` if
201
+ # there's only one selection of the field. But if there are multiple
202
+ # selections of the field, it will be an Array of nodes
203
+ if field_ast_nodes_or_ast_node.is_a?(Array)
204
+ field_ast_nodes = field_ast_nodes_or_ast_node
205
+ ast_node = field_ast_nodes.first
206
+ else
207
+ field_ast_nodes = nil
208
+ ast_node = field_ast_nodes_or_ast_node
209
+ end
210
+ field_name = ast_node.name
211
+ field_defn = @fields_cache[owner_type][field_name] ||= owner_type.get_field(field_name)
212
+ is_introspection = false
213
+ if field_defn.nil?
214
+ field_defn = if owner_type == schema.query && (entry_point_field = schema.introspection_system.entry_point(name: field_name))
215
+ is_introspection = true
216
+ entry_point_field
217
+ elsif (dynamic_field = schema.introspection_system.dynamic_field(name: field_name))
218
+ is_introspection = true
219
+ dynamic_field
220
+ else
221
+ raise "Invariant: no field for #{owner_type}.#{field_name}"
222
+ end
223
+ end
224
+ return_type = field_defn.type
154
225
 
155
- # This seems janky, but we need to know
156
- # the field's return type at this path in order
157
- # to propagate `null`
158
- set_type_at_path(next_path, return_type)
159
- # Set this before calling `run_with_directives`, so that the directive can have the latest path
160
- @interpreter_context[:current_path] = next_path
161
- @interpreter_context[:current_field] = field_defn
226
+ next_path = path.dup
227
+ next_path << result_name
228
+ next_path.freeze
162
229
 
163
- context.scoped_context = scoped_context
164
- object = owner_object
230
+ # This seems janky, but we need to know
231
+ # the field's return type at this path in order
232
+ # to propagate `null`
233
+ set_type_at_path(next_path, return_type)
234
+ # Set this before calling `run_with_directives`, so that the directive can have the latest path
235
+ set_all_interpreter_context(nil, field_defn, nil, next_path)
165
236
 
166
- if is_introspection
167
- object = authorized_new(field_defn.owner, object, context, next_path)
168
- end
237
+ context.scoped_context = scoped_context
238
+ object = owner_object
169
239
 
170
- begin
171
- kwarg_arguments = arguments(object, field_defn, ast_node)
172
- rescue GraphQL::ExecutionError => e
173
- continue_value(next_path, e, field_defn, return_type.non_null?, ast_node)
174
- next
175
- end
240
+ if is_introspection
241
+ object = authorized_new(field_defn.owner, object, context, next_path)
242
+ end
176
243
 
177
- after_lazy(kwarg_arguments, owner: owner_type, field: field_defn, path: next_path, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |resolved_arguments|
178
- if resolved_arguments.is_a? GraphQL::ExecutionError
179
- continue_value(next_path, resolved_arguments, field_defn, return_type.non_null?, ast_node)
180
- next
181
- end
244
+ begin
245
+ kwarg_arguments = arguments(object, field_defn, ast_node)
246
+ rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => e
247
+ continue_value(next_path, e, owner_type, field_defn, return_type.non_null?, ast_node)
248
+ return
249
+ end
182
250
 
183
- kwarg_arguments = resolved_arguments.keyword_arguments
251
+ after_lazy(kwarg_arguments, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |resolved_arguments|
252
+ if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
253
+ continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node)
254
+ next
255
+ end
184
256
 
257
+ kwarg_arguments = if resolved_arguments.empty? && field_defn.extras.empty?
258
+ # We can avoid allocating the `{ Symbol => Object }` hash in this case
259
+ NO_ARGS
260
+ else
261
+ # Bundle up the extras, then make a new arguments instance
262
+ # that includes the extras, too.
263
+ extra_args = {}
185
264
  field_defn.extras.each do |extra|
186
265
  case extra
187
266
  when :ast_node
188
- kwarg_arguments[:ast_node] = ast_node
267
+ extra_args[:ast_node] = ast_node
189
268
  when :execution_errors
190
- kwarg_arguments[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
269
+ extra_args[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
191
270
  when :path
192
- kwarg_arguments[:path] = next_path
271
+ extra_args[:path] = next_path
193
272
  when :lookahead
194
273
  if !field_ast_nodes
195
274
  field_ast_nodes = [ast_node]
196
275
  end
197
- kwarg_arguments[:lookahead] = Execution::Lookahead.new(
276
+
277
+ extra_args[:lookahead] = Execution::Lookahead.new(
198
278
  query: query,
199
279
  ast_nodes: field_ast_nodes,
200
280
  field: field_defn,
201
281
  )
202
282
  when :argument_details
203
- kwarg_arguments[:argument_details] = resolved_arguments
283
+ # Use this flag to tell Interpreter::Arguments to add itself
284
+ # to the keyword args hash _before_ freezing everything.
285
+ extra_args[:argument_details] = :__arguments_add_self
204
286
  else
205
- kwarg_arguments[extra] = field_defn.fetch_extra(extra, context)
287
+ extra_args[extra] = field_defn.fetch_extra(extra, context)
206
288
  end
207
289
  end
290
+ resolved_arguments = resolved_arguments.merge_extras(extra_args)
291
+ resolved_arguments.keyword_arguments
292
+ end
208
293
 
209
- @interpreter_context[:current_arguments] = kwarg_arguments
294
+ set_all_interpreter_context(nil, nil, kwarg_arguments, nil)
210
295
 
211
- # Optimize for the case that field is selected only once
212
- if field_ast_nodes.nil? || field_ast_nodes.size == 1
213
- next_selections = ast_node.selections
214
- else
215
- next_selections = []
216
- field_ast_nodes.each { |f| next_selections.concat(f.selections) }
217
- end
296
+ # Optimize for the case that field is selected only once
297
+ if field_ast_nodes.nil? || field_ast_nodes.size == 1
298
+ next_selections = ast_node.selections
299
+ else
300
+ next_selections = []
301
+ field_ast_nodes.each { |f| next_selections.concat(f.selections) }
302
+ end
218
303
 
219
- field_result = resolve_with_directives(object, ast_node) do
220
- # Actually call the field resolver and capture the result
221
- app_result = begin
222
- query.with_error_handling do
223
- query.trace("execute_field", {owner: owner_type, field: field_defn, path: next_path, query: query, object: object, arguments: kwarg_arguments}) do
224
- field_defn.resolve(object, kwarg_arguments, context)
225
- end
304
+ field_result = resolve_with_directives(object, ast_node) do
305
+ # Actually call the field resolver and capture the result
306
+ app_result = begin
307
+ query.with_error_handling do
308
+ query.trace("execute_field", {owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments}) do
309
+ field_defn.resolve(object, kwarg_arguments, context)
226
310
  end
227
- rescue GraphQL::ExecutionError => err
228
- err
229
311
  end
230
- after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |inner_result|
231
- continue_value = continue_value(next_path, inner_result, field_defn, return_type.non_null?, ast_node)
232
- if RawValue === continue_value
233
- # Write raw value directly to the response without resolving nested objects
234
- write_in_response(next_path, continue_value.resolve)
235
- elsif HALT != continue_value
236
- continue_field(next_path, continue_value, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments)
237
- end
312
+ rescue GraphQL::ExecutionError => err
313
+ err
314
+ end
315
+ after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |inner_result|
316
+ continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node)
317
+ if RawValue === continue_value
318
+ # Write raw value directly to the response without resolving nested objects
319
+ write_in_response(next_path, continue_value.resolve)
320
+ elsif HALT != continue_value
321
+ continue_field(next_path, continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments)
238
322
  end
239
323
  end
324
+ end
240
325
 
241
- # If this field is a root mutation field, immediately resolve
242
- # all of its child fields before moving on to the next root mutation field.
243
- # (Subselections of this mutation will still be resolved level-by-level.)
244
- if root_operation_type == "mutation"
245
- Interpreter::Resolve.resolve_all([field_result])
246
- else
247
- field_result
248
- end
326
+ # If this field is a root mutation field, immediately resolve
327
+ # all of its child fields before moving on to the next root mutation field.
328
+ # (Subselections of this mutation will still be resolved level-by-level.)
329
+ if is_eager_field
330
+ Interpreter::Resolve.resolve_all([field_result])
249
331
  end
332
+
333
+ nil
250
334
  end
251
335
  end
252
336
 
253
337
  HALT = Object.new
254
- def continue_value(path, value, field, is_non_null, ast_node)
338
+ def continue_value(path, value, parent_type, field, is_non_null, ast_node)
255
339
  if value.nil?
256
340
  if is_non_null
257
- parent_type = field.owner_type
258
341
  err = parent_type::InvalidNullError.new(parent_type, field, value)
259
342
  write_invalid_null_in_response(path, err)
260
343
  else
@@ -282,7 +365,7 @@ module GraphQL
282
365
  err
283
366
  end
284
367
 
285
- continue_value(path, next_value, field, is_non_null, ast_node)
368
+ continue_value(path, next_value, parent_type, field, is_non_null, ast_node)
286
369
  elsif GraphQL::Execution::Execute::SKIP == value
287
370
  HALT
288
371
  else
@@ -298,49 +381,50 @@ module GraphQL
298
381
  # Location information from `path` and `ast_node`.
299
382
  #
300
383
  # @return [Lazy, Array, Hash, Object] Lazy, Array, and Hash are all traversed to resolve lazy values later
301
- def continue_field(path, value, field, type, ast_node, next_selections, is_non_null, owner_object, arguments) # rubocop:disable Metrics/ParameterLists
302
- case type.kind.name
384
+ def continue_field(path, value, owner_type, field, current_type, ast_node, next_selections, is_non_null, owner_object, arguments) # rubocop:disable Metrics/ParameterLists
385
+ case current_type.kind.name
303
386
  when "SCALAR", "ENUM"
304
- r = type.coerce_result(value, context)
387
+ r = current_type.coerce_result(value, context)
305
388
  write_in_response(path, r)
306
389
  r
307
390
  when "UNION", "INTERFACE"
308
- resolved_type_or_lazy, resolved_value = resolve_type(type, value, path)
391
+ resolved_type_or_lazy, resolved_value = resolve_type(current_type, value, path)
309
392
  resolved_value ||= value
310
393
 
311
- after_lazy(resolved_type_or_lazy, owner: type, path: path, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |resolved_type|
312
- possible_types = query.possible_types(type)
394
+ after_lazy(resolved_type_or_lazy, owner: current_type, path: path, ast_node: ast_node, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |resolved_type|
395
+ possible_types = query.possible_types(current_type)
313
396
 
314
397
  if !possible_types.include?(resolved_type)
315
398
  parent_type = field.owner_type
316
- err_class = type::UnresolvedTypeError
399
+ err_class = current_type::UnresolvedTypeError
317
400
  type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types)
318
401
  schema.type_error(type_error, context)
319
402
  write_in_response(path, nil)
320
403
  nil
321
404
  else
322
- continue_field(path, resolved_value, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments)
405
+ continue_field(path, resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments)
323
406
  end
324
407
  end
325
408
  when "OBJECT"
326
409
  object_proxy = begin
327
- authorized_new(type, value, context, path)
410
+ authorized_new(current_type, value, context, path)
328
411
  rescue GraphQL::ExecutionError => err
329
412
  err
330
413
  end
331
- after_lazy(object_proxy, owner: type, path: path, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |inner_object|
332
- continue_value = continue_value(path, inner_object, field, is_non_null, ast_node)
414
+ after_lazy(object_proxy, owner: current_type, path: path, ast_node: ast_node, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |inner_object|
415
+ continue_value = continue_value(path, inner_object, owner_type, field, is_non_null, ast_node)
333
416
  if HALT != continue_value
334
417
  response_hash = {}
335
418
  write_in_response(path, response_hash)
336
- evaluate_selections(path, context.scoped_context, continue_value, type, next_selections)
419
+ gathered_selections = gather_selections(continue_value, current_type, next_selections)
420
+ evaluate_selections(path, context.scoped_context, continue_value, current_type, is_eager_selection: false, gathered_selections: gathered_selections, after: nil)
337
421
  response_hash
338
422
  end
339
423
  end
340
424
  when "LIST"
341
425
  response_list = []
342
426
  write_in_response(path, response_list)
343
- inner_type = type.of_type
427
+ inner_type = current_type.of_type
344
428
  idx = 0
345
429
  scoped_context = context.scoped_context
346
430
  begin
@@ -351,10 +435,10 @@ module GraphQL
351
435
  idx += 1
352
436
  set_type_at_path(next_path, inner_type)
353
437
  # This will update `response_list` with the lazy
354
- after_lazy(inner_value, owner: inner_type, path: next_path, scoped_context: scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |inner_inner_value|
355
- continue_value = continue_value(next_path, inner_inner_value, field, inner_type.non_null?, ast_node)
438
+ after_lazy(inner_value, owner: inner_type, path: next_path, ast_node: ast_node, scoped_context: scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |inner_inner_value|
439
+ continue_value = continue_value(next_path, inner_inner_value, owner_type, field, inner_type.non_null?, ast_node)
356
440
  if HALT != continue_value
357
- continue_field(next_path, continue_value, field, inner_type, ast_node, next_selections, false, owner_object, arguments)
441
+ continue_field(next_path, continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments)
358
442
  end
359
443
  end
360
444
  end
@@ -371,12 +455,12 @@ module GraphQL
371
455
 
372
456
  response_list
373
457
  when "NON_NULL"
374
- inner_type = type.of_type
458
+ inner_type = current_type.of_type
375
459
  # Don't `set_type_at_path` because we want the static type,
376
460
  # we're going to use that to determine whether a `nil` should be propagated or not.
377
- continue_field(path, value, field, inner_type, ast_node, next_selections, true, owner_object, arguments)
461
+ continue_field(path, value, owner_type, field, inner_type, ast_node, next_selections, true, owner_object, arguments)
378
462
  else
379
- raise "Invariant: Unhandled type kind #{type.kind} (#{type})"
463
+ raise "Invariant: Unhandled type kind #{current_type.kind} (#{current_type})"
380
464
  end
381
465
  end
382
466
 
@@ -413,30 +497,39 @@ module GraphQL
413
497
  true
414
498
  end
415
499
 
500
+ def set_all_interpreter_context(object, field, arguments, path)
501
+ if object
502
+ @context[:current_object] = @interpreter_context[:current_object] = object
503
+ end
504
+ if field
505
+ @context[:current_field] = @interpreter_context[:current_field] = field
506
+ end
507
+ if arguments
508
+ @context[:current_arguments] = @interpreter_context[:current_arguments] = arguments
509
+ end
510
+ if path
511
+ @context[:current_path] = @interpreter_context[:current_path] = path
512
+ end
513
+ end
514
+
416
515
  # @param obj [Object] Some user-returned value that may want to be batched
417
516
  # @param path [Array<String>]
418
517
  # @param field [GraphQL::Schema::Field]
419
518
  # @param eager [Boolean] Set to `true` for mutation root fields only
420
519
  # @param trace [Boolean] If `false`, don't wrap this with field tracing
421
520
  # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
422
- def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, eager: false, trace: true, &block)
423
- @interpreter_context[:current_object] = owner_object
424
- @interpreter_context[:current_arguments] = arguments
425
- @interpreter_context[:current_path] = path
426
- @interpreter_context[:current_field] = field
521
+ def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, ast_node:, eager: false, trace: true, &block)
522
+ set_all_interpreter_context(owner_object, field, arguments, path)
427
523
  if schema.lazy?(lazy_obj)
428
524
  lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do
429
- @interpreter_context[:current_path] = path
430
- @interpreter_context[:current_field] = field
431
- @interpreter_context[:current_object] = owner_object
432
- @interpreter_context[:current_arguments] = arguments
525
+ set_all_interpreter_context(owner_object, field, arguments, path)
433
526
  context.scoped_context = scoped_context
434
527
  # Wrap the execution of _this_ method with tracing,
435
528
  # but don't wrap the continuation below
436
529
  inner_obj = begin
437
530
  query.with_error_handling do
438
531
  if trace
439
- query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments}) do
532
+ query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments, ast_node: ast_node}) do
440
533
  schema.sync_lazy(lazy_obj)
441
534
  end
442
535
  else
@@ -446,7 +539,7 @@ module GraphQL
446
539
  rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
447
540
  err
448
541
  end
449
- after_lazy(inner_obj, owner: owner, field: field, path: path, scoped_context: context.scoped_context, owner_object: owner_object, arguments: arguments, eager: eager, trace: trace, &block)
542
+ after_lazy(inner_obj, owner: owner, field: field, path: path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: owner_object, arguments: arguments, eager: eager, trace: trace, &block)
450
543
  end
451
544
 
452
545
  if eager
@@ -461,9 +554,7 @@ module GraphQL
461
554
  end
462
555
 
463
556
  def arguments(graphql_object, arg_owner, ast_node)
464
- # Don't cache arguments if field extras or extensions are requested since they can mutate the argument data structure
465
- if arg_owner.arguments_statically_coercible? &&
466
- (!arg_owner.is_a?(GraphQL::Schema::Field) || (arg_owner.extras.empty? && arg_owner.extensions.empty?))
557
+ if arg_owner.arguments_statically_coercible?
467
558
  query.arguments_for(ast_node, arg_owner)
468
559
  else
469
560
  # The arguments must be prepared in the context of the given object
@@ -504,6 +595,16 @@ module GraphQL
504
595
  end
505
596
  end
506
597
 
598
+ def value_at(path)
599
+ i = 0
600
+ value = @response.final_value
601
+ while value && (part = path[i])
602
+ value = value[part]
603
+ i += 1
604
+ end
605
+ value
606
+ end
607
+
507
608
  # To propagate nulls, we have to know what the field type was
508
609
  # at previous parts of the response.
509
610
  # This hash matches the response
@@ -540,6 +641,18 @@ module GraphQL
540
641
  res && res[:__dead]
541
642
  end
542
643
 
644
+ # Set this pair in the Query context, but also in the interpeter namespace,
645
+ # for compatibility.
646
+ def set_interpreter_context(key, value)
647
+ @interpreter_context[key] = value
648
+ @context[key] = value
649
+ end
650
+
651
+ def delete_interpreter_context(key)
652
+ @interpreter_context.delete(key)
653
+ @context.delete(key)
654
+ end
655
+
543
656
  def resolve_type(type, value, path)
544
657
  trace_payload = { context: context, type: type, object: value, path: path }
545
658
  resolved_type, resolved_value = query.trace("resolve_type", trace_payload) do