graphql 1.11.7 → 1.12.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (140) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install_generator.rb +7 -5
  3. data/lib/generators/graphql/relay.rb +55 -0
  4. data/lib/generators/graphql/relay_generator.rb +20 -0
  5. data/lib/generators/graphql/templates/base_connection.erb +8 -0
  6. data/lib/generators/graphql/templates/base_edge.erb +8 -0
  7. data/lib/generators/graphql/templates/node_type.erb +9 -0
  8. data/lib/generators/graphql/templates/object.erb +1 -1
  9. data/lib/generators/graphql/templates/query_type.erb +1 -3
  10. data/lib/generators/graphql/templates/schema.erb +8 -35
  11. data/lib/graphql.rb +38 -4
  12. data/lib/graphql/analysis/analyze_query.rb +7 -0
  13. data/lib/graphql/analysis/ast.rb +11 -2
  14. data/lib/graphql/analysis/ast/visitor.rb +9 -1
  15. data/lib/graphql/backtrace.rb +28 -19
  16. data/lib/graphql/backtrace/inspect_result.rb +0 -1
  17. data/lib/graphql/backtrace/legacy_tracer.rb +56 -0
  18. data/lib/graphql/backtrace/table.rb +22 -3
  19. data/lib/graphql/backtrace/traced_error.rb +0 -1
  20. data/lib/graphql/backtrace/tracer.rb +37 -10
  21. data/lib/graphql/backwards_compatibility.rb +2 -1
  22. data/lib/graphql/base_type.rb +1 -1
  23. data/lib/graphql/compatibility/execution_specification.rb +1 -0
  24. data/lib/graphql/compatibility/lazy_execution_specification.rb +2 -0
  25. data/lib/graphql/compatibility/query_parser_specification.rb +2 -0
  26. data/lib/graphql/compatibility/schema_parser_specification.rb +2 -0
  27. data/lib/graphql/dataloader.rb +208 -0
  28. data/lib/graphql/dataloader/null_dataloader.rb +21 -0
  29. data/lib/graphql/dataloader/request.rb +19 -0
  30. data/lib/graphql/dataloader/request_all.rb +19 -0
  31. data/lib/graphql/dataloader/source.rb +107 -0
  32. data/lib/graphql/define/assign_global_id_field.rb +1 -1
  33. data/lib/graphql/define/instance_definable.rb +32 -2
  34. data/lib/graphql/define/type_definer.rb +5 -5
  35. data/lib/graphql/deprecated_dsl.rb +7 -2
  36. data/lib/graphql/deprecation.rb +13 -0
  37. data/lib/graphql/enum_type.rb +2 -0
  38. data/lib/graphql/execution/errors.rb +4 -0
  39. data/lib/graphql/execution/execute.rb +7 -0
  40. data/lib/graphql/execution/interpreter.rb +11 -7
  41. data/lib/graphql/execution/interpreter/arguments.rb +51 -14
  42. data/lib/graphql/execution/interpreter/arguments_cache.rb +37 -14
  43. data/lib/graphql/execution/interpreter/handles_raw_value.rb +0 -7
  44. data/lib/graphql/execution/interpreter/resolve.rb +33 -25
  45. data/lib/graphql/execution/interpreter/runtime.rb +173 -123
  46. data/lib/graphql/execution/multiplex.rb +36 -23
  47. data/lib/graphql/function.rb +4 -0
  48. data/lib/graphql/input_object_type.rb +2 -0
  49. data/lib/graphql/interface_type.rb +3 -1
  50. data/lib/graphql/internal_representation/document.rb +2 -2
  51. data/lib/graphql/internal_representation/rewrite.rb +1 -1
  52. data/lib/graphql/language/document_from_schema_definition.rb +50 -23
  53. data/lib/graphql/object_type.rb +2 -2
  54. data/lib/graphql/pagination/connection.rb +5 -1
  55. data/lib/graphql/pagination/connections.rb +6 -16
  56. data/lib/graphql/parse_error.rb +0 -1
  57. data/lib/graphql/query.rb +10 -2
  58. data/lib/graphql/query/arguments.rb +1 -1
  59. data/lib/graphql/query/arguments_cache.rb +0 -1
  60. data/lib/graphql/query/context.rb +4 -2
  61. data/lib/graphql/query/executor.rb +0 -1
  62. data/lib/graphql/query/null_context.rb +3 -2
  63. data/lib/graphql/query/serial_execution.rb +1 -0
  64. data/lib/graphql/query/variable_validation_error.rb +1 -1
  65. data/lib/graphql/relay/base_connection.rb +7 -0
  66. data/lib/graphql/relay/connection_instrumentation.rb +4 -4
  67. data/lib/graphql/relay/connection_type.rb +1 -1
  68. data/lib/graphql/relay/mutation.rb +1 -0
  69. data/lib/graphql/relay/node.rb +3 -0
  70. data/lib/graphql/relay/type_extensions.rb +2 -0
  71. data/lib/graphql/scalar_type.rb +2 -0
  72. data/lib/graphql/schema.rb +64 -26
  73. data/lib/graphql/schema/argument.rb +86 -7
  74. data/lib/graphql/schema/build_from_definition.rb +139 -51
  75. data/lib/graphql/schema/directive.rb +76 -0
  76. data/lib/graphql/schema/directive/flagged.rb +57 -0
  77. data/lib/graphql/schema/enum.rb +3 -0
  78. data/lib/graphql/schema/enum_value.rb +12 -6
  79. data/lib/graphql/schema/field.rb +40 -16
  80. data/lib/graphql/schema/field/connection_extension.rb +3 -2
  81. data/lib/graphql/schema/find_inherited_value.rb +3 -1
  82. data/lib/graphql/schema/input_object.rb +39 -24
  83. data/lib/graphql/schema/interface.rb +1 -0
  84. data/lib/graphql/schema/member.rb +4 -0
  85. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -0
  86. data/lib/graphql/schema/member/build_type.rb +3 -3
  87. data/lib/graphql/schema/member/has_arguments.rb +54 -49
  88. data/lib/graphql/schema/member/has_deprecation_reason.rb +25 -0
  89. data/lib/graphql/schema/member/has_directives.rb +98 -0
  90. data/lib/graphql/schema/member/has_fields.rb +1 -4
  91. data/lib/graphql/schema/member/has_validators.rb +31 -0
  92. data/lib/graphql/schema/member/instrumentation.rb +0 -1
  93. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  94. data/lib/graphql/schema/middleware_chain.rb +1 -1
  95. data/lib/graphql/schema/object.rb +11 -0
  96. data/lib/graphql/schema/printer.rb +5 -4
  97. data/lib/graphql/schema/resolver.rb +7 -0
  98. data/lib/graphql/schema/resolver/has_payload_type.rb +2 -0
  99. data/lib/graphql/schema/subscription.rb +19 -1
  100. data/lib/graphql/schema/timeout_middleware.rb +3 -1
  101. data/lib/graphql/schema/validation.rb +4 -2
  102. data/lib/graphql/schema/validator.rb +163 -0
  103. data/lib/graphql/schema/validator/exclusion_validator.rb +31 -0
  104. data/lib/graphql/schema/validator/format_validator.rb +49 -0
  105. data/lib/graphql/schema/validator/inclusion_validator.rb +33 -0
  106. data/lib/graphql/schema/validator/length_validator.rb +57 -0
  107. data/lib/graphql/schema/validator/numericality_validator.rb +71 -0
  108. data/lib/graphql/schema/validator/required_validator.rb +68 -0
  109. data/lib/graphql/static_validation/validator.rb +4 -0
  110. data/lib/graphql/subscriptions.rb +17 -20
  111. data/lib/graphql/subscriptions/event.rb +0 -1
  112. data/lib/graphql/subscriptions/instrumentation.rb +0 -1
  113. data/lib/graphql/subscriptions/serialize.rb +0 -1
  114. data/lib/graphql/subscriptions/subscription_root.rb +1 -1
  115. data/lib/graphql/tracing.rb +2 -2
  116. data/lib/graphql/tracing/appoptics_tracing.rb +3 -1
  117. data/lib/graphql/tracing/platform_tracing.rb +3 -1
  118. data/lib/graphql/tracing/skylight_tracing.rb +1 -1
  119. data/lib/graphql/types/relay.rb +11 -3
  120. data/lib/graphql/types/relay/base_connection.rb +2 -92
  121. data/lib/graphql/types/relay/base_edge.rb +2 -35
  122. data/lib/graphql/types/relay/connection_behaviors.rb +123 -0
  123. data/lib/graphql/types/relay/default_relay.rb +27 -0
  124. data/lib/graphql/types/relay/edge_behaviors.rb +42 -0
  125. data/lib/graphql/types/relay/has_node_field.rb +41 -0
  126. data/lib/graphql/types/relay/has_nodes_field.rb +41 -0
  127. data/lib/graphql/types/relay/node.rb +2 -4
  128. data/lib/graphql/types/relay/node_behaviors.rb +15 -0
  129. data/lib/graphql/types/relay/node_field.rb +1 -19
  130. data/lib/graphql/types/relay/nodes_field.rb +1 -19
  131. data/lib/graphql/types/relay/page_info.rb +2 -14
  132. data/lib/graphql/types/relay/page_info_behaviors.rb +25 -0
  133. data/lib/graphql/union_type.rb +2 -0
  134. data/lib/graphql/upgrader/member.rb +1 -0
  135. data/lib/graphql/upgrader/schema.rb +1 -0
  136. data/lib/graphql/version.rb +1 -1
  137. metadata +50 -93
  138. data/lib/graphql/types/relay/base_field.rb +0 -22
  139. data/lib/graphql/types/relay/base_interface.rb +0 -29
  140. data/lib/graphql/types/relay/base_object.rb +0 -26
