graphql 1.11.6 → 1.12.3

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 (143) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install_generator.rb +5 -5
  3. data/lib/generators/graphql/object_generator.rb +2 -0
  4. data/lib/generators/graphql/relay_generator.rb +63 -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 +39 -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/legacy_tracer.rb +56 -0
  17. data/lib/graphql/backtrace/table.rb +22 -2
  18. data/lib/graphql/backtrace/tracer.rb +40 -9
  19. data/lib/graphql/backwards_compatibility.rb +2 -1
  20. data/lib/graphql/base_type.rb +1 -1
  21. data/lib/graphql/compatibility/execution_specification.rb +1 -0
  22. data/lib/graphql/compatibility/lazy_execution_specification.rb +2 -0
  23. data/lib/graphql/compatibility/query_parser_specification.rb +2 -0
  24. data/lib/graphql/compatibility/schema_parser_specification.rb +2 -0
  25. data/lib/graphql/dataloader.rb +198 -0
  26. data/lib/graphql/dataloader/null_dataloader.rb +21 -0
  27. data/lib/graphql/dataloader/request.rb +24 -0
  28. data/lib/graphql/dataloader/request_all.rb +22 -0
  29. data/lib/graphql/dataloader/source.rb +93 -0
  30. data/lib/graphql/define/assign_global_id_field.rb +1 -1
  31. data/lib/graphql/define/instance_definable.rb +32 -2
  32. data/lib/graphql/define/type_definer.rb +5 -5
  33. data/lib/graphql/deprecated_dsl.rb +7 -2
  34. data/lib/graphql/deprecation.rb +13 -0
  35. data/lib/graphql/enum_type.rb +2 -0
  36. data/lib/graphql/execution/errors.rb +4 -0
  37. data/lib/graphql/execution/execute.rb +7 -0
  38. data/lib/graphql/execution/interpreter.rb +10 -6
  39. data/lib/graphql/execution/interpreter/arguments.rb +57 -5
  40. data/lib/graphql/execution/interpreter/arguments_cache.rb +8 -0
  41. data/lib/graphql/execution/interpreter/handles_raw_value.rb +0 -7
  42. data/lib/graphql/execution/interpreter/runtime.rb +219 -117
  43. data/lib/graphql/execution/multiplex.rb +20 -6
  44. data/lib/graphql/function.rb +4 -0
  45. data/lib/graphql/input_object_type.rb +2 -0
  46. data/lib/graphql/integer_decoding_error.rb +17 -0
  47. data/lib/graphql/interface_type.rb +3 -1
  48. data/lib/graphql/internal_representation/document.rb +2 -2
  49. data/lib/graphql/internal_representation/rewrite.rb +1 -1
  50. data/lib/graphql/invalid_null_error.rb +1 -1
  51. data/lib/graphql/language/document_from_schema_definition.rb +50 -23
  52. data/lib/graphql/object_type.rb +2 -0
  53. data/lib/graphql/pagination/connection.rb +5 -1
  54. data/lib/graphql/pagination/connections.rb +6 -16
  55. data/lib/graphql/query.rb +6 -1
  56. data/lib/graphql/query/arguments.rb +1 -1
  57. data/lib/graphql/query/context.rb +8 -1
  58. data/lib/graphql/query/serial_execution.rb +1 -0
  59. data/lib/graphql/query/validation_pipeline.rb +1 -1
  60. data/lib/graphql/relay/array_connection.rb +2 -2
  61. data/lib/graphql/relay/base_connection.rb +7 -0
  62. data/lib/graphql/relay/connection_instrumentation.rb +4 -4
  63. data/lib/graphql/relay/connection_type.rb +1 -1
  64. data/lib/graphql/relay/mutation.rb +1 -0
  65. data/lib/graphql/relay/node.rb +3 -0
  66. data/lib/graphql/relay/type_extensions.rb +2 -0
  67. data/lib/graphql/scalar_type.rb +2 -0
  68. data/lib/graphql/schema.rb +80 -29
  69. data/lib/graphql/schema/argument.rb +25 -7
  70. data/lib/graphql/schema/build_from_definition.rb +139 -51
  71. data/lib/graphql/schema/default_type_error.rb +2 -0
  72. data/lib/graphql/schema/directive.rb +76 -0
  73. data/lib/graphql/schema/directive/flagged.rb +57 -0
  74. data/lib/graphql/schema/enum.rb +3 -0
  75. data/lib/graphql/schema/enum_value.rb +12 -6
  76. data/lib/graphql/schema/field.rb +50 -22
  77. data/lib/graphql/schema/field/connection_extension.rb +3 -2
  78. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  79. data/lib/graphql/schema/input_object.rb +33 -22
  80. data/lib/graphql/schema/interface.rb +1 -0
  81. data/lib/graphql/schema/member.rb +4 -0
  82. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -0
  83. data/lib/graphql/schema/member/build_type.rb +3 -3
  84. data/lib/graphql/schema/member/has_arguments.rb +67 -50
  85. data/lib/graphql/schema/member/has_deprecation_reason.rb +25 -0
  86. data/lib/graphql/schema/member/has_directives.rb +98 -0
  87. data/lib/graphql/schema/member/has_validators.rb +31 -0
  88. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  89. data/lib/graphql/schema/middleware_chain.rb +1 -1
  90. data/lib/graphql/schema/object.rb +11 -0
  91. data/lib/graphql/schema/printer.rb +5 -4
  92. data/lib/graphql/schema/relay_classic_mutation.rb +1 -1
  93. data/lib/graphql/schema/resolver.rb +7 -0
  94. data/lib/graphql/schema/resolver/has_payload_type.rb +2 -0
  95. data/lib/graphql/schema/subscription.rb +19 -1
  96. data/lib/graphql/schema/timeout_middleware.rb +3 -1
  97. data/lib/graphql/schema/validation.rb +4 -2
  98. data/lib/graphql/schema/validator.rb +163 -0
  99. data/lib/graphql/schema/validator/exclusion_validator.rb +31 -0
  100. data/lib/graphql/schema/validator/format_validator.rb +49 -0
  101. data/lib/graphql/schema/validator/inclusion_validator.rb +33 -0
  102. data/lib/graphql/schema/validator/length_validator.rb +57 -0
  103. data/lib/graphql/schema/validator/numericality_validator.rb +71 -0
  104. data/lib/graphql/schema/validator/required_validator.rb +68 -0
  105. data/lib/graphql/static_validation.rb +1 -0
  106. data/lib/graphql/static_validation/all_rules.rb +1 -0
  107. data/lib/graphql/static_validation/rules/fields_will_merge.rb +25 -17
  108. data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +30 -0
  109. data/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb +30 -0
  110. data/lib/graphql/static_validation/validation_timeout_error.rb +25 -0
  111. data/lib/graphql/static_validation/validator.rb +32 -9
  112. data/lib/graphql/subscriptions.rb +17 -20
  113. data/lib/graphql/subscriptions/subscription_root.rb +1 -1
  114. data/lib/graphql/tracing.rb +2 -2
  115. data/lib/graphql/tracing/appoptics_tracing.rb +3 -1
  116. data/lib/graphql/tracing/platform_tracing.rb +4 -2
  117. data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +4 -1
  118. data/lib/graphql/tracing/skylight_tracing.rb +1 -1
  119. data/lib/graphql/types/int.rb +9 -2
  120. data/lib/graphql/types/relay.rb +11 -3
  121. data/lib/graphql/types/relay/base_connection.rb +2 -91
  122. data/lib/graphql/types/relay/base_edge.rb +2 -34
  123. data/lib/graphql/types/relay/connection_behaviors.rb +123 -0
  124. data/lib/graphql/types/relay/default_relay.rb +27 -0
  125. data/lib/graphql/types/relay/edge_behaviors.rb +42 -0
  126. data/lib/graphql/types/relay/has_node_field.rb +41 -0
  127. data/lib/graphql/types/relay/has_nodes_field.rb +41 -0
  128. data/lib/graphql/types/relay/node.rb +2 -4
  129. data/lib/graphql/types/relay/node_behaviors.rb +15 -0
  130. data/lib/graphql/types/relay/node_field.rb +1 -19
  131. data/lib/graphql/types/relay/nodes_field.rb +1 -19
  132. data/lib/graphql/types/relay/page_info.rb +2 -14
  133. data/lib/graphql/types/relay/page_info_behaviors.rb +25 -0
  134. data/lib/graphql/types/string.rb +7 -1
  135. data/lib/graphql/union_type.rb +2 -0
  136. data/lib/graphql/upgrader/member.rb +1 -0
  137. data/lib/graphql/upgrader/schema.rb +1 -0
  138. data/lib/graphql/version.rb +1 -1
  139. data/readme.md +1 -1
  140. metadata +53 -9
  141. data/lib/graphql/types/relay/base_field.rb +0 -22
  142. data/lib/graphql/types/relay/base_interface.rb +0 -29
  143. data/lib/graphql/types/relay/base_object.rb +0 -26
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Dataloader
5
+ class Source
6
+ # @api private
7
+ attr_reader :results
8
+
9
+ # Called by {Dataloader} to prepare the {Source}'s internal state
10
+ # @api private
11
+ def setup(dataloader)
12
+ @pending_keys = []
13
+ @results = {}
14
+ @dataloader = dataloader
15
+ end
16
+
17
+ attr_reader :dataloader
18
+
19
+ # @return [Dataloader::Request] a pending request for a value from `key`. Call `.load` on that object to wait for the result.
20
+ def request(key)
21
+ if !@results.key?(key)
22
+ @pending_keys << key
23
+ end
24
+ Dataloader::Request.new(self, key)
25
+ end
26
+
27
+ # @return [Dataloader::Request] a pending request for a values from `keys`. Call `.load` on that object to wait for the results.
28
+ def request_all(keys)
29
+ pending_keys = keys.select { |k| !@results.key?(k) }
30
+ @pending_keys.concat(pending_keys)
31
+ Dataloader::RequestAll.new(self, keys)
32
+ end
33
+
34
+ # @param key [Object] A loading key which will be passed to {#fetch} if it isn't already in the internal cache.
35
+ # @return [Object] The result from {#fetch} for `key`. If `key` hasn't been loaded yet, the Fiber will yield until it's loaded.
36
+ def load(key)
37
+ if @results.key?(key)
38
+ @results[key]
39
+ else
40
+ @pending_keys << key
41
+ sync
42
+ @results[key]
43
+ end
44
+ end
45
+
46
+ # @param keys [Array<Object>] Loading keys which will be passed to `#fetch` (or read from the internal cache).
47
+ # @return [Object] The result from {#fetch} for `keys`. If `keys` haven't been loaded yet, the Fiber will yield until they're loaded.
48
+ def load_all(keys)
49
+ if keys.any? { |k| !@results.key?(k) }
50
+ pending_keys = keys.select { |k| !@results.key?(k) }
51
+ @pending_keys.concat(pending_keys)
52
+ sync
53
+ end
54
+
55
+ keys.map { |k| @results[k] }
56
+ end
57
+
58
+ # Subclasses must implement this method to return a value for each of `keys`
59
+ # @param keys [Array<Object>] keys passed to {#load}, {#load_all}, {#request}, or {#request_all}
60
+ # @return [Array<Object>] A loaded value for each of `keys`. The array must match one-for-one to the list of `keys`.
61
+ def fetch(keys)
62
+ # somehow retrieve these from the backend
63
+ raise "Implement `#{self.class}#fetch(#{keys.inspect}) to return a record for each of the keys"
64
+ end
65
+
66
+ # Wait for a batch, if there's anything to batch.
67
+ # Then run the batch and update the cache.
68
+ # @return [void]
69
+ def sync
70
+ @dataloader.yield
71
+ end
72
+
73
+ # @return [Boolean] True if this source has any pending requests for data.
74
+ def pending?
75
+ @pending_keys.any?
76
+ end
77
+
78
+ # Called by {GraphQL::Dataloader} to resolve and pending requests to this source.
79
+ # @api private
80
+ # @return [void]
81
+ def run_pending_keys
82
+ return if @pending_keys.empty?
83
+ fetch_keys = @pending_keys.uniq
84
+ @pending_keys = []
85
+ results = fetch(fetch_keys)
86
+ fetch_keys.each_with_index do |key, idx|
87
+ @results[key] = results[idx]
88
+ end
89
+ nil
90
+ end
91
+ end
92
+ end
93
+ end
@@ -4,7 +4,7 @@ module GraphQL
4
4
  module AssignGlobalIdField
