graphql 1.10.2 → 2.0.21

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

Potentially problematic release.


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

Files changed (402) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/core.rb +21 -10
  3. data/lib/generators/graphql/enum_generator.rb +4 -10
  4. data/lib/generators/graphql/field_extractor.rb +31 -0
  5. data/lib/generators/graphql/input_generator.rb +50 -0
  6. data/lib/generators/graphql/install/mutation_root_generator.rb +34 -0
  7. data/lib/generators/graphql/{templates → install/templates}/base_mutation.erb +2 -0
  8. data/lib/generators/graphql/{templates → install/templates}/mutation_type.erb +2 -0
  9. data/lib/generators/graphql/install_generator.rb +45 -8
  10. data/lib/generators/graphql/interface_generator.rb +7 -7
  11. data/lib/generators/graphql/loader_generator.rb +1 -0
  12. data/lib/generators/graphql/mutation_create_generator.rb +22 -0
  13. data/lib/generators/graphql/mutation_delete_generator.rb +22 -0
  14. data/lib/generators/graphql/mutation_generator.rb +6 -30
  15. data/lib/generators/graphql/mutation_update_generator.rb +22 -0
  16. data/lib/generators/graphql/object_generator.rb +28 -12
  17. data/lib/generators/graphql/orm_mutations_base.rb +40 -0
  18. data/lib/generators/graphql/relay.rb +49 -0
  19. data/lib/generators/graphql/relay_generator.rb +21 -0
  20. data/lib/generators/graphql/scalar_generator.rb +4 -2
  21. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  22. data/lib/generators/graphql/templates/base_connection.erb +8 -0
  23. data/lib/generators/graphql/templates/base_edge.erb +8 -0
  24. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  25. data/lib/generators/graphql/templates/base_field.erb +2 -0
  26. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  27. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  28. data/lib/generators/graphql/templates/base_object.erb +2 -0
  29. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  30. data/lib/generators/graphql/templates/base_union.erb +2 -0
  31. data/lib/generators/graphql/templates/enum.erb +7 -1
  32. data/lib/generators/graphql/templates/graphql_controller.erb +16 -12
  33. data/lib/generators/graphql/templates/input.erb +9 -0
  34. data/lib/generators/graphql/templates/interface.erb +6 -2
  35. data/lib/generators/graphql/templates/loader.erb +2 -0
  36. data/lib/generators/graphql/templates/mutation.erb +3 -1
  37. data/lib/generators/graphql/templates/mutation_create.erb +20 -0
  38. data/lib/generators/graphql/templates/mutation_delete.erb +20 -0
  39. data/lib/generators/graphql/templates/mutation_update.erb +21 -0
  40. data/lib/generators/graphql/templates/node_type.erb +9 -0
  41. data/lib/generators/graphql/templates/object.erb +7 -3
  42. data/lib/generators/graphql/templates/query_type.erb +3 -3
  43. data/lib/generators/graphql/templates/scalar.erb +5 -1
  44. data/lib/generators/graphql/templates/schema.erb +24 -33
  45. data/lib/generators/graphql/templates/union.erb +6 -2
  46. data/lib/generators/graphql/type_generator.rb +47 -10
  47. data/lib/generators/graphql/union_generator.rb +5 -5
  48. data/lib/graphql/analysis/ast/field_usage.rb +30 -1
  49. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -1
  50. data/lib/graphql/analysis/ast/query_complexity.rb +125 -117
  51. data/lib/graphql/analysis/ast/query_depth.rb +0 -1
  52. data/lib/graphql/analysis/ast/visitor.rb +52 -36
  53. data/lib/graphql/analysis/ast.rb +7 -8
  54. data/lib/graphql/analysis.rb +0 -7
  55. data/lib/graphql/backtrace/inspect_result.rb +0 -1
  56. data/lib/graphql/backtrace/table.rb +31 -18
  57. data/lib/graphql/backtrace/trace.rb +96 -0
  58. data/lib/graphql/backtrace/traced_error.rb +0 -1
  59. data/lib/graphql/backtrace/tracer.rb +39 -9
  60. data/lib/graphql/backtrace.rb +26 -18
  61. data/lib/graphql/dataloader/null_dataloader.rb +24 -0
  62. data/lib/graphql/dataloader/request.rb +19 -0
  63. data/lib/graphql/dataloader/request_all.rb +19 -0
  64. data/lib/graphql/dataloader/source.rb +164 -0
  65. data/lib/graphql/dataloader.rb +311 -0
  66. data/lib/graphql/date_encoding_error.rb +16 -0
  67. data/lib/graphql/deprecation.rb +9 -0
  68. data/lib/graphql/dig.rb +1 -1
  69. data/lib/graphql/execution/errors.rb +77 -44
  70. data/lib/graphql/execution/interpreter/argument_value.rb +28 -0
  71. data/lib/graphql/execution/interpreter/arguments.rb +88 -0
  72. data/lib/graphql/execution/interpreter/arguments_cache.rb +104 -0
  73. data/lib/graphql/execution/interpreter/handles_raw_value.rb +18 -0
  74. data/lib/graphql/execution/interpreter/resolve.rb +62 -24
  75. data/lib/graphql/execution/interpreter/runtime.rb +826 -464
  76. data/lib/graphql/execution/interpreter.rb +206 -68
  77. data/lib/graphql/execution/lazy.rb +11 -21
  78. data/lib/graphql/execution/lookahead.rb +55 -136
  79. data/lib/graphql/execution/multiplex.rb +6 -162
  80. data/lib/graphql/execution.rb +11 -4
  81. data/lib/graphql/filter.rb +7 -2
  82. data/lib/graphql/integer_decoding_error.rb +17 -0
  83. data/lib/graphql/integer_encoding_error.rb +18 -2
  84. data/lib/graphql/introspection/directive_location_enum.rb +2 -2
  85. data/lib/graphql/introspection/directive_type.rb +11 -5
  86. data/lib/graphql/introspection/dynamic_fields.rb +3 -8
  87. data/lib/graphql/introspection/entry_points.rb +4 -17
  88. data/lib/graphql/introspection/enum_value_type.rb +2 -2
  89. data/lib/graphql/introspection/field_type.rb +9 -5
  90. data/lib/graphql/introspection/input_value_type.rb +15 -3
  91. data/lib/graphql/introspection/introspection_query.rb +6 -92
  92. data/lib/graphql/introspection/schema_type.rb +11 -6
  93. data/lib/graphql/introspection/type_type.rb +31 -14
  94. data/lib/graphql/introspection.rb +100 -0
  95. data/lib/graphql/invalid_null_error.rb +18 -0
  96. data/lib/graphql/language/block_string.rb +20 -5
  97. data/lib/graphql/language/cache.rb +37 -0
  98. data/lib/graphql/language/document_from_schema_definition.rb +96 -44
  99. data/lib/graphql/language/lexer.rb +216 -1462
  100. data/lib/graphql/language/nodes.rb +126 -129
  101. data/lib/graphql/language/parser.rb +997 -933
  102. data/lib/graphql/language/parser.y +148 -118
  103. data/lib/graphql/language/printer.rb +48 -23
  104. data/lib/graphql/language/sanitized_printer.rb +222 -0
  105. data/lib/graphql/language/token.rb +0 -4
  106. data/lib/graphql/language/visitor.rb +192 -84
  107. data/lib/graphql/language.rb +2 -0
  108. data/lib/graphql/name_validator.rb +2 -7
  109. data/lib/graphql/pagination/active_record_relation_connection.rb +45 -3
  110. data/lib/graphql/pagination/array_connection.rb +6 -4
  111. data/lib/graphql/pagination/connection.rb +105 -23
  112. data/lib/graphql/pagination/connections.rb +62 -35
  113. data/lib/graphql/pagination/relation_connection.rb +88 -36
  114. data/lib/graphql/parse_error.rb +0 -1
  115. data/lib/graphql/query/context.rb +203 -198
  116. data/lib/graphql/query/fingerprint.rb +26 -0
  117. data/lib/graphql/query/input_validation_result.rb +33 -7
  118. data/lib/graphql/query/null_context.rb +22 -9
  119. data/lib/graphql/query/validation_pipeline.rb +16 -38
  120. data/lib/graphql/query/variable_validation_error.rb +3 -3
  121. data/lib/graphql/query/variables.rb +36 -12
  122. data/lib/graphql/query.rb +92 -44
  123. data/lib/graphql/railtie.rb +6 -102
  124. data/lib/graphql/rake_task/validate.rb +1 -1
  125. data/lib/graphql/rake_task.rb +41 -10
  126. data/lib/graphql/relay/range_add.rb +17 -10
  127. data/lib/graphql/relay.rb +0 -15
  128. data/lib/graphql/rubocop/graphql/base_cop.rb +36 -0
  129. data/lib/graphql/rubocop/graphql/default_null_true.rb +43 -0
  130. data/lib/graphql/rubocop/graphql/default_required_true.rb +43 -0
  131. data/lib/graphql/rubocop.rb +4 -0
  132. data/lib/graphql/schema/addition.rb +245 -0
  133. data/lib/graphql/schema/argument.rb +250 -46
  134. data/lib/graphql/schema/base_64_encoder.rb +2 -0
  135. data/lib/graphql/schema/build_from_definition/resolve_map.rb +3 -1
  136. data/lib/graphql/schema/build_from_definition.rb +243 -89
  137. data/lib/graphql/schema/directive/deprecated.rb +1 -1
  138. data/lib/graphql/schema/directive/feature.rb +1 -1
  139. data/lib/graphql/schema/directive/flagged.rb +57 -0
  140. data/lib/graphql/schema/directive/include.rb +1 -1
  141. data/lib/graphql/schema/directive/one_of.rb +12 -0
  142. data/lib/graphql/schema/directive/skip.rb +1 -1
  143. data/lib/graphql/schema/directive/transform.rb +14 -2
  144. data/lib/graphql/schema/directive.rb +108 -20
  145. data/lib/graphql/schema/enum.rb +105 -44
  146. data/lib/graphql/schema/enum_value.rb +15 -25
  147. data/lib/graphql/schema/field/connection_extension.rb +50 -30
  148. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  149. data/lib/graphql/schema/field.rb +476 -331
  150. data/lib/graphql/schema/field_extension.rb +86 -2
  151. data/lib/graphql/schema/find_inherited_value.rb +6 -8
  152. data/lib/graphql/schema/finder.rb +5 -5
  153. data/lib/graphql/schema/input_object.rb +133 -121
  154. data/lib/graphql/schema/interface.rb +17 -45
  155. data/lib/graphql/schema/introspection_system.rb +3 -8
  156. data/lib/graphql/schema/late_bound_type.rb +8 -2
  157. data/lib/graphql/schema/list.rb +25 -8
  158. data/lib/graphql/schema/loader.rb +139 -103
  159. data/lib/graphql/schema/member/base_dsl_methods.rb +29 -35
  160. data/lib/graphql/schema/member/build_type.rb +19 -14
  161. data/lib/graphql/schema/member/has_arguments.rb +310 -26
  162. data/lib/graphql/schema/member/has_ast_node.rb +16 -1
  163. data/lib/graphql/schema/member/has_deprecation_reason.rb +24 -0
  164. data/lib/graphql/schema/member/has_directives.rb +118 -0
  165. data/lib/graphql/schema/member/has_fields.rb +164 -42
  166. data/lib/graphql/schema/member/has_interfaces.rb +129 -0
  167. data/lib/graphql/schema/member/has_unresolved_type_error.rb +15 -0
  168. data/lib/graphql/schema/member/has_validators.rb +57 -0
  169. data/lib/graphql/schema/member/relay_shortcuts.rb +47 -2
  170. data/lib/graphql/schema/member/type_system_helpers.rb +20 -3
  171. data/lib/graphql/schema/member/validates_input.rb +3 -3
  172. data/lib/graphql/schema/member.rb +6 -6
  173. data/lib/graphql/schema/mutation.rb +4 -9
  174. data/lib/graphql/schema/non_null.rb +12 -7
  175. data/lib/graphql/schema/object.rb +35 -69
  176. data/lib/graphql/schema/printer.rb +16 -34
  177. data/lib/graphql/schema/relay_classic_mutation.rb +90 -43
  178. data/lib/graphql/schema/resolver/has_payload_type.rb +51 -11
  179. data/lib/graphql/schema/resolver.rb +144 -79
  180. data/lib/graphql/schema/scalar.rb +27 -18
  181. data/lib/graphql/schema/subscription.rb +55 -26
  182. data/lib/graphql/schema/timeout.rb +45 -35
  183. data/lib/graphql/schema/type_expression.rb +1 -1
  184. data/lib/graphql/schema/type_membership.rb +21 -4
  185. data/lib/graphql/schema/union.rb +48 -13
  186. data/lib/graphql/schema/unique_within_type.rb +1 -2
  187. data/lib/graphql/schema/validator/allow_blank_validator.rb +29 -0
  188. data/lib/graphql/schema/validator/allow_null_validator.rb +26 -0
  189. data/lib/graphql/schema/validator/exclusion_validator.rb +33 -0
  190. data/lib/graphql/schema/validator/format_validator.rb +48 -0
  191. data/lib/graphql/schema/validator/inclusion_validator.rb +35 -0
  192. data/lib/graphql/schema/validator/length_validator.rb +59 -0
  193. data/lib/graphql/schema/validator/numericality_validator.rb +82 -0
  194. data/lib/graphql/schema/validator/required_validator.rb +82 -0
  195. data/lib/graphql/schema/validator.rb +171 -0
  196. data/lib/graphql/schema/warden.rb +185 -32
  197. data/lib/graphql/schema/wrapper.rb +0 -5
  198. data/lib/graphql/schema.rb +471 -1116
  199. data/lib/graphql/static_validation/all_rules.rb +3 -0
  200. data/lib/graphql/static_validation/base_visitor.rb +13 -27
  201. data/lib/graphql/static_validation/definition_dependencies.rb +7 -2
  202. data/lib/graphql/static_validation/error.rb +3 -1
  203. data/lib/graphql/static_validation/literal_validator.rb +69 -26
  204. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +44 -87
  205. data/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb +22 -6
  206. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +28 -22
  207. data/lib/graphql/static_validation/rules/arguments_are_defined_error.rb +4 -2
  208. data/lib/graphql/static_validation/rules/directives_are_defined.rb +12 -6
  209. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +13 -13
  210. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +12 -4
  211. data/lib/graphql/static_validation/rules/fields_will_merge.rb +92 -49
  212. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +25 -4
  213. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -2
  214. data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +30 -0
  215. data/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb +30 -0
  216. data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid.rb +66 -0
  217. data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid_error.rb +29 -0
  218. data/lib/graphql/static_validation/rules/query_root_exists.rb +17 -0
  219. data/lib/graphql/static_validation/rules/query_root_exists_error.rb +26 -0
  220. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +4 -2
  221. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +6 -7
  222. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +13 -7
  223. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +9 -10
  224. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +14 -8
  225. data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +4 -2
  226. data/lib/graphql/static_validation/validation_context.rb +13 -3
  227. data/lib/graphql/static_validation/validation_timeout_error.rb +25 -0
  228. data/lib/graphql/static_validation/validator.rb +32 -20
  229. data/lib/graphql/static_validation.rb +1 -2
  230. data/lib/graphql/string_encoding_error.rb +13 -3
  231. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +126 -19
  232. data/lib/graphql/subscriptions/broadcast_analyzer.rb +81 -0
  233. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +58 -0
  234. data/lib/graphql/subscriptions/event.rb +81 -35
  235. data/lib/graphql/subscriptions/instrumentation.rb +0 -52
  236. data/lib/graphql/subscriptions/serialize.rb +53 -6
  237. data/lib/graphql/subscriptions.rb +113 -58
  238. data/lib/graphql/tracing/active_support_notifications_trace.rb +16 -0
  239. data/lib/graphql/tracing/active_support_notifications_tracing.rb +8 -21
  240. data/lib/graphql/tracing/appoptics_trace.rb +231 -0
  241. data/lib/graphql/tracing/appoptics_tracing.rb +173 -0
  242. data/lib/graphql/tracing/appsignal_trace.rb +77 -0
  243. data/lib/graphql/tracing/appsignal_tracing.rb +15 -0
  244. data/lib/graphql/tracing/data_dog_trace.rb +148 -0
  245. data/lib/graphql/tracing/data_dog_tracing.rb +26 -2
  246. data/lib/graphql/tracing/legacy_trace.rb +65 -0
  247. data/lib/graphql/tracing/new_relic_trace.rb +75 -0
  248. data/lib/graphql/tracing/new_relic_tracing.rb +1 -12
  249. data/lib/graphql/tracing/notifications_trace.rb +42 -0
  250. data/lib/graphql/tracing/notifications_tracing.rb +59 -0
  251. data/lib/graphql/tracing/platform_trace.rb +109 -0
  252. data/lib/graphql/tracing/platform_tracing.rb +64 -43
  253. data/lib/graphql/tracing/prometheus_trace.rb +89 -0
  254. data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +5 -2
  255. data/lib/graphql/tracing/prometheus_tracing.rb +3 -3
  256. data/lib/graphql/tracing/scout_trace.rb +72 -0
  257. data/lib/graphql/tracing/scout_tracing.rb +11 -0
  258. data/lib/graphql/tracing/statsd_trace.rb +56 -0
  259. data/lib/graphql/tracing/statsd_tracing.rb +42 -0
  260. data/lib/graphql/tracing/trace.rb +75 -0
  261. data/lib/graphql/tracing.rb +23 -71
  262. data/lib/graphql/type_kinds.rb +6 -3
  263. data/lib/graphql/types/big_int.rb +5 -1
  264. data/lib/graphql/types/int.rb +10 -3
  265. data/lib/graphql/types/iso_8601_date.rb +20 -9
  266. data/lib/graphql/types/iso_8601_date_time.rb +36 -10
  267. data/lib/graphql/types/relay/base_connection.rb +18 -92
  268. data/lib/graphql/types/relay/base_edge.rb +2 -34
  269. data/lib/graphql/types/relay/connection_behaviors.rb +176 -0
  270. data/lib/graphql/types/relay/edge_behaviors.rb +75 -0
  271. data/lib/graphql/types/relay/has_node_field.rb +41 -0
  272. data/lib/graphql/types/relay/has_nodes_field.rb +41 -0
  273. data/lib/graphql/types/relay/node.rb +2 -4
  274. data/lib/graphql/types/relay/node_behaviors.rb +25 -0
  275. data/lib/graphql/types/relay/page_info.rb +2 -14
  276. data/lib/graphql/types/relay/page_info_behaviors.rb +30 -0
  277. data/lib/graphql/types/relay.rb +10 -5
  278. data/lib/graphql/types/string.rb +8 -2
  279. data/lib/graphql/unauthorized_error.rb +2 -2
  280. data/lib/graphql/version.rb +1 -1
  281. data/lib/graphql.rb +54 -65
  282. data/readme.md +3 -6
  283. metadata +116 -236
  284. data/lib/graphql/analysis/analyze_query.rb +0 -91
  285. data/lib/graphql/analysis/field_usage.rb +0 -45
  286. data/lib/graphql/analysis/max_query_complexity.rb +0 -26
  287. data/lib/graphql/analysis/max_query_depth.rb +0 -26
  288. data/lib/graphql/analysis/query_complexity.rb +0 -88
  289. data/lib/graphql/analysis/query_depth.rb +0 -43
  290. data/lib/graphql/analysis/reducer_state.rb +0 -48
  291. data/lib/graphql/argument.rb +0 -131
  292. data/lib/graphql/authorization.rb +0 -82
  293. data/lib/graphql/backwards_compatibility.rb +0 -60
  294. data/lib/graphql/base_type.rb +0 -230
  295. data/lib/graphql/boolean_type.rb +0 -2
  296. data/lib/graphql/compatibility/execution_specification/counter_schema.rb +0 -53
  297. data/lib/graphql/compatibility/execution_specification/specification_schema.rb +0 -200
  298. data/lib/graphql/compatibility/execution_specification.rb +0 -435
  299. data/lib/graphql/compatibility/lazy_execution_specification/lazy_schema.rb +0 -111
  300. data/lib/graphql/compatibility/lazy_execution_specification.rb +0 -213
  301. data/lib/graphql/compatibility/query_parser_specification/parse_error_specification.rb +0 -87
  302. data/lib/graphql/compatibility/query_parser_specification/query_assertions.rb +0 -79
  303. data/lib/graphql/compatibility/query_parser_specification.rb +0 -264
  304. data/lib/graphql/compatibility/schema_parser_specification.rb +0 -680
  305. data/lib/graphql/compatibility.rb +0 -5
  306. data/lib/graphql/define/assign_argument.rb +0 -12
  307. data/lib/graphql/define/assign_connection.rb +0 -13
  308. data/lib/graphql/define/assign_enum_value.rb +0 -18
  309. data/lib/graphql/define/assign_global_id_field.rb +0 -11
  310. data/lib/graphql/define/assign_mutation_function.rb +0 -34
  311. data/lib/graphql/define/assign_object_field.rb +0 -42
  312. data/lib/graphql/define/defined_object_proxy.rb +0 -53
  313. data/lib/graphql/define/instance_definable.rb +0 -210
  314. data/lib/graphql/define/no_definition_error.rb +0 -7
  315. data/lib/graphql/define/non_null_with_bang.rb +0 -16
  316. data/lib/graphql/define/type_definer.rb +0 -31
  317. data/lib/graphql/define.rb +0 -31
  318. data/lib/graphql/deprecated_dsl.rb +0 -42
  319. data/lib/graphql/directive/deprecated_directive.rb +0 -2
  320. data/lib/graphql/directive/include_directive.rb +0 -2
  321. data/lib/graphql/directive/skip_directive.rb +0 -2
  322. data/lib/graphql/directive.rb +0 -107
  323. data/lib/graphql/enum_type.rb +0 -127
  324. data/lib/graphql/execution/execute.rb +0 -326
  325. data/lib/graphql/execution/flatten.rb +0 -40
  326. data/lib/graphql/execution/instrumentation.rb +0 -92
  327. data/lib/graphql/execution/interpreter/hash_response.rb +0 -46
  328. data/lib/graphql/execution/lazy/resolve.rb +0 -91
  329. data/lib/graphql/execution/typecast.rb +0 -50
  330. data/lib/graphql/field/resolve.rb +0 -59
  331. data/lib/graphql/field.rb +0 -222
  332. data/lib/graphql/float_type.rb +0 -2
  333. data/lib/graphql/function.rb +0 -124
  334. data/lib/graphql/id_type.rb +0 -2
  335. data/lib/graphql/input_object_type.rb +0 -132
  336. data/lib/graphql/int_type.rb +0 -2
  337. data/lib/graphql/interface_type.rb +0 -65
  338. data/lib/graphql/internal_representation/document.rb +0 -27
  339. data/lib/graphql/internal_representation/node.rb +0 -206
  340. data/lib/graphql/internal_representation/print.rb +0 -51
  341. data/lib/graphql/internal_representation/rewrite.rb +0 -184
  342. data/lib/graphql/internal_representation/scope.rb +0 -88
  343. data/lib/graphql/internal_representation/visit.rb +0 -36
  344. data/lib/graphql/internal_representation.rb +0 -7
  345. data/lib/graphql/language/lexer.rl +0 -258
  346. data/lib/graphql/list_type.rb +0 -80
  347. data/lib/graphql/literal_validation_error.rb +0 -6
  348. data/lib/graphql/non_null_type.rb +0 -71
  349. data/lib/graphql/object_type.rb +0 -121
  350. data/lib/graphql/query/arguments.rb +0 -188
  351. data/lib/graphql/query/arguments_cache.rb +0 -25
  352. data/lib/graphql/query/executor.rb +0 -53
  353. data/lib/graphql/query/literal_input.rb +0 -136
  354. data/lib/graphql/query/serial_execution/field_resolution.rb +0 -92
  355. data/lib/graphql/query/serial_execution/operation_resolution.rb +0 -19
  356. data/lib/graphql/query/serial_execution/selection_resolution.rb +0 -23
  357. data/lib/graphql/query/serial_execution/value_resolution.rb +0 -87
  358. data/lib/graphql/query/serial_execution.rb +0 -39
  359. data/lib/graphql/relay/array_connection.rb +0 -85
  360. data/lib/graphql/relay/base_connection.rb +0 -176
  361. data/lib/graphql/relay/connection_instrumentation.rb +0 -54
  362. data/lib/graphql/relay/connection_resolve.rb +0 -43
  363. data/lib/graphql/relay/connection_type.rb +0 -41
  364. data/lib/graphql/relay/edge.rb +0 -27
  365. data/lib/graphql/relay/edge_type.rb +0 -19
  366. data/lib/graphql/relay/edges_instrumentation.rb +0 -40
  367. data/lib/graphql/relay/global_id_resolve.rb +0 -18
  368. data/lib/graphql/relay/mongo_relation_connection.rb +0 -50
  369. data/lib/graphql/relay/mutation/instrumentation.rb +0 -23
  370. data/lib/graphql/relay/mutation/resolve.rb +0 -56
  371. data/lib/graphql/relay/mutation/result.rb +0 -38
  372. data/lib/graphql/relay/mutation.rb +0 -105
  373. data/lib/graphql/relay/node.rb +0 -36
  374. data/lib/graphql/relay/page_info.rb +0 -7
  375. data/lib/graphql/relay/relation_connection.rb +0 -190
  376. data/lib/graphql/relay/type_extensions.rb +0 -30
  377. data/lib/graphql/scalar_type.rb +0 -76
  378. data/lib/graphql/schema/catchall_middleware.rb +0 -35
  379. data/lib/graphql/schema/default_parse_error.rb +0 -10
  380. data/lib/graphql/schema/default_type_error.rb +0 -15
  381. data/lib/graphql/schema/member/accepts_definition.rb +0 -152
  382. data/lib/graphql/schema/member/cached_graphql_definition.rb +0 -31
  383. data/lib/graphql/schema/member/instrumentation.rb +0 -132
  384. data/lib/graphql/schema/middleware_chain.rb +0 -82
  385. data/lib/graphql/schema/possible_types.rb +0 -39
  386. data/lib/graphql/schema/rescue_middleware.rb +0 -60
  387. data/lib/graphql/schema/timeout_middleware.rb +0 -86
  388. data/lib/graphql/schema/traversal.rb +0 -228
  389. data/lib/graphql/schema/validation.rb +0 -303
  390. data/lib/graphql/static_validation/default_visitor.rb +0 -15
  391. data/lib/graphql/static_validation/no_validate_visitor.rb +0 -10
  392. data/lib/graphql/string_type.rb +0 -2
  393. data/lib/graphql/subscriptions/subscription_root.rb +0 -65
  394. data/lib/graphql/tracing/skylight_tracing.rb +0 -70
  395. data/lib/graphql/types/relay/base_field.rb +0 -22
  396. data/lib/graphql/types/relay/base_interface.rb +0 -29
  397. data/lib/graphql/types/relay/base_object.rb +0 -26
  398. data/lib/graphql/types/relay/node_field.rb +0 -43
  399. data/lib/graphql/types/relay/nodes_field.rb +0 -45
  400. data/lib/graphql/union_type.rb +0 -113
  401. data/lib/graphql/upgrader/member.rb +0 -936
  402. data/lib/graphql/upgrader/schema.rb +0 -37