@@ -6,17 +6,21 @@ module GraphQL
6
6
  class ArgumentsCache
7
7
  def initialize(query)
8
8
  @query = query
9
+ @dataloader = query.context.dataloader
9
10
  @storage = Hash.new do |h, ast_node|
10
11
  h[ast_node] = Hash.new do |h2, arg_owner|
11
12
  h2[arg_owner] = Hash.new do |h3, parent_object|
12
- # First, normalize all AST or Ruby values to a plain Ruby hash
13
- args_hash = prepare_args_hash(ast_node)
14
- # Then call into the schema to coerce those incoming values
15
- args = arg_owner.coerce_arguments(parent_object, args_hash, query.context)
13
+ dataload_for(ast_node, arg_owner, parent_object) do |kwarg_arguments|
14
+ h3[parent_object] = @query.schema.after_lazy(kwarg_arguments) do |resolved_args|
15
+ h3[parent_object] = resolved_args
16
+ end
17
+ end
16
18
 
17
- h3[parent_object] = @query.schema.after_lazy(args) do |resolved_args|
18
- # when this promise is resolved, update the cache with the resolved value
19
- h3[parent_object] = resolved_args
19
+ if !h3.key?(parent_object)
20
+ # TODO should i bother putting anything here?
21
+ h3[parent_object] = NO_ARGUMENTS
22
+ else
23
+ h3[parent_object]
20
24
  end
