graphql 2.2.17 → 2.5.16

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.
Files changed (240) 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_generator.rb +46 -0
  4. data/lib/generators/graphql/orm_mutations_base.rb +1 -1
  5. data/lib/generators/graphql/templates/base_resolver.erb +2 -0
  6. data/lib/generators/graphql/templates/schema.erb +3 -0
  7. data/lib/generators/graphql/type_generator.rb +1 -1
  8. data/lib/graphql/analysis/analyzer.rb +90 -0
  9. data/lib/graphql/analysis/field_usage.rb +82 -0
  10. data/lib/graphql/analysis/max_query_complexity.rb +20 -0
  11. data/lib/graphql/analysis/max_query_depth.rb +20 -0
  12. data/lib/graphql/analysis/query_complexity.rb +263 -0
  13. data/lib/graphql/analysis/{ast/query_depth.rb → query_depth.rb} +23 -25
  14. data/lib/graphql/analysis/visitor.rb +280 -0
  15. data/lib/graphql/analysis.rb +95 -1
  16. data/lib/graphql/autoload.rb +38 -0
  17. data/lib/graphql/backtrace/table.rb +118 -55
  18. data/lib/graphql/backtrace.rb +1 -19
  19. data/lib/graphql/current.rb +57 -0
  20. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  21. data/lib/graphql/dashboard/installable.rb +22 -0
  22. data/lib/graphql/dashboard/limiters.rb +93 -0
  23. data/lib/graphql/dashboard/operation_store.rb +199 -0
  24. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  25. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  26. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  27. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  28. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  29. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  30. data/lib/graphql/dashboard/statics/icon.png +0 -0
  31. data/lib/graphql/dashboard/subscriptions.rb +96 -0
  32. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  33. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  34. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  35. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  36. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +23 -0
  37. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  38. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  39. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  40. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  41. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  42. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  43. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  44. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  45. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  46. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  47. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  48. data/lib/graphql/dashboard.rb +158 -0
  49. data/lib/graphql/dataloader/active_record_association_source.rb +84 -0
  50. data/lib/graphql/dataloader/active_record_source.rb +47 -0
  51. data/lib/graphql/dataloader/async_dataloader.rb +46 -19
  52. data/lib/graphql/dataloader/null_dataloader.rb +51 -10
  53. data/lib/graphql/dataloader/source.rb +20 -9
  54. data/lib/graphql/dataloader.rb +153 -45
  55. data/lib/graphql/date_encoding_error.rb +1 -1
  56. data/lib/graphql/dig.rb +2 -1
  57. data/lib/graphql/execution/interpreter/argument_value.rb +5 -1
  58. data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
  59. data/lib/graphql/execution/interpreter/resolve.rb +23 -25
  60. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +63 -5
  61. data/lib/graphql/execution/interpreter/runtime.rb +321 -222
  62. data/lib/graphql/execution/interpreter.rb +23 -30
  63. data/lib/graphql/execution/lookahead.rb +18 -11
  64. data/lib/graphql/execution/multiplex.rb +6 -5
  65. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  66. data/lib/graphql/introspection/directive_type.rb +1 -1
  67. data/lib/graphql/introspection/entry_points.rb +2 -2
  68. data/lib/graphql/introspection/field_type.rb +1 -1
  69. data/lib/graphql/introspection/schema_type.rb +6 -11
  70. data/lib/graphql/introspection/type_type.rb +5 -5
  71. data/lib/graphql/invalid_name_error.rb +1 -1
  72. data/lib/graphql/invalid_null_error.rb +20 -17
  73. data/lib/graphql/language/cache.rb +13 -0
  74. data/lib/graphql/language/comment.rb +18 -0
  75. data/lib/graphql/language/document_from_schema_definition.rb +64 -35
  76. data/lib/graphql/language/lexer.rb +72 -42
  77. data/lib/graphql/language/nodes.rb +93 -52
  78. data/lib/graphql/language/parser.rb +168 -61
  79. data/lib/graphql/language/printer.rb +31 -15
  80. data/lib/graphql/language/sanitized_printer.rb +1 -1
  81. data/lib/graphql/language.rb +61 -1
  82. data/lib/graphql/pagination/connection.rb +1 -1
  83. data/lib/graphql/query/context/scoped_context.rb +1 -1
  84. data/lib/graphql/query/context.rb +46 -47
  85. data/lib/graphql/query/null_context.rb +3 -5
  86. data/lib/graphql/query/partial.rb +179 -0
  87. data/lib/graphql/query/validation_pipeline.rb +2 -2
  88. data/lib/graphql/query/variable_validation_error.rb +1 -1
  89. data/lib/graphql/query.rb +123 -69
  90. data/lib/graphql/railtie.rb +7 -0
  91. data/lib/graphql/rubocop/graphql/base_cop.rb +1 -1
  92. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +144 -0
  93. data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
  94. data/lib/graphql/rubocop.rb +2 -0
  95. data/lib/graphql/schema/addition.rb +26 -13
  96. data/lib/graphql/schema/always_visible.rb +7 -2
  97. data/lib/graphql/schema/argument.rb +57 -8
  98. data/lib/graphql/schema/build_from_definition.rb +116 -49
  99. data/lib/graphql/schema/directive/flagged.rb +4 -2
  100. data/lib/graphql/schema/directive.rb +54 -2
  101. data/lib/graphql/schema/enum.rb +107 -24
  102. data/lib/graphql/schema/enum_value.rb +10 -2
  103. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  104. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  105. data/lib/graphql/schema/field.rb +134 -45
  106. data/lib/graphql/schema/field_extension.rb +1 -1
  107. data/lib/graphql/schema/has_single_input_argument.rb +6 -2
  108. data/lib/graphql/schema/input_object.rb +122 -64
  109. data/lib/graphql/schema/interface.rb +23 -5
  110. data/lib/graphql/schema/introspection_system.rb +6 -17
  111. data/lib/graphql/schema/late_bound_type.rb +4 -0
  112. data/lib/graphql/schema/list.rb +3 -3
  113. data/lib/graphql/schema/loader.rb +3 -2
  114. data/lib/graphql/schema/member/base_dsl_methods.rb +15 -0
  115. data/lib/graphql/schema/member/has_arguments.rb +44 -58
  116. data/lib/graphql/schema/member/has_dataloader.rb +62 -0
  117. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  118. data/lib/graphql/schema/member/has_directives.rb +4 -4
  119. data/lib/graphql/schema/member/has_fields.rb +26 -6
  120. data/lib/graphql/schema/member/has_interfaces.rb +6 -6
  121. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  122. data/lib/graphql/schema/member/has_validators.rb +1 -1
  123. data/lib/graphql/schema/member/relay_shortcuts.rb +1 -1
  124. data/lib/graphql/schema/member/type_system_helpers.rb +17 -4
  125. data/lib/graphql/schema/member.rb +1 -0
  126. data/lib/graphql/schema/mutation.rb +7 -0
  127. data/lib/graphql/schema/object.rb +25 -8
  128. data/lib/graphql/schema/printer.rb +1 -0
  129. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  130. data/lib/graphql/schema/relay_classic_mutation.rb +0 -1
  131. data/lib/graphql/schema/resolver.rb +29 -23
  132. data/lib/graphql/schema/scalar.rb +1 -6
  133. data/lib/graphql/schema/subscription.rb +52 -6
  134. data/lib/graphql/schema/timeout.rb +19 -2
  135. data/lib/graphql/schema/type_expression.rb +2 -2
  136. data/lib/graphql/schema/union.rb +1 -1
  137. data/lib/graphql/schema/validator/all_validator.rb +62 -0
  138. data/lib/graphql/schema/validator/required_validator.rb +92 -11
  139. data/lib/graphql/schema/validator.rb +3 -1
  140. data/lib/graphql/schema/visibility/migration.rb +188 -0
  141. data/lib/graphql/schema/visibility/profile.rb +445 -0
  142. data/lib/graphql/schema/visibility/visit.rb +190 -0
  143. data/lib/graphql/schema/visibility.rb +311 -0
  144. data/lib/graphql/schema/warden.rb +190 -20
  145. data/lib/graphql/schema.rb +695 -167
  146. data/lib/graphql/static_validation/all_rules.rb +2 -2
  147. data/lib/graphql/static_validation/base_visitor.rb +6 -5
  148. data/lib/graphql/static_validation/literal_validator.rb +4 -4
  149. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  150. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
  151. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +3 -2
  152. data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
  153. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
  154. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +12 -2
  155. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +47 -13
  156. data/lib/graphql/static_validation/rules/fields_will_merge.rb +88 -25
  157. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
  158. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  159. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +12 -2
  160. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  161. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  162. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  163. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  164. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  165. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +4 -4
  166. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +3 -3
  167. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  168. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +7 -3
  169. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  170. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  171. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
  172. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +11 -2
  173. data/lib/graphql/static_validation/validation_context.rb +18 -2
  174. data/lib/graphql/static_validation/validator.rb +6 -1
  175. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +5 -3
  176. data/lib/graphql/subscriptions/broadcast_analyzer.rb +11 -5
  177. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  178. data/lib/graphql/subscriptions/event.rb +13 -2
  179. data/lib/graphql/subscriptions/serialize.rb +1 -1
  180. data/lib/graphql/subscriptions.rb +7 -5
  181. data/lib/graphql/testing/helpers.rb +48 -16
  182. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  183. data/lib/graphql/testing.rb +1 -0
  184. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  185. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  186. data/lib/graphql/tracing/appoptics_trace.rb +5 -1
  187. data/lib/graphql/tracing/appoptics_tracing.rb +7 -0
  188. data/lib/graphql/tracing/appsignal_trace.rb +32 -59
  189. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  190. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  191. data/lib/graphql/tracing/data_dog_trace.rb +46 -162
  192. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  193. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  194. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  195. data/lib/graphql/tracing/detailed_trace.rb +141 -0
  196. data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
  197. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  198. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  199. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  200. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  201. data/lib/graphql/tracing/notifications_trace.rb +183 -37
  202. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  203. data/lib/graphql/tracing/null_trace.rb +9 -0
  204. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  205. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  206. data/lib/graphql/tracing/perfetto_trace.rb +818 -0
  207. data/lib/graphql/tracing/platform_tracing.rb +1 -1
  208. data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
  209. data/lib/graphql/tracing/prometheus_trace.rb +73 -73
  210. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  211. data/lib/graphql/tracing/scout_trace.rb +32 -58
  212. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  213. data/lib/graphql/tracing/sentry_trace.rb +64 -98
  214. data/lib/graphql/tracing/statsd_trace.rb +33 -45
  215. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  216. data/lib/graphql/tracing/trace.rb +111 -1
  217. data/lib/graphql/tracing.rb +31 -30
  218. data/lib/graphql/type_kinds.rb +2 -1
  219. data/lib/graphql/types/relay/connection_behaviors.rb +12 -2
  220. data/lib/graphql/types/relay/edge_behaviors.rb +11 -1
  221. data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
  222. data/lib/graphql/types.rb +18 -11
  223. data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
  224. data/lib/graphql/version.rb +1 -1
  225. data/lib/graphql.rb +64 -54
  226. metadata +197 -22
  227. data/lib/graphql/analysis/ast/analyzer.rb +0 -91
  228. data/lib/graphql/analysis/ast/field_usage.rb +0 -82
  229. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -22
  230. data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
  231. data/lib/graphql/analysis/ast/query_complexity.rb +0 -182
  232. data/lib/graphql/analysis/ast/visitor.rb +0 -276
  233. data/lib/graphql/analysis/ast.rb +0 -94
  234. data/lib/graphql/backtrace/inspect_result.rb +0 -50
  235. data/lib/graphql/backtrace/trace.rb +0 -93
  236. data/lib/graphql/backtrace/tracer.rb +0 -80
  237. data/lib/graphql/language/token.rb +0 -34
  238. data/lib/graphql/schema/invalid_type_error.rb +0 -7
  239. data/lib/graphql/schema/null_mask.rb +0 -11
  240. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