@@ -8,6 +8,191 @@ module GraphQL
8
8
  #
9
9
  # @api private
10
10
  class Runtime
11
+ class CurrentState
12
+ def initialize
13
+ @current_object = nil
14
+ @current_field = nil
15
+ @current_arguments = nil
16
+ @current_result_name = nil
17
+ @current_result = nil
18
+ end
19
+
20
+ attr_accessor :current_result, :current_result_name,
21
+ :current_arguments, :current_field, :current_object
22
+ end
23
+
24
+ module GraphQLResult
25
+ def initialize(result_name, parent_result)
26
+ @graphql_parent = parent_result
27
+ if parent_result && parent_result.graphql_dead
28
+ @graphql_dead = true
29
+ end
30
+ @graphql_result_name = result_name
31
+ # Jump through some hoops to avoid creating this duplicate storage if at all possible.
32
+ @graphql_metadata = nil
33
+ end
34
+
35
+ def path
36
+ @path ||= build_path([])
37
+ end
38
+
39
+ def build_path(path_array)
40
+ graphql_result_name && path_array.unshift(graphql_result_name)
41
+ @graphql_parent ? @graphql_parent.build_path(path_array) : path_array
42
+ end
43
+
44
+ attr_accessor :graphql_dead
45
+ attr_reader :graphql_parent, :graphql_result_name
46
+
47
+ # Although these are used by only one of the Result classes,
48
+ # it's handy to have the methods implemented on both (even though they just return `nil`)
49
+ # because it makes it easy to check if anything is assigned.
50
+ # @return [nil, Array<String>]
51
+ attr_accessor :graphql_non_null_field_names
52
+ # @return [nil, true]
53
+ attr_accessor :graphql_non_null_list_items
54
+
55
+ # @return [Hash] Plain-Ruby result data (`@graphql_metadata` contains Result wrapper objects)
56
+ attr_accessor :graphql_result_data
57
+ end
58
+
59
+ class GraphQLResultHash
60
+ def initialize(_result_name, _parent_result)
61
+ super
62
+ @graphql_result_data = {}
63
+ end
64
+
65
+ include GraphQLResult
66
+
67
+ attr_accessor :graphql_merged_into
68
+
69
+ def set_leaf(key, value)
70
+ # This is a hack.
71
+ # Basically, this object is merged into the root-level result at some point.
72
+ # But the problem is, some lazies are created whose closures retain reference to _this_
73
+ # object. When those lazies are resolved, they cause an update to this object.
74
+ #
75
+ # In order to return a proper top-level result, we have to update that top-level result object.
76
+ # In order to return a proper partial result (eg, for a directive), we have to update this object, too.
77
+ # Yowza.
78
+ if (t = @graphql_merged_into)
79
+ t.set_leaf(key, value)
80
+ end
81
+
82
+ @graphql_result_data[key] = value
83
+ # keep this up-to-date if it's been initialized
84
+ @graphql_metadata && @graphql_metadata[key] = value
85
+
86
+ value
87
+ end
88
+
89
+ def set_child_result(key, value)
90
+ if (t = @graphql_merged_into)
91
+ t.set_child_result(key, value)
92
+ end
93
+ @graphql_result_data[key] = value.graphql_result_data
94
+ # If we encounter some part of this response that requires metadata tracking,
95
+ # then create the metadata hash if necessary. It will be kept up-to-date after this.
96
+ (@graphql_metadata ||= @graphql_result_data.dup)[key] = value
97
+ value
98
+ end
99
+
100
+ def delete(key)
101
+ @graphql_metadata && @graphql_metadata.delete(key)
102
+ @graphql_result_data.delete(key)
103
+ end
104
+
105
+ def each
106
+ (@graphql_metadata || @graphql_result_data).each { |k, v| yield(k, v) }
107
+ end
108
+
109
+ def values
110
+ (@graphql_metadata || @graphql_result_data).values
111
+ end
112
+
113
+ def key?(k)
114
+ @graphql_result_data.key?(k)
115
+ end
116
+
117
+ def [](k)
118
+ (@graphql_metadata || @graphql_result_data)[k]
119
+ end
120
+
121
+ def merge_into(into_result)
122
+ self.each do |key, value|
123
+ case value
124
+ when GraphQLResultHash
125
+ next_into = into_result[key]
126
+ if next_into
127
+ value.merge_into(next_into)
128
+ else
129
+ into_result.set_child_result(key, value)
130
+ end
131
+ when GraphQLResultArray
132
+ # There's no special handling of arrays because currently, there's no way to split the execution
133
+ # of a list over several concurrent flows.
134
+ next_result.set_child_result(key, value)
135
+ else
136
+ # We have to assume that, since this passed the `fields_will_merge` selection,
137
+ # that the old and new values are the same.
138
+ into_result.set_leaf(key, value)
139
+ end
140
+ end
141
+ @graphql_merged_into = into_result
142
+ end
143
+ end
144
+
145
+ class GraphQLResultArray
146
+ include GraphQLResult
147
+
148
+ def initialize(_result_name, _parent_result)
149
+ super
150
+ @graphql_result_data = []
151
+ end
152
+
153
+ def graphql_skip_at(index)
154
+ # Mark this index as dead. It's tricky because some indices may already be storing
155
+ # `Lazy`s. So the runtime is still holding indexes _before_ skipping,
156
+ # this object has to coordinate incoming writes to account for any already-skipped indices.
157
+ @skip_indices ||= []
158
+ @skip_indices << index
159
+ offset_by = @skip_indices.count { |skipped_idx| skipped_idx < index}
160
+ delete_at_index = index - offset_by
161
+ @graphql_metadata && @graphql_metadata.delete_at(delete_at_index)
162
+ @graphql_result_data.delete_at(delete_at_index)
163
+ end
164
+
165
+ def set_leaf(idx, value)
166
+ if @skip_indices
167
+ offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx }
168
+ idx -= offset_by
169
+ end
170
+ @graphql_result_data[idx] = value
171
+ @graphql_metadata && @graphql_metadata[idx] = value
172
+ value
173
+ end
174
+
175
+ def set_child_result(idx, value)
176
+ if @skip_indices
177
+ offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx }
178
+ idx -= offset_by
179
+ end
180
+ @graphql_result_data[idx] = value.graphql_result_data
181
+ # If we encounter some part of this response that requires metadata tracking,
182
+ # then create the metadata hash if necessary. It will be kept up-to-date after this.
183
+ (@graphql_metadata ||= @graphql_result_data.dup)[idx] = value
184
+ value
185
+ end
186
+
187
+ def values
188
+ (@graphql_metadata || @graphql_result_data)
189
+ end
190
+ end
191
+
192
+ class GraphQLSelectionSet < Hash
193
+ attr_accessor :graphql_directives
194
+ end
195
+
11
196
  # @return [GraphQL::Query]
