graphql 2.0.13 → 2.3.10

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 (228) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install/mutation_root_generator.rb +2 -2
  3. data/lib/generators/graphql/install/templates/base_mutation.erb +2 -0
  4. data/lib/generators/graphql/install/templates/mutation_type.erb +2 -0
  5. data/lib/generators/graphql/install_generator.rb +3 -0
  6. data/lib/generators/graphql/mutation_delete_generator.rb +1 -1
  7. data/lib/generators/graphql/mutation_update_generator.rb +1 -1
  8. data/lib/generators/graphql/relay.rb +18 -1
  9. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  10. data/lib/generators/graphql/templates/base_connection.erb +2 -0
  11. data/lib/generators/graphql/templates/base_edge.erb +2 -0
  12. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  13. data/lib/generators/graphql/templates/base_field.erb +2 -0
  14. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  15. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  16. data/lib/generators/graphql/templates/base_object.erb +2 -0
  17. data/lib/generators/graphql/templates/base_resolver.erb +6 -0
  18. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  19. data/lib/generators/graphql/templates/base_union.erb +2 -0
  20. data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
  21. data/lib/generators/graphql/templates/loader.erb +2 -0
  22. data/lib/generators/graphql/templates/mutation.erb +2 -0
  23. data/lib/generators/graphql/templates/node_type.erb +2 -0
  24. data/lib/generators/graphql/templates/query_type.erb +2 -0
  25. data/lib/generators/graphql/templates/schema.erb +8 -0
  26. data/lib/graphql/analysis/analyzer.rb +89 -0
  27. data/lib/graphql/analysis/field_usage.rb +82 -0
  28. data/lib/graphql/analysis/max_query_complexity.rb +20 -0
  29. data/lib/graphql/analysis/max_query_depth.rb +20 -0
  30. data/lib/graphql/analysis/query_complexity.rb +183 -0
  31. data/lib/graphql/analysis/query_depth.rb +58 -0
  32. data/lib/graphql/analysis/visitor.rb +283 -0
  33. data/lib/graphql/analysis.rb +92 -1
  34. data/lib/graphql/backtrace/inspect_result.rb +0 -12
  35. data/lib/graphql/backtrace/table.rb +2 -2
  36. data/lib/graphql/backtrace/trace.rb +93 -0
  37. data/lib/graphql/backtrace/tracer.rb +1 -1
  38. data/lib/graphql/backtrace.rb +2 -1
  39. data/lib/graphql/coercion_error.rb +1 -9
  40. data/lib/graphql/dataloader/async_dataloader.rb +88 -0
  41. data/lib/graphql/dataloader/null_dataloader.rb +1 -1
  42. data/lib/graphql/dataloader/request.rb +5 -0
  43. data/lib/graphql/dataloader/source.rb +89 -45
  44. data/lib/graphql/dataloader.rb +115 -142
  45. data/lib/graphql/duration_encoding_error.rb +16 -0
  46. data/lib/graphql/execution/interpreter/argument_value.rb +5 -1
  47. data/lib/graphql/execution/interpreter/arguments.rb +1 -1
  48. data/lib/graphql/execution/interpreter/arguments_cache.rb +33 -33
  49. data/lib/graphql/execution/interpreter/resolve.rb +19 -0
  50. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +175 -0
  51. data/lib/graphql/execution/interpreter/runtime.rb +331 -455
  52. data/lib/graphql/execution/interpreter.rb +125 -61
  53. data/lib/graphql/execution/lazy.rb +6 -12
  54. data/lib/graphql/execution/lookahead.rb +124 -46
  55. data/lib/graphql/execution/multiplex.rb +3 -117
  56. data/lib/graphql/execution.rb +0 -1
  57. data/lib/graphql/introspection/directive_type.rb +3 -3
  58. data/lib/graphql/introspection/dynamic_fields.rb +1 -1
  59. data/lib/graphql/introspection/entry_points.rb +11 -5
  60. data/lib/graphql/introspection/field_type.rb +2 -2
  61. data/lib/graphql/introspection/schema_type.rb +10 -13
  62. data/lib/graphql/introspection/type_type.rb +17 -10
  63. data/lib/graphql/introspection.rb +3 -2
  64. data/lib/graphql/language/block_string.rb +34 -18
  65. data/lib/graphql/language/definition_slice.rb +1 -1
  66. data/lib/graphql/language/document_from_schema_definition.rb +75 -59
  67. data/lib/graphql/language/lexer.rb +358 -1506
  68. data/lib/graphql/language/nodes.rb +166 -93
  69. data/lib/graphql/language/parser.rb +795 -1953
  70. data/lib/graphql/language/printer.rb +340 -160
  71. data/lib/graphql/language/sanitized_printer.rb +21 -23
  72. data/lib/graphql/language/static_visitor.rb +167 -0
  73. data/lib/graphql/language/visitor.rb +188 -141
  74. data/lib/graphql/language.rb +61 -1
  75. data/lib/graphql/load_application_object_failed_error.rb +5 -1
  76. data/lib/graphql/pagination/active_record_relation_connection.rb +0 -8
  77. data/lib/graphql/pagination/array_connection.rb +6 -6
  78. data/lib/graphql/pagination/connection.rb +33 -6
  79. data/lib/graphql/pagination/mongoid_relation_connection.rb +1 -2
  80. data/lib/graphql/query/context/scoped_context.rb +101 -0
  81. data/lib/graphql/query/context.rb +117 -112
  82. data/lib/graphql/query/null_context.rb +12 -25
  83. data/lib/graphql/query/validation_pipeline.rb +6 -5
  84. data/lib/graphql/query/variables.rb +3 -3
  85. data/lib/graphql/query.rb +86 -30
  86. data/lib/graphql/railtie.rb +9 -6
  87. data/lib/graphql/rake_task.rb +29 -11
  88. data/lib/graphql/rubocop/graphql/base_cop.rb +1 -1
  89. data/lib/graphql/schema/addition.rb +59 -23
  90. data/lib/graphql/schema/always_visible.rb +11 -0
  91. data/lib/graphql/schema/argument.rb +55 -26
  92. data/lib/graphql/schema/base_64_encoder.rb +3 -5
  93. data/lib/graphql/schema/build_from_definition.rb +56 -32
  94. data/lib/graphql/schema/directive/one_of.rb +24 -0
  95. data/lib/graphql/schema/directive/specified_by.rb +14 -0
  96. data/lib/graphql/schema/directive/transform.rb +1 -1
  97. data/lib/graphql/schema/directive.rb +15 -3
  98. data/lib/graphql/schema/enum.rb +35 -24
  99. data/lib/graphql/schema/enum_value.rb +2 -3
  100. data/lib/graphql/schema/field/connection_extension.rb +2 -16
  101. data/lib/graphql/schema/field/scope_extension.rb +8 -1
  102. data/lib/graphql/schema/field.rb +147 -107
  103. data/lib/graphql/schema/field_extension.rb +1 -4
  104. data/lib/graphql/schema/find_inherited_value.rb +2 -7
  105. data/lib/graphql/schema/has_single_input_argument.rb +158 -0
  106. data/lib/graphql/schema/input_object.rb +47 -11
  107. data/lib/graphql/schema/interface.rb +15 -21
  108. data/lib/graphql/schema/introspection_system.rb +7 -17
  109. data/lib/graphql/schema/late_bound_type.rb +10 -0
  110. data/lib/graphql/schema/list.rb +2 -2
  111. data/lib/graphql/schema/loader.rb +2 -3
  112. data/lib/graphql/schema/member/base_dsl_methods.rb +18 -14
  113. data/lib/graphql/schema/member/build_type.rb +11 -3
  114. data/lib/graphql/schema/member/has_arguments.rb +170 -130
  115. data/lib/graphql/schema/member/has_ast_node.rb +12 -0
  116. data/lib/graphql/schema/member/has_deprecation_reason.rb +3 -4
  117. data/lib/graphql/schema/member/has_directives.rb +81 -61
  118. data/lib/graphql/schema/member/has_fields.rb +100 -38
  119. data/lib/graphql/schema/member/has_interfaces.rb +65 -10
  120. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  121. data/lib/graphql/schema/member/has_validators.rb +32 -6
  122. data/lib/graphql/schema/member/relay_shortcuts.rb +19 -0
  123. data/lib/graphql/schema/member/scoped.rb +19 -0
  124. data/lib/graphql/schema/member/type_system_helpers.rb +16 -0
  125. data/lib/graphql/schema/member/validates_input.rb +3 -3
  126. data/lib/graphql/schema/mutation.rb +7 -0
  127. data/lib/graphql/schema/object.rb +16 -5
  128. data/lib/graphql/schema/printer.rb +11 -8
  129. data/lib/graphql/schema/relay_classic_mutation.rb +7 -129
  130. data/lib/graphql/schema/resolver/has_payload_type.rb +9 -9
  131. data/lib/graphql/schema/resolver.rb +47 -32
  132. data/lib/graphql/schema/scalar.rb +3 -3
  133. data/lib/graphql/schema/subscription.rb +11 -4
  134. data/lib/graphql/schema/subset.rb +397 -0
  135. data/lib/graphql/schema/timeout.rb +25 -29
  136. data/lib/graphql/schema/type_expression.rb +2 -2
  137. data/lib/graphql/schema/type_membership.rb +3 -0
  138. data/lib/graphql/schema/union.rb +11 -2
  139. data/lib/graphql/schema/unique_within_type.rb +1 -1
  140. data/lib/graphql/schema/validator/all_validator.rb +60 -0
  141. data/lib/graphql/schema/validator.rb +4 -2
  142. data/lib/graphql/schema/warden.rb +238 -93
  143. data/lib/graphql/schema.rb +498 -103
  144. data/lib/graphql/static_validation/all_rules.rb +2 -1
  145. data/lib/graphql/static_validation/base_visitor.rb +7 -6
  146. data/lib/graphql/static_validation/definition_dependencies.rb +7 -1
  147. data/lib/graphql/static_validation/literal_validator.rb +24 -7
  148. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  149. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
  150. data/lib/graphql/static_validation/rules/directives_are_defined.rb +1 -2
  151. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +1 -1
  152. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +12 -4
  153. data/lib/graphql/static_validation/rules/fields_will_merge.rb +10 -10
  154. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  155. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +1 -1
  156. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  157. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  158. data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid.rb +66 -0
  159. data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid_error.rb +29 -0
  160. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  161. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +4 -4
  162. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +5 -5
  163. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +1 -1
  164. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  165. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +1 -1
  166. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
  167. data/lib/graphql/static_validation/validation_context.rb +5 -5
  168. data/lib/graphql/static_validation/validator.rb +4 -1
  169. data/lib/graphql/static_validation.rb +0 -1
  170. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +11 -4
  171. data/lib/graphql/subscriptions/broadcast_analyzer.rb +11 -5
  172. data/lib/graphql/subscriptions/event.rb +11 -10
  173. data/lib/graphql/subscriptions/serialize.rb +2 -0
  174. data/lib/graphql/subscriptions.rb +20 -13
  175. data/lib/graphql/testing/helpers.rb +151 -0
  176. data/lib/graphql/testing.rb +2 -0
  177. data/lib/graphql/tracing/active_support_notifications_trace.rb +16 -0
  178. data/lib/graphql/tracing/appoptics_trace.rb +251 -0
  179. data/lib/graphql/tracing/appoptics_tracing.rb +2 -2
  180. data/lib/graphql/tracing/appsignal_trace.rb +77 -0
  181. data/lib/graphql/tracing/data_dog_trace.rb +183 -0
  182. data/lib/graphql/tracing/data_dog_tracing.rb +9 -21
  183. data/lib/graphql/{execution/instrumentation.rb → tracing/legacy_hooks_trace.rb} +10 -28
  184. data/lib/graphql/tracing/legacy_trace.rb +69 -0
  185. data/lib/graphql/tracing/new_relic_trace.rb +75 -0
  186. data/lib/graphql/tracing/notifications_trace.rb +45 -0
  187. data/lib/graphql/tracing/platform_trace.rb +118 -0
  188. data/lib/graphql/tracing/platform_tracing.rb +17 -3
  189. data/lib/graphql/tracing/{prometheus_tracing → prometheus_trace}/graphql_collector.rb +4 -2
  190. data/lib/graphql/tracing/prometheus_trace.rb +89 -0
  191. data/lib/graphql/tracing/prometheus_tracing.rb +3 -3
  192. data/lib/graphql/tracing/scout_trace.rb +72 -0
  193. data/lib/graphql/tracing/sentry_trace.rb +112 -0
  194. data/lib/graphql/tracing/statsd_trace.rb +56 -0
  195. data/lib/graphql/tracing/trace.rb +76 -0
  196. data/lib/graphql/tracing.rb +20 -40
  197. data/lib/graphql/type_kinds.rb +7 -4
  198. data/lib/graphql/types/iso_8601_duration.rb +77 -0
  199. data/lib/graphql/types/relay/base_connection.rb +1 -1
  200. data/lib/graphql/types/relay/connection_behaviors.rb +68 -6
  201. data/lib/graphql/types/relay/edge_behaviors.rb +33 -5
  202. data/lib/graphql/types/relay/node_behaviors.rb +8 -2
  203. data/lib/graphql/types/relay/page_info_behaviors.rb +11 -2
  204. data/lib/graphql/types/relay.rb +0 -1
  205. data/lib/graphql/types/string.rb +1 -1
  206. data/lib/graphql/types.rb +1 -0
  207. data/lib/graphql/version.rb +1 -1
  208. data/lib/graphql.rb +27 -20
  209. data/readme.md +13 -3
  210. metadata +96 -47
  211. data/lib/graphql/analysis/ast/analyzer.rb +0 -84
  212. data/lib/graphql/analysis/ast/field_usage.rb +0 -57
  213. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -22
  214. data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
  215. data/lib/graphql/analysis/ast/query_complexity.rb +0 -230
  216. data/lib/graphql/analysis/ast/query_depth.rb +0 -55
  217. data/lib/graphql/analysis/ast/visitor.rb +0 -269
  218. data/lib/graphql/analysis/ast.rb +0 -81
  219. data/lib/graphql/deprecation.rb +0 -9
  220. data/lib/graphql/filter.rb +0 -53
  221. data/lib/graphql/language/lexer.rl +0 -280
  222. data/lib/graphql/language/parser.y +0 -554
  223. data/lib/graphql/language/token.rb +0 -34
  224. data/lib/graphql/schema/base_64_bp.rb +0 -26
  225. data/lib/graphql/schema/invalid_type_error.rb +0 -7
  226. data/lib/graphql/static_validation/type_stack.rb +0 -216
  227. data/lib/graphql/subscriptions/instrumentation.rb +0 -28
  228. data/lib/graphql/types/relay/default_relay.rb +0 -21
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require "graphql/execution/interpreter/runtime/graphql_result"
2
3
 