21
25
  end
22
26
  end
@@ -25,6 +29,25 @@ module GraphQL
25
29
 
26
30
  def fetch(ast_node, argument_owner, parent_object)
27
31
  @storage[ast_node][argument_owner][parent_object]
32
+ # If any jobs were enqueued, run them now,
33
+ # since this might have been called outside of execution.
34
+ # (The jobs are responsible for updating `result` in-place.)
35
+ @dataloader.run
36
+ # Ack, the _hash_ is updated, but the key is eventually
37
+ # overridden with an immutable arguments instance.
38
+ # The first call queues up the job,
39
+ # then this call fetches the result.
40
+ # TODO this should be better, find a solution
41
+ # that works with merging the runtime.rb code
42
+ @storage[ast_node][argument_owner][parent_object]
43
+ end
44
+
45
+ # @yield [Interpreter::Arguments, Lazy<Interpreter::Arguments>] The finally-loaded arguments
46
+ def dataload_for(ast_node, argument_owner, parent_object, &block)
47
+ # First, normalize all AST or Ruby values to a plain Ruby hash
48
+ args_hash = self.class.prepare_args_hash(@query, ast_node)
49
+ argument_owner.coerce_arguments(parent_object, args_hash, @query.context, &block)
50
+ nil
28
51
  end
29
52
 
30
53
  private
@@ -33,7 +56,7 @@ module GraphQL
33
56
 
34
57
  NO_VALUE_GIVEN = Object.new
35
58
 
36
- def prepare_args_hash(ast_arg_or_hash_or_value)
59
+ def self.prepare_args_hash(query, ast_arg_or_hash_or_value)
37
60
  case ast_arg_or_hash_or_value