@@ -11,7 +11,6 @@ module GraphQL
11
11
  class Runtime
12
12
  class CurrentState
13
13
  def initialize
14
- @current_object = nil
15
14
  @current_field = nil
16
15
  @current_arguments = nil
17
16
  @current_result_name = nil
@@ -19,8 +18,12 @@ module GraphQL
19
18
  @was_authorized_by_scope_items = nil
20
19
  end
21
20
 
21
+ def current_object
22
+ @current_result.graphql_application_value
23
+ end
24
+
22
25
  attr_accessor :current_result, :current_result_name,
23
- :current_arguments, :current_field, :current_object, :was_authorized_by_scope_items
26
+ :current_arguments, :current_field, :was_authorized_by_scope_items
24
27
  end
25
28
 
26
29
  # @return [GraphQL::Query]
@@ -32,14 +35,13 @@ module GraphQL
32
35
  # @return [GraphQL::Query::Context]
33
36
  attr_reader :context
34
37
 
35
- def initialize(query:, lazies_at_depth:)
38
+ def initialize(query:)
36
39
  @query = query
37
40
  @current_trace = query.current_trace
38
41
  @dataloader = query.multiplex.dataloader
39
- @lazies_at_depth = lazies_at_depth
40
42
  @schema = query.schema
41
43
  @context = query.context