12
197
  attr_reader :query
13
198
 
@@ -17,60 +202,118 @@ module GraphQL
17
202
  # @return [GraphQL::Query::Context]
18
203
  attr_reader :context
19
204
 
20
- def initialize(query:, response:)
205
+ def initialize(query:, lazies_at_depth:)
21
206
  @query = query
207
+ @dataloader = query.multiplex.dataloader
208
+ @lazies_at_depth = lazies_at_depth
22
209
  @schema = query.schema
23
210
  @context = query.context
24
- @interpreter_context = @context.namespace(:interpreter)
25
- @response = response
26
- @dead_paths = {}
27
- @types_at_paths = {}
211
+ @multiplex_context = query.multiplex.context
212
+ # Start this off empty:
213
+ Thread.current[:__graphql_runtime_info] = nil
214
+ @response = GraphQLResultHash.new(nil, nil)
215
+ # Identify runtime directives by checking which of this schema's directives have overridden `def self.resolve`
216
+ @runtime_directive_names = []
217
+ noop_resolve_owner = GraphQL::Schema::Directive.singleton_class
218
+ @schema_directives = schema.directives
219
+ @schema_directives.each do |name, dir_defn|
220
+ if dir_defn.method(:resolve).owner != noop_resolve_owner
221
+ @runtime_directive_names << name
222
+ end
223
+ end
28
224
  # A cache of { Class => { String => Schema::Field } }