38
61
  when Hash
39
62
  if ast_arg_or_hash_or_value.empty?
@@ -41,27 +64,27 @@ module GraphQL
41
64
  end
42
65
  args_hash = {}
43
66
  ast_arg_or_hash_or_value.each do |k, v|
44
- args_hash[k] = prepare_args_hash(v)
67
+ args_hash[k] = prepare_args_hash(query, v)
45
68
  end
46
69
  args_hash
47
70
  when Array
48
- ast_arg_or_hash_or_value.map { |v| prepare_args_hash(v) }
71
+ ast_arg_or_hash_or_value.map { |v| prepare_args_hash(query, v) }
49
72
  when GraphQL::Language::Nodes::Field, GraphQL::Language::Nodes::InputObject, GraphQL::Language::Nodes::Directive
50
73
  if ast_arg_or_hash_or_value.arguments.empty?
51
74
  return NO_ARGUMENTS
52
75
  end
53
76
  args_hash = {}
54
77
  ast_arg_or_hash_or_value.arguments.each do |arg|
55
- v = prepare_args_hash(arg.value)
78
+ v = prepare_args_hash(query, arg.value)
56
79
  if v != NO_VALUE_GIVEN
57
80
  args_hash[arg.name] = v
58
81
  end
59
82
  end
60
83
  args_hash
61
84
  when GraphQL::Language::Nodes::VariableIdentifier
62
- if @query.variables.key?(ast_arg_or_hash_or_value.name)
63
- variable_value = @query.variables[ast_arg_or_hash_or_value.name]
64
- prepare_args_hash(variable_value)
85
+ if query.variables.key?(ast_arg_or_hash_or_value.name)
86
+ variable_value = query.variables[ast_arg_or_hash_or_value.name]
87
+ prepare_args_hash(query, variable_value)
65
88
  else
66
89
  NO_VALUE_GIVEN
67
90
  end
@@ -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
@@ -6,10 +6,9 @@ module GraphQL
6
6
  module Resolve
7
7
  # Continue field results in `results` until there's nothing else to continue.
8
8
  # @return [void]
9
- def self.resolve_all(results)
10
- while results.any?
11
- results = resolve(results)
12
- end
9
+ def self.resolve_all(results, dataloader)
10
+ dataloader.append_job { resolve(results, dataloader) }
11
+ nil
13
12
  end
14
13
 
15
14
  # After getting `results` back from an interpreter evaluation,
@@ -24,33 +23,42 @@ module GraphQL
24
23
  # return {Lazy} instances if there's more work to be done,
25
24
  # or return {Hash}/{Array} if the query should be continued.
26
25
  #
27
- # @param results [Array]
28
- # @return [Array] Same size, filled with finished values
29
- def self.resolve(results)
26
+ # @return [void]
27
+ def self.resolve(results, dataloader)
28
+ # There might be pending jobs here that _will_ write lazies
29
+ # into the result hash. We should run them out, so we
30
+ # can be sure that all lazies will be present in the result hashes.
31
+ # A better implementation would somehow interleave (or unify)
32
+ # these approaches.
33
+ dataloader.run
30
34
  next_results = []
31
-
32
- # Work through the queue until it's empty
33
- while results.size > 0
35
+ while results.any?
34
36
  result_value = results.shift
35
-
36
- if result_value.is_a?(Lazy)
37
- result_value = result_value.value
38
- end
39
-
40
- if result_value.is_a?(Lazy)
41
- # Since this field returned another lazy,
42
- # add it to the same queue
43
- results << result_value
44
- elsif result_value.is_a?(Hash)
45
- # This is part of the next level, add it
46
- next_results.concat(result_value.values)
37
+ if result_value.is_a?(Hash)
38
+ results.concat(result_value.values)
39
+ next
47
40
  elsif result_value.is_a?(Array)
48
- # This is part of the next level, add it
49
- next_results.concat(result_value)
41
+ results.concat(result_value)
42
+ next
43
+ elsif result_value.is_a?(Lazy)
44
+ loaded_value = result_value.value
45
+ if loaded_value.is_a?(Lazy)
46
+ # Since this field returned another lazy,
47
+ # add it to the same queue
48
+ results << loaded_value
49
+ elsif loaded_value.is_a?(Hash) || loaded_value.is_a?(Array)
50
+ # Add these values in wholesale --
51
+ # they might be modified by later work in the dataloader.
52
+ next_results << loaded_value
53
+ end
50
54
  end