42
- @response = GraphQLResultHash.new(nil, nil, false)
44
+ @response = nil
43
45
  # Identify runtime directives by checking which of this schema's directives have overridden `def self.resolve`
44
46
  @runtime_directive_names = []
45
47
  noop_resolve_owner = GraphQL::Schema::Directive.singleton_class
@@ -50,93 +52,164 @@ module GraphQL
50
52
  end
51
53
  end
52
54
  # { Class => Boolean }
53
- @lazy_cache = {}
54
- @lazy_cache.compare_by_identity
55
+ @lazy_cache = {}.compare_by_identity
55
56
  end
56
57
 
57
58
  def final_result
58
- @response && @response.graphql_result_data
59
+ @response.respond_to?(:graphql_result_data) ? @response.graphql_result_data : @response
59
60
  end
60
61
 
61
62
  def inspect
62
63
  "#<#{self.class.name} response=#{@response.inspect}>"
63
64
  end
64
65
 
65
- def tap_or_each(obj_or_array)
66
- if obj_or_array.is_a?(Array)
67
- obj_or_array.each do |item|
68
- yield(item, true)
69
- end
70
- else
71
- yield(obj_or_array, false)
72
- end
73
- end
74
-
75
- # This _begins_ the execution. Some deferred work
76
- # might be stored up in lazies.
77
66
  # @return [void]
78
67
  def run_eager
79
- root_operation = query.selected_operation
80
- root_op_type = root_operation.operation_type || "query"
81
- root_type = schema.root_type_for_operation(root_op_type)
82
-
83
- st = get_current_runtime_state
84
- st.current_object = query.root_value
85
- st.current_result = @response
86
- runtime_object = root_type.wrap(query.root_value, context)
87
- runtime_object = schema.sync_lazy(runtime_object)
88
-
89
- if runtime_object.nil?
90
- # Root .authorized? returned false.
91
- @response = nil
68
+ root_type = query.root_type
69
+ case query
70
+ when GraphQL::Query
71
+ ast_node = query.selected_operation
72
+ selections = ast_node.selections
73
+ object = query.root_value
74
+ is_eager = ast_node.operation_type == "mutation"
75
+ base_path = nil
76
+ when GraphQL::Query::Partial
77
+ ast_node = query.ast_nodes.first
78
+ selections = query.ast_nodes.map(&:selections).inject(&:+)
79
+ object = query.object
80
+ is_eager = false
81
+ base_path = query.path
92
82
  else
93
- call_method_on_directives(:resolve, runtime_object, root_operation.directives) do # execute query level directives
94
- gathered_selections = gather_selections(runtime_object, root_type, root_operation.selections)
95
- # This is kind of a hack -- `gathered_selections` is an Array if any of the selections
96
- # require isolation during execution (because of runtime directives). In that case,
97
- # make a new, isolated result hash for writing the result into. (That isolated response
98
- # is eventually merged back into the main response)
99
- #
100
- # Otherwise, `gathered_selections` is a hash of selections which can be
101
- # directly evaluated and the results can be written right into the main response hash.
102
- tap_or_each(gathered_selections) do |selections, is_selection_array|
103
- if is_selection_array
104
- selection_response = GraphQLResultHash.new(nil, nil, false)
105
- final_response = @response
106
- else
107
- selection_response = @response
108
- final_response = nil
109
- end
110
-
111
- @dataloader.append_job {
112
- st = get_current_runtime_state
113
- st.current_object = query.root_value
114
- st.current_result_name = nil
115
- st.current_result = selection_response
116
- # This is a less-frequent case; use a fast check since it's often not there.
117
- if (directives = selections[:graphql_directives])
118
- selections.delete(:graphql_directives)
83
+ raise ArgumentError, "Unexpected Runnable, can't execute: #{query.class} (#{query.inspect})"
84
+ end
85
+ object = schema.sync_lazy(object) # TODO test query partial with lazy root object
86
+ runtime_state = get_current_runtime_state
87
+ case root_type.kind.name
88
+ when "OBJECT"
89
+ object_proxy = root_type.wrap(object, context)
90
+ object_proxy = schema.sync_lazy(object_proxy)
91
+ if object_proxy.nil?
92
+ @response = nil
93
+ else
94
+ @response = GraphQLResultHash.new(nil, root_type, object_proxy, nil, false, selections, is_eager, ast_node, nil, nil)
95
+ @response.base_path = base_path
96
+ runtime_state.current_result = @response
97
+ call_method_on_directives(:resolve, object, ast_node.directives) do
98
+ each_gathered_selections(@response) do |selections, is_selection_array, ordered_result_keys|
99
+ @response.ordered_result_keys ||= ordered_result_keys
100
+ if is_selection_array
101
+ selection_response = GraphQLResultHash.new(nil, root_type, object_proxy, nil, false, selections, is_eager, ast_node, nil, nil)
102
+ selection_response.ordered_result_keys = ordered_result_keys
103
+ final_response = @response
104
+ else
105
+ selection_response = @response
106
+ final_response = nil
119
107
  end
120
- call_method_on_directives(:resolve, runtime_object, directives) do
108
+
109
+ @dataloader.append_job {
121
110
  evaluate_selections(
122
- runtime_object,
123
- root_type,
124
- root_op_type == "mutation",
125
111
  selections,
126
112
  selection_response,
127
113
  final_response,
128
114
  nil,
129
- st,
130
115
  )
131
- end
132
- }
116
+ }
117
+ end
118
+ end
119
+ end
120
+ when "LIST"
121
+ inner_type = root_type.unwrap
122
+ case inner_type.kind.name
123
+ when "SCALAR", "ENUM"
124
+ result_name = ast_node.alias || ast_node.name
125
+ field_defn = query.field_definition
126
+ owner_type = field_defn.owner
127
+ selection_result = GraphQLResultHash.new(nil, owner_type, nil, nil, false, EmptyObjects::EMPTY_ARRAY, false, ast_node, nil, nil)
128
+ selection_result.base_path = base_path
129
+ selection_result.ordered_result_keys = [result_name]
130
+ runtime_state = get_current_runtime_state
131
+ runtime_state.current_result = selection_result
132
+ runtime_state.current_result_name = result_name
133
+ continue_value = continue_value(object, field_defn, false, ast_node, result_name, selection_result)
134
+ if HALT != continue_value
135
+ continue_field(continue_value, owner_type, field_defn, root_type, ast_node, nil, false, nil, nil, result_name, selection_result, false, runtime_state) # rubocop:disable Metrics/ParameterLists
136
+ end
137
+ @response = selection_result[result_name]
138
+ else
139
+ @response = GraphQLResultArray.new(nil, root_type, nil, nil, false, selections, false, ast_node, nil, nil)
140
+ @response.base_path = base_path
141
+ idx = nil
142
+ object.each do |inner_value|
143
+ idx ||= 0
144
+ this_idx = idx
145
+ idx += 1
146
+ @dataloader.append_job do
147
+ runtime_state.current_result_name = this_idx
148
+ runtime_state.current_result = @response
149
+ continue_field(
150
+ inner_value, root_type, nil, inner_type, nil, @response.graphql_selections, false, object_proxy,
151
+ nil, this_idx, @response, false, runtime_state
152
+ )
153
+ end
133
154
  end