3
4
  module GraphQL
4
5
  module Execution
@@ -8,135 +9,21 @@ module GraphQL
8
9
  #
9
10
  # @api private
10
11
  class Runtime
11
-
12
- module GraphQLResult
13
- def initialize(result_name, parent_result)
14
- @graphql_parent = parent_result
15
- if parent_result && parent_result.graphql_dead
16
- @graphql_dead = true
17
- end
18
- @graphql_result_name = result_name
19
- # Jump through some hoops to avoid creating this duplicate storage if at all possible.
20
- @graphql_metadata = nil
12
+ class CurrentState
13
+ def initialize
14
+ @current_field = nil
15
+ @current_arguments = nil
16
+ @current_result_name = nil
17
+ @current_result = nil
18
+ @was_authorized_by_scope_items = nil
21
19
  end
22
20
 
23
- attr_accessor :graphql_dead
24
- attr_reader :graphql_parent, :graphql_result_name
25
-
26
- # Although these are used by only one of the Result classes,
27
- # it's handy to have the methods implemented on both (even though they just return `nil`)
28
- # because it makes it easy to check if anything is assigned.
29
- # @return [nil, Array<String>]
30
- attr_accessor :graphql_non_null_field_names
31
- # @return [nil, true]
32
- attr_accessor :graphql_non_null_list_items
33
-
34
- # @return [Hash] Plain-Ruby result data (`@graphql_metadata` contains Result wrapper objects)
35
- attr_accessor :graphql_result_data
36
- end
37
-
38
- class GraphQLResultHash
39
- def initialize(_result_name, _parent_result)
40
- super
41
- @graphql_result_data = {}
21
+ def current_object
22
+ @current_result.graphql_application_value
42
23
  end