51
55
  end
52
56
 
53
- next_results
57
+ if next_results.any?
58
+ dataloader.append_job { resolve(next_results, dataloader) }
59
+ end
60
+
61
+ nil
54
62
  end
55
63
  end
56
64
  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 = {}
@@ -54,7 +56,18 @@ module GraphQL
54
56
  # Root .authorized? returned false.
55
57
  write_in_response(path, nil)
56
58
  else
57
- evaluate_selections(path, context.scoped_context, object_proxy, root_type, root_operation.selections, root_operation_type: root_op_type)
59
+ gathered_selections = gather_selections(object_proxy, root_type, root_operation.selections)
60
+ # Make the first fiber which will begin execution
61
+ @dataloader.append_job {
62
+ evaluate_selections(
63
+ path,
64
+ context.scoped_context,
65
+ object_proxy,
66
+ root_type,
67
+ root_op_type == "mutation",
68
+ gathered_selections,
69
+ )
70
+ }
58
71
  end
59
72
  delete_interpreter_context(:current_path)
60
73
  delete_interpreter_context(:current_field)
@@ -63,7 +76,7 @@ module GraphQL
63
76
  nil
64
77
  end
65
78
 
66
- def gather_selections(owner_object, owner_type, selections, selections_by_name)
79
+ def gather_selections(owner_object, owner_type, selections, selections_by_name = {})
67
80
  selections.each do |node|
68
81
  # Skip gathering this if the directive says so
69
82
  if !directives_include?(node, owner_object, owner_type)
@@ -115,144 +128,172 @@ module GraphQL
115
128
  raise "Invariant: unexpected selection class: #{node.class}"
116
129
  end
117
130
  end
131
+ selections_by_name
118
132
  end
119
133
 
120
134
  NO_ARGS = {}.freeze
121
135
 
122
- def evaluate_selections(path, scoped_context, owner_object, owner_type, selections, root_operation_type: nil)
136
+ # @return [void]
137
+ def evaluate_selections(path, scoped_context, owner_object, owner_type, is_eager_selection, gathered_selections)
123
138
  set_all_interpreter_context(owner_object, nil, nil, path)
124
- selections_by_name = {}
125
- gather_selections(owner_object, owner_type, selections, selections_by_name)
126
- selections_by_name.each do |result_name, field_ast_nodes_or_ast_node|
127
- # As a performance optimization, the hash key will be a `Node` if
128
- # there's only one selection of the field. But if there are multiple
129
- # selections of the field, it will be an Array of nodes
130
- if field_ast_nodes_or_ast_node.is_a?(Array)
131
- field_ast_nodes = field_ast_nodes_or_ast_node
132
- ast_node = field_ast_nodes.first
139
+
140
+ gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
141
+ @dataloader.append_job {
142
+ evaluate_selection(
143
+ path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_selection
144
+ )
145
+ }
146
+ end
147
+
148
+ nil
149
+ end
150
+
151
+ attr_reader :progress_path
152
+
153
+ # @return [void]
154
+ def evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_field)
155
+ # As a performance optimization, the hash key will be a `Node` if
156
+ # there's only one selection of the field. But if there are multiple
157
+ # selections of the field, it will be an Array of nodes
158
+ if field_ast_nodes_or_ast_node.is_a?(Array)
159
+ field_ast_nodes = field_ast_nodes_or_ast_node
160
+ ast_node = field_ast_nodes.first
161
+ else
162
+ field_ast_nodes = nil
163
+ ast_node = field_ast_nodes_or_ast_node
164
+ end
165
+ field_name = ast_node.name
166
+ field_defn = @fields_cache[owner_type][field_name] ||= owner_type.get_field(field_name)
167
+ is_introspection = false
168
+ if field_defn.nil?
169
+ field_defn = if owner_type == schema.query && (entry_point_field = schema.introspection_system.entry_point(name: field_name))
170
+ is_introspection = true
171
+ entry_point_field
172
+ elsif (dynamic_field = schema.introspection_system.dynamic_field(name: field_name))
173
+ is_introspection = true
174
+ dynamic_field
133
175
  else