5
5
  def self.call(type_defn, field_name, **field_kwargs)
6
6
  resolve = GraphQL::Relay::GlobalIdResolve.new(type: type_defn)
7
- GraphQL::Define::AssignObjectField.call(type_defn, field_name, **field_kwargs, type: GraphQL::ID_TYPE.to_non_null_type, resolve: resolve)
7
+ GraphQL::Define::AssignObjectField.call(type_defn, field_name, **field_kwargs, type: GraphQL::DEPRECATED_ID_TYPE.to_non_null_type, resolve: resolve)
8
8
  end
9
9
  end
10
10
  end
@@ -3,6 +3,23 @@ module GraphQL
3
3
  module Define
4
4
  # @api deprecated
5
5
  module InstanceDefinable
6
+ module DeprecatedDefine
7
+ def define(**kwargs, &block)
8
+ deprecated_caller = caller(1, 1).first
9
+ if deprecated_caller.include?("lib/graphql")
10
+ deprecated_caller = caller(2, 10).find { |c| !c.include?("lib/graphql") }
11
+ end
12
+
13
+ if deprecated_caller
14
+ GraphQL::Deprecation.warn <<-ERR
15
+ #{self}.define will be removed in GraphQL-Ruby 2.0; use a class-based definition instead. See https://graphql-ruby.org/schema/class_based_api.html.
16
+ -> called from #{deprecated_caller}
17
+ ERR
18
+ end
19
+ deprecated_define(**kwargs, &block)
20
+ end
21
+ end
22
+
6
23
  def self.included(base)