43
24
 
44
- include GraphQLResult
45
-
46
- attr_accessor :graphql_merged_into
47
-
48
- def []=(key, value)
49
- # This is a hack.
50
- # Basically, this object is merged into the root-level result at some point.
51
- # But the problem is, some lazies are created whose closures retain reference to _this_
52
- # object. When those lazies are resolved, they cause an update to this object.
53
- #
54
- # In order to return a proper top-level result, we have to update that top-level result object.
55
- # In order to return a proper partial result (eg, for a directive), we have to update this object, too.
56
- # Yowza.
57
- if (t = @graphql_merged_into)
58
- t[key] = value
59
- end
60
-
61
- if value.respond_to?(:graphql_result_data)
62
- @graphql_result_data[key] = value.graphql_result_data
63
- # If we encounter some part of this response that requires metadata tracking,
64
- # then create the metadata hash if necessary. It will be kept up-to-date after this.
65
- (@graphql_metadata ||= @graphql_result_data.dup)[key] = value
66
- else
67
- @graphql_result_data[key] = value
68
- # keep this up-to-date if it's been initialized
69
- @graphql_metadata && @graphql_metadata[key] = value
70
- end
71
-
72
- value
73
- end
74
-
75
- def delete(key)
76
- @graphql_metadata && @graphql_metadata.delete(key)
77
- @graphql_result_data.delete(key)
78
- end
79
-
80
- def each
81
- (@graphql_metadata || @graphql_result_data).each { |k, v| yield(k, v) }
82
- end
83
-
84
- def values
85
- (@graphql_metadata || @graphql_result_data).values
86
- end
87
-
88
- def key?(k)
89
- @graphql_result_data.key?(k)
90
- end
91
-
92
- def [](k)
93
- (@graphql_metadata || @graphql_result_data)[k]
94
- end
95
- end
96
-
97
- class GraphQLResultArray
98
- include GraphQLResult
99
-
100
- def initialize(_result_name, _parent_result)
101
- super
102
- @graphql_result_data = []
103
- end
104
-
105
- def graphql_skip_at(index)
106
- # Mark this index as dead. It's tricky because some indices may already be storing
107
- # `Lazy`s. So the runtime is still holding indexes _before_ skipping,
108
- # this object has to coordinate incoming writes to account for any already-skipped indices.
109
- @skip_indices ||= []
110
- @skip_indices << index
111
- offset_by = @skip_indices.count { |skipped_idx| skipped_idx < index}
112
- delete_at_index = index - offset_by
113
- @graphql_metadata && @graphql_metadata.delete_at(delete_at_index)
114
- @graphql_result_data.delete_at(delete_at_index)
115
- end
116
-
117
- def []=(idx, value)
118
- if @skip_indices
119
- offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx }
120
- idx -= offset_by
121
- end
122
- if value.respond_to?(:graphql_result_data)
123
- @graphql_result_data[idx] = value.graphql_result_data
124
- (@graphql_metadata ||= @graphql_result_data.dup)[idx] = value
125
- else
126
- @graphql_result_data[idx] = value
127
- @graphql_metadata && @graphql_metadata[idx] = value
128
- end
129
-
130
- value
131
- end
132
-
133
- def values
134
- (@graphql_metadata || @graphql_result_data)
135
- end
136
- end
137
-
138
- class GraphQLSelectionSet < Hash
139
- attr_accessor :graphql_directives
25
+ attr_accessor :current_result, :current_result_name,
26
+ :current_arguments, :current_field, :was_authorized_by_scope_items
140
27
  end
141
28
 
142
29
  # @return [GraphQL::Query]
@@ -148,14 +35,14 @@ module GraphQL
148
35
  # @return [GraphQL::Query::Context]
149
36
  attr_reader :context
150
37
 
151
- def initialize(query:)
38
+ def initialize(query:, lazies_at_depth:)
152
39
  @query = query
40
+ @current_trace = query.current_trace
153
41
  @dataloader = query.multiplex.dataloader
42
+ @lazies_at_depth = lazies_at_depth
154
43
  @schema = query.schema
155
44
  @context = query.context
156
- @multiplex_context = query.multiplex.context
157
- @interpreter_context = @context.namespace(:interpreter)
158
- @response = GraphQLResultHash.new(nil, nil)
45
+ @response = nil
159
46
  # Identify runtime directives by checking which of this schema's directives have overridden `def self.resolve`
160
47
  @runtime_directive_names = []
161
48
  noop_resolve_owner = GraphQL::Schema::Directive.singleton_class
@@ -165,12 +52,9 @@ module GraphQL
165
52
  @runtime_directive_names << name
166
53
  end
167
54
  end
168
- # A cache of { Class => { String => Schema::Field } }
169
- # Which assumes that MyObject.get_field("myField") will return the same field
170
- # during the lifetime of a query
171
- @fields_cache = Hash.new { |h, k| h[k] = {} }
172
55
  # { Class => Boolean }
173
56
  @lazy_cache = {}
57
+ @lazy_cache.compare_by_identity
174
58
  end
175
59
 
176
60
  def final_result
@@ -181,16 +65,6 @@ module GraphQL
181
65
  "#<#{self.class.name} response=#{@response.inspect}>"
182
66
  end
183
67
 
184
- def tap_or_each(obj_or_array)
185
- if obj_or_array.is_a?(Array)
186
- obj_or_array.each do |item|
187
- yield(item, true)
188
- end
189
- else
190
- yield(obj_or_array, false)
191
- end
192
- end
193
-
194
68
  # This _begins_ the execution. Some deferred work
195
69
  # might be stored up in lazies.
196
70
  # @return [void]
@@ -198,27 +72,21 @@ module GraphQL
198
72
  root_operation = query.selected_operation
199
73
  root_op_type = root_operation.operation_type || "query"
200
74
  root_type = schema.root_type_for_operation(root_op_type)
201
- path = []
202
- set_all_interpreter_context(query.root_value, nil, nil, path)
203
- object_proxy = authorized_new(root_type, query.root_value, context)
204
- object_proxy = schema.sync_lazy(object_proxy)
205
-
206
- if object_proxy.nil?
75
+ runtime_object = root_type.wrap(query.root_value, context)
76
+ runtime_object = schema.sync_lazy(runtime_object)
77
+ is_eager = root_op_type == "mutation"
78
+ @response = GraphQLResultHash.new(nil, root_type, runtime_object, nil, false, root_operation.selections, is_eager)
79
+ st = get_current_runtime_state
80
+ st.current_result = @response
81
+
82
+ if runtime_object.nil?
207
83
  # Root .authorized? returned false.
208
84
  @response = nil
209
85
  else
210
- call_method_on_directives(:resolve, object_proxy, root_operation.directives) do # execute query level directives
211
- gathered_selections = gather_selections(object_proxy, root_type, root_operation.selections)
212
- # This is kind of a hack -- `gathered_selections` is an Array if any of the selections
213
- # require isolation during execution (because of runtime directives). In that case,
214
- # make a new, isolated result hash for writing the result into. (That isolated response
215
- # is eventually merged back into the main response)
216
- #
217
- # Otherwise, `gathered_selections` is a hash of selections which can be
218
- # directly evaluated and the results can be written right into the main response hash.
219
- tap_or_each(gathered_selections) do |selections, is_selection_array|
86
+ call_method_on_directives(:resolve, runtime_object, root_operation.directives) do # execute query level directives
87
+ each_gathered_selections(@response) do |selections, is_selection_array|
220
88
  if is_selection_array
221
- selection_response = GraphQLResultHash.new(nil, nil)
89
+ selection_response = GraphQLResultHash.new(nil, root_type, runtime_object, nil, false, selections, is_eager)
222
90
  final_response = @response
223
91
  else
224
92
  selection_response = @response