134
- field_ast_nodes = nil
135
- ast_node = field_ast_nodes_or_ast_node
176
+ raise "Invariant: no field for #{owner_type}.#{field_name}"
136
177
  end
137
- field_name = ast_node.name
138
- field_defn = @fields_cache[owner_type][field_name] ||= owner_type.get_field(field_name)
139
- is_introspection = false
140
- if field_defn.nil?
141
- field_defn = if owner_type == schema.query && (entry_point_field = schema.introspection_system.entry_point(name: field_name))
142
- is_introspection = true
143
- entry_point_field
144
- elsif (dynamic_field = schema.introspection_system.dynamic_field(name: field_name))
145
- is_introspection = true
146
- dynamic_field
147
- else
148
- raise "Invariant: no field for #{owner_type}.#{field_name}"
149
- end
150
- end
151
- return_type = field_defn.type
178
+ end
179
+ return_type = field_defn.type
152
180
 
153
- next_path = path.dup
154
- next_path << result_name
155
- next_path.freeze
181
+ next_path = path.dup
182
+ next_path << result_name
183
+ next_path.freeze
156
184
 
157
- # This seems janky, but we need to know
158
- # the field's return type at this path in order
159
- # to propagate `null`
160
- set_type_at_path(next_path, return_type)
161
- # Set this before calling `run_with_directives`, so that the directive can have the latest path
162
- set_all_interpreter_context(nil, field_defn, nil, next_path)
185
+ # This seems janky, but we need to know
186
+ # the field's return type at this path in order
187
+ # to propagate `null`
188
+ set_type_at_path(next_path, return_type)
189
+ # Set this before calling `run_with_directives`, so that the directive can have the latest path
190
+ set_all_interpreter_context(nil, field_defn, nil, next_path)
163
191
 
164
- context.scoped_context = scoped_context
165
- object = owner_object
192
+ context.scoped_context = scoped_context
193
+ object = owner_object
166
194
 
167
- if is_introspection
168
- object = authorized_new(field_defn.owner, object, context, next_path)
195
+ if is_introspection
196
+ object = authorized_new(field_defn.owner, object, context, next_path)
197
+ end
198
+
199
+ total_args_count = field_defn.arguments.size
200
+ if total_args_count == 0
201
+ kwarg_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
202
+ evaluate_selection_with_args(kwarg_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field)
203
+ else
204
+ # TODO remove all arguments(...) usages?
205
+ @query.arguments_cache.dataload_for(ast_node, field_defn, object) do |resolved_arguments|
206
+ evaluate_selection_with_args(resolved_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field)
169
207
  end
208
+ end
209
+ end
170
210
 
171
- begin
172
- kwarg_arguments = arguments(object, field_defn, ast_node)
173
- rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => e
174
- continue_value(next_path, e, owner_type, field_defn, return_type.non_null?, ast_node)
211
+ def evaluate_selection_with_args(kwarg_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field) # rubocop:disable Metrics/ParameterLists
212
+ context.scoped_context = scoped_context
213
+ return_type = field_defn.type
214
+ 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|
215
+ if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
216
+ continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node)
175
217
  next
176
218
  end
177
219
 