29
225
  # Which assumes that MyObject.get_field("myField") will return the same field
30
226
  # during the lifetime of a query
31
227
  @fields_cache = Hash.new { |h, k| h[k] = {} }
228
+ # { Class => Boolean }
229
+ @lazy_cache = {}
32
230
  end
33
231
 
34
- def final_value
35
- @response.final_value
232
+ def final_result
233
+ @response && @response.graphql_result_data
36
234
  end
37
235
 
38
236
  def inspect
39
237
  "#<#{self.class.name} response=#{@response.inspect}>"
40
238
  end
41
239
 
240
+ def tap_or_each(obj_or_array)
241
+ if obj_or_array.is_a?(Array)
242
+ obj_or_array.each do |item|
243
+ yield(item, true)
244
+ end
245
+ else
246
+ yield(obj_or_array, false)
247
+ end
248
+ end
249
+
42
250
  # This _begins_ the execution. Some deferred work
43
251
  # might be stored up in lazies.
44
252
  # @return [void]
45
253
  def run_eager
46
-
47
254
  root_operation = query.selected_operation
48
255
  root_op_type = root_operation.operation_type || "query"
49
256
  root_type = schema.root_type_for_operation(root_op_type)
50
- path = []
51
- @interpreter_context[:current_object] = query.root_value
52
- @interpreter_context[:current_path] = path
53
- object_proxy = authorized_new(root_type, query.root_value, context, path)
257
+ st = get_current_runtime_state
258
+ st.current_object = query.root_value
259
+ st.current_result = @response
260
+ object_proxy = authorized_new(root_type, query.root_value, context)
54
261
  object_proxy = schema.sync_lazy(object_proxy)
262
+
55
263
  if object_proxy.nil?
56
264
  # Root .authorized? returned false.
57
- write_in_response(path, nil)
58
- nil
265
+ @response = nil
59
266
  else
60
- evaluate_selections(path, context.scoped_context, object_proxy, root_type, root_operation.selections, root_operation_type: root_op_type)
61
- nil
267
+ call_method_on_directives(:resolve, object_proxy, root_operation.directives) do # execute query level directives
268
+ gathered_selections = gather_selections(object_proxy, root_type, root_operation.selections)
269
+ # This is kind of a hack -- `gathered_selections` is an Array if any of the selections
270
+ # require isolation during execution (because of runtime directives). In that case,
271
+ # make a new, isolated result hash for writing the result into. (That isolated response
272
+ # is eventually merged back into the main response)
273
+ #
274
+ # Otherwise, `gathered_selections` is a hash of selections which can be
275
+ # directly evaluated and the results can be written right into the main response hash.
276
+ tap_or_each(gathered_selections) do |selections, is_selection_array|
277
+ if is_selection_array
278
+ selection_response = GraphQLResultHash.new(nil, nil)
279
+ final_response = @response
280
+ else
281
+ selection_response = @response
282
+ final_response = nil
283
+ end
284
+
285
+ @dataloader.append_job {
286
+ st = get_current_runtime_state
287
+ st.current_object = query.root_value
288
+ st.current_result = selection_response
289
+
290
+ call_method_on_directives(:resolve, object_proxy, selections.graphql_directives) do
291
+ evaluate_selections(
292
+ object_proxy,
293
+ root_type,
294
+ root_op_type == "mutation",
295
+ selections,
296
+ selection_response,
297
+ final_response,
298
+ nil,
299
+ )
300
+ end
301
+ }
302
+ end
303
+ end
62
304
  end
305
+ delete_all_interpreter_context
306
+ nil
63
307
  end
64
308
 
65
- def gather_selections(owner_object, owner_type, selections, selections_by_name)
309
+ def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = GraphQLSelectionSet.new)
66
310
  selections.each do |node|
67
311
  # Skip gathering this if the directive says so
68
312
  if !directives_include?(node, owner_object, owner_type)
69
313
  next
70
314
  end
71
315
 
72
- case node
73
- when GraphQL::Language::Nodes::Field
316
+ if node.is_a?(GraphQL::Language::Nodes::Field)
74
317
  response_key = node.alias || node.name
75
318
  selections = selections_by_name[response_key]
76
319
  # if there was already a selection of this field,
@@ -86,192 +329,413 @@ module GraphQL
86
329
  # No selection was found for this field yet
87
330
  selections_by_name[response_key] = node
88
331
  end
89
- when GraphQL::Language::Nodes::InlineFragment
90
- if node.type
91
- type_defn = schema.get_type(node.type.name)
92
- # Faster than .map{}.include?()
93
- query.warden.possible_types(type_defn).each do |t|
332
+ else
333
+ # This is an InlineFragment or a FragmentSpread
334
+ if @runtime_directive_names.any? && node.directives.any? { |d| @runtime_directive_names.include?(d.name) }
335
+ next_selections = GraphQLSelectionSet.new
336
+ next_selections.graphql_directives = node.directives
337
+ if selections_to_run
338
+ selections_to_run << next_selections
339
+ else
340
+ selections_to_run = []
341
+ selections_to_run << selections_by_name
342
+ selections_to_run << next_selections
343
+ end
344
+ else
345
+ next_selections = selections_by_name
346
+ end
347
+
348
+ case node
349
+ when GraphQL::Language::Nodes::InlineFragment
350
+ if node.type
351
+ type_defn = schema.get_type(node.type.name, context)
352
+
353
+ # Faster than .map{}.include?()
354
+ query.warden.possible_types(type_defn).each do |t|
355
+ if t == owner_type
356
+ gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
357
+ break
358
+ end
359
+ end
360
+ else
361
+ # it's an untyped fragment, definitely continue
362
+ gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
363
+ end
364
+ when GraphQL::Language::Nodes::FragmentSpread
365
+ fragment_def = query.fragments[node.name]
366
+ type_defn = query.get_type(fragment_def.type.name)
367
+ possible_types = query.warden.possible_types(type_defn)
368
+ possible_types.each do |t|
94
369
  if t == owner_type
95
- gather_selections(owner_object, owner_type, node.selections, selections_by_name)
370
+ gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections)
96
371
  break
97
372
  end
98
373
  end
99
374
  else
100
- # it's an untyped fragment, definitely continue
101
- gather_selections(owner_object, owner_type, node.selections, selections_by_name)
375
+ raise "Invariant: unexpected selection class: #{node.class}"
102
376
  end
103
- when GraphQL::Language::Nodes::FragmentSpread
104
- fragment_def = query.fragments[node.name]
105
- type_defn = schema.get_type(fragment_def.type.name)
106
- possible_types = query.warden.possible_types(type_defn)
107
- possible_types.each do |t|
108
- if t == owner_type
109
- gather_selections(owner_object, owner_type, fragment_def.selections, selections_by_name)
110
- break
111
- end
112
- end
113
- else
114
- raise "Invariant: unexpected selection class: #{node.class}"
115
377
  end
116
378
  end
379
+ selections_to_run || selections_by_name
117
380
  end
118
381
 