@@ -226,53 +94,31 @@ module GraphQL
226
94
  end
227
95
 
228
96
  @dataloader.append_job {
229
- set_all_interpreter_context(query.root_value, nil, nil, path)
230
- call_method_on_directives(:resolve, object_proxy, selections.graphql_directives) do
231
- evaluate_selections(
232
- path,
233
- object_proxy,
234
- root_type,
235
- root_op_type == "mutation",
236
- selections,
237
- selection_response,
238
- final_response,
239
- nil,
240
- )
241
- end
97
+ evaluate_selections(
98
+ selections,
99
+ selection_response,
100
+ final_response,
101
+ nil,
102
+ )
242
103
  }
243
104
  end
244
105
  end
245
106
  end
246
- delete_interpreter_context(:current_path)
247
- delete_interpreter_context(:current_field)
248
- delete_interpreter_context(:current_object)
249
- delete_interpreter_context(:current_arguments)
250
107
  nil
251
108
  end
252
109
 
253
- # @return [void]
254
- def deep_merge_selection_result(from_result, into_result)
255
- from_result.each do |key, value|
256
- if !into_result.key?(key)
257
- into_result[key] = value
258
- else
259
- case value
260
- when GraphQLResultHash
261
- deep_merge_selection_result(value, into_result[key])
262
- else
263
- # We have to assume that, since this passed the `fields_will_merge` selection,
264
- # that the old and new values are the same.
265
- # There's no special handling of arrays because currently, there's no way to split the execution
266
- # of a list over several concurrent flows.
267
- into_result[key] = value
268
- end
110
+ def each_gathered_selections(response_hash)
111
+ gathered_selections = gather_selections(response_hash.graphql_application_value, response_hash.graphql_result_type, response_hash.graphql_selections)
112
+ if gathered_selections.is_a?(Array)
113
+ gathered_selections.each do |item|
114
+ yield(item, true)
269
115
  end
116
+ else
117
+ yield(gathered_selections, false)
270
118
  end
271
- from_result.graphql_merged_into = into_result
272
- nil
273
119
  end
274
120
 
275
- def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = GraphQLSelectionSet.new)
121
+ def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = {})
276
122
  selections.each do |node|
277
123
  # Skip gathering this if the directive says so
278
124
  if !directives_include?(node, owner_object, owner_type)
@@ -284,7 +130,7 @@ module GraphQL
284
130
  selections = selections_by_name[response_key]
285
131
  # if there was already a selection of this field,
286
132
  # use an array to hold all selections,
287
- # otherise, use the single node to represent the selection
133
+ # otherwise, use the single node to represent the selection
288
134
  if selections
289
135
  # This field was already selected at least once,
290
136
  # add this node to the list of selections
@@ -298,8 +144,8 @@ module GraphQL
298
144
  else
299
145
  # This is an InlineFragment or a FragmentSpread
300
146
  if @runtime_directive_names.any? && node.directives.any? { |d| @runtime_directive_names.include?(d.name) }
301
- next_selections = GraphQLSelectionSet.new
302
- next_selections.graphql_directives = node.directives
147
+ next_selections = {}
148
+ next_selections[:graphql_directives] = node.directives
303
149
  if selections_to_run
304
150
  selections_to_run << next_selections
305
151
  else
@@ -314,27 +160,28 @@ module GraphQL
314
160
  case node
315
161
  when GraphQL::Language::Nodes::InlineFragment
316
162
  if node.type
317
- type_defn = schema.get_type(node.type.name, context)
163
+ type_defn = query.types.type(node.type.name)
318
164
 
319
- # Faster than .map{}.include?()
320
- query.warden.possible_types(type_defn).each do |t|
321
- if t == owner_type
322
- gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
323
- break
165
+ if query.types.possible_types(type_defn).include?(owner_type)
166
+ result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
167
+ if !result.equal?(next_selections)
168
+ selections_to_run = result
324
169
  end
325
170
  end
326
171
  else
327
172
  # it's an untyped fragment, definitely continue
328
- gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
173
+ result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
174
+ if !result.equal?(next_selections)
175
+ selections_to_run = result
176
+ end
329
177
  end
330
178
  when GraphQL::Language::Nodes::FragmentSpread
331
179
  fragment_def = query.fragments[node.name]
332
- type_defn = query.get_type(fragment_def.type.name)
333
- possible_types = query.warden.possible_types(type_defn)
334
- possible_types.each do |t|
335
- if t == owner_type
336
- gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections)
337
- break
180
+ type_defn = query.types.type(fragment_def.type.name)
181
+ if query.types.possible_types(type_defn).include?(owner_type)
182
+ result = gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections)
183
+ if !result.equal?(next_selections)
184
+ selections_to_run = result
338
185
  end
339
186
  end
340
187
  else
@@ -345,34 +192,47 @@ module GraphQL
345
192
  selections_to_run || selections_by_name
346
193
  end
347
194
 
348
- NO_ARGS = {}.freeze
195
+ NO_ARGS = GraphQL::EmptyObjects::EMPTY_HASH
349
196
 
350
197
  # @return [void]
351
- def evaluate_selections(path, owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result, parent_object) # rubocop:disable Metrics/ParameterLists
352
- set_all_interpreter_context(owner_object, nil, nil, path)
353
-
354
- finished_jobs = 0
355
- enqueued_jobs = gathered_selections.size
356
- gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
357
- @dataloader.append_job {
358
- evaluate_selection(
359
- path, result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_selection, selections_result, parent_object
360
- )
361
- finished_jobs += 1
362
- if target_result && finished_jobs == enqueued_jobs
363
- deep_merge_selection_result(selections_result, target_result)
198
+ def evaluate_selections(gathered_selections, selections_result, target_result, runtime_state) # rubocop:disable Metrics/ParameterLists
199
+ runtime_state ||= get_current_runtime_state
200
+ runtime_state.current_result_name = nil
201
+ runtime_state.current_result = selections_result
202
+ # This is a less-frequent case; use a fast check since it's often not there.
203
+ if (directives = gathered_selections[:graphql_directives])
204
+ gathered_selections.delete(:graphql_directives)
205
+ end
206
+
207
+ call_method_on_directives(:resolve, selections_result.graphql_application_value, directives) do
208
+ finished_jobs = 0
209
+ enqueued_jobs = gathered_selections.size
210
+ gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
211
+ @dataloader.append_job {
212
+ evaluate_selection(
213
+ result_name, field_ast_nodes_or_ast_node, selections_result
214
+ )
215
+ finished_jobs += 1
216
+ if target_result && finished_jobs == enqueued_jobs
217
+ selections_result.merge_into(target_result)
218
+ end
219
+ }
220
+ # Field resolution may pause the fiber,
221
+ # so it wouldn't get to the `Resolve` call that happens below.
222
+ # So instead trigger a run from this outer context.
223
+ if selections_result.graphql_is_eager
224
+ @dataloader.clear_cache
225
+ @dataloader.run
226
+ @dataloader.clear_cache
364
227
  end
365
- }
228
+ end
229
+ selections_result
366
230
  end
367
-
368
- selections_result
369
231
  end
370
232
 
371
- attr_reader :progress_path
372
-
373
233
  # @return [void]
374
- def evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_field, selections_result, parent_object) # rubocop:disable Metrics/ParameterLists
375
- return if dead_result?(selections_result)
234
+ def evaluate_selection(result_name, field_ast_nodes_or_ast_node, selections_result) # rubocop:disable Metrics/ParameterLists
235
+ return if selections_result.graphql_dead
376
236
  # As a performance optimization, the hash key will be a `Node` if
377
237
  # there's only one selection of the field. But if there are multiple
378
238
  # selections of the field, it will be an Array of nodes
@@ -384,63 +244,52 @@ module GraphQL
384
244
  ast_node = field_ast_nodes_or_ast_node
385
245
  end
386
246
  field_name = ast_node.name