178
- 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|
179
- case resolved_arguments
180
- when GraphQL::ExecutionError, GraphQL::UnauthorizedError
181
- continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node)
182
- next
183
- end
184
-
185
- if resolved_arguments.empty? && field_defn.extras.empty?
186
- # We can avoid allocating the `{ Symbol => Object }` hash in this case
187
- kwarg_arguments = NO_ARGS
188
- else
189
- kwarg_arguments = resolved_arguments.keyword_arguments
190
-
191
- field_defn.extras.each do |extra|
192
- case extra
193
- when :ast_node
194
- kwarg_arguments[:ast_node] = ast_node
195
- when :execution_errors
196
- kwarg_arguments[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
197
- when :path
198
- kwarg_arguments[:path] = next_path
199
- when :lookahead
200
- if !field_ast_nodes
201
- field_ast_nodes = [ast_node]
202
- end
203
- kwarg_arguments[:lookahead] = Execution::Lookahead.new(
204
- query: query,
205
- ast_nodes: field_ast_nodes,
206
- field: field_defn,
207
- )
208
- when :argument_details
209
- kwarg_arguments[:argument_details] = resolved_arguments
210
- else
211
- kwarg_arguments[extra] = field_defn.fetch_extra(extra, context)
220
+ kwarg_arguments = if resolved_arguments.empty? && field_defn.extras.empty?
221
+ # We can avoid allocating the `{ Symbol => Object }` hash in this case
222
+ NO_ARGS
223
+ else
224
+ # Bundle up the extras, then make a new arguments instance
225
+ # that includes the extras, too.
226
+ extra_args = {}
227
+ field_defn.extras.each do |extra|
228
+ case extra
229
+ when :ast_node
230
+ extra_args[:ast_node] = ast_node
231
+ when :execution_errors
232
+ extra_args[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
233
+ when :path
234
+ extra_args[:path] = next_path
235
+ when :lookahead
236
+ if !field_ast_nodes
237
+ field_ast_nodes = [ast_node]
212
238
  end
239
+
240
+ extra_args[:lookahead] = Execution::Lookahead.new(
241
+ query: query,
242
+ ast_nodes: field_ast_nodes,
243
+ field: field_defn,
244
+ )
245
+ when :argument_details
246
+ # Use this flag to tell Interpreter::Arguments to add itself
247
+ # to the keyword args hash _before_ freezing everything.
248
+ extra_args[:argument_details] = :__arguments_add_self
249
+ else
250
+ extra_args[extra] = field_defn.fetch_extra(extra, context)
213
251
  end
214
252
  end
253
+ resolved_arguments = resolved_arguments.merge_extras(extra_args)
254
+ resolved_arguments.keyword_arguments
255
+ end
215
256
 
216
- set_all_interpreter_context(nil, nil, kwarg_arguments, nil)
257
+ set_all_interpreter_context(nil, nil, kwarg_arguments, nil)
217
258
 
218
- # Optimize for the case that field is selected only once
219
- if field_ast_nodes.nil? || field_ast_nodes.size == 1
220
- next_selections = ast_node.selections
221
- else
222
- next_selections = []
223
- field_ast_nodes.each { |f| next_selections.concat(f.selections) }
224
- end
259
+ # Optimize for the case that field is selected only once
260
+ if field_ast_nodes.nil? || field_ast_nodes.size == 1
261
+ next_selections = ast_node.selections
262
+ else
263
+ next_selections = []
264
+ field_ast_nodes.each { |f| next_selections.concat(f.selections) }
265
+ end
225
266
 
226
- field_result = resolve_with_directives(object, ast_node) do
227
- # Actually call the field resolver and capture the result
228
- app_result = begin
229
- query.with_error_handling do
230
- query.trace("execute_field", {owner: owner_type, field: field_defn, path: next_path, query: query, object: object, arguments: kwarg_arguments}) do
231
- field_defn.resolve(object, kwarg_arguments, context)
232
- end
267
+ field_result = resolve_with_directives(object, ast_node) do
268
+ # Actually call the field resolver and capture the result
269
+ app_result = begin
270
+ query.with_error_handling do
271
+ 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
272
+ field_defn.resolve(object, kwarg_arguments, context)
233
273
  end
234
- rescue GraphQL::ExecutionError => err
235
- err
236
274
  end
237
- 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|
238
- continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node)
239
- if RawValue === continue_value
240
- # Write raw value directly to the response without resolving nested objects
241
- write_in_response(next_path, continue_value.resolve)
242
- elsif HALT != continue_value
243
- continue_field(next_path, continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments)
244
- end
275
+ rescue GraphQL::ExecutionError => err
276
+ err
277
+ end
278
+ 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|
279
+ continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node)
280
+ if RawValue === continue_value
281
+ # Write raw value directly to the response without resolving nested objects
282
+ write_in_response(next_path, continue_value.resolve)
283
+ elsif HALT != continue_value
284
+ continue_field(next_path, continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments)
245
285
  end
246
286
  end
287
+ end
247
288
 
248
- # If this field is a root mutation field, immediately resolve
249
- # all of its child fields before moving on to the next root mutation field.
250
- # (Subselections of this mutation will still be resolved level-by-level.)
251
- if root_operation_type == "mutation"
252
- Interpreter::Resolve.resolve_all([field_result])
253
- else
254
- field_result
255
- end
289
+ # If this field is a root mutation field, immediately resolve
290
+ # all of its child fields before moving on to the next root mutation field.
291
+ # (Subselections of this mutation will still be resolved level-by-level.)
292
+ if is_eager_field
293
+ Interpreter::Resolve.resolve_all([field_result], @dataloader)
294
+ else
295
+ # Return this from `after_lazy` because it might be another lazy that needs to be resolved
296
+ field_result
256
297
  end