7
24
  base.extend(ClassMethods)
8
25
  base.ensure_defined(:metadata)
@@ -14,7 +31,7 @@ module GraphQL
14
31
  end
15
32
 
16
33
  # @api deprecated
17
- def define(**kwargs, &block)
34
+ def deprecated_define(**kwargs, &block)
18
35
  # make sure the previous definition_proc was executed:
19
36
  ensure_defined
20
37
  stash_dependent_methods
@@ -22,11 +39,16 @@ module GraphQL
22
39
  nil
23
40
  end
24
41
 
42
+ # @api deprecated
43
+ def define(**kwargs, &block)
44
+ deprecated_define(**kwargs, &block)
45
+ end
46
+
25
47
  # @api deprecated
26
48
  def redefine(**kwargs, &block)
27
49
  ensure_defined
28
50
  new_inst = self.dup
29
- new_inst.define(**kwargs, &block)
51
+ new_inst.deprecated_define(**kwargs, &block)
30
52
  new_inst
31
53
  end
32
54
 
@@ -125,8 +147,16 @@ module GraphQL
125
147
  module ClassMethods
126
148
  # Create a new instance
127
149
  # and prepare a definition using its {.definitions}.
150
+ # @api deprecated
128
151
  # @param kwargs [Hash] Key-value pairs corresponding to defininitions from `accepts_definitions`