387
- # This can't use `query.get_field` because it gets confused on introspection below if `field_defn` isn't `nil`,
388
- # because of how `is_introspection` is used to call `.authorized_new` later on.
389
- field_defn = @fields_cache[owner_type][field_name] ||= owner_type.get_field(field_name, @context)
390
- is_introspection = false
391
- if field_defn.nil?
392
- field_defn = if owner_type == schema.query && (entry_point_field = schema.introspection_system.entry_point(name: field_name))
393
- is_introspection = true
394
- entry_point_field
395
- elsif (dynamic_field = schema.introspection_system.dynamic_field(name: field_name))
396
- is_introspection = true
397
- dynamic_field
398
- else
399
- raise "Invariant: no field for #{owner_type}.#{field_name}"
400
- end
401
- end
402
- return_type = field_defn.type
247
+ owner_type = selections_result.graphql_result_type
248
+ field_defn = query.types.field(owner_type, field_name)
403
249
 
404
- next_path = path + [result_name]
405
- next_path.freeze
406
-
407
- # This seems janky, but we need to know
408
- # the field's return type at this path in order
409
- # to propagate `null`
410
- if return_type.non_null?
411
- (selections_result.graphql_non_null_field_names ||= []).push(result_name)
412
- end
413
250
  # Set this before calling `run_with_directives`, so that the directive can have the latest path
414
- set_all_interpreter_context(nil, field_defn, nil, next_path)
415
- object = owner_object
251
+ runtime_state = get_current_runtime_state
252
+ runtime_state.current_field = field_defn
253
+ runtime_state.current_result = selections_result
254
+ runtime_state.current_result_name = result_name
416
255
 
417
- if is_introspection
418
- object = authorized_new(field_defn.owner, object, context)
256
+ owner_object = selections_result.graphql_application_value
257
+ if field_defn.dynamic_introspection
258
+ owner_object = field_defn.owner.wrap(owner_object, context)
419
259
  end
420
260
 
421
- total_args_count = field_defn.arguments(context).size
422
- if total_args_count == 0
261
+ if !field_defn.any_arguments?
423
262
  resolved_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
424
- evaluate_selection_with_args(resolved_arguments, field_defn, next_path, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object)
263
+ if field_defn.extras.size == 0
264
+ evaluate_selection_with_resolved_keyword_args(
265
+ NO_ARGS, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_object, result_name, selections_result, runtime_state
266
+ )
267
+ else
268
+ evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_object, result_name, selections_result, runtime_state)
269
+ end
425
270
  else
426
- # TODO remove all arguments(...) usages?
427
- @query.arguments_cache.dataload_for(ast_node, field_defn, object) do |resolved_arguments|
428
- evaluate_selection_with_args(resolved_arguments, field_defn, next_path, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object)
271
+ @query.arguments_cache.dataload_for(ast_node, field_defn, owner_object) do |resolved_arguments|
272
+ runtime_state = get_current_runtime_state # This might be in a different fiber
273
+ evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_object, result_name, selections_result, runtime_state)
429
274
  end
430
275
  end
431
276
  end
432
277
 
433
- def evaluate_selection_with_args(arguments, field_defn, next_path, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selection_result, parent_object) # rubocop:disable Metrics/ParameterLists
434
- return_type = field_defn.type
435
- after_lazy(arguments, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, owner_object: object, arguments: arguments, result_name: result_name, result: selection_result) do |resolved_arguments|
278
+ def evaluate_selection_with_args(arguments, field_defn, ast_node, field_ast_nodes, object, result_name, selection_result, runtime_state) # rubocop:disable Metrics/ParameterLists
279
+ after_lazy(arguments, field: field_defn, ast_node: ast_node, owner_object: object, arguments: arguments, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |resolved_arguments, runtime_state|
436
280
  if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
437
- continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
281
+ return_type_non_null = field_defn.type.non_null?
282
+ continue_value(resolved_arguments, field_defn, return_type_non_null, ast_node, result_name, selection_result)
438
283
  next
439
284
  end
440
285
 
441
- kwarg_arguments = if resolved_arguments.empty? && field_defn.extras.empty?
442
- # We can avoid allocating the `{ Symbol => Object }` hash in this case
443
- NO_ARGS
286
+ kwarg_arguments = if field_defn.extras.empty?
287
+ if resolved_arguments.empty?
288
+ # We can avoid allocating the `{ Symbol => Object }` hash in this case
289
+ NO_ARGS
290
+ else
291
+ resolved_arguments.keyword_arguments
292
+ end
444
293
  else
445
294
  # Bundle up the extras, then make a new arguments instance
446
295
  # that includes the extras, too.
@@ -450,9 +299,9 @@ module GraphQL
450
299
  when :ast_node
451
300
  extra_args[:ast_node] = ast_node
452
301
  when :execution_errors
453
- extra_args[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
302
+ extra_args[:execution_errors] = ExecutionErrors.new(context, ast_node, current_path)
454
303
  when :path
455
- extra_args[:path] = next_path
304
+ extra_args[:path] = current_path
456
305
  when :lookahead
457
306
  if !field_ast_nodes
458
307
  field_ast_nodes = [ast_node]
@@ -468,7 +317,8 @@ module GraphQL
468
317
  # to the keyword args hash _before_ freezing everything.
469
318
  extra_args[:argument_details] = :__arguments_add_self
470
319
  when :parent
471
- extra_args[:parent] = parent_object
320
+ parent_result = selection_result.graphql_parent
321
+ extra_args[:parent] = parent_result&.graphql_application_value&.object
472
322
  else
473
323
  extra_args[extra] = field_defn.fetch_extra(extra, context)
474
324
  end
@@ -479,67 +329,73 @@ module GraphQL
479
329
  resolved_arguments.keyword_arguments
480
330
  end
481
331
 
482
- set_all_interpreter_context(nil, nil, resolved_arguments, nil)
332
+ evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, object, result_name, selection_result, runtime_state)
333
+ end
334
+ end
483
335
 
484
- # Optimize for the case that field is selected only once
485
- if field_ast_nodes.nil? || field_ast_nodes.size == 1
486
- next_selections = ast_node.selections
487
- directives = ast_node.directives
488
- else
489
- next_selections = []
490
- directives = []
491
- field_ast_nodes.each { |f|
492
- next_selections.concat(f.selections)
493
- directives.concat(f.directives)
494
- }
495
- end
336
+ def evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, object, result_name, selection_result, runtime_state) # rubocop:disable Metrics/ParameterLists
337
+ runtime_state.current_field = field_defn
338
+ runtime_state.current_arguments = resolved_arguments
339
+ runtime_state.current_result_name = result_name
340
+ runtime_state.current_result = selection_result
341
+ # Optimize for the case that field is selected only once
342
+ if field_ast_nodes.nil? || field_ast_nodes.size == 1
343
+ next_selections = ast_node.selections
344
+ directives = ast_node.directives
345
+ else
346
+ next_selections = []
347
+ directives = []
348
+ field_ast_nodes.each { |f|
349
+ next_selections.concat(f.selections)
350
+ directives.concat(f.directives)
351
+ }
352
+ end
496
353
 
497
- field_result = call_method_on_directives(:resolve, object, directives) do
498
- # Actually call the field resolver and capture the result
499
- app_result = begin
500
- 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
501
- field_defn.resolve(object, kwarg_arguments, context)
502
- end
503
- rescue GraphQL::ExecutionError => err
504
- err
505
- rescue StandardError => err
506
- begin
507
- query.handle_or_reraise(err)
508
- rescue GraphQL::ExecutionError => ex_err
509
- ex_err
510
- end
354
+ field_result = call_method_on_directives(:resolve, object, directives) do
355
+ if directives.any?
356
+ # This might be executed in a different context; reset this info
357
+ runtime_state = get_current_runtime_state
358
+ runtime_state.current_field = field_defn
359
+ runtime_state.current_arguments = resolved_arguments
360
+ runtime_state.current_result_name = result_name
361
+ runtime_state.current_result = selection_result
362
+ end
363
+ # Actually call the field resolver and capture the result
364
+ app_result = begin
365
+ @current_trace.execute_field(field: field_defn, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments) do
366
+ field_defn.resolve(object, kwarg_arguments, context)
511
367
  end
512
- after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result) do |inner_result|
513
- continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
514
- if HALT != continue_value
515
- continue_field(next_path, continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result)
516
- end
368
+ rescue GraphQL::ExecutionError => err
369
+ err
370
+ rescue StandardError => err
371
+ begin
372
+ query.handle_or_reraise(err)
373
+ rescue GraphQL::ExecutionError => ex_err
374
+ ex_err
517
375
  end