134
155
  end
156
+ when "SCALAR", "ENUM"
157
+ result_name = ast_node.alias || ast_node.name
158
+ field_defn = query.field_definition
159
+ owner_type = field_defn.owner
160
+ selection_result = GraphQLResultHash.new(nil, owner_type, nil, nil, false, EmptyObjects::EMPTY_ARRAY, false, ast_node, nil, nil)
161
+ selection_result.ordered_result_keys = [result_name]
162
+ selection_result.base_path = base_path
163
+ runtime_state = get_current_runtime_state
164
+ runtime_state.current_result = selection_result
165
+ runtime_state.current_result_name = result_name
166
+ continue_value = continue_value(object, field_defn, false, ast_node, result_name, selection_result)
167
+ if HALT != continue_value
168
+ continue_field(continue_value, owner_type, field_defn, query.root_type, ast_node, nil, false, nil, nil, result_name, selection_result, false, runtime_state) # rubocop:disable Metrics/ParameterLists
169
+ end
170
+ @response = selection_result[result_name]
171
+ when "UNION", "INTERFACE"
172
+ resolved_type, _resolved_obj = resolve_type(root_type, object)
173
+ resolved_type = schema.sync_lazy(resolved_type)
174
+ object_proxy = resolved_type.wrap(object, context)
175
+ object_proxy = schema.sync_lazy(object_proxy)
176
+ @response = GraphQLResultHash.new(nil, resolved_type, object_proxy, nil, false, selections, false, query.ast_nodes.first, nil, nil)
177
+ @response.base_path = base_path
178
+ each_gathered_selections(@response) do |selections, is_selection_array, ordered_result_keys|
179
+ @response.ordered_result_keys ||= ordered_result_keys
180
+ if is_selection_array == true
181
+ raise "This isn't supported yet"
182
+ end
183
+
184
+ @dataloader.append_job {
185
+ evaluate_selections(
186
+ selections,
187
+ @response,
188
+ nil,
189
+ runtime_state,
190
+ )
191
+ }
192
+ end
193
+ else
194
+ raise "Invariant: unsupported type kind for partial execution: #{root_type.kind.inspect} (#{root_type})"
135
195
  end
136
196
  nil
137
197
  end
138
198
 
139
- def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = {})
199
+ def each_gathered_selections(response_hash)
200
+ ordered_result_keys = []
201
+ gathered_selections = gather_selections(response_hash.graphql_application_value, response_hash.graphql_result_type, response_hash.graphql_selections, nil, {}, ordered_result_keys)
202
+ ordered_result_keys.uniq!
203
+ if gathered_selections.is_a?(Array)
204
+ gathered_selections.each do |item|
205
+ yield(item, true, ordered_result_keys)
206
+ end
207
+ else
208
+ yield(gathered_selections, false, ordered_result_keys)
209
+ end
210
+ end
211
+
212
+ def gather_selections(owner_object, owner_type, selections, selections_to_run, selections_by_name, ordered_result_keys)
140
213
  selections.each do |node|
141
214
  # Skip gathering this if the directive says so
142
215
  if !directives_include?(node, owner_object, owner_type)
@@ -145,10 +218,11 @@ module GraphQL
145
218
 
146
219
  if node.is_a?(GraphQL::Language::Nodes::Field)
147
220
  response_key = node.alias || node.name
221
+ ordered_result_keys << response_key
148
222
  selections = selections_by_name[response_key]
149
223
  # if there was already a selection of this field,
150
224
  # use an array to hold all selections,
151
- # otherise, use the single node to represent the selection
225
+ # otherwise, use the single node to represent the selection
152
226
  if selections
153
227
  # This field was already selected at least once,
154
228
  # add this node to the list of selections
@@ -161,7 +235,7 @@ module GraphQL
161
235
  end
162
236
  else
163
237
  # This is an InlineFragment or a FragmentSpread
164
- if @runtime_directive_names.any? && node.directives.any? { |d| @runtime_directive_names.include?(d.name) }
238
+ if !@runtime_directive_names.empty? && node.directives.any? { |d| @runtime_directive_names.include?(d.name) }
165
239
  next_selections = {}
166
240
  next_selections[:graphql_directives] = node.directives
167
241
  if selections_to_run
@@ -178,26 +252,26 @@ module GraphQL
178
252
  case node
179
253
  when GraphQL::Language::Nodes::InlineFragment
180
254
  if node.type
181
- type_defn = schema.get_type(node.type.name, context)
255
+ type_defn = query.types.type(node.type.name)
182
256
 
183
- if query.warden.possible_types(type_defn).include?(owner_type)
184
- result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
257
+ if query.types.possible_types(type_defn).include?(owner_type)
258
+ result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections, ordered_result_keys)
185
259
  if !result.equal?(next_selections)
186
260
  selections_to_run = result
187
261
  end
188
262
  end
189
263
  else
190
264
  # it's an untyped fragment, definitely continue
191
- result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
265
+ result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections, ordered_result_keys)
192
266
  if !result.equal?(next_selections)
193
267
  selections_to_run = result
194
268
  end
195
269
  end
196
270
  when GraphQL::Language::Nodes::FragmentSpread
197
271
  fragment_def = query.fragments[node.name]
198
- type_defn = query.get_type(fragment_def.type.name)
199
- if query.warden.possible_types(type_defn).include?(owner_type)
200
- result = gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections)
272
+ type_defn = query.types.type(fragment_def.type.name)
273
+ if query.types.possible_types(type_defn).include?(owner_type)
274
+ result = gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections, ordered_result_keys)
201
275
  if !result.equal?(next_selections)
202
276
  selections_to_run = result
203
277
  end
@@ -213,36 +287,46 @@ module GraphQL
213
287
  NO_ARGS = GraphQL::EmptyObjects::EMPTY_HASH
214
288
 
215
289
  # @return [void]
