graphql 1.13.24 → 2.5.11

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