518
376
  end
519
-
520
- # If this field is a root mutation field, immediately resolve
521
- # all of its child fields before moving on to the next root mutation field.
522
- # (Subselections of this mutation will still be resolved level-by-level.)
523
- if is_eager_field
524
- Interpreter::Resolve.resolve_all([field_result], @dataloader)
525
- else
526
- # Return this from `after_lazy` because it might be another lazy that needs to be resolved
527
- field_result
377
+ after_lazy(app_result, field: field_defn, ast_node: ast_node, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |inner_result, runtime_state|
378
+ owner_type = selection_result.graphql_result_type
379
+ return_type = field_defn.type
380
+ continue_value = continue_value(inner_result, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
381
+ if HALT != continue_value
382
+ was_scoped = runtime_state.was_authorized_by_scope_items
383
+ runtime_state.was_authorized_by_scope_items = nil
384
+ continue_field(continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result, was_scoped, runtime_state)
385
+ end
528
386
  end
529
387
  end
388
+ # If this field is a root mutation field, immediately resolve
389
+ # all of its child fields before moving on to the next root mutation field.
390
+ # (Subselections of this mutation will still be resolved level-by-level.)
391
+ if selection_result.graphql_is_eager
392
+ Interpreter::Resolve.resolve_all([field_result], @dataloader)
393
+ end
530
394
  end
531
395
 
532
- def dead_result?(selection_result)
533
- selection_result.graphql_dead || ((parent = selection_result.graphql_parent) && parent.graphql_dead)
534
- end
535
-
536
- def set_result(selection_result, result_name, value)
537
- if !dead_result?(selection_result)
538
- if value.nil? &&
539
- ( # there are two conditions under which `nil` is not allowed in the response:
540
- (selection_result.graphql_non_null_list_items) || # this value would be written into a list that doesn't allow nils
541
- ((nn = selection_result.graphql_non_null_field_names) && nn.include?(result_name)) # this value would be written into a field that doesn't allow nils
542
- )
396
+ def set_result(selection_result, result_name, value, is_child_result, is_non_null)
397
+ if !selection_result.graphql_dead
398
+ if value.nil? && is_non_null
543
399
  # This is an invalid nil that should be propagated
544
400
  # One caller of this method passes a block,
545
401
  # namely when application code returns a `nil` to GraphQL and it doesn't belong there.
@@ -549,15 +405,18 @@ module GraphQL
549
405
  # TODO the code is trying to tell me something.
550
406
  yield if block_given?
551
407
  parent = selection_result.graphql_parent
552
- name_in_parent = selection_result.graphql_result_name
553
408
  if parent.nil? # This is a top-level result hash
554
409
  @response = nil
555
410
  else
556
- set_result(parent, name_in_parent, nil)
411
+ name_in_parent = selection_result.graphql_result_name
412
+ is_non_null_in_parent = selection_result.graphql_is_non_null_in_parent
413
+ set_result(parent, name_in_parent, nil, false, is_non_null_in_parent)
557
414
  set_graphql_dead(selection_result)
558
415
  end
416
+ elsif is_child_result
417
+ selection_result.set_child_result(result_name, value)
559
418
  else
560
- selection_result[result_name] = value
419
+ selection_result.set_leaf(result_name, value)
561
420
  end
562
421
  end
563
422
  end
@@ -577,18 +436,31 @@ module GraphQL
577
436
  end
578
437
  end
579
438
 
439
+ def current_path
440
+ st = get_current_runtime_state
441
+ result = st.current_result
442
+ path = result && result.path
443
+ if path && (rn = st.current_result_name)
444
+ path = path.dup
445
+ path.push(rn)
446
+ end
447
+ path
448
+ end
449
+
580
450
  HALT = Object.new
581
- def continue_value(path, value, parent_type, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
451
+ def continue_value(value, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
582
452
  case value
583
453
  when nil
584
454
  if is_non_null
585
- set_result(selection_result, result_name, nil) do
455
+ set_result(selection_result, result_name, nil, false, is_non_null) do
456
+ # When this comes from a list item, use the parent object:
457
+ parent_type = selection_result.is_a?(GraphQLResultArray) ? selection_result.graphql_parent.graphql_result_type : selection_result.graphql_result_type
586
458
  # This block is called if `result_name` is not dead. (Maybe a previous invalid nil caused it be marked dead.)
587
459
  err = parent_type::InvalidNullError.new(parent_type, field, value)
588
460
  schema.type_error(err, context)
589
461
  end
590
462
  else
591
- set_result(selection_result, result_name, nil)
463
+ set_result(selection_result, result_name, nil, false, is_non_null)
592
464
  end
593
465
  HALT
594
466
  when GraphQL::Error
@@ -596,15 +468,25 @@ module GraphQL
596
468
  # to avoid the overhead of checking three different classes
597
469
  # every time.
598
470
  if value.is_a?(GraphQL::ExecutionError)
599
- if selection_result.nil? || !dead_result?(selection_result)
600
- value.path ||= path
471
+ if selection_result.nil? || !selection_result.graphql_dead
472
+ value.path ||= current_path
601
473
  value.ast_node ||= ast_node
602
474
  context.errors << value
603
475
  if selection_result
604
- set_result(selection_result, result_name, nil)
476
+ set_result(selection_result, result_name, nil, false, is_non_null)
605
477
  end
606
478
  end
607
479
  HALT
480
+ elsif value.is_a?(GraphQL::UnauthorizedFieldError)
481
+ value.field ||= field
482
+ # this hook might raise & crash, or it might return
483
+ # a replacement value
484
+ next_value = begin
485
+ schema.unauthorized_field(value)
486
+ rescue GraphQL::ExecutionError => err
487
+ err
488
+ end
489
+ continue_value(next_value, field, is_non_null, ast_node, result_name, selection_result)
608
490
  elsif value.is_a?(GraphQL::UnauthorizedError)
609
491
  # this hook might raise & crash, or it might return
610
492
  # a replacement value
@@ -613,7 +495,7 @@ module GraphQL
613
495
  rescue GraphQL::ExecutionError => err
614
496
  err
615
497
  end
616
- continue_value(path, next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
498
+ continue_value(next_value, field, is_non_null, ast_node, result_name, selection_result)
617
499
  elsif GraphQL::Execution::SKIP == value
618
500
  # It's possible a lazy was already written here
619
501
  case selection_result
@@ -634,20 +516,20 @@ module GraphQL
634
516
  end
635
517
  when Array
636
518
  # It's an array full of execution errors; add them all.
637
- if value.any? && value.all? { |v| v.is_a?(GraphQL::ExecutionError) }
519
+ if value.any? && value.all?(GraphQL::ExecutionError)
638
520
  list_type_at_all = (field && (field.type.list?))
639
- if selection_result.nil? || !dead_result?(selection_result)
521
+ if selection_result.nil? || !selection_result.graphql_dead
640
522
  value.each_with_index do |error, index|
641
523
  error.ast_node ||= ast_node
642
- error.path ||= path + (list_type_at_all ? [index] : [])
524
+ error.path ||= current_path + (list_type_at_all ? [index] : [])
643
525
  context.errors << error
644
526
  end
645
527
  if selection_result
646
528
  if list_type_at_all
647
529
  result_without_errors = value.map { |v| v.is_a?(GraphQL::ExecutionError) ? nil : v }
648
- set_result(selection_result, result_name, result_without_errors)
530
+ set_result(selection_result, result_name, result_without_errors, false, is_non_null)
649
531
  else
650
- set_result(selection_result, result_name, nil)
532
+ set_result(selection_result, result_name, nil, false, is_non_null)
651
533
  end
652
534
  end
653
535
  end
@@ -657,7 +539,7 @@ module GraphQL
657
539
  end
658
540
  when GraphQL::Execution::Interpreter::RawValue
659
541
  # Write raw value directly to the response without resolving nested objects
660
- set_result(selection_result, result_name, value.resolve)
542
+ set_result(selection_result, result_name, value.resolve, false, is_non_null)
661
543
  HALT
662
544
  else
663
545
  value
@@ -672,7 +554,7 @@ module GraphQL
672
554
  # Location information from `path` and `ast_node`.
673
555
  #
674
556
  # @return [Lazy, Array, Hash, Object] Lazy, Array, and Hash are all traversed to resolve lazy values later
675
- def continue_field(path, value, owner_type, field, current_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
557
+ def continue_field(value, owner_type, field, current_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result, was_scoped, runtime_state) # rubocop:disable Metrics/ParameterLists
676
558
  if current_type.non_null?
677
559
  current_type = current_type.of_type
678
560
  is_non_null = true
@@ -680,12 +562,16 @@ module GraphQL
680
562
 
681
563
  case current_type.kind.name
682
564
  when "SCALAR", "ENUM"
683
- r = current_type.coerce_result(value, context)
684
- set_result(selection_result, result_name, r)
565
+ r = begin
566
+ current_type.coerce_result(value, context)
567
+ rescue StandardError => err
568
+ schema.handle_or_reraise(context, err)
569
+ end
570
+ set_result(selection_result, result_name, r, false, is_non_null)
685
571
  r
686
572
  when "UNION", "INTERFACE"
687
- resolved_type_or_lazy = resolve_type(current_type, value, path)
688
- after_lazy(resolved_type_or_lazy, owner: current_type, path: path, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |resolved_type_result|
573
+ resolved_type_or_lazy = resolve_type(current_type, value)
574
+ after_lazy(resolved_type_or_lazy, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |resolved_type_result, runtime_state|
689
575
  if resolved_type_result.is_a?(Array) && resolved_type_result.length == 2
690
576
  resolved_type, resolved_value = resolved_type_result
691
577
  else
@@ -693,60 +579,44 @@ module GraphQL
693
579
  resolved_value = value
694
580
  end
695
581
 
696
- possible_types = query.possible_types(current_type)
582
+ possible_types = query.types.possible_types(current_type)
697
583
  if !possible_types.include?(resolved_type)
698
584
  parent_type = field.owner_type
699
585
  err_class = current_type::UnresolvedTypeError
700
586
  type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types)
701
587
  schema.type_error(type_error, context)
702
- set_result(selection_result, result_name, nil)
588
+ set_result(selection_result, result_name, nil, false, is_non_null)
703
589
  nil
704
590
  else
705
- continue_field(path, resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result)
591
+ continue_field(resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result, was_scoped, runtime_state)
706
592
  end
707
593
  end
708
594
  when "OBJECT"
709
595
  object_proxy = begin
710
- authorized_new(current_type, value, context)
596
+ was_scoped ? current_type.wrap_scoped(value, context) : current_type.wrap(value, context)
711
597
  rescue GraphQL::ExecutionError => err
712
598
  err
713
599
  end
714
- after_lazy(object_proxy, owner: current_type, path: path, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |inner_object|
715
- continue_value = continue_value(path, inner_object, owner_type, field, is_non_null, ast_node, result_name, selection_result)
600
+ after_lazy(object_proxy, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |inner_object, runtime_state|
601
+ continue_value = continue_value(inner_object, field, is_non_null, ast_node, result_name, selection_result)
716
602
  if HALT != continue_value
717
- response_hash = GraphQLResultHash.new(result_name, selection_result)
718
- set_result(selection_result, result_name, response_hash)
719
- gathered_selections = gather_selections(continue_value, current_type, next_selections)
720
- # There are two possibilities for `gathered_selections`:
721
- # 1. All selections of this object should be evaluated together (there are no runtime directives modifying execution).
722
- # This case is handled below, and the result can be written right into the main `response_hash` above.
723
- # In this case, `gathered_selections` is a hash of selections.
724
- # 2. Some selections of this object have runtime directives that may or may not modify execution.
725
- # That part of the selection is evaluated in an isolated way, writing into a sub-response object which is
726
- # eventually merged into the final response. In this case, `gathered_selections` is an array of things to run in isolation.
727
- # (Technically, it's possible that one of those entries _doesn't_ require isolation.)
728
- tap_or_each(gathered_selections) do |selections, is_selection_array|
603
+ response_hash = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, next_selections, false)
604
+ set_result(selection_result, result_name, response_hash, true, is_non_null)
605
+ each_gathered_selections(response_hash) do |selections, is_selection_array|
729
606
  if is_selection_array
730
- this_result = GraphQLResultHash.new(result_name, selection_result)
607
+ this_result = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, selections, false)
731
608
  final_result = response_hash
732
609
  else
733
610
  this_result = response_hash
734
611
  final_result = nil
735
612
  end
736
- set_all_interpreter_context(continue_value, nil, nil, path) # reset this mutable state
737
- call_method_on_directives(:resolve, continue_value, selections.graphql_directives) do
738
- evaluate_selections(
739
- path,
740
- continue_value,
741
- current_type,
742
- false,
743
- selections,
744
- this_result,
745
- final_result,
746
- owner_object.object,
747
- )
748
- this_result
749
- end
613
+
614
+ evaluate_selections(
615
+ selections,
616
+ this_result,
617
+ final_result,
618
+ runtime_state,
619
+ )
750
620
  end
751
621
  end
752
622
  end
@@ -754,43 +624,30 @@ module GraphQL
754
624
  inner_type = current_type.of_type
755
625
  # This is true for objects, unions, and interfaces
756
626
  use_dataloader_job = !inner_type.unwrap.kind.input?
757
- response_list = GraphQLResultArray.new(result_name, selection_result)
758
- response_list.graphql_non_null_list_items = inner_type.non_null?
759
- set_result(selection_result, result_name, response_list)
760
- result_was_set = false
761
- idx = 0
627
+ inner_type_non_null = inner_type.non_null?
628
+ response_list = GraphQLResultArray.new(result_name, current_type, owner_object, selection_result, is_non_null, next_selections, false)
629
+ set_result(selection_result, result_name, response_list, true, is_non_null)
630
+ idx = nil
762
631
  list_value = begin
763
632
  value.each do |inner_value|
764
- break if dead_result?(response_list)
765
- if !result_was_set
766
- # Don't set the result unless `.each` is successful
767
- set_result(selection_result, result_name, response_list)
768
- result_was_set = true
769
- end
770
- next_path = path + [idx]
633
+ idx ||= 0
771
634
  this_idx = idx
772
- next_path.freeze
773
635
  idx += 1
774
636
  if use_dataloader_job
775
637
  @dataloader.append_job do
776
- resolve_list_item(inner_value, inner_type, next_path, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type)
638
+ resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, owner_type, was_scoped, runtime_state)
777
639
  end
778
640
  else
779
- resolve_list_item(inner_value, inner_type, next_path, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type)
641
+ resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, owner_type, was_scoped, runtime_state)
780
642
  end
781
643
  end
782
- # Maybe the list was empty and the block was never called.
783
- if !result_was_set
784
- set_result(selection_result, result_name, response_list)
785
- result_was_set = true
786
- end
787
644
 
788
645
  response_list
789
646
  rescue NoMethodError => err
790
647
  # Ruby 2.2 doesn't have NoMethodError#receiver, can't check that one in this case. (It's been EOL since 2017.)
791
648
  if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true)
792
649
  # This happens when the GraphQL schema doesn't match the implementation. Help the dev debug.
793
- raise ListResultFailedError.new(value: value, field: field, path: path)
650
+ raise ListResultFailedError.new(value: value, field: field, path: current_path)
794
651
  else
795
652
  # This was some other NoMethodError -- let it bubble to reveal the real error.
796
653
  raise
@@ -804,21 +661,23 @@ module GraphQL
804
661
  ex_err
805
662
  end
806
663
  end
807
-
808
- continue_value(path, list_value, owner_type, field, inner_type.non_null?, ast_node, result_name, selection_result)
664
+ # Detect whether this error came while calling `.each` (before `idx` is set) or while running list *items* (after `idx` is set)
665
+ error_is_non_null = idx.nil? ? is_non_null : inner_type.non_null?
666
+ continue_value(list_value, field, error_is_non_null, ast_node, result_name, selection_result)
809
667
  else
810
668
  raise "Invariant: Unhandled type kind #{current_type.kind} (#{current_type})"
811
669
  end
812
670
  end
813
671
 
814
- def resolve_list_item(inner_value, inner_type, next_path, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type) # rubocop:disable Metrics/ParameterLists
815
- set_all_interpreter_context(nil, nil, nil, next_path)
672
+ def resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, owner_type, was_scoped, runtime_state) # rubocop:disable Metrics/ParameterLists
673
+ runtime_state.current_result_name = this_idx
674
+ runtime_state.current_result = response_list
816
675
  call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do
817
676
  # This will update `response_list` with the lazy
818
- after_lazy(inner_value, owner: inner_type, path: next_path, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, result_name: this_idx, result: response_list) do |inner_inner_value|
819
- continue_value = continue_value(next_path, inner_inner_value, owner_type, field, inner_type.non_null?, ast_node, this_idx, response_list)
677
+ after_lazy(inner_value, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, result_name: this_idx, result: response_list, runtime_state: runtime_state) do |inner_inner_value, runtime_state|
678
+ continue_value = continue_value(inner_inner_value, field, inner_type_non_null, ast_node, this_idx, response_list)
820
679
  if HALT != continue_value
821
- continue_field(next_path, continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list)
680
+ continue_field(continue_value, owner_type, field, inner_type, ast_node, response_list.graphql_selections, false, owner_object, arguments, this_idx, response_list, was_scoped, runtime_state)
822
681
  end
823
682
  end
824
683
  end
@@ -837,9 +696,7 @@ module GraphQL
837
696
  dir_defn = @schema_directives.fetch(dir_node.name)
838
697
  raw_dir_args = arguments(nil, dir_defn, dir_node)
839
698
  dir_args = continue_value(
840
- @context[:current_path], # path
841
699
  raw_dir_args, # value
842
- dir_defn, # parent_type
843
700
  nil, # field
844
701
  false, # is_non_null
845
702
  dir_node, # ast_node
@@ -869,36 +726,52 @@ module GraphQL
869
726
  true
870
727
  end
871
728
 
872
- def set_all_interpreter_context(object, field, arguments, path)
873
- if object
874
- @context[:current_object] = @interpreter_context[:current_object] = object
875
- end
876
- if field
877
- @context[:current_field] = @interpreter_context[:current_field] = field
878
- end
879
- if arguments
880
- @context[:current_arguments] = @interpreter_context[:current_arguments] = arguments
729
+ def get_current_runtime_state
730
+ current_state = Thread.current[:__graphql_runtime_info] ||= begin
731
+ per_query_state = {}
732
+ per_query_state.compare_by_identity
733
+ per_query_state
881
734
  end
882
- if path
883
- @context[:current_path] = @interpreter_context[:current_path] = path
735
+
736
+ current_state[@query] ||= CurrentState.new
737
+ end
738
+
739
+ def minimal_after_lazy(value, &block)
740
+ if lazy?(value)
741
+ GraphQL::Execution::Lazy.new do
742
+ result = @schema.sync_lazy(value)
743
+ # The returned result might also be lazy, so check it, too
744
+ minimal_after_lazy(result, &block)
745
+ end
746
+ else
747
+ yield(value)
884
748
  end
885
749
  end
886
750
 
887
751
  # @param obj [Object] Some user-returned value that may want to be batched
888
- # @param path [Array<String>]
889
752
  # @param field [GraphQL::Schema::Field]
890
753
  # @param eager [Boolean] Set to `true` for mutation root fields only
891
754
  # @param trace [Boolean] If `false`, don't wrap this with field tracing
892
755
  # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
893
- def after_lazy(lazy_obj, owner:, field:, path:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, trace: true, &block)
756
+ def after_lazy(lazy_obj, field:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, runtime_state:, trace: true, &block)
894
757
  if lazy?(lazy_obj)
895
- lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do
896
- set_all_interpreter_context(owner_object, field, arguments, path)
758
+ orig_result = result
759
+ was_authorized_by_scope_items = runtime_state.was_authorized_by_scope_items
760
+ lazy = GraphQL::Execution::Lazy.new(field: field) do
761
+ # This block might be called in a new fiber;
762
+ # In that case, this will initialize a new state
763
+ # to avoid conflicting with the parent fiber.
764
+ runtime_state = get_current_runtime_state
765
+ runtime_state.current_field = field
766
+ runtime_state.current_arguments = arguments
767
+ runtime_state.current_result_name = result_name
768
+ runtime_state.current_result = orig_result
769
+ runtime_state.was_authorized_by_scope_items = was_authorized_by_scope_items
897
770
  # Wrap the execution of _this_ method with tracing,
898
771
  # but don't wrap the continuation below
899
772
  inner_obj = begin
900
773
  if trace
901
- query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments, ast_node: ast_node}) do
774
+ @current_trace.execute_field_lazy(field: field, query: query, object: owner_object, arguments: arguments, ast_node: ast_node) do
902
775
  schema.sync_lazy(lazy_obj)
903
776
  end
904
777
  else
@@ -913,18 +786,24 @@ module GraphQL
913
786
  ex_err
914
787
  end
915
788
  end
916
- yield(inner_obj)
789
+ yield(inner_obj, runtime_state)
917
790
  end
918
791
 
919
792
  if eager
920
793
  lazy.value
921
794
  else
922
- set_result(result, result_name, lazy)
795
+ set_result(result, result_name, lazy, false, false) # is_non_null is irrelevant here
796
+ current_depth = 0
797
+ while result
798
+ current_depth += 1
799
+ result = result.graphql_parent
800
+ end
801
+ @lazies_at_depth[current_depth] << lazy
923
802
  lazy
924
803
  end
925
804
  else
926
- set_all_interpreter_context(owner_object, field, arguments, path)
927
- yield(lazy_obj)
805
+ # Don't need to reset state here because it _wasn't_ lazy.
806
+ yield(lazy_obj, runtime_state)
928
807
  end
929
808
  end
930
809
 
@@ -937,27 +816,25 @@ module GraphQL
937
816
  end
938
817
  end
939
818
 
940
- # Set this pair in the Query context, but also in the interpeter namespace,
941
- # for compatibility.
942
- def set_interpreter_context(key, value)
943
- @interpreter_context[key] = value
944
- @context[key] = value
945
- end
946
-
947
- def delete_interpreter_context(key)
948
- @interpreter_context.delete(key)
949
- @context.delete(key)
819
+ def delete_all_interpreter_context
820
+ per_query_state = Thread.current[:__graphql_runtime_info]
821
+ if per_query_state
822
+ per_query_state.delete(@query)
823
+ if per_query_state.size == 0
824
+ Thread.current[:__graphql_runtime_info] = nil
825
+ end
826
+ end
827
+ nil
950
828
  end
951
829
 
952
- def resolve_type(type, value, path)
953
- trace_payload = { context: context, type: type, object: value, path: path }
954
- resolved_type, resolved_value = query.trace("resolve_type", trace_payload) do
830
+ def resolve_type(type, value)
831
+ resolved_type, resolved_value = @current_trace.resolve_type(query: query, type: type, object: value) do
955
832
  query.resolve_type(type, value)
956
833
  end
957
834
 
958
835
  if lazy?(resolved_type)
959
836
  GraphQL::Execution::Lazy.new do
960
- query.trace("resolve_type_lazy", trace_payload) do
837
+ @current_trace.resolve_type_lazy(query: query, type: type, object: value) do
961
838
  schema.sync_lazy(resolved_type)
962
839
  end
963
840
  end
@@ -966,14 +843,13 @@ module GraphQL
966
843
  end
967
844
  end
968
845
 
969
- def authorized_new(type, value, context)
970
- type.authorized_new(value, context)
971
- end
972
-
973
846
  def lazy?(object)
974
- @lazy_cache.fetch(object.class) {
975
- @lazy_cache[object.class] = @schema.lazy?(object)
976
- }
847
+ obj_class = object.class
848
+ is_lazy = @lazy_cache[obj_class]
849
+ if is_lazy.nil?
850
+ is_lazy = @lazy_cache[obj_class] = @schema.lazy?(object)
851
+ end
852
+ is_lazy
977
853
  end
978
854
  end
979
855
  end