216
- def evaluate_selections(owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result, parent_object, runtime_state) # rubocop:disable Metrics/ParameterLists
217
- finished_jobs = 0
218
- enqueued_jobs = gathered_selections.size
219
- gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
220
- @dataloader.append_job {
221
- runtime_state = get_current_runtime_state
222
- evaluate_selection(
223
- result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_selection, selections_result, parent_object, runtime_state
224
- )
225
- finished_jobs += 1
226
- if target_result && finished_jobs == enqueued_jobs
227
- selections_result.merge_into(target_result)
290
+ def evaluate_selections(gathered_selections, selections_result, target_result, runtime_state) # rubocop:disable Metrics/ParameterLists
291
+ runtime_state ||= get_current_runtime_state
292
+ runtime_state.current_result_name = nil
293
+ runtime_state.current_result = selections_result
294
+ # This is a less-frequent case; use a fast check since it's often not there.
295
+ if (directives = gathered_selections[:graphql_directives])
296
+ gathered_selections.delete(:graphql_directives)
297
+ end
298
+
299
+ call_method_on_directives(:resolve, selections_result.graphql_application_value, directives) do
300
+ gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
301
+ # Field resolution may pause the fiber,
302
+ # so it wouldn't get to the `Resolve` call that happens below.
303
+ # So instead trigger a run from this outer context.
304
+ if selections_result.graphql_is_eager
305
+ @dataloader.clear_cache
306
+ @dataloader.run_isolated {
307
+ evaluate_selection(
308
+ result_name, field_ast_nodes_or_ast_node, selections_result
309
+ )
310
+ @dataloader.clear_cache
311
+ }
312
+ else
313
+ @dataloader.append_job {
314
+ evaluate_selection(
315
+ result_name, field_ast_nodes_or_ast_node, selections_result
316
+ )
317
+ }
228
318
  end
229
- }
230
- # Field resolution may pause the fiber,
231
- # so it wouldn't get to the `Resolve` call that happens below.
232
- # So instead trigger a run from this outer context.
233
- if is_eager_selection
234
- @dataloader.clear_cache
235
- @dataloader.run
236
- @dataloader.clear_cache
237
319
  end
320
+ if target_result
321
+ selections_result.merge_into(target_result)
322
+ end
323
+ selections_result
238
324
  end
239
-
240
- selections_result
241
325
  end
242
326
 
243
327
  # @return [void]
244
- def evaluate_selection(result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_field, selections_result, parent_object, runtime_state) # rubocop:disable Metrics/ParameterLists
245
- return if dead_result?(selections_result)
328
+ def evaluate_selection(result_name, field_ast_nodes_or_ast_node, selections_result) # rubocop:disable Metrics/ParameterLists
329
+ return if selections_result.graphql_dead
246
330
  # As a performance optimization, the hash key will be a `Node` if
247
331
  # there's only one selection of the field. But if there are multiple
248
332
  # selections of the field, it will be an Array of nodes
@@ -254,40 +338,48 @@ module GraphQL
254
338
  ast_node = field_ast_nodes_or_ast_node
255
339
  end
256
340
  field_name = ast_node.name
257
- field_defn = query.warden.get_field(owner_type, field_name)
341
+ owner_type = selections_result.graphql_result_type
342
+ field_defn = query.types.field(owner_type, field_name)
258
343
 
259
344
  # Set this before calling `run_with_directives`, so that the directive can have the latest path
345
+ runtime_state = get_current_runtime_state
260
346
  runtime_state.current_field = field_defn
261
347
  runtime_state.current_result = selections_result
262
348
  runtime_state.current_result_name = result_name
263
349
 
350
+ owner_object = selections_result.graphql_application_value
264
351
  if field_defn.dynamic_introspection
265
352
  owner_object = field_defn.owner.wrap(owner_object, context)
266
353
  end
267
354
 
268
- return_type = field_defn.type
269
355
  if !field_defn.any_arguments?
270
356
  resolved_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
271
357
  if field_defn.extras.size == 0
272
358
  evaluate_selection_with_resolved_keyword_args(
273
- NO_ARGS, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type.non_null?, runtime_state
359
+ NO_ARGS, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_object, result_name, selections_result, runtime_state
274
360
  )
275
361
  else
276
- evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, runtime_state)
362
+ evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_object, result_name, selections_result, runtime_state)
277
363
  end
278
364
  else
279
365
  @query.arguments_cache.dataload_for(ast_node, field_defn, owner_object) do |resolved_arguments|
280
366
  runtime_state = get_current_runtime_state # This might be in a different fiber
281
- evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, runtime_state)
367
+ runtime_state.current_field = field_defn
368
+ runtime_state.current_arguments = resolved_arguments
369
+ runtime_state.current_result_name = result_name
370
+ runtime_state.current_result = selections_result
371
+ evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_object, result_name, selections_result, runtime_state)
282
372
  end
283
373
  end
284
374
  end
285
375
 
286
- def evaluate_selection_with_args(arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selection_result, parent_object, return_type, runtime_state) # rubocop:disable Metrics/ParameterLists
376
+ def evaluate_selection_with_args(arguments, field_defn, ast_node, field_ast_nodes, object, result_name, selection_result, runtime_state) # rubocop:disable Metrics/ParameterLists
287
377
  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|
288
- return_type_non_null = return_type.non_null?
289
378
  if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
290
- continue_value(resolved_arguments, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result)
379
+ next if selection_result.collect_result(result_name, resolved_arguments)
380
+
381
+ return_type_non_null = field_defn.type.non_null?
382
+ continue_value(resolved_arguments, field_defn, return_type_non_null, ast_node, result_name, selection_result)
291
383
  next
292
384
  end
293
385
 
@@ -325,24 +417,24 @@ module GraphQL
325
417
  # to the keyword args hash _before_ freezing everything.
326
418
  extra_args[:argument_details] = :__arguments_add_self
327
419
  when :parent
328
- extra_args[:parent] = parent_object
420
+ parent_result = selection_result.graphql_parent
421
+ extra_args[:parent] = parent_result&.graphql_application_value&.object
329
422
  else
330
423
  extra_args[extra] = field_defn.fetch_extra(extra, context)
331
424
  end
332
425
  end
333
- if extra_args.any?
426
+ if !extra_args.empty?
334
427
  resolved_arguments = resolved_arguments.merge_extras(extra_args)
335
428
  end
336
429
  resolved_arguments.keyword_arguments
337
430
  end
338
431
 
339
- evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selection_result, parent_object, return_type, return_type_non_null, runtime_state)
432
+ evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, object, result_name, selection_result, runtime_state)
340
433
  end
341
434
  end
342
435
 
343
- def evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selection_result, parent_object, return_type, return_type_non_null, runtime_state) # rubocop:disable Metrics/ParameterLists
436
+ 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
344
437
  runtime_state.current_field = field_defn