129
152
  # @param block [Proc] Block which calls helper methods from `accepts_definitions`
153
+ def deprecated_define(**kwargs, &block)
154
+ instance = self.new
155
+ instance.deprecated_define(**kwargs, &block)
156
+ instance
157
+ end
158
+
159
+ # @api deprecated
130
160
  def define(**kwargs, &block)
131
161
  instance = self.new
132
162
  instance.define(**kwargs, &block)
@@ -7,11 +7,11 @@ module GraphQL
7
7
  class TypeDefiner
8
8
  include Singleton
9
9
  # rubocop:disable Naming/MethodName
10
- def Int; GraphQL::INT_TYPE; end
11
- def String; GraphQL::STRING_TYPE; end
12
- def Float; GraphQL::FLOAT_TYPE; end
13
- def Boolean; GraphQL::BOOLEAN_TYPE; end
14
- def ID; GraphQL::ID_TYPE; end
10
+ def Int; GraphQL::DEPRECATED_INT_TYPE; end
11
+ def String; GraphQL::DEPRECATED_STRING_TYPE; end
12
+ def Float; GraphQL::DEPRECATED_FLOAT_TYPE; end
13
+ def Boolean; GraphQL::DEPRECATED_BOOLEAN_TYPE; end
14
+ def ID; GraphQL::DEPRECATED_ID_TYPE; end
15
15
  # rubocop:enable Naming/MethodName
16
16
 
17
17
  # Make a {ListType} which wraps the input type
@@ -4,13 +4,13 @@ module GraphQL
4
4
  #
5
5
  # 1. Scoped by file (CRuby only), add to the top of the file:
6
6
  #
7
- # using GraphQL::DeprecatedDSL
7
+ # using GraphQL::DeprecationDSL
8
8
  #
9
9
  # (This is a "refinement", there are also other ways to scope it.)
10
10
  #
11
11
  # 2. Global application, add before schema definition:
12
12
  #
13
- # GraphQL::DeprecatedDSL.activate
13
+ # GraphQL::DeprecationDSL.activate
14
14
  #
15
15
  module DeprecatedDSL
16
16
  TYPE_CLASSES = [
@@ -23,12 +23,17 @@ module GraphQL
23
23
  ]
24
24
 
25
25
  def self.activate
26
+ deprecated_caller = caller(1, 1).first
27
+ GraphQL::Deprecation.warn "DeprecatedDSL will be removed from GraphQL-Ruby 2.0, use `.to_non_null_type` instead of `!` and remove `.activate` from #{deprecated_caller}"
26
28
  TYPE_CLASSES.each { |c| c.extend(Methods) }
27
29
  GraphQL::Schema::List.include(Methods)
28
30
  GraphQL::Schema::NonNull.include(Methods)
29
31
  end
32
+
30
33
  module Methods
31
34
  def !
35
+ deprecated_caller = caller(1, 1).first
36
+ GraphQL::Deprecation.warn "DeprecatedDSL will be removed from GraphQL-Ruby 2.0, use `.to_non_null_type` instead of `!` at #{deprecated_caller}"
32
37
  to_non_null_type
33
38
  end
34
39
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Deprecation
5
+ def self.warn(message)
6
+ if defined?(ActiveSupport::Deprecation)
7
+ ActiveSupport::Deprecation.warn(message)
8
+ else
9
+ Kernel.warn(message)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -2,6 +2,8 @@
2
2
  module GraphQL
3
3
  # @api deprecated
4
4
  class EnumType < GraphQL::BaseType
5
+ extend Define::InstanceDefinable::DeprecatedDefine
6
+
5
7
  accepts_definitions :values, value: GraphQL::Define::AssignEnumValue
6
8
  ensure_defined(:values, :validate_non_null_input, :coerce_non_null_input, :coerce_result)
7
9
  attr_accessor :ast_node
@@ -18,6 +18,10 @@ module GraphQL
18
18
  #
19
19
  class Errors
20
20
  def self.use(schema)
21
+ if schema.plugins.any? { |(plugin, kwargs)| plugin == self }
22
+ definition_line = caller(2, 1).first
23
+ GraphQL::Deprecation.warn("GraphQL::Execution::Errors is now installed by default, remove `use GraphQL::Execution::Errors` from #{definition_line}")
24
+ end
21
25
  schema.error_handler = self.new(schema)
22
26
  end
23
27
 
@@ -18,7 +18,14 @@ module GraphQL
18
18
  # @api private
19
19
  PROPAGATE_NULL = PropagateNull.new
20
20
 
21
+ def self.use(schema_class)
22
+ schema_class.query_execution_strategy(self)
23
+ schema_class.mutation_execution_strategy(self)
24
+ schema_class.subscription_execution_strategy(self)
25
+ end
26
+
21
27
  def execute(ast_operation, root_type, query)
28
+ GraphQL::Deprecation.warn "#{self.class} will be removed in GraphQL-Ruby 2.0, please upgrade to the Interpreter: https://graphql-ruby.org/queries/interpreter.html"
22
29
  result = resolve_root_selection(query)