119
- def evaluate_selections(path, scoped_context, owner_object, owner_type, selections, root_operation_type: nil)
120
- @interpreter_context[:current_object] = owner_object
121
- @interpreter_context[:current_path] = path
122
- selections_by_name = {}
123
- gather_selections(owner_object, owner_type, selections, selections_by_name)
124
- selections_by_name.each do |result_name, field_ast_nodes_or_ast_node|
125
- # As a performance optimization, the hash key will be a `Node` if
126
- # there's only one selection of the field. But if there are multiple
127
- # selections of the field, it will be an Array of nodes
128
- if field_ast_nodes_or_ast_node.is_a?(Array)
129
- field_ast_nodes = field_ast_nodes_or_ast_node
130
- ast_node = field_ast_nodes.first
131
- else
132
- field_ast_nodes = nil
133
- ast_node = field_ast_nodes_or_ast_node
134
- end
135
- field_name = ast_node.name
136
- field_defn = @fields_cache[owner_type][field_name] ||= owner_type.get_field(field_name)
137
- is_introspection = false
138
- if field_defn.nil?
139
- field_defn = if owner_type == schema.query && (entry_point_field = schema.introspection_system.entry_point(name: field_name))
140
- is_introspection = true
141
- entry_point_field
142
- elsif (dynamic_field = schema.introspection_system.dynamic_field(name: field_name))
143
- is_introspection = true
144
- dynamic_field
145
- else
146
- raise "Invariant: no field for #{owner_type}.#{field_name}"
382
+ NO_ARGS = GraphQL::EmptyObjects::EMPTY_HASH
383
+
384
+ # @return [void]
385
+ def evaluate_selections(owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result, parent_object) # rubocop:disable Metrics/ParameterLists
386
+ st = get_current_runtime_state
387
+ st.current_object = owner_object
388
+ st.current_result_name = nil
389
+ st.current_result = selections_result
390
+
391
+ finished_jobs = 0
392
+ enqueued_jobs = gathered_selections.size
393
+ gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
394
+ @dataloader.append_job {
395
+ evaluate_selection(
396
+ result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_selection, selections_result, parent_object
397
+ )
398
+ finished_jobs += 1
399
+ if target_result && finished_jobs == enqueued_jobs
400
+ selections_result.merge_into(target_result)
147
401
  end
402
+ }
403
+ end
404
+
405
+ selections_result
406
+ end
407
+
408
+ # @return [void]
409
+ def evaluate_selection(result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_field, selections_result, parent_object) # rubocop:disable Metrics/ParameterLists
410
+ return if dead_result?(selections_result)
411
+ # As a performance optimization, the hash key will be a `Node` if
412
+ # there's only one selection of the field. But if there are multiple
413
+ # selections of the field, it will be an Array of nodes
414
+ if field_ast_nodes_or_ast_node.is_a?(Array)
415
+ field_ast_nodes = field_ast_nodes_or_ast_node
416
+ ast_node = field_ast_nodes.first
417
+ else
418
+ field_ast_nodes = nil
419
+ ast_node = field_ast_nodes_or_ast_node
420
+ end
421
+ field_name = ast_node.name
422
+ # This can't use `query.get_field` because it gets confused on introspection below if `field_defn` isn't `nil`,
423
+ # because of how `is_introspection` is used to call `.authorized_new` later on.
424
+ field_defn = @fields_cache[owner_type][field_name] ||= owner_type.get_field(field_name, @context)
425
+ is_introspection = false
426
+ if field_defn.nil?
427
+ field_defn = if owner_type == schema.query && (entry_point_field = schema.introspection_system.entry_point(name: field_name))
428
+ is_introspection = true
429
+ entry_point_field
430
+ elsif (dynamic_field = schema.introspection_system.dynamic_field(name: field_name))
431
+ is_introspection = true
432
+ dynamic_field
433
+ else
434
+ raise "Invariant: no field for #{owner_type}.#{field_name}"
148
435
  end
436
+ end
149
437
 
150
- return_type = field_defn.type
438
+ return_type = field_defn.type
151
439
 
152
- next_path = path.dup
153
- next_path << result_name
154
- next_path.freeze
440
+ # This seems janky, but we need to know
441
+ # the field's return type at this path in order
442
+ # to propagate `null`
443
+ return_type_non_null = return_type.non_null?
444
+ if return_type_non_null
445
+ (selections_result.graphql_non_null_field_names ||= []).push(result_name)
446
+ end
447
+ # Set this before calling `run_with_directives`, so that the directive can have the latest path
448
+ st = get_current_runtime_state
449
+ st.current_field = field_defn
450
+ st.current_result = selections_result
451
+ st.current_result_name = result_name
155
452
 
156
- # This seems janky, but we need to know
157
- # the field's return type at this path in order
158
- # to propagate `null`
159
- set_type_at_path(next_path, return_type)
160
- # Set this before calling `run_with_directives`, so that the directive can have the latest path
161
- @interpreter_context[:current_path] = next_path
162
- @interpreter_context[:current_field] = field_defn
453
+ object = owner_object
163
454
 
164
- context.scoped_context = scoped_context
165
- object = owner_object
455
+ if is_introspection
456
+ object = authorized_new(field_defn.owner, object, context)
457
+ end
166
458
 
167
- if is_introspection
168
- object = authorized_new(field_defn.owner, object, context, next_path)
459
+ total_args_count = field_defn.arguments(context).size
460
+ if total_args_count == 0
461
+ resolved_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
462
+ if field_defn.extras.size == 0
463
+ evaluate_selection_with_resolved_keyword_args(
464
+ NO_ARGS, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null
465
+ )
466
+ else
467
+ evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null)
169
468
  end
469
+ else
470
+ @query.arguments_cache.dataload_for(ast_node, field_defn, object) do |resolved_arguments|
471
+ evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null)
472
+ end
473
+ end
474
+ end
170
475
 
171
- begin
172
- kwarg_arguments = arguments(object, field_defn, ast_node)
173
- rescue GraphQL::ExecutionError => e
174
- continue_value(next_path, e, field_defn, return_type.non_null?, ast_node)
476
+ 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, return_type_non_null) # rubocop:disable Metrics/ParameterLists
477
+ after_lazy(arguments, owner: owner_type, field: field_defn, ast_node: ast_node, owner_object: object, arguments: arguments, result_name: result_name, result: selection_result) do |resolved_arguments|
478
+ if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
479
+ continue_value(resolved_arguments, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result)
175
480
  next
176
481
  end
177
482
 
178
- # It might turn out that making arguments for every field is slow.
179
- # If we have to cache them, we'll need a more subtle approach here.
180
- field_defn.extras.each do |extra|
181
- case extra
182
- when :ast_node
183
- kwarg_arguments[:ast_node] = ast_node
184
- when :execution_errors
185
- kwarg_arguments[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
186
- when :path
187
- kwarg_arguments[:path] = next_path
188
- when :lookahead
189
- if !field_ast_nodes
190
- field_ast_nodes = [ast_node]
191
- end
192
- kwarg_arguments[:lookahead] = Execution::Lookahead.new(
193
- query: query,
194
- ast_nodes: field_ast_nodes,
195
- field: field_defn,
196
- )
483
+ kwarg_arguments = if field_defn.extras.empty?
484
+ if resolved_arguments.empty?
485
+ # We can avoid allocating the `{ Symbol => Object }` hash in this case
486
+ NO_ARGS
197
487
  else
198
- kwarg_arguments[extra] = field_defn.fetch_extra(extra, context)
488
+ resolved_arguments.keyword_arguments
489
+ end
490
+ else
491
+ # Bundle up the extras, then make a new arguments instance
492
+ # that includes the extras, too.
493
+ extra_args = {}
494
+ field_defn.extras.each do |extra|
495
+ case extra
496
+ when :ast_node
497
+ extra_args[:ast_node] = ast_node
498
+ when :execution_errors
499
+ extra_args[:execution_errors] = ExecutionErrors.new(context, ast_node, current_path)
500
+ when :path
501
+ extra_args[:path] = current_path
502
+ when :lookahead
503
+ if !field_ast_nodes
504
+ field_ast_nodes = [ast_node]
505
+ end
506
+
507
+ extra_args[:lookahead] = Execution::Lookahead.new(
508
+ query: query,
509
+ ast_nodes: field_ast_nodes,
510
+ field: field_defn,
511
+ )
512
+ when :argument_details
513
+ # Use this flag to tell Interpreter::Arguments to add itself
514
+ # to the keyword args hash _before_ freezing everything.
515
+ extra_args[:argument_details] = :__arguments_add_self
516
+ when :parent
517
+ extra_args[:parent] = parent_object
518
+ else
519
+ extra_args[extra] = field_defn.fetch_extra(extra, context)
520
+ end
521
+ end
522
+ if extra_args.any?
523
+ resolved_arguments = resolved_arguments.merge_extras(extra_args)
199
524
  end
525
+ resolved_arguments.keyword_arguments
200
526
  end
201
527
 
202
- @interpreter_context[:current_arguments] = kwarg_arguments
528
+ 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)
529
+ end
530
+ end
203
531
 
204
- # Optimize for the case that field is selected only once
205
- if field_ast_nodes.nil? || field_ast_nodes.size == 1
206
- next_selections = ast_node.selections
207
- else
208
- next_selections = []
209
- field_ast_nodes.each { |f| next_selections.concat(f.selections) }
210
- end
532
+ 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) # rubocop:disable Metrics/ParameterLists
533
+ st = get_current_runtime_state
534
+ st.current_field = field_defn
535
+ st.current_object = object
536
+ st.current_arguments = resolved_arguments
537
+ st.current_result_name = result_name
538
+ st.current_result = selection_result
539
+ # Optimize for the case that field is selected only once
540
+ if field_ast_nodes.nil? || field_ast_nodes.size == 1
541
+ next_selections = ast_node.selections
542
+ directives = ast_node.directives
543
+ else
544
+ next_selections = []
545
+ directives = []
546
+ field_ast_nodes.each { |f|
547
+ next_selections.concat(f.selections)
548
+ directives.concat(f.directives)
549
+ }
550
+ end
211
551
 
212
- field_result = resolve_with_directives(object, ast_node) do
213
- # Actually call the field resolver and capture the result
214
- app_result = begin
215
- query.with_error_handling do
216
- query.trace("execute_field", {owner: owner_type, field: field_defn, path: next_path, query: query, object: object, arguments: kwarg_arguments}) do
217
- field_defn.resolve(object, kwarg_arguments, context)
218
- end
219
- end
220
- rescue GraphQL::ExecutionError => err
221
- err
552
+ field_result = call_method_on_directives(:resolve, object, directives) do
553
+ # Actually call the field resolver and capture the result
554
+ app_result = begin
555
+ query.current_trace.execute_field(field: field_defn, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments) do
556
+ field_defn.resolve(object, kwarg_arguments, context)
222
557
  end
223
- after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |inner_result|
224
- continue_value = continue_value(next_path, inner_result, field_defn, return_type.non_null?, ast_node)
225
- if HALT != continue_value
226
- continue_field(next_path, continue_value, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments)
227
- end
558
+ rescue GraphQL::ExecutionError => err
559
+ err
560
+ rescue StandardError => err
561
+ begin
562
+ query.handle_or_reraise(err)
563
+ rescue GraphQL::ExecutionError => ex_err
564
+ ex_err
228
565
  end
229
566
  end
567
+ after_lazy(app_result, owner: owner_type, field: field_defn, ast_node: ast_node, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result) do |inner_result|
568
+ continue_value = continue_value(inner_result, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result)
569
+ if HALT != continue_value
570
+ continue_field(continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result)
571
+ end
572
+ end
573
+ end
230
574
 