345
- runtime_state.current_object = object
346
438
  runtime_state.current_arguments = resolved_arguments
347
439
  runtime_state.current_result_name = result_name
348
440
  runtime_state.current_result = selection_result
@@ -359,18 +451,18 @@ module GraphQL
359
451
  }
360
452
  end
361
453
 
362
- field_result = call_method_on_directives(:resolve, object, directives) do
363
- if directives.any?
454
+ call_method_on_directives(:resolve, object, directives) do
455
+ if !directives.empty?
364
456
  # This might be executed in a different context; reset this info
365
457
  runtime_state = get_current_runtime_state
366
458
  runtime_state.current_field = field_defn
367
- runtime_state.current_object = object
368
459
  runtime_state.current_arguments = resolved_arguments
369
460
  runtime_state.current_result_name = result_name
370
461
  runtime_state.current_result = selection_result
371
462
  end
372
463
  # Actually call the field resolver and capture the result
373
464
  app_result = begin
465
+ @current_trace.begin_execute_field(field_defn, object, kwarg_arguments, query)
374
466
  @current_trace.execute_field(field: field_defn, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments) do
375
467
  field_defn.resolve(object, kwarg_arguments, context)
376
468
  end
@@ -383,34 +475,32 @@ module GraphQL
383
475
  ex_err
384
476
  end
385
477
  end
478
+ @current_trace.end_execute_field(field_defn, object, kwarg_arguments, query, app_result)
386
479
  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|
387
- continue_value = continue_value(inner_result, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result)
480
+ next if selection_result.collect_result(result_name, inner_result)
481
+
482
+ owner_type = selection_result.graphql_result_type
483
+ return_type = field_defn.type
484
+ continue_value = continue_value(inner_result, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
388
485
  if HALT != continue_value
389
486
  was_scoped = runtime_state.was_authorized_by_scope_items
390
487
  runtime_state.was_authorized_by_scope_items = nil
391
488
  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)
489
+ else
490
+ nil
392
491
  end
393
492
  end
394
493
  end
395
-
396
494
  # If this field is a root mutation field, immediately resolve
397
495
  # all of its child fields before moving on to the next root mutation field.
398
496
  # (Subselections of this mutation will still be resolved level-by-level.)
399
- if is_eager_field
400
- Interpreter::Resolve.resolve_all([field_result], @dataloader)
401
- else
402
- # Return this from `after_lazy` because it might be another lazy that needs to be resolved
403
- field_result
497
+ if selection_result.graphql_is_eager
498
+ @dataloader.run
404
499
  end
405
500
  end
406
501
 
407
-
408
- def dead_result?(selection_result)
409
- selection_result.graphql_dead # || ((parent = selection_result.graphql_parent) && parent.graphql_dead)
410
- end
411
-
412
502
  def set_result(selection_result, result_name, value, is_child_result, is_non_null)
413
- if !dead_result?(selection_result)
503
+ if !selection_result.graphql_dead
414
504
  if value.nil? && is_non_null
415
505
  # This is an invalid nil that should be propagated
416
506
  # One caller of this method passes a block,
@@ -463,14 +553,17 @@ module GraphQL
463
553
  path
464
554
  end
465
555
 