23
30
  lazy_resolve_root_selection(result, **{query: query})
24
31
  GraphQL::Execution::Flatten.call(query.context)
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require "fiber"
2
3
  require "graphql/execution/interpreter/argument_value"
3
4
  require "graphql/execution/interpreter/arguments"
4
5
  require "graphql/execution/interpreter/arguments_cache"
@@ -22,12 +23,15 @@ module GraphQL
22
23
  end
23
24
 
24
25
  def self.use(schema_class)
25
- schema_class.interpreter = true
26
- schema_class.query_execution_strategy(GraphQL::Execution::Interpreter)
27
- schema_class.mutation_execution_strategy(GraphQL::Execution::Interpreter)
28
- schema_class.subscription_execution_strategy(GraphQL::Execution::Interpreter)
29
- schema_class.add_subscription_extension_if_necessary
30
- GraphQL::Schema::Object.include(HandlesRawValue)
26
+ if schema_class.interpreter?
27
+ definition_line = caller(2, 1).first
28
+ GraphQL::Deprecation.warn("GraphQL::Execution::Interpreter is now the default; remove `use GraphQL::Execution::Interpreter` from the schema definition (#{definition_line})")
29
+ else
30
+ schema_class.query_execution_strategy(self)
31
+ schema_class.mutation_execution_strategy(self)
32
+ schema_class.subscription_execution_strategy(self)
33
+ schema_class.add_subscription_extension_if_necessary
34
+ end
31
35
  end
32
36
 
33
37
  def self.begin_multiplex(multiplex)
@@ -5,6 +5,9 @@ module GraphQL
5
5
  class Interpreter
6
6
  # A wrapper for argument hashes in GraphQL queries.
7
7
  #
8
+ # This object is immutable so that the runtime code can be sure that
9
+ # modifications don't leak from one use to another
10
+ #
8
11
  # @see GraphQL::Query#arguments_for to get access to these objects.
9
12
  class Arguments
10
13
  extend Forwardable
@@ -16,20 +19,69 @@ module GraphQL
16
19
  # @return [Hash<Symbol, Object>]
17
20
  attr_reader :keyword_arguments
18
21
 
19
- def initialize(keyword_arguments:, argument_values:)
20
- @keyword_arguments = keyword_arguments
21
- @argument_values = argument_values
22
+ # @param argument_values [nil, Hash{Symbol => ArgumentValue}]
23
+ # @param keyword_arguments [nil, Hash{Symbol => Object}]
24
+ def initialize(keyword_arguments: nil, argument_values:)
25
+ @empty = argument_values.nil? || argument_values.empty?
26
+ # This is only present when `extras` have been merged in:
27
+ if keyword_arguments
28
+ # This is a little crazy. We expect the `:argument_details` extra to _include extras_,
29
+ # but the object isn't created until _after_ extras are put together.
30
+ # So, we have to use a special flag here to say, "at the last minute, add yourself to the keyword args."
31
+ #
32
+ # Otherwise:
33
+ # - We can't access the final Arguments instance _while_ we're preparing extras
34
+ # - After we _can_ access it, it's frozen, so we can't add anything.
35
+ #
36
+ # So, this flag gives us a chance to sneak it in before freezing, _and_ while we have access
37
+ # to the new Arguments instance itself.
38
+ if keyword_arguments[:argument_details] == :__arguments_add_self
39
+ keyword_arguments[:argument_details] = self
40
+ end
41
+ @keyword_arguments = keyword_arguments.freeze
42
+ elsif !@empty
43
+ @keyword_arguments = {}
44
+ argument_values.each do |name, arg_val|
45
+ @keyword_arguments[name] = arg_val.value
46
+ end
47
+ @keyword_arguments.freeze
48
+ else
49
+ @keyword_arguments = NO_ARGS
50
+ end
51
+ @argument_values = argument_values ? argument_values.freeze : NO_ARGS
52
+ freeze
22
53
  end
23
54
 
24
55
  # @return [Hash{Symbol => ArgumentValue}]
25
56
  attr_reader :argument_values
26
57
 
27
- def_delegators :@keyword_arguments, :key?, :[], :fetch, :keys, :each, :values
28
- def_delegators :@argument_values, :each_value
58
+ def empty?
59
+ @empty
60
+ end
61
+
62
+ def_delegators :keyword_arguments, :key?, :[], :fetch, :keys, :each, :values
63
+ def_delegators :argument_values, :each_value
29
64
 
30
65
  def inspect
31
66
  "#<#{self.class} @keyword_arguments=#{keyword_arguments.inspect}>"
32
67
  end
68
+
69
+ # Create a new arguments instance which includes these extras.
70
+ #
71
+ # This is called by the runtime to implement field `extras: [...]`
72
+ #
73
+ # @param extra_args [Hash<Symbol => Object>]
74
+ # @return [Interpreter::Arguments]
75
+ # @api private
76
+ def merge_extras(extra_args)
77
+ self.class.new(
78
+ argument_values: argument_values,
79
+ keyword_arguments: keyword_arguments.merge(extra_args)
80
+ )
81
+ end
82
+
83
+ NO_ARGS = {}.freeze
84
+ EMPTY = self.new(argument_values: nil, keyword_arguments: NO_ARGS).freeze
33
85
  end