231
- # If this field is a root mutation field, immediately resolve
232
- # all of its child fields before moving on to the next root mutation field.
233
- # (Subselections of this mutation will still be resolved level-by-level.)
234
- if root_operation_type == "mutation"
235
- Interpreter::Resolve.resolve_all([field_result])
575
+ # If this field is a root mutation field, immediately resolve
576
+ # all of its child fields before moving on to the next root mutation field.
577
+ # (Subselections of this mutation will still be resolved level-by-level.)
578
+ if is_eager_field
579
+ Interpreter::Resolve.resolve_all([field_result], @dataloader)
580
+ else
581
+ # Return this from `after_lazy` because it might be another lazy that needs to be resolved
582
+ field_result
583
+ end
584
+ end
585
+
586
+
587
+ def dead_result?(selection_result)
588
+ selection_result.graphql_dead # || ((parent = selection_result.graphql_parent) && parent.graphql_dead)
589
+ end
590
+
591
+ def set_result(selection_result, result_name, value, is_child_result)
592
+ if !dead_result?(selection_result)
593
+ if value.nil? &&
594
+ ( # there are two conditions under which `nil` is not allowed in the response:
595
+ (selection_result.graphql_non_null_list_items) || # this value would be written into a list that doesn't allow nils
596
+ ((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
597
+ )
598
+ # This is an invalid nil that should be propagated
599
+ # One caller of this method passes a block,
600
+ # namely when application code returns a `nil` to GraphQL and it doesn't belong there.
601
+ # The other possibility for reaching here is when a field returns an ExecutionError, so we write
602
+ # `nil` to the response, not knowing whether it's an invalid `nil` or not.
603
+ # (And in that case, we don't have to call the schema's handler, since it's not a bug in the application.)
604
+ # TODO the code is trying to tell me something.
605
+ yield if block_given?
606
+ parent = selection_result.graphql_parent
607
+ name_in_parent = selection_result.graphql_result_name
608
+ if parent.nil? # This is a top-level result hash
609
+ @response = nil
610
+ else
611
+ set_result(parent, name_in_parent, nil, false)
612
+ set_graphql_dead(selection_result)
613
+ end
614
+ elsif is_child_result
615
+ selection_result.set_child_result(result_name, value)
236
616
  else
237
- field_result
617
+ selection_result.set_leaf(result_name, value)
238
618
  end
239
619
  end
240
620
  end
241
621
 
622
+ # Mark this node and any already-registered children as dead,
623
+ # so that it accepts no more writes.
624
+ def set_graphql_dead(selection_result)
625
+ case selection_result
626
+ when GraphQLResultArray
627
+ selection_result.graphql_dead = true
628
+ selection_result.values.each { |v| set_graphql_dead(v) }
629
+ when GraphQLResultHash
630
+ selection_result.graphql_dead = true
631
+ selection_result.each { |k, v| set_graphql_dead(v) }
632
+ else
633
+ # It's a scalar, no way to mark it dead.
634
+ end
635
+ end
636
+
637
+ def current_path
638
+ st = get_current_runtime_state
639
+ result = st.current_result
640
+ path = result && result.path
641
+ if path && (rn = st.current_result_name)
642
+ path = path.dup
643
+ path.push(rn)
644
+ end
645
+ path
646
+ end
647
+
242
648
  HALT = Object.new
243
- def continue_value(path, value, field, is_non_null, ast_node)
244
- if value.nil?
649
+ def continue_value(value, parent_type, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
650
+ case value
651
+ when nil
245
652
  if is_non_null
246
- err = GraphQL::InvalidNullError.new(field.owner, field, value)
247
- write_invalid_null_in_response(path, err)
653
+ set_result(selection_result, result_name, nil, false) do
654
+ # This block is called if `result_name` is not dead. (Maybe a previous invalid nil caused it be marked dead.)
655
+ err = parent_type::InvalidNullError.new(parent_type, field, value)
656
+ schema.type_error(err, context)
657
+ end
248
658
  else
249
- write_in_response(path, nil)
659
+ set_result(selection_result, result_name, nil, false)
250
660
  end
251
661
  HALT
252
- elsif value.is_a?(GraphQL::ExecutionError)
253
- value.path ||= path
254
- value.ast_node ||= ast_node
255
- write_execution_errors_in_response(path, [value])
256
- HALT
257
- elsif value.is_a?(Array) && value.any? && value.all? { |v| v.is_a?(GraphQL::ExecutionError) }
258
- value.each_with_index do |error, index|
259
- error.ast_node ||= ast_node
260
- error.path ||= path + (field.type.list? ? [index] : [])
662
+ when GraphQL::Error
663
+ # Handle these cases inside a single `when`
664
+ # to avoid the overhead of checking three different classes
665
+ # every time.
666
+ if value.is_a?(GraphQL::ExecutionError)
667
+ if selection_result.nil? || !dead_result?(selection_result)
668
+ value.path ||= current_path
669
+ value.ast_node ||= ast_node
670
+ context.errors << value
671
+ if selection_result
672
+ set_result(selection_result, result_name, nil, false)
673
+ end
674
+ end
675
+ HALT
676
+ elsif value.is_a?(GraphQL::UnauthorizedFieldError)
677
+ value.field ||= field
678
+ # this hook might raise & crash, or it might return
679
+ # a replacement value
680
+ next_value = begin
681
+ schema.unauthorized_field(value)
682
+ rescue GraphQL::ExecutionError => err
683
+ err
684
+ end
685
+ continue_value(next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
686
+ elsif value.is_a?(GraphQL::UnauthorizedError)
687
+ # this hook might raise & crash, or it might return
688
+ # a replacement value
689
+ next_value = begin
690
+ schema.unauthorized_object(value)
691
+ rescue GraphQL::ExecutionError => err
692
+ err
693
+ end
694
+ continue_value(next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
695
+ elsif GraphQL::Execution::SKIP == value
696
+ # It's possible a lazy was already written here
697
+ case selection_result
698
+ when GraphQLResultHash
699
+ selection_result.delete(result_name)
700
+ when GraphQLResultArray
701
+ selection_result.graphql_skip_at(result_name)
702
+ when nil
703
+ # this can happen with directives
704
+ else
705
+ raise "Invariant: unexpected result class #{selection_result.class} (#{selection_result.inspect})"
706
+ end
707
+ HALT
708
+ else
709
+ # What could this actually _be_? Anyhow,
710
+ # preserve the default behavior of doing nothing with it.
711
+ value
261
712
  end
262
- write_execution_errors_in_response(path, value)
263
- HALT
264
- elsif value.is_a?(GraphQL::UnauthorizedError)
265
- # this hook might raise & crash, or it might return
266
- # a replacement value
267
- next_value = begin
268
- schema.unauthorized_object(value)
269
- rescue GraphQL::ExecutionError => err
270
- err
713
+ when Array
714
+ # It's an array full of execution errors; add them all.
715
+ if value.any? && value.all? { |v| v.is_a?(GraphQL::ExecutionError) }
716
+ list_type_at_all = (field && (field.type.list?))
717
+ if selection_result.nil? || !dead_result?(selection_result)
718
+ value.each_with_index do |error, index|
719
+ error.ast_node ||= ast_node
720
+ error.path ||= current_path + (list_type_at_all ? [index] : [])
721
+ context.errors << error
722
+ end
723
+ if selection_result
724
+ if list_type_at_all
725
+ result_without_errors = value.map { |v| v.is_a?(GraphQL::ExecutionError) ? nil : v }
726
+ set_result(selection_result, result_name, result_without_errors, false)
727
+ else
728
+ set_result(selection_result, result_name, nil, false)
729
+ end
730
+ end
731
+ end
732
+ HALT
733
+ else
734
+ value
271
735
  end
272
-
273
- continue_value(path, next_value, field, is_non_null, ast_node)
274
- elsif GraphQL::Execution::Execute::SKIP == value
736
+ when GraphQL::Execution::Interpreter::RawValue
737
+ # Write raw value directly to the response without resolving nested objects
738
+ set_result(selection_result, result_name, value.resolve, false)
275
739
  HALT
276
740
  else
277
741
  value
@@ -286,90 +750,184 @@ module GraphQL
286
750
  # Location information from `path` and `ast_node`.
287
751
  #
288
752
  # @return [Lazy, Array, Hash, Object] Lazy, Array, and Hash are all traversed to resolve lazy values later
289
- def continue_field(path, value, field, type, ast_node, next_selections, is_non_null, owner_object, arguments) # rubocop:disable Metrics/ParameterLists
290
- case type.kind.name
753
+ def continue_field(value, owner_type, field, current_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
754
+ if current_type.non_null?
755
+ current_type = current_type.of_type
756
+ is_non_null = true
757
+ end
758
+
759
+ case current_type.kind.name
291
760
  when "SCALAR", "ENUM"
292
- r = type.coerce_result(value, context)
293
- write_in_response(path, r)
761
+ r = begin
762
+ current_type.coerce_result(value, context)
763
+ rescue StandardError => err
764
+ schema.handle_or_reraise(context, err)
765
+ end
766
+ set_result(selection_result, result_name, r, false)
294
767
  r
295
768
  when "UNION", "INTERFACE"
296
- resolved_type_or_lazy = resolve_type(type, value, path)
297
- after_lazy(resolved_type_or_lazy, owner: type, path: path, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |resolved_type|
298
- possible_types = query.possible_types(type)
769
+ resolved_type_or_lazy = resolve_type(current_type, value)
770
+ after_lazy(resolved_type_or_lazy, owner: current_type, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |resolved_type_result|
771
+ if resolved_type_result.is_a?(Array) && resolved_type_result.length == 2
772
+ resolved_type, resolved_value = resolved_type_result
773
+ else
774
+ resolved_type = resolved_type_result
775
+ resolved_value = value
776
+ end
299
777
 
778
+ possible_types = query.possible_types(current_type)
300
779
  if !possible_types.include?(resolved_type)
301
- parent_type = field.owner
302
- type_error = GraphQL::UnresolvedTypeError.new(value, field, parent_type, resolved_type, possible_types)
780
+ parent_type = field.owner_type
781
+ err_class = current_type::UnresolvedTypeError
782
+ type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types)
303
783
  schema.type_error(type_error, context)
304
- write_in_response(path, nil)
784
+ set_result(selection_result, result_name, nil, false)
305
785
  nil
306
786
  else
307
- continue_field(path, value, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments)
787
+ continue_field(resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result)
308
788
  end
309
789
  end
310
790
  when "OBJECT"
311
791
  object_proxy = begin
312
- authorized_new(type, value, context, path)
792
+ authorized_new(current_type, value, context)
313
793
  rescue GraphQL::ExecutionError => err
314
794
  err
315
795
  end
316
- after_lazy(object_proxy, owner: type, path: path, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |inner_object|
317
- continue_value = continue_value(path, inner_object, field, is_non_null, ast_node)
796
+ after_lazy(object_proxy, owner: current_type, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |inner_object|
797
+ continue_value = continue_value(inner_object, owner_type, field, is_non_null, ast_node, result_name, selection_result)
318
798
  if HALT != continue_value
319
- response_hash = {}
320
- write_in_response(path, response_hash)
321
- evaluate_selections(path, context.scoped_context, continue_value, type, next_selections)
322
- response_hash
799
+ response_hash = GraphQLResultHash.new(result_name, selection_result)
800
+ set_result(selection_result, result_name, response_hash, true)
801
+ gathered_selections = gather_selections(continue_value, current_type, next_selections)
802
+ # There are two possibilities for `gathered_selections`:
803
+ # 1. All selections of this object should be evaluated together (there are no runtime directives modifying execution).
804
+ # This case is handled below, and the result can be written right into the main `response_hash` above.
805
+ # In this case, `gathered_selections` is a hash of selections.
806
+ # 2. Some selections of this object have runtime directives that may or may not modify execution.
807
+ # That part of the selection is evaluated in an isolated way, writing into a sub-response object which is
808
+ # eventually merged into the final response. In this case, `gathered_selections` is an array of things to run in isolation.
809
+ # (Technically, it's possible that one of those entries _doesn't_ require isolation.)
810
+ tap_or_each(gathered_selections) do |selections, is_selection_array|
811
+ if is_selection_array
812
+ this_result = GraphQLResultHash.new(result_name, selection_result)
813
+ final_result = response_hash
814
+ else
815
+ this_result = response_hash
816
+ final_result = nil
817
+ end
818
+ # reset this mutable state
819
+ # Unset `result_name` here because it's already included in the new response hash
820
+ st = get_current_runtime_state
821
+ st.current_object = continue_value
822
+ st.current_result_name = nil
823
+ st.current_result = this_result
824
+
825
+ call_method_on_directives(:resolve, continue_value, selections.graphql_directives) do
826
+ evaluate_selections(
827
+ continue_value,
828
+ current_type,
829
+ false,
830
+ selections,
831
+ this_result,
832
+ final_result,
833
+ owner_object.object,
834
+ )
835
+ this_result
836
+ end
837
+ end
323
838
  end
324
839
  end
325
840
  when "LIST"
326
- response_list = []
327
- write_in_response(path, response_list)
328
- inner_type = type.of_type
841
+ inner_type = current_type.of_type
842
+ # This is true for objects, unions, and interfaces
843
+ use_dataloader_job = !inner_type.unwrap.kind.input?
844
+ inner_type_non_null = inner_type.non_null?
845
+ response_list = GraphQLResultArray.new(result_name, selection_result)
846
+ response_list.graphql_non_null_list_items = inner_type_non_null
847
+ set_result(selection_result, result_name, response_list, true)
329
848
  idx = 0
330
- scoped_context = context.scoped_context
331
- value.each do |inner_value|
332
- next_path = path.dup
333
- next_path << idx
334
- next_path.freeze
335
- idx += 1
336
- set_type_at_path(next_path, inner_type)
337
- # This will update `response_list` with the lazy
338
- after_lazy(inner_value, owner: inner_type, path: next_path, scoped_context: scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |inner_inner_value|
339
- # reset `is_non_null` here and below, because the inner type will have its own nullability constraint
340
- continue_value = continue_value(next_path, inner_inner_value, field, false, ast_node)
341
- if HALT != continue_value
342
- continue_field(next_path, continue_value, field, inner_type, ast_node, next_selections, false, owner_object, arguments)
849
+ list_value = begin
850
+ value.each do |inner_value|
851
+ this_idx = idx
852
+ idx += 1
853
+ if use_dataloader_job
854
+ @dataloader.append_job do
855
+ 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)
856
+ end
857
+ else
858
+ 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)
343
859
  end
344
860
  end
861
+
862
+ response_list
863
+ rescue NoMethodError => err
864
+ # Ruby 2.2 doesn't have NoMethodError#receiver, can't check that one in this case. (It's been EOL since 2017.)
865
+ if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true)
866
+ # This happens when the GraphQL schema doesn't match the implementation. Help the dev debug.
867
+ raise ListResultFailedError.new(value: value, field: field, path: current_path)
868
+ else
869
+ # This was some other NoMethodError -- let it bubble to reveal the real error.
870
+ raise
871
+ end
872
+ rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => ex_err
873
+ ex_err
874
+ rescue StandardError => err
875
+ begin
876
+ query.handle_or_reraise(err)
877
+ rescue GraphQL::ExecutionError => ex_err
878
+ ex_err
879
+ end
345
880
  end
346
- response_list
347
- when "NON_NULL"
348
- inner_type = type.of_type
349
- # Don't `set_type_at_path` because we want the static type,
350
- # we're going to use that to determine whether a `nil` should be propagated or not.
351
- continue_field(path, value, field, inner_type, ast_node, next_selections, true, owner_object, arguments)
881
+
882
+ continue_value(list_value, owner_type, field, inner_type.non_null?, ast_node, result_name, selection_result)
352
883
  else
353
- raise "Invariant: Unhandled type kind #{type.kind} (#{type})"
884
+ raise "Invariant: Unhandled type kind #{current_type.kind} (#{current_type})"
354
885
  end
355
886
  end
356
887
 
357
- def resolve_with_directives(object, ast_node)
358
- run_directive(object, ast_node, 0) { yield }
888
+ 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) # rubocop:disable Metrics/ParameterLists
889
+ st = get_current_runtime_state
890
+ st.current_result_name = this_idx
891
+ st.current_result = response_list
892
+ call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do
893
+ # This will update `response_list` with the lazy
894
+ after_lazy(inner_value, owner: inner_type, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, result_name: this_idx, result: response_list) do |inner_inner_value|
895
+ continue_value = continue_value(inner_inner_value, owner_type, field, inner_type_non_null, ast_node, this_idx, response_list)
896
+ if HALT != continue_value
897
+ continue_field(continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list)
898
+ end
899
+ end
900
+ end
359
901
  end
360
902
 
361
- def run_directive(object, ast_node, idx)
362
- dir_node = ast_node.directives[idx]
903
+ def call_method_on_directives(method_name, object, directives, &block)
904
+ return yield if directives.nil? || directives.empty?
905
+ run_directive(method_name, object, directives, 0, &block)
906
+ end
907
+
908
+ def run_directive(method_name, object, directives, idx, &block)
909
+ dir_node = directives[idx]
363
910
  if !dir_node
364
911
  yield
365
912
  else
366
- dir_defn = schema.directives.fetch(dir_node.name)
367
- if !dir_defn.is_a?(Class)
368
- dir_defn = dir_defn.type_class || raise("Only class-based directives are supported (not `@#{dir_node.name}`)")
369
- end
370
- dir_args = arguments(nil, dir_defn, dir_node)
371
- dir_defn.resolve(object, dir_args, context) do
372
- run_directive(object, ast_node, idx + 1) { yield }
913
+ dir_defn = @schema_directives.fetch(dir_node.name)
914
+ raw_dir_args = arguments(nil, dir_defn, dir_node)
915
+ dir_args = continue_value(
916
+ raw_dir_args, # value
917
+ dir_defn, # parent_type
918
+ nil, # field
919
+ false, # is_non_null
920
+ dir_node, # ast_node
921
+ nil, # result_name
922
+ nil, # selection_result
923
+ )
924
+
925
+ if dir_args == HALT
926
+ nil
927
+ else
928
+ dir_defn.public_send(method_name, object, dir_args, context) do
929
+ run_directive(method_name, object, directives, idx + 1, &block)
930
+ end
373
931
  end
374
932
  end
375
933
  end
@@ -377,7 +935,7 @@ module GraphQL
377
935
  # Check {Schema::Directive.include?} for each directive that's present
378
936
  def directives_include?(node, graphql_object, parent_type)
379
937
  node.directives.each do |dir_node|
380
- dir_defn = schema.directives.fetch(dir_node.name).type_class || raise("Only class-based directives are supported (not #{dir_node.name.inspect})")
938
+ dir_defn = @schema_directives.fetch(dir_node.name)
381
939
  args = arguments(graphql_object, dir_defn, dir_node)
382
940
  if !dir_defn.include?(graphql_object, args, context)
383
941
  return false
@@ -386,298 +944,102 @@ module GraphQL
386
944
  true
387
945
  end
388
946
 
947
+ def get_current_runtime_state
948
+ Thread.current[:__graphql_runtime_info] ||= CurrentState.new
949
+ end
950
+
389
951
  # @param obj [Object] Some user-returned value that may want to be batched
390
- # @param path [Array<String>]
391
952
  # @param field [GraphQL::Schema::Field]
392
953
  # @param eager [Boolean] Set to `true` for mutation root fields only
393
954
  # @param trace [Boolean] If `false`, don't wrap this with field tracing
394
955
  # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
395
- def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, eager: false, trace: true)
396
- @interpreter_context[:current_object] = owner_object
397
- @interpreter_context[:current_arguments] = arguments
398
- @interpreter_context[:current_path] = path
399
- @interpreter_context[:current_field] = field
400
- if schema.lazy?(lazy_obj)
401
- lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do
402
- @interpreter_context[:current_path] = path
403
- @interpreter_context[:current_field] = field
404
- @interpreter_context[:current_object] = owner_object
405
- @interpreter_context[:current_arguments] = arguments
406
- context.scoped_context = scoped_context
956
+ def after_lazy(lazy_obj, owner:, field:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, trace: true, &block)
957
+ if lazy?(lazy_obj)
958
+ orig_result = result
959
+ lazy = GraphQL::Execution::Lazy.new(field: field) do
960
+ st = get_current_runtime_state
961
+ st.current_object = owner_object
962
+ st.current_field = field
963
+ st.current_arguments = arguments
964
+ st.current_result_name = result_name
965
+ st.current_result = orig_result
407
966
  # Wrap the execution of _this_ method with tracing,
408
967
  # but don't wrap the continuation below
409
968
  inner_obj = begin
410
- query.with_error_handling do
411
- if trace
412
- query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments}) do
413
- schema.sync_lazy(lazy_obj)
414
- end
415
- else
969
+ if trace
970
+ query.current_trace.execute_field_lazy(field: field, query: query, object: owner_object, arguments: arguments, ast_node: ast_node) do
416
971
  schema.sync_lazy(lazy_obj)
417
972
  end
973
+ else
974
+ schema.sync_lazy(lazy_obj)
975
+ end
976
+ rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => ex_err
977
+ ex_err
978
+ rescue StandardError => err
979
+ begin
980
+ query.handle_or_reraise(err)
981
+ rescue GraphQL::ExecutionError => ex_err
982
+ ex_err
418
983
  end
419
- rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
420
- yield(err)
421
- end
422
- after_lazy(inner_obj, owner: owner, field: field, path: path, scoped_context: context.scoped_context, owner_object: owner_object, arguments: arguments, eager: eager) do |really_inner_obj|
423
- yield(really_inner_obj)
424
984
  end
985
+ yield(inner_obj)
425
986
  end
426
987
 
427
988
  if eager
428
989
  lazy.value
429
990
  else
430
- write_in_response(path, lazy)
991
+ set_result(result, result_name, lazy, false)
992
+ current_depth = 0
993
+ while result
994
+ current_depth += 1
995
+ result = result.graphql_parent
996
+ end
997
+ @lazies_at_depth[current_depth] << lazy
431
998
  lazy
432
999
  end
433
1000
  else
1001
+ # Don't need to reset state here because it _wasn't_ lazy.
434
1002
  yield(lazy_obj)
435
1003
  end
436
1004
  end
437
1005
 
438
- def each_argument_pair(ast_args_or_hash)
439
- case ast_args_or_hash
440
- when GraphQL::Language::Nodes::Field, GraphQL::Language::Nodes::InputObject, GraphQL::Language::Nodes::Directive
441
- ast_args_or_hash.arguments.each do |arg|
442
- yield(arg.name, arg.value)
443
- end
444
- when Hash
445
- ast_args_or_hash.each do |key, value|
446
- normalized_name = GraphQL::Schema::Member::BuildType.camelize(key.to_s)
447
- yield(normalized_name, value)
448
- end
449
- else
450
- raise "Invariant, unexpected #{ast_args_or_hash.inspect}"
451
- end
452
- end
453
-
454
- def arguments(graphql_object, arg_owner, ast_node_or_hash)
455
- kwarg_arguments = {}
456
- arg_defns = arg_owner.arguments
457
- each_argument_pair(ast_node_or_hash) do |arg_name, arg_value|
458
- arg_defn = arg_defns[arg_name]
459
- # Need to distinguish between client-provided `nil`
460
- # and nothing-at-all
461
- is_present, value = arg_to_value(graphql_object, arg_defn.type, arg_value, already_arguments: false)
462
- if is_present
463
- # This doesn't apply to directives, which are legacy
464
- # Can remove this when Skip and Include use classes or something.
465
- if graphql_object
466
- value = arg_defn.prepare_value(graphql_object, value)
467
- end
468
- kwarg_arguments[arg_defn.keyword] = value
469
- end
470
- end
471
- arg_defns.each do |name, arg_defn|
472
- if arg_defn.default_value? && !kwarg_arguments.key?(arg_defn.keyword)
473
- _is_present, value = arg_to_value(graphql_object, arg_defn.type, arg_defn.default_value, already_arguments: false)
474
- kwarg_arguments[arg_defn.keyword] = value
475
- end
476
- end
477
- kwarg_arguments
478
- end
479
-
480
- # TODO CAN THIS USE `.coerce_input` ???
481
-
482
- # Get a Ruby-ready value from a client query.
483
- # @param graphql_object [Object] The owner of the field whose argument this is
484
- # @param arg_type [Class, GraphQL::Schema::NonNull, GraphQL::Schema::List]
485
- # @param ast_value [GraphQL::Language::Nodes::VariableIdentifier, String, Integer, Float, Boolean]
486
- # @param already_arguments [Boolean] if true, don't re-coerce these with `arguments(...)`
487
- # @return [Array(is_present, value)]
488
- def arg_to_value(graphql_object, arg_type, ast_value, already_arguments:)
489
- if ast_value.is_a?(GraphQL::Language::Nodes::VariableIdentifier)
490
- # If it's not here, it will get added later
491
- if query.variables.key?(ast_value.name)
492
- variable_value = query.variables[ast_value.name]
493
- arg_to_value(graphql_object, arg_type, variable_value, already_arguments: true)
494
- else
495
- return false, nil
496
- end
497
- elsif ast_value.is_a?(GraphQL::Language::Nodes::NullValue)
498
- return true, nil
499
- elsif arg_type.is_a?(GraphQL::Schema::NonNull)
500
- arg_to_value(graphql_object, arg_type.of_type, ast_value, already_arguments: already_arguments)
501
- elsif arg_type.is_a?(GraphQL::Schema::List)
502
- if ast_value.nil?
503
- return true, nil
504
- else
505
- # Treat a single value like a list
506
- arg_value = Array(ast_value)
507
- list = []
508
- arg_value.map do |inner_v|
509
- _present, value = arg_to_value(graphql_object, arg_type.of_type, inner_v, already_arguments: already_arguments)
510
- list << value
511
- end
512
- return true, list
513
- end
514
- elsif arg_type.is_a?(Class) && arg_type < GraphQL::Schema::InputObject
515
- if already_arguments
516
- # This came from a variable, already prepared.
517
- # But replace `nil` with `{}` like we would for `arg_type`
518
- if ast_value.nil?
519
- return false, nil
520
- else
521
- return true, ast_value
522
- end
523
- else
524
- # For these, `prepare` is applied during `#initialize`.
525
- # Pass `nil` so it will be skipped in `#arguments`.
526
- # What a mess.
527
- args = arguments(nil, arg_type, ast_value)
528
- end
529
-
530
- input_obj = query.with_error_handling do
531
- # We're not tracking defaults_used, but for our purposes
532
- # we compare the value to the default value.
533
- arg_type.new(ruby_kwargs: args, context: context, defaults_used: nil)
534
- end
535
- return true, input_obj
536
- else
537
- flat_value = if already_arguments
538
- # It was coerced by variable handling
539
- ast_value
540
- else
541
- v = flatten_ast_value(ast_value)
542
- arg_type.coerce_input(v, context)
543
- end
544
- return true, flat_value
545
- end
546
- end
547
-
548
- def flatten_ast_value(v)
549
- case v
550
- when GraphQL::Language::Nodes::Enum
551
- v.name
552
- when GraphQL::Language::Nodes::InputObject
553
- h = {}
554
- v.arguments.each do |arg|
555
- h[arg.name] = flatten_ast_value(arg.value)
556
- end
557
- h
558
- when Array
559
- v.map { |v2| flatten_ast_value(v2) }
560
- when GraphQL::Language::Nodes::VariableIdentifier
561
- flatten_ast_value(query.variables[v.name])
562
- else
563
- v
564
- end
565
- end
566
-
567
- def write_invalid_null_in_response(path, invalid_null_error)
568
- if !dead_path?(path)
569
- schema.type_error(invalid_null_error, context)
570
- write_in_response(path, nil)
571
- add_dead_path(path)
572
- end
573
- end
574
-
575
- def write_execution_errors_in_response(path, errors)
576
- if !dead_path?(path)
577
- errors.each do |v|
578
- context.errors << v
579
- end
580
- write_in_response(path, nil)
581
- add_dead_path(path)
582
- end
583
- end
584
-
585
- def write_in_response(path, value)
586
- if dead_path?(path)
587
- return
1006
+ def arguments(graphql_object, arg_owner, ast_node)
1007
+ if arg_owner.arguments_statically_coercible?
1008
+ query.arguments_for(ast_node, arg_owner)
588
1009
  else
589
- if value.nil? && path.any? && type_at(path).non_null?
590
- # This nil is invalid, try writing it at the previous spot
591
- propagate_path = path[0..-2]
592
- write_in_response(propagate_path, value)
593
- add_dead_path(propagate_path)
594
- else
595
- @response.write(path, value)
596
- end
597
- end
598
- end
599
-
600
- # To propagate nulls, we have to know what the field type was
601
- # at previous parts of the response.
602
- # This hash matches the response
603
- def type_at(path)
604
- t = @types_at_paths
605
- path.each do |part|
606
- t = t[part] || (raise("Invariant: #{part.inspect} not found in #{t}"))
607
- end
608
- t = t[:__type]
609
- t
610
- end
611
-
612
- def set_type_at_path(path, type)
613
- types = @types_at_paths
614
- path.each do |part|
615
- types = types[part] ||= {}
616
- end
617
- # Use this magic key so that the hash contains:
618
- # - string keys for nested fields
619
- # - :__type for the object type of a selection
620
- types[:__type] ||= type
621
- nil
622
- end
623
-
624
- # Mark `path` as having been permanently nulled out.
625
- # No values will be added beyond that path.
626
- def add_dead_path(path)
627
- dead = @dead_paths
628
- path.each do |part|
629
- dead = dead[part] ||= {}
1010
+ # The arguments must be prepared in the context of the given object
1011
+ query.arguments_for(ast_node, arg_owner, parent_object: graphql_object)
630
1012
  end
631
- dead[:__dead] = true
632
1013
  end
633
1014
 
634
- def dead_path?(path)
635
- res = @dead_paths
636
- path.each do |part|
637
- if res
638
- if res[:__dead]
639
- break
640
- else
641
- res = res[part]
642
- end
643
- end
644
- end
645
- res && res[:__dead]
1015
+ def delete_all_interpreter_context
1016
+ Thread.current[:__graphql_runtime_info] = nil
646
1017
  end
647
1018
 
648
- def resolve_type(type, value, path)
649
- trace_payload = { context: context, type: type, object: value, path: path }
650
- resolved_type = query.trace("resolve_type", trace_payload) do
1019
+ def resolve_type(type, value)
1020
+ resolved_type, resolved_value = query.current_trace.resolve_type(query: query, type: type, object: value) do
651
1021
  query.resolve_type(type, value)
652
1022
  end
653
1023
 
654
- if schema.lazy?(resolved_type)
1024
+ if lazy?(resolved_type)
655
1025
  GraphQL::Execution::Lazy.new do
656
- query.trace("resolve_type_lazy", trace_payload) do
1026
+ query.current_trace.resolve_type_lazy(query: query, type: type, object: value) do
657
1027
  schema.sync_lazy(resolved_type)
658
1028
  end
659
1029
  end
660
1030
  else
661
- resolved_type
1031
+ [resolved_type, resolved_value]
662
1032
  end
663
1033
  end
664
1034
 
665
- def authorized_new(type, value, context, path)
666
- trace_payload = { context: context, type: type, object: value, path: path }
667
-
668
- auth_val = context.query.trace("authorized", trace_payload) do
669
- type.authorized_new(value, context)
670
- end
1035
+ def authorized_new(type, value, context)
1036
+ type.authorized_new(value, context)
1037
+ end
671
1038
 
672
- if context.schema.lazy?(auth_val)
673
- GraphQL::Execution::Lazy.new do
674
- context.query.trace("authorized_lazy", trace_payload) do
675
- context.schema.sync_lazy(auth_val)
676
- end
677
- end
678
- else
679
- auth_val
680
- end
1039
+ def lazy?(object)
1040
+ @lazy_cache.fetch(object.class) {
1041
+ @lazy_cache[object.class] = @schema.lazy?(object)
1042
+ }
681
1043
  end
682
1044
  end
683
1045
  end