466
- HALT = Object.new
467
- def continue_value(value, parent_type, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
556
+ HALT = Object.new.freeze
557
+ def continue_value(value, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
468
558
  case value
469
559
  when nil
470
560
  if is_non_null
471
561
  set_result(selection_result, result_name, nil, false, is_non_null) do
562
+ # When this comes from a list item, use the parent object:
563
+ is_from_array = selection_result.is_a?(GraphQLResultArray)
564
+ parent_type = is_from_array ? selection_result.graphql_parent.graphql_result_type : selection_result.graphql_result_type
472
565
  # This block is called if `result_name` is not dead. (Maybe a previous invalid nil caused it be marked dead.)
473
- err = parent_type::InvalidNullError.new(parent_type, field, value)
566
+ err = parent_type::InvalidNullError.new(parent_type, field, ast_node, is_from_array: is_from_array)
474
567
  schema.type_error(err, context)
475
568
  end
476
569
  else
@@ -482,7 +575,7 @@ module GraphQL
482
575
  # to avoid the overhead of checking three different classes
483
576
  # every time.
484
577
  if value.is_a?(GraphQL::ExecutionError)
485
- if selection_result.nil? || !dead_result?(selection_result)
578
+ if selection_result.nil? || !selection_result.graphql_dead
486
579
  value.path ||= current_path
487
580
  value.ast_node ||= ast_node
488
581
  context.errors << value
@@ -500,7 +593,7 @@ module GraphQL
500
593
  rescue GraphQL::ExecutionError => err
501
594
  err
502
595
  end
503
- continue_value(next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
596
+ continue_value(next_value, field, is_non_null, ast_node, result_name, selection_result)
504
597
  elsif value.is_a?(GraphQL::UnauthorizedError)
505
598
  # this hook might raise & crash, or it might return
506
599
  # a replacement value
@@ -509,7 +602,7 @@ module GraphQL
509
602
  rescue GraphQL::ExecutionError => err
510
603
  err
511
604
  end
512
- continue_value(next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
605
+ continue_value(next_value, field, is_non_null, ast_node, result_name, selection_result)
513
606
  elsif GraphQL::Execution::SKIP == value
514
607
  # It's possible a lazy was already written here
515
608
  case selection_result
@@ -530,9 +623,9 @@ module GraphQL
530
623
  end
531
624
  when Array
532
625
  # It's an array full of execution errors; add them all.
533
- if value.any? && value.all?(GraphQL::ExecutionError)
626
+ if !value.empty? && value.all?(GraphQL::ExecutionError)
534
627
  list_type_at_all = (field && (field.type.list?))
535
- if selection_result.nil? || !dead_result?(selection_result)
628
+ if selection_result.nil? || !selection_result.graphql_dead
536
629
  value.each_with_index do |error, index|
537
630
  error.ast_node ||= ast_node
538
631
  error.path ||= current_path + (list_type_at_all ? [index] : [])
@@ -578,13 +671,29 @@ module GraphQL
578
671
  when "SCALAR", "ENUM"
579
672
  r = begin
580
673
  current_type.coerce_result(value, context)
674
+ rescue GraphQL::ExecutionError => ex_err
675
+ return continue_value(ex_err, field, is_non_null, ast_node, result_name, selection_result)
581
676
  rescue StandardError => err
582
- schema.handle_or_reraise(context, err)
677
+ begin
678
+ query.handle_or_reraise(err)
679
+ rescue GraphQL::ExecutionError => ex_err
680
+ return continue_value(ex_err, field, is_non_null, ast_node, result_name, selection_result)
681
+ end
583
682
  end
584
683
  set_result(selection_result, result_name, r, false, is_non_null)
585
684
  r
586
685
  when "UNION", "INTERFACE"
587
- resolved_type_or_lazy = resolve_type(current_type, value)
686
+ resolved_type_or_lazy = begin
687
+ resolve_type(current_type, value)
688
+ rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => ex_err
689
+ return continue_value(ex_err, field, is_non_null, ast_node, result_name, selection_result)
690
+ rescue StandardError => err
691
+ begin
692
+ query.handle_or_reraise(err)
693
+ rescue GraphQL::ExecutionError => ex_err
694
+ return continue_value(ex_err, field, is_non_null, ast_node, result_name, selection_result)
695
+ end
696
+ end
588
697
  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|
589
698
  if resolved_type_result.is_a?(Array) && resolved_type_result.length == 2
590
699
  resolved_type, resolved_value = resolved_type_result
@@ -593,7 +702,7 @@ module GraphQL
593
702
  resolved_value = value
594
703
  end
595
704
 
596
- possible_types = query.possible_types(current_type)
705
+ possible_types = query.types.possible_types(current_type)
597
706
  if !possible_types.include?(resolved_type)
598
707
  parent_type = field.owner_type
599
708
  err_class = current_type::UnresolvedTypeError
@@ -612,50 +721,27 @@ module GraphQL
612
721
  err
613
722
  end
614
723
  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|
615
- continue_value = continue_value(inner_object, owner_type, field, is_non_null, ast_node, result_name, selection_result)
724
+ continue_value = continue_value(inner_object, field, is_non_null, ast_node, result_name, selection_result)
616
725
  if HALT != continue_value
617
- response_hash = GraphQLResultHash.new(result_name, selection_result, is_non_null)
726
+ response_hash = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, next_selections, false, ast_node, arguments, field)
618
727
  set_result(selection_result, result_name, response_hash, true, is_non_null)
619
-
620
- gathered_selections = gather_selections(continue_value, current_type, next_selections)
621
- # There are two possibilities for `gathered_selections`:
622
- # 1. All selections of this object should be evaluated together (there are no runtime directives modifying execution).
623
- # This case is handled below, and the result can be written right into the main `response_hash` above.
624
- # In this case, `gathered_selections` is a hash of selections.
625
- # 2. Some selections of this object have runtime directives that may or may not modify execution.
626
- # That part of the selection is evaluated in an isolated way, writing into a sub-response object which is
627
- # eventually merged into the final response. In this case, `gathered_selections` is an array of things to run in isolation.
628
- # (Technically, it's possible that one of those entries _doesn't_ require isolation.)
629
- tap_or_each(gathered_selections) do |selections, is_selection_array|
728
+ each_gathered_selections(response_hash) do |selections, is_selection_array, ordered_result_keys|
729
+ response_hash.ordered_result_keys ||= ordered_result_keys
630
730
  if is_selection_array
631
- this_result = GraphQLResultHash.new(result_name, selection_result, is_non_null)
731
+ this_result = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, selections, false, ast_node, arguments, field)
732
+ this_result.ordered_result_keys = ordered_result_keys
632
733
  final_result = response_hash
633
734
  else
634
735
  this_result = response_hash
635
736
  final_result = nil
636
737
  end
637
- # reset this mutable state
638
- # Unset `result_name` here because it's already included in the new response hash
639
- runtime_state.current_object = continue_value
640
- runtime_state.current_result_name = nil
641
- runtime_state.current_result = this_result
642
- # This is a less-frequent case; use a fast check since it's often not there.
643
- if (directives = selections[:graphql_directives])
644
- selections.delete(:graphql_directives)
645
- end
646
- call_method_on_directives(:resolve, continue_value, directives) do
647
- evaluate_selections(
648
- continue_value,
649
- current_type,
650
- false,
651
- selections,
652
- this_result,
653
- final_result,
654
- owner_object.object,
655
- runtime_state,
656
- )
657
- this_result
658
- end
738
+
739
+ evaluate_selections(
740
+ selections,
741
+ this_result,
742
+ final_result,
743
+ runtime_state,
744
+ )
659
745
  end
660
746
  end
661
747
  end
@@ -664,35 +750,43 @@ module GraphQL
664
750
  # This is true for objects, unions, and interfaces
665
751
  use_dataloader_job = !inner_type.unwrap.kind.input?
666
752
  inner_type_non_null = inner_type.non_null?
667
- response_list = GraphQLResultArray.new(result_name, selection_result, is_non_null)
753
+ response_list = GraphQLResultArray.new(result_name, current_type, owner_object, selection_result, is_non_null, next_selections, false, ast_node, arguments, field)
668
754
  set_result(selection_result, result_name, response_list, true, is_non_null)
669
755
  idx = nil
670
756
  list_value = begin
671
- value.each do |inner_value|
672
- idx ||= 0
673
- this_idx = idx
674
- idx += 1
675
- if use_dataloader_job
676
- @dataloader.append_job do
677
- resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type, was_scoped, runtime_state)
757
+ begin
758
+ value.each do |inner_value|
759
+ idx ||= 0
760
+ this_idx = idx
761
+ idx += 1
762
+ if use_dataloader_job
763
+ @dataloader.append_job do
764
+ 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)
765
+ end
766
+ else
767
+ 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)
678
768
  end
679
- else
680
- resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type, was_scoped, runtime_state)
681
769
  end
682
- end
683
770
 
684
- response_list
685
- rescue NoMethodError => err
686
- # Ruby 2.2 doesn't have NoMethodError#receiver, can't check that one in this case. (It's been EOL since 2017.)
687
- if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true)
688
- # This happens when the GraphQL schema doesn't match the implementation. Help the dev debug.
689
- raise ListResultFailedError.new(value: value, field: field, path: current_path)
690
- else
691
- # This was some other NoMethodError -- let it bubble to reveal the real error.
692
- raise
771
+ response_list
772
+ rescue NoMethodError => err
773
+ # Ruby 2.2 doesn't have NoMethodError#receiver, can't check that one in this case. (It's been EOL since 2017.)
774
+ if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true)
775
+ # This happens when the GraphQL schema doesn't match the implementation. Help the dev debug.
776
+ raise ListResultFailedError.new(value: value, field: field, path: current_path)
777
+ else
778
+ # This was some other NoMethodError -- let it bubble to reveal the real error.
779
+ raise
780
+ end
781
+ rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => ex_err
782
+ ex_err
783
+ rescue StandardError => err
784
+ begin
785
+ query.handle_or_reraise(err)
786
+ rescue GraphQL::ExecutionError => ex_err
787
+ ex_err
788
+ end
693
789
  end