34
86
  end
35
87
  end
@@ -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 = {}
@@ -47,15 +49,24 @@ module GraphQL
47
49
  root_op_type = root_operation.operation_type || "query"
48
50
  root_type = schema.root_type_for_operation(root_op_type)
49
51
  path = []
50
- set_interpreter_context(:current_object, query.root_value)
51
- set_interpreter_context(:current_path, path)
52
+ set_all_interpreter_context(query.root_value, nil, nil, path)
52
53
  object_proxy = authorized_new(root_type, query.root_value, context, path)
53
54
  object_proxy = schema.sync_lazy(object_proxy)
54
55
  if object_proxy.nil?
55
56
  # Root .authorized? returned false.
56
57
  write_in_response(path, nil)
57
58
  else
58
- evaluate_selections(path, context.scoped_context, object_proxy, root_type, root_operation.selections, root_operation_type: root_op_type)
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
59
70
  end
60
71
  delete_interpreter_context(:current_path)
61
72
  delete_interpreter_context(:current_field)
@@ -64,7 +75,33 @@ module GraphQL
64
75
  nil
65
76
  end
66
77
 
67
- def gather_selections(owner_object, owner_type, selections, selections_by_name)
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
102
+ end
103
+
104
+ def gather_selections(owner_object, owner_type, selections, selections_by_name = {})
68
105
  selections.each do |node|
69
106
  # Skip gathering this if the directive says so
70
107
  if !directives_include?(node, owner_object, owner_type)
@@ -116,139 +153,186 @@ module GraphQL
116
153
  raise "Invariant: unexpected selection class: #{node.class}"
117
154
  end
118
155
  end
156
+ selections_by_name
119
157
  end
120
158
 
121
- def evaluate_selections(path, scoped_context, owner_object, owner_type, selections, root_operation_type: nil)
122
- set_interpreter_context(:current_object, owner_object)
123
- set_interpreter_context(:current_path, 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
133
- else
134
- field_ast_nodes = nil
135
- 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
136
183
  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
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?(path)
192
+ break
150
193
  end
151
- return_type = field_defn.type
194
+ end
195
+ nil
196
+ end
152
197
 
153
- next_path = path.dup
154
- next_path << result_name
155
- next_path.freeze
198
+ attr_reader :progress_path
156
199
 
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_interpreter_context(:current_path, next_path)
163
- set_interpreter_context(:current_field, field_defn)
200
+ # @return [void]
201
+ def evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_field)
202
+ # As a performance optimization, the hash key will be a `Node` if
203
+ # there's only one selection of the field. But if there are multiple
204
+ # selections of the field, it will be an Array of nodes
205
+ if field_ast_nodes_or_ast_node.is_a?(Array)
206
+ field_ast_nodes = field_ast_nodes_or_ast_node
207
+ ast_node = field_ast_nodes.first
208
+ else
209
+ field_ast_nodes = nil
210
+ ast_node = field_ast_nodes_or_ast_node
211
+ end
212
+ field_name = ast_node.name
213
+ field_defn = @fields_cache[owner_type][field_name] ||= owner_type.get_field(field_name)
214
+ is_introspection = false
215
+ if field_defn.nil?
216
+ field_defn = if owner_type == schema.query && (entry_point_field = schema.introspection_system.entry_point(name: field_name))
217
+ is_introspection = true
218
+ entry_point_field
219
+ elsif (dynamic_field = schema.introspection_system.dynamic_field(name: field_name))
220
+ is_introspection = true
221
+ dynamic_field
222
+ else
223
+ raise "Invariant: no field for #{owner_type}.#{field_name}"
224
+ end
225
+ end
226
+ return_type = field_defn.type
164
227
 
165
- context.scoped_context = scoped_context
166
- object = owner_object
228
+ next_path = path.dup
229
+ next_path << result_name
230
+ next_path.freeze
167
231
 
168
- if is_introspection
169
- object = authorized_new(field_defn.owner, object, context, next_path)
170
- end
232
+ # This seems janky, but we need to know
233
+ # the field's return type at this path in order
234
+ # to propagate `null`
235
+ set_type_at_path(next_path, return_type)
236
+ # Set this before calling `run_with_directives`, so that the directive can have the latest path
237
+ set_all_interpreter_context(nil, field_defn, nil, next_path)
171
238
 
172
- begin
173
- kwarg_arguments = arguments(object, field_defn, ast_node)
174
- rescue GraphQL::ExecutionError => e
175
- continue_value(next_path, e, owner_type, field_defn, return_type.non_null?, ast_node)
176
- next
177
- end
239
+ context.scoped_context = scoped_context
240
+ object = owner_object
178
241
 
179
- 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|
180
- if resolved_arguments.is_a? GraphQL::ExecutionError
181
- continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node)
182
- next
183
- end
242
+ if is_introspection
243
+ object = authorized_new(field_defn.owner, object, context, next_path)
244
+ end
184
245
 