257
298
  end
258
299
  end
@@ -314,7 +355,7 @@ module GraphQL
314
355
  resolved_type_or_lazy, resolved_value = resolve_type(current_type, value, path)
315
356
  resolved_value ||= value
316
357
 
317
- after_lazy(resolved_type_or_lazy, owner: current_type, path: path, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |resolved_type|
358
+ 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|
318
359
  possible_types = query.possible_types(current_type)
319
360
 
320
361
  if !possible_types.include?(resolved_type)
@@ -334,12 +375,13 @@ module GraphQL
334
375
  rescue GraphQL::ExecutionError => err
335
376
  err
336
377
  end
337
- after_lazy(object_proxy, owner: current_type, path: path, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |inner_object|
378
+ 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|
338
379
  continue_value = continue_value(path, inner_object, owner_type, field, is_non_null, ast_node)
339
380
  if HALT != continue_value
340
381
  response_hash = {}
341
382
  write_in_response(path, response_hash)
342
- evaluate_selections(path, context.scoped_context, continue_value, current_type, next_selections)
383
+ gathered_selections = gather_selections(continue_value, current_type, next_selections)
384
+ evaluate_selections(path, context.scoped_context, continue_value, current_type, false, gathered_selections)
343
385
  response_hash
344
386
  end
345
387
  end
@@ -357,7 +399,7 @@ module GraphQL
357
399
  idx += 1
358
400
  set_type_at_path(next_path, inner_type)
359
401
  # This will update `response_list` with the lazy
360
- 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|
402
+ 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|
361
403
  continue_value = continue_value(next_path, inner_inner_value, owner_type, field, inner_type.non_null?, ast_node)
362
404
  if HALT != continue_value
363
405
  continue_field(next_path, continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments)
@@ -440,7 +482,7 @@ module GraphQL
440
482
  # @param eager [Boolean] Set to `true` for mutation root fields only
441
483
  # @param trace [Boolean] If `false`, don't wrap this with field tracing
442
484
  # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
443
- def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, eager: false, trace: true, &block)
485
+ def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, ast_node:, eager: false, trace: true, &block)
444
486
  set_all_interpreter_context(owner_object, field, arguments, path)
445
487
  if schema.lazy?(lazy_obj)
446
488
  lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do
@@ -451,7 +493,7 @@ module GraphQL
451
493
  inner_obj = begin
452
494
  query.with_error_handling do
453
495
  if trace
454
- query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments}) do
496
+ query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments, ast_node: ast_node}) do
455
497
  schema.sync_lazy(lazy_obj)
456
498
  end
457
499
  else
@@ -461,7 +503,7 @@ module GraphQL
461
503
  rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
462
504
  err
463
505
  end
464
- 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)
506
+ 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)
465
507
  end
466
508
 
467
509
  if eager
@@ -476,9 +518,7 @@ module GraphQL
476
518
  end
477
519
 
478
520
  def arguments(graphql_object, arg_owner, ast_node)
479
- # Don't cache arguments if field extras or extensions are requested since they can mutate the argument data structure
480
- if arg_owner.arguments_statically_coercible? &&
481
- (!arg_owner.is_a?(GraphQL::Schema::Field) || (arg_owner.extras.empty? && arg_owner.extensions.empty?))
521
+ if arg_owner.arguments_statically_coercible?
482
522
  query.arguments_for(ast_node, arg_owner)
483
523
  else
484
524
  # The arguments must be prepared in the context of the given object
@@ -519,6 +559,16 @@ module GraphQL
519
559
  end
520
560
  end
521
561
 
562
+ def value_at(path)
563
+ i = 0
564
+ value = @response.final_value
565
+ while value && (part = path[i])
566
+ value = value[part]
567
+ i += 1
568
+ end
569
+ value
570
+ end
571
+
522
572
  # To propagate nulls, we have to know what the field type was
523
573
  # at previous parts of the response.
524
574
  # This hash matches the response