694
- rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => ex_err
695
- ex_err
696
790
  rescue StandardError => err
697
791
  begin
698
792
  query.handle_or_reraise(err)
@@ -702,21 +796,21 @@ module GraphQL
702
796
  end
703
797
  # Detect whether this error came while calling `.each` (before `idx` is set) or while running list *items* (after `idx` is set)
704
798
  error_is_non_null = idx.nil? ? is_non_null : inner_type.non_null?
705
- continue_value(list_value, owner_type, field, error_is_non_null, ast_node, result_name, selection_result)
799
+ continue_value(list_value, field, error_is_non_null, ast_node, result_name, selection_result)
706
800
  else
707
801
  raise "Invariant: Unhandled type kind #{current_type.kind} (#{current_type})"
708
802
  end
709
803
  end
710
804
 
711
- def resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type, was_scoped, runtime_state) # rubocop:disable Metrics/ParameterLists
805
+ 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
712
806
  runtime_state.current_result_name = this_idx
713
807
  runtime_state.current_result = response_list
714
808
  call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do
715
809
  # This will update `response_list` with the lazy
716
810
  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|
717
- continue_value = continue_value(inner_inner_value, owner_type, field, inner_type_non_null, ast_node, this_idx, response_list)
811
+ continue_value = continue_value(inner_inner_value, field, inner_type_non_null, ast_node, this_idx, response_list)
718
812
  if HALT != continue_value
719
- continue_field(continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list, was_scoped, runtime_state)
813
+ 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)
720
814
  end
721
815
  end
722
816
  end
@@ -734,9 +828,15 @@ module GraphQL
734
828
  else
735
829
  dir_defn = @schema_directives.fetch(dir_node.name)
736
830
  raw_dir_args = arguments(nil, dir_defn, dir_node)
831
+ if !raw_dir_args.is_a?(GraphQL::ExecutionError)
832
+ begin
833
+ dir_defn.validate!(raw_dir_args, context)
834
+ rescue GraphQL::ExecutionError => err
835
+ raw_dir_args = err
836
+ end
837
+ end
737
838
  dir_args = continue_value(
738
839
  raw_dir_args, # value
739
- dir_defn, # parent_type
740
840
  nil, # field
741
841
  false, # is_non_null
742
842
  dir_node, # ast_node
@@ -767,12 +867,7 @@ module GraphQL
767
867
  end
768
868
 
769
869
  def get_current_runtime_state
770
- current_state = Thread.current[:__graphql_runtime_info] ||= begin
771
- per_query_state = {}
772
- per_query_state.compare_by_identity
773
- per_query_state
774
- end
775
-
870
+ current_state = Fiber[:__graphql_runtime_info] ||= {}.compare_by_identity
776
871
  current_state[@query] ||= CurrentState.new
777
872
  end
778
873
 
@@ -795,23 +890,23 @@ module GraphQL
795
890
  # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
796
891
  def after_lazy(lazy_obj, field:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, runtime_state:, trace: true, &block)
797
892
  if lazy?(lazy_obj)
798
- orig_result = result
799
893
  was_authorized_by_scope_items = runtime_state.was_authorized_by_scope_items
800
894
  lazy = GraphQL::Execution::Lazy.new(field: field) do
801
895
  # This block might be called in a new fiber;
802
896
  # In that case, this will initialize a new state
803
897
  # to avoid conflicting with the parent fiber.
804
898
  runtime_state = get_current_runtime_state
805
- runtime_state.current_object = owner_object
806
899
  runtime_state.current_field = field
807
900
  runtime_state.current_arguments = arguments
808
901
  runtime_state.current_result_name = result_name
809
- runtime_state.current_result = orig_result
902
+ runtime_state.current_result = result
810
903
  runtime_state.was_authorized_by_scope_items = was_authorized_by_scope_items
811
904
  # Wrap the execution of _this_ method with tracing,
812
905
  # but don't wrap the continuation below
906
+ sync_result = nil
813
907
  inner_obj = begin
814
- if trace
908
+ sync_result = if trace
909
+ @current_trace.begin_execute_field(field, owner_object, arguments, query)
815
910
  @current_trace.execute_field_lazy(field: field, query: query, object: owner_object, arguments: arguments, ast_node: ast_node) do
816
911
  schema.sync_lazy(lazy_obj)
817
912
  end
@@ -826,6 +921,10 @@ module GraphQL
826
921
  rescue GraphQL::ExecutionError => ex_err
827
922
  ex_err
828
923
  end
924
+ ensure
925
+ if trace
926
+ @current_trace.end_execute_field(field, owner_object, arguments, query, sync_result)
927
+ end
829
928
  end
830
929
  yield(inner_obj, runtime_state)
831
930
  end
@@ -834,12 +933,7 @@ module GraphQL
834
933
  lazy.value
835
934
  else
836
935
  set_result(result, result_name, lazy, false, false) # is_non_null is irrelevant here
837
- current_depth = 0
838
- while result
839
- current_depth += 1
840
- result = result.graphql_parent
841
- end
842
- @lazies_at_depth[current_depth] << lazy
936
+ @dataloader.lazy_at_depth(result.depth, lazy)
843
937
  lazy
844
938
  end
845
939
  else
@@ -858,25 +952,30 @@ module GraphQL
858
952
  end
859
953
 
860
954
  def delete_all_interpreter_context
861
- per_query_state = Thread.current[:__graphql_runtime_info]
955
+ per_query_state = Fiber[:__graphql_runtime_info]
862
956
  if per_query_state
863
957
  per_query_state.delete(@query)
864
958
  if per_query_state.size == 0
865
- Thread.current[:__graphql_runtime_info] = nil
959
+ Fiber[:__graphql_runtime_info] = nil
866
960
  end
867
961
  end
868
962
  nil
869
963
  end
870
964
 
871
965
  def resolve_type(type, value)
966
+ @current_trace.begin_resolve_type(type, value, context)
872
967
  resolved_type, resolved_value = @current_trace.resolve_type(query: query, type: type, object: value) do
873
968
  query.resolve_type(type, value)
874
969
  end
970
+ @current_trace.end_resolve_type(type, value, context, resolved_type)
875
971
 
876
972
  if lazy?(resolved_type)
877
973
  GraphQL::Execution::Lazy.new do
974
+ @current_trace.begin_resolve_type(type, value, context)
878
975
  @current_trace.resolve_type_lazy(query: query, type: type, object: value) do
879
- schema.sync_lazy(resolved_type)
976
+ rt = schema.sync_lazy(resolved_type)
977
+ @current_trace.end_resolve_type(type, value, context, rt)
978
+ rt
880
979
  end
881
980
  end
882
981
  else