185
- kwarg_arguments = resolved_arguments.keyword_arguments
246
+ begin
247
+ kwarg_arguments = arguments(object, field_defn, ast_node)
248
+ rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => e
249
+ continue_value(next_path, e, owner_type, field_defn, return_type.non_null?, ast_node)
250
+ return
251
+ end
186
252
 
253
+ 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|
254
+ if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
255
+ continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node)
256
+ next
257
+ end
258
+
259
+ kwarg_arguments = if resolved_arguments.empty? && field_defn.extras.empty?
260
+ # We can avoid allocating the `{ Symbol => Object }` hash in this case
261
+ NO_ARGS
262
+ else
263
+ # Bundle up the extras, then make a new arguments instance
264
+ # that includes the extras, too.
265
+ extra_args = {}
187
266
  field_defn.extras.each do |extra|
188
267
  case extra
189
268
  when :ast_node
190
- kwarg_arguments[:ast_node] = ast_node
269
+ extra_args[:ast_node] = ast_node
191
270
  when :execution_errors
192
- kwarg_arguments[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
271
+ extra_args[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
193
272
  when :path
194
- kwarg_arguments[:path] = next_path
273
+ extra_args[:path] = next_path
195
274
  when :lookahead
196
275
  if !field_ast_nodes
197
276
  field_ast_nodes = [ast_node]
198
277
  end
199
- kwarg_arguments[:lookahead] = Execution::Lookahead.new(
278
+
279
+ extra_args[:lookahead] = Execution::Lookahead.new(
200
280
  query: query,
201
281
  ast_nodes: field_ast_nodes,
202
282
  field: field_defn,
203
283
  )
204
284
  when :argument_details
205
- kwarg_arguments[:argument_details] = resolved_arguments
285
+ # Use this flag to tell Interpreter::Arguments to add itself
286
+ # to the keyword args hash _before_ freezing everything.
287
+ extra_args[:argument_details] = :__arguments_add_self
206
288
  else
207
- kwarg_arguments[extra] = field_defn.fetch_extra(extra, context)
289
+ extra_args[extra] = field_defn.fetch_extra(extra, context)
208
290
  end
209
291
  end
292
+ resolved_arguments = resolved_arguments.merge_extras(extra_args)
293
+ resolved_arguments.keyword_arguments
294
+ end
210
295
 
211
- set_interpreter_context(:current_arguments, kwarg_arguments)
296
+ set_all_interpreter_context(nil, nil, kwarg_arguments, nil)
212
297
 
213
- # Optimize for the case that field is selected only once
214
- if field_ast_nodes.nil? || field_ast_nodes.size == 1
215
- next_selections = ast_node.selections
216
- else
217
- next_selections = []
218
- field_ast_nodes.each { |f| next_selections.concat(f.selections) }
219
- end
298
+ # Optimize for the case that field is selected only once
299
+ if field_ast_nodes.nil? || field_ast_nodes.size == 1
300
+ next_selections = ast_node.selections
301
+ else
302
+ next_selections = []
303
+ field_ast_nodes.each { |f| next_selections.concat(f.selections) }
304
+ end
220
305
 
221
- field_result = resolve_with_directives(object, ast_node) do
222
- # Actually call the field resolver and capture the result
223
- app_result = begin
224
- query.with_error_handling do
225
- query.trace("execute_field", {owner: owner_type, field: field_defn, path: next_path, query: query, object: object, arguments: kwarg_arguments}) do
226
- field_defn.resolve(object, kwarg_arguments, context)
227
- end
306
+ field_result = resolve_with_directives(object, ast_node) do
307
+ # Actually call the field resolver and capture the result
308
+ app_result = begin
309
+ query.with_error_handling do
310
+ 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
311
+ field_defn.resolve(object, kwarg_arguments, context)
228
312
  end
229
- rescue GraphQL::ExecutionError => err
230
- err
231
313
  end
232
- 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|
233
- continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node)
234
- if RawValue === continue_value
235
- # Write raw value directly to the response without resolving nested objects
236
- write_in_response(next_path, continue_value.resolve)
237
- elsif HALT != continue_value
238
- continue_field(next_path, continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments)
239
- end
314
+ rescue GraphQL::ExecutionError => err
315
+ err
316
+ end
317
+ 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|
318
+ continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node)
319
+ if RawValue === continue_value
320
+ # Write raw value directly to the response without resolving nested objects
321
+ write_in_response(next_path, continue_value.resolve)
322
+ elsif HALT != continue_value
323
+ continue_field(next_path, continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments)
240
324
  end
241
325
  end
326
+ end
242
327
 
243
- # If this field is a root mutation field, immediately resolve
244
- # all of its child fields before moving on to the next root mutation field.
245
- # (Subselections of this mutation will still be resolved level-by-level.)
246
- if root_operation_type == "mutation"
247
- Interpreter::Resolve.resolve_all([field_result])
248
- else
249
- field_result
250
- end
328
+ # If this field is a root mutation field, immediately resolve
329
+ # all of its child fields before moving on to the next root mutation field.
330
+ # (Subselections of this mutation will still be resolved level-by-level.)
331
+ if is_eager_field
332
+ Interpreter::Resolve.resolve_all([field_result])
251
333
  end
334
+
335
+ nil
252
336
  end
253
337
  end
254
338
 
@@ -309,7 +393,7 @@ module GraphQL
309
393
  resolved_type_or_lazy, resolved_value = resolve_type(current_type, value, path)
310
394
  resolved_value ||= value
311
395
 
312
- 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|
396
+ 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|
313
397
  possible_types = query.possible_types(current_type)
314
398
 
315
399
  if !possible_types.include?(resolved_type)
@@ -329,12 +413,13 @@ module GraphQL
329
413
  rescue GraphQL::ExecutionError => err
330
414
  err
331
415
  end
332
- 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|
416
+ 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|
333
417
  continue_value = continue_value(path, inner_object, owner_type, field, is_non_null, ast_node)
334
418
  if HALT != continue_value
335
419
  response_hash = {}
336
420
  write_in_response(path, response_hash)
337
- evaluate_selections(path, context.scoped_context, continue_value, current_type, next_selections)
421
+ gathered_selections = gather_selections(continue_value, current_type, next_selections)
422
+ evaluate_selections(path, context.scoped_context, continue_value, current_type, is_eager_selection: false, gathered_selections: gathered_selections, after: nil)
338
423
  response_hash
339
424
  end
340
425
  end
@@ -352,7 +437,7 @@ module GraphQL
352
437
  idx += 1
353
438
  set_type_at_path(next_path, inner_type)
354
439
  # This will update `response_list` with the lazy
355
- 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|
440
+ 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|
356
441
  continue_value = continue_value(next_path, inner_inner_value, owner_type, field, inner_type.non_null?, ast_node)
357
442
  if HALT != continue_value
358
443
  continue_field(next_path, continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments)
@@ -414,30 +499,39 @@ module GraphQL
414
499
  true
415
500
  end
416
501
 
502
+ def set_all_interpreter_context(object, field, arguments, path)
503
+ if object
504
+ @context[:current_object] = @interpreter_context[:current_object] = object
505
+ end
506
+ if field
507
+ @context[:current_field] = @interpreter_context[:current_field] = field
508
+ end
509
+ if arguments
510
+ @context[:current_arguments] = @interpreter_context[:current_arguments] = arguments
511
+ end
512
+ if path
513
+ @context[:current_path] = @interpreter_context[:current_path] = path
514
+ end
515
+ end
516
+
417
517
  # @param obj [Object] Some user-returned value that may want to be batched
418
518
  # @param path [Array<String>]
419
519
  # @param field [GraphQL::Schema::Field]
420
520
  # @param eager [Boolean] Set to `true` for mutation root fields only
421
521
  # @param trace [Boolean] If `false`, don't wrap this with field tracing
422
522
  # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
423
- def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, eager: false, trace: true, &block)
424
- set_interpreter_context(:current_object, owner_object)
425
- set_interpreter_context(:current_arguments, arguments)
426
- set_interpreter_context(:current_path, path)
427
- set_interpreter_context(:current_field, field)
523
+ def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, ast_node:, eager: false, trace: true, &block)
524
+ set_all_interpreter_context(owner_object, field, arguments, path)
428
525
  if schema.lazy?(lazy_obj)
