graphql 1.11.3 → 1.12.0

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.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

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