429
526
  lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do
430
- set_interpreter_context(:current_path, path)
431
- set_interpreter_context(:current_field, field)
432
- set_interpreter_context(:current_object, owner_object)
433
- set_interpreter_context(:current_arguments, arguments)
527
+ set_all_interpreter_context(owner_object, field, arguments, path)
434
528
  context.scoped_context = scoped_context
435
529
  # Wrap the execution of _this_ method with tracing,
436
530
  # but don't wrap the continuation below
437
531
  inner_obj = begin
438
532
  query.with_error_handling do
439
533
  if trace
440
- query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments}) do
534
+ query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments, ast_node: ast_node}) do
441
535
  schema.sync_lazy(lazy_obj)
442
536
  end
443
537
  else
@@ -447,7 +541,7 @@ module GraphQL
447
541
  rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
448
542
  err
449
543
  end
450
- 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)
544
+ 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)
451
545
  end
452
546
 
453
547
  if eager
@@ -462,9 +556,7 @@ module GraphQL
462
556
  end
463
557
 
464
558
  def arguments(graphql_object, arg_owner, ast_node)
465
- # Don't cache arguments if field extras or extensions are requested since they can mutate the argument data structure
466
- if arg_owner.arguments_statically_coercible? &&
467
- (!arg_owner.is_a?(GraphQL::Schema::Field) || (arg_owner.extras.empty? && arg_owner.extensions.empty?))
559
+ if arg_owner.arguments_statically_coercible?
468
560
  query.arguments_for(ast_node, arg_owner)
469
561
  else
470
562
  # The arguments must be prepared in the context of the given object
@@ -505,6 +597,16 @@ module GraphQL
505
597
  end
506
598
  end
507
599
 
600
+ def value_at(path)
601
+ i = 0
602
+ value = @response.final_value
603
+ while value && (part = path[i])
604
+ value = value[part]
605
+ i += 1
606
+ end
607
+ value
608
+ end
609
+
508
610
  # To propagate nulls, we have to know what the field type was
509
611
  # at previous parts of the response.
510
612
  # This hash matches the response