graphql 1.9.17 → 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 (416) 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 +25 -27
  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 +31 -2
  49. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -1
  50. data/lib/graphql/analysis/ast/query_complexity.rb +175 -68
  51. data/lib/graphql/analysis/ast/query_depth.rb +0 -1
  52. data/lib/graphql/analysis/ast/visitor.rb +54 -38
  53. data/lib/graphql/analysis/ast.rb +16 -16
  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 +37 -16
  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/directive_checks.rb +2 -2
  70. data/lib/graphql/execution/errors.rb +77 -45
  71. data/lib/graphql/execution/interpreter/argument_value.rb +28 -0
  72. data/lib/graphql/execution/interpreter/arguments.rb +88 -0
  73. data/lib/graphql/execution/interpreter/arguments_cache.rb +104 -0
  74. data/lib/graphql/execution/interpreter/handles_raw_value.rb +18 -0
  75. data/lib/graphql/execution/interpreter/resolve.rb +62 -24
  76. data/lib/graphql/execution/interpreter/runtime.rb +830 -417
  77. data/lib/graphql/execution/interpreter.rb +206 -74
  78. data/lib/graphql/execution/lazy/lazy_method_map.rb +4 -0
  79. data/lib/graphql/execution/lazy.rb +11 -21
  80. data/lib/graphql/execution/lookahead.rb +65 -136
  81. data/lib/graphql/execution/multiplex.rb +6 -152
  82. data/lib/graphql/execution.rb +11 -4
  83. data/lib/graphql/filter.rb +8 -3
  84. data/lib/graphql/integer_decoding_error.rb +17 -0
  85. data/lib/graphql/integer_encoding_error.rb +18 -2
  86. data/lib/graphql/introspection/base_object.rb +2 -5
  87. data/lib/graphql/introspection/directive_location_enum.rb +2 -2
  88. data/lib/graphql/introspection/directive_type.rb +12 -6
  89. data/lib/graphql/introspection/dynamic_fields.rb +3 -8
  90. data/lib/graphql/introspection/entry_points.rb +5 -18
  91. data/lib/graphql/introspection/enum_value_type.rb +2 -2
  92. data/lib/graphql/introspection/field_type.rb +9 -5
  93. data/lib/graphql/introspection/input_value_type.rb +41 -11
  94. data/lib/graphql/introspection/introspection_query.rb +6 -92
  95. data/lib/graphql/introspection/schema_type.rb +12 -12
  96. data/lib/graphql/introspection/type_type.rb +34 -17
  97. data/lib/graphql/introspection.rb +100 -0
  98. data/lib/graphql/invalid_null_error.rb +18 -0
  99. data/lib/graphql/language/block_string.rb +20 -5
  100. data/lib/graphql/language/cache.rb +37 -0
  101. data/lib/graphql/language/definition_slice.rb +21 -10
  102. data/lib/graphql/language/document_from_schema_definition.rb +136 -78
  103. data/lib/graphql/language/lexer.rb +216 -1462
  104. data/lib/graphql/language/nodes.rb +129 -132
  105. data/lib/graphql/language/parser.rb +994 -932
  106. data/lib/graphql/language/parser.y +152 -120
  107. data/lib/graphql/language/printer.rb +48 -23
  108. data/lib/graphql/language/sanitized_printer.rb +222 -0
  109. data/lib/graphql/language/token.rb +0 -4
  110. data/lib/graphql/language/visitor.rb +192 -84
  111. data/lib/graphql/language.rb +3 -1
  112. data/lib/graphql/name_validator.rb +2 -7
  113. data/lib/graphql/pagination/active_record_relation_connection.rb +77 -0
  114. data/lib/graphql/pagination/array_connection.rb +79 -0
  115. data/lib/graphql/pagination/connection.rb +253 -0
  116. data/lib/graphql/pagination/connections.rb +135 -0
  117. data/lib/graphql/pagination/mongoid_relation_connection.rb +25 -0
  118. data/lib/graphql/pagination/relation_connection.rb +228 -0
  119. data/lib/graphql/pagination/sequel_dataset_connection.rb +28 -0
  120. data/lib/graphql/pagination.rb +6 -0
  121. data/lib/graphql/parse_error.rb +0 -1
  122. data/lib/graphql/query/context.rb +205 -203
  123. data/lib/graphql/query/fingerprint.rb +26 -0
  124. data/lib/graphql/query/input_validation_result.rb +33 -7
  125. data/lib/graphql/query/null_context.rb +22 -9
  126. data/lib/graphql/query/validation_pipeline.rb +16 -38
  127. data/lib/graphql/query/variable_validation_error.rb +3 -3
  128. data/lib/graphql/query/variables.rb +39 -12
  129. data/lib/graphql/query.rb +95 -43
  130. data/lib/graphql/railtie.rb +6 -102
  131. data/lib/graphql/rake_task/validate.rb +4 -1
  132. data/lib/graphql/rake_task.rb +41 -10
  133. data/lib/graphql/relay/range_add.rb +17 -10
  134. data/lib/graphql/relay.rb +0 -15
  135. data/lib/graphql/rubocop/graphql/base_cop.rb +36 -0
  136. data/lib/graphql/rubocop/graphql/default_null_true.rb +43 -0
  137. data/lib/graphql/rubocop/graphql/default_required_true.rb +43 -0
  138. data/lib/graphql/rubocop.rb +4 -0
  139. data/lib/graphql/schema/addition.rb +245 -0
  140. data/lib/graphql/schema/argument.rb +285 -36
  141. data/lib/graphql/schema/base_64_encoder.rb +2 -0
  142. data/lib/graphql/schema/build_from_definition/resolve_map/default_resolve.rb +1 -1
  143. data/lib/graphql/schema/build_from_definition/resolve_map.rb +13 -5
  144. data/lib/graphql/schema/build_from_definition.rb +348 -205
  145. data/lib/graphql/schema/built_in_types.rb +5 -5
  146. data/lib/graphql/schema/directive/deprecated.rb +18 -0
  147. data/lib/graphql/schema/directive/feature.rb +1 -1
  148. data/lib/graphql/schema/directive/flagged.rb +57 -0
  149. data/lib/graphql/schema/directive/include.rb +2 -2
  150. data/lib/graphql/schema/directive/one_of.rb +12 -0
  151. data/lib/graphql/schema/directive/skip.rb +2 -2
  152. data/lib/graphql/schema/directive/transform.rb +14 -2
  153. data/lib/graphql/schema/directive.rb +134 -15
  154. data/lib/graphql/schema/enum.rb +137 -39
  155. data/lib/graphql/schema/enum_value.rb +17 -23
  156. data/lib/graphql/schema/field/connection_extension.rb +50 -20
  157. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  158. data/lib/graphql/schema/field.rb +504 -331
  159. data/lib/graphql/schema/field_extension.rb +86 -2
  160. data/lib/graphql/schema/find_inherited_value.rb +12 -1
  161. data/lib/graphql/schema/finder.rb +16 -14
  162. data/lib/graphql/schema/input_object.rb +182 -60
  163. data/lib/graphql/schema/interface.rb +24 -49
  164. data/lib/graphql/schema/introspection_system.rb +103 -37
  165. data/lib/graphql/schema/late_bound_type.rb +9 -2
  166. data/lib/graphql/schema/list.rb +61 -3
  167. data/lib/graphql/schema/loader.rb +144 -96
  168. data/lib/graphql/schema/member/base_dsl_methods.rb +41 -37
  169. data/lib/graphql/schema/member/build_type.rb +24 -15
  170. data/lib/graphql/schema/member/has_arguments.rb +310 -26
  171. data/lib/graphql/schema/member/has_ast_node.rb +32 -0
  172. data/lib/graphql/schema/member/has_deprecation_reason.rb +24 -0
  173. data/lib/graphql/schema/member/has_directives.rb +118 -0
  174. data/lib/graphql/schema/member/has_fields.rb +166 -44
  175. data/lib/graphql/schema/member/has_interfaces.rb +129 -0
  176. data/lib/graphql/schema/member/has_unresolved_type_error.rb +15 -0
  177. data/lib/graphql/schema/member/has_validators.rb +57 -0
  178. data/lib/graphql/schema/member/relay_shortcuts.rb +47 -2
  179. data/lib/graphql/schema/member/type_system_helpers.rb +20 -3
  180. data/lib/graphql/schema/member/validates_input.rb +33 -0
  181. data/lib/graphql/schema/member.rb +11 -6
  182. data/lib/graphql/schema/mutation.rb +4 -9
  183. data/lib/graphql/schema/non_null.rb +34 -4
  184. data/lib/graphql/schema/object.rb +36 -60
  185. data/lib/graphql/schema/printer.rb +16 -35
  186. data/lib/graphql/schema/relay_classic_mutation.rb +91 -44
  187. data/lib/graphql/schema/resolver/has_payload_type.rb +51 -11
  188. data/lib/graphql/schema/resolver.rb +147 -94
  189. data/lib/graphql/schema/scalar.rb +40 -15
  190. data/lib/graphql/schema/subscription.rb +60 -31
  191. data/lib/graphql/schema/timeout.rb +45 -35
  192. data/lib/graphql/schema/type_expression.rb +21 -13
  193. data/lib/graphql/schema/type_membership.rb +23 -6
  194. data/lib/graphql/schema/union.rb +49 -15
  195. data/lib/graphql/schema/unique_within_type.rb +1 -2
  196. data/lib/graphql/schema/validator/allow_blank_validator.rb +29 -0
  197. data/lib/graphql/schema/validator/allow_null_validator.rb +26 -0
  198. data/lib/graphql/schema/validator/exclusion_validator.rb +33 -0
  199. data/lib/graphql/schema/validator/format_validator.rb +48 -0
  200. data/lib/graphql/schema/validator/inclusion_validator.rb +35 -0
  201. data/lib/graphql/schema/validator/length_validator.rb +59 -0
  202. data/lib/graphql/schema/validator/numericality_validator.rb +82 -0
  203. data/lib/graphql/schema/validator/required_validator.rb +82 -0
  204. data/lib/graphql/schema/validator.rb +171 -0
  205. data/lib/graphql/schema/warden.rb +213 -35
  206. data/lib/graphql/schema/wrapper.rb +0 -5
  207. data/lib/graphql/schema.rb +857 -884
  208. data/lib/graphql/static_validation/all_rules.rb +3 -0
  209. data/lib/graphql/static_validation/base_visitor.rb +21 -31
  210. data/lib/graphql/static_validation/definition_dependencies.rb +7 -2
  211. data/lib/graphql/static_validation/error.rb +3 -1
  212. data/lib/graphql/static_validation/literal_validator.rb +69 -26
  213. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +45 -83
  214. data/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb +22 -6
  215. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +35 -26
  216. data/lib/graphql/static_validation/rules/arguments_are_defined_error.rb +4 -2
  217. data/lib/graphql/static_validation/rules/directives_are_defined.rb +12 -6
  218. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +14 -14
  219. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -4
  220. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +15 -7
  221. data/lib/graphql/static_validation/rules/fields_will_merge.rb +96 -53
  222. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +25 -4
  223. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  224. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -2
  225. data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +30 -0
  226. data/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb +30 -0
  227. data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid.rb +66 -0
  228. data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid_error.rb +29 -0
  229. data/lib/graphql/static_validation/rules/query_root_exists.rb +17 -0
  230. data/lib/graphql/static_validation/rules/query_root_exists_error.rb +26 -0
  231. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +4 -2
  232. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +9 -10
  233. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +13 -7
  234. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +12 -13
  235. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +19 -14
  236. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
  237. data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +5 -3
  238. data/lib/graphql/static_validation/type_stack.rb +2 -2
  239. data/lib/graphql/static_validation/validation_context.rb +13 -3
  240. data/lib/graphql/static_validation/validation_timeout_error.rb +25 -0
  241. data/lib/graphql/static_validation/validator.rb +32 -20
  242. data/lib/graphql/static_validation.rb +1 -2
  243. data/lib/graphql/string_encoding_error.rb +13 -3
  244. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +129 -22
  245. data/lib/graphql/subscriptions/broadcast_analyzer.rb +81 -0
  246. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +58 -0
  247. data/lib/graphql/subscriptions/event.rb +84 -35
  248. data/lib/graphql/subscriptions/instrumentation.rb +0 -47
  249. data/lib/graphql/subscriptions/serialize.rb +53 -6
  250. data/lib/graphql/subscriptions.rb +137 -57
  251. data/lib/graphql/tracing/active_support_notifications_trace.rb +16 -0
  252. data/lib/graphql/tracing/active_support_notifications_tracing.rb +8 -17
  253. data/lib/graphql/tracing/appoptics_trace.rb +231 -0
  254. data/lib/graphql/tracing/appoptics_tracing.rb +173 -0
  255. data/lib/graphql/tracing/appsignal_trace.rb +77 -0
  256. data/lib/graphql/tracing/appsignal_tracing.rb +23 -0
  257. data/lib/graphql/tracing/data_dog_trace.rb +148 -0
  258. data/lib/graphql/tracing/data_dog_tracing.rb +34 -2
  259. data/lib/graphql/tracing/legacy_trace.rb +65 -0
  260. data/lib/graphql/tracing/new_relic_trace.rb +75 -0
  261. data/lib/graphql/tracing/new_relic_tracing.rb +9 -12
  262. data/lib/graphql/tracing/notifications_trace.rb +42 -0
  263. data/lib/graphql/tracing/notifications_tracing.rb +59 -0
  264. data/lib/graphql/tracing/platform_trace.rb +109 -0
  265. data/lib/graphql/tracing/platform_tracing.rb +76 -35
  266. data/lib/graphql/tracing/prometheus_trace.rb +89 -0
  267. data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +5 -2
  268. data/lib/graphql/tracing/prometheus_tracing.rb +11 -3
  269. data/lib/graphql/tracing/scout_trace.rb +72 -0
  270. data/lib/graphql/tracing/scout_tracing.rb +19 -0
  271. data/lib/graphql/tracing/statsd_trace.rb +56 -0
  272. data/lib/graphql/tracing/statsd_tracing.rb +42 -0
  273. data/lib/graphql/tracing/trace.rb +75 -0
  274. data/lib/graphql/tracing.rb +23 -67
  275. data/lib/graphql/type_kinds.rb +6 -3
  276. data/lib/graphql/types/big_int.rb +5 -1
  277. data/lib/graphql/types/int.rb +10 -3
  278. data/lib/graphql/types/iso_8601_date.rb +20 -9
  279. data/lib/graphql/types/iso_8601_date_time.rb +36 -10
  280. data/lib/graphql/types/relay/base_connection.rb +18 -90
  281. data/lib/graphql/types/relay/base_edge.rb +2 -34
  282. data/lib/graphql/types/relay/connection_behaviors.rb +176 -0
  283. data/lib/graphql/types/relay/edge_behaviors.rb +75 -0
  284. data/lib/graphql/types/relay/has_node_field.rb +41 -0
  285. data/lib/graphql/types/relay/has_nodes_field.rb +41 -0
  286. data/lib/graphql/types/relay/node.rb +2 -4
  287. data/lib/graphql/types/relay/node_behaviors.rb +25 -0
  288. data/lib/graphql/types/relay/page_info.rb +2 -14
  289. data/lib/graphql/types/relay/page_info_behaviors.rb +30 -0
  290. data/lib/graphql/types/relay.rb +10 -5
  291. data/lib/graphql/types/string.rb +8 -2
  292. data/lib/graphql/unauthorized_error.rb +2 -2
  293. data/lib/graphql/unresolved_type_error.rb +2 -2
  294. data/lib/graphql/version.rb +1 -1
  295. data/lib/graphql.rb +63 -65
  296. data/readme.md +3 -6
  297. metadata +127 -236
  298. data/lib/graphql/analysis/analyze_query.rb +0 -91
  299. data/lib/graphql/analysis/field_usage.rb +0 -45
  300. data/lib/graphql/analysis/max_query_complexity.rb +0 -26
  301. data/lib/graphql/analysis/max_query_depth.rb +0 -26
  302. data/lib/graphql/analysis/query_complexity.rb +0 -88
  303. data/lib/graphql/analysis/query_depth.rb +0 -43
  304. data/lib/graphql/analysis/reducer_state.rb +0 -48
  305. data/lib/graphql/argument.rb +0 -159
  306. data/lib/graphql/authorization.rb +0 -82
  307. data/lib/graphql/backwards_compatibility.rb +0 -60
  308. data/lib/graphql/base_type.rb +0 -226
  309. data/lib/graphql/boolean_type.rb +0 -2
  310. data/lib/graphql/compatibility/execution_specification/counter_schema.rb +0 -53
  311. data/lib/graphql/compatibility/execution_specification/specification_schema.rb +0 -200
  312. data/lib/graphql/compatibility/execution_specification.rb +0 -435
  313. data/lib/graphql/compatibility/lazy_execution_specification/lazy_schema.rb +0 -111
  314. data/lib/graphql/compatibility/lazy_execution_specification.rb +0 -213
  315. data/lib/graphql/compatibility/query_parser_specification/parse_error_specification.rb +0 -91
  316. data/lib/graphql/compatibility/query_parser_specification/query_assertions.rb +0 -79
  317. data/lib/graphql/compatibility/query_parser_specification.rb +0 -264
  318. data/lib/graphql/compatibility/schema_parser_specification.rb +0 -680
  319. data/lib/graphql/compatibility.rb +0 -5
  320. data/lib/graphql/define/assign_argument.rb +0 -12
  321. data/lib/graphql/define/assign_connection.rb +0 -13
  322. data/lib/graphql/define/assign_enum_value.rb +0 -18
  323. data/lib/graphql/define/assign_global_id_field.rb +0 -11
  324. data/lib/graphql/define/assign_mutation_function.rb +0 -34
  325. data/lib/graphql/define/assign_object_field.rb +0 -42
  326. data/lib/graphql/define/defined_object_proxy.rb +0 -50
  327. data/lib/graphql/define/instance_definable.rb +0 -300
  328. data/lib/graphql/define/no_definition_error.rb +0 -7
  329. data/lib/graphql/define/non_null_with_bang.rb +0 -16
  330. data/lib/graphql/define/type_definer.rb +0 -31
  331. data/lib/graphql/define.rb +0 -31
  332. data/lib/graphql/deprecated_dsl.rb +0 -42
  333. data/lib/graphql/directive/deprecated_directive.rb +0 -13
  334. data/lib/graphql/directive/include_directive.rb +0 -2
  335. data/lib/graphql/directive/skip_directive.rb +0 -2
  336. data/lib/graphql/directive.rb +0 -104
  337. data/lib/graphql/enum_type.rb +0 -193
  338. data/lib/graphql/execution/execute.rb +0 -326
  339. data/lib/graphql/execution/flatten.rb +0 -40
  340. data/lib/graphql/execution/instrumentation.rb +0 -92
  341. data/lib/graphql/execution/interpreter/hash_response.rb +0 -46
  342. data/lib/graphql/execution/lazy/resolve.rb +0 -91
  343. data/lib/graphql/execution/typecast.rb +0 -50
  344. data/lib/graphql/field/resolve.rb +0 -59
  345. data/lib/graphql/field.rb +0 -330
  346. data/lib/graphql/float_type.rb +0 -2
  347. data/lib/graphql/function.rb +0 -153
  348. data/lib/graphql/id_type.rb +0 -2
  349. data/lib/graphql/input_object_type.rb +0 -154
  350. data/lib/graphql/int_type.rb +0 -2
  351. data/lib/graphql/interface_type.rb +0 -86
  352. data/lib/graphql/internal_representation/document.rb +0 -27
  353. data/lib/graphql/internal_representation/node.rb +0 -206
  354. data/lib/graphql/internal_representation/print.rb +0 -51
  355. data/lib/graphql/internal_representation/rewrite.rb +0 -184
  356. data/lib/graphql/internal_representation/scope.rb +0 -88
  357. data/lib/graphql/internal_representation/visit.rb +0 -36
  358. data/lib/graphql/internal_representation.rb +0 -7
  359. data/lib/graphql/language/lexer.rl +0 -258
  360. data/lib/graphql/list_type.rb +0 -80
  361. data/lib/graphql/literal_validation_error.rb +0 -6
  362. data/lib/graphql/non_null_type.rb +0 -81
  363. data/lib/graphql/object_type.rb +0 -141
  364. data/lib/graphql/query/arguments.rb +0 -187
  365. data/lib/graphql/query/arguments_cache.rb +0 -25
  366. data/lib/graphql/query/executor.rb +0 -53
  367. data/lib/graphql/query/literal_input.rb +0 -116
  368. data/lib/graphql/query/serial_execution/field_resolution.rb +0 -92
  369. data/lib/graphql/query/serial_execution/operation_resolution.rb +0 -19
  370. data/lib/graphql/query/serial_execution/selection_resolution.rb +0 -23
  371. data/lib/graphql/query/serial_execution/value_resolution.rb +0 -87
  372. data/lib/graphql/query/serial_execution.rb +0 -39
  373. data/lib/graphql/relay/array_connection.rb +0 -85
  374. data/lib/graphql/relay/base_connection.rb +0 -172
  375. data/lib/graphql/relay/connection_instrumentation.rb +0 -54
  376. data/lib/graphql/relay/connection_resolve.rb +0 -43
  377. data/lib/graphql/relay/connection_type.rb +0 -40
  378. data/lib/graphql/relay/edge.rb +0 -27
  379. data/lib/graphql/relay/edge_type.rb +0 -18
  380. data/lib/graphql/relay/edges_instrumentation.rb +0 -40
  381. data/lib/graphql/relay/global_id_resolve.rb +0 -18
  382. data/lib/graphql/relay/mongo_relation_connection.rb +0 -50
  383. data/lib/graphql/relay/mutation/instrumentation.rb +0 -23
  384. data/lib/graphql/relay/mutation/resolve.rb +0 -56
  385. data/lib/graphql/relay/mutation/result.rb +0 -38
  386. data/lib/graphql/relay/mutation.rb +0 -190
  387. data/lib/graphql/relay/node.rb +0 -36
  388. data/lib/graphql/relay/page_info.rb +0 -7
  389. data/lib/graphql/relay/relation_connection.rb +0 -190
  390. data/lib/graphql/relay/type_extensions.rb +0 -30
  391. data/lib/graphql/scalar_type.rb +0 -133
  392. data/lib/graphql/schema/catchall_middleware.rb +0 -35
  393. data/lib/graphql/schema/default_parse_error.rb +0 -10
  394. data/lib/graphql/schema/default_type_error.rb +0 -15
  395. data/lib/graphql/schema/member/accepts_definition.rb +0 -152
  396. data/lib/graphql/schema/member/cached_graphql_definition.rb +0 -26
  397. data/lib/graphql/schema/member/instrumentation.rb +0 -132
  398. data/lib/graphql/schema/middleware_chain.rb +0 -82
  399. data/lib/graphql/schema/possible_types.rb +0 -39
  400. data/lib/graphql/schema/rescue_middleware.rb +0 -60
  401. data/lib/graphql/schema/timeout_middleware.rb +0 -86
  402. data/lib/graphql/schema/traversal.rb +0 -228
  403. data/lib/graphql/schema/validation.rb +0 -303
  404. data/lib/graphql/static_validation/default_visitor.rb +0 -15
  405. data/lib/graphql/static_validation/no_validate_visitor.rb +0 -10
  406. data/lib/graphql/string_type.rb +0 -2
  407. data/lib/graphql/subscriptions/subscription_root.rb +0 -66
  408. data/lib/graphql/tracing/skylight_tracing.rb +0 -62
  409. data/lib/graphql/types/relay/base_field.rb +0 -22
  410. data/lib/graphql/types/relay/base_interface.rb +0 -29
  411. data/lib/graphql/types/relay/base_object.rb +0 -26
  412. data/lib/graphql/types/relay/node_field.rb +0 -43
  413. data/lib/graphql/types/relay/nodes_field.rb +0 -45
  414. data/lib/graphql/union_type.rb +0 -128
  415. data/lib/graphql/upgrader/member.rb +0 -936
  416. 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,61 +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
- legacy_root_type = schema.root_type_for_operation(root_op_type)
50
- root_type = legacy_root_type.metadata[:type_class] || raise("Invariant: type must be class-based: #{legacy_root_type}")
51
- path = []
52
- @interpreter_context[:current_object] = query.root_value
53
- @interpreter_context[:current_path] = path
54
- object_proxy = root_type.authorized_new(query.root_value, context)
256
+ root_type = schema.root_type_for_operation(root_op_type)
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)
55
261
  object_proxy = schema.sync_lazy(object_proxy)
262
+
56
263
  if object_proxy.nil?
57
264
  # Root .authorized? returned false.
58
- write_in_response(path, nil)
59
- nil
265
+ @response = nil
60
266
  else
61
- evaluate_selections(path, context.scoped_context, object_proxy, root_type, root_operation.selections, root_operation_type: root_op_type)
62
- 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
63
304
  end
305
+ delete_all_interpreter_context
306
+ nil
64
307
  end
65
308
 
66
- 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)
67
310
  selections.each do |node|
68
311
  # Skip gathering this if the directive says so
69
312
  if !directives_include?(node, owner_object, owner_type)
70
313
  next
71
314
  end
72
315
 
73
- case node
74
- when GraphQL::Language::Nodes::Field
316
+ if node.is_a?(GraphQL::Language::Nodes::Field)
75
317
  response_key = node.alias || node.name
76
318
  selections = selections_by_name[response_key]
77
319
  # if there was already a selection of this field,
@@ -87,193 +329,413 @@ module GraphQL
87
329
  # No selection was found for this field yet
88
330
  selections_by_name[response_key] = node
89
331
  end
90
- when GraphQL::Language::Nodes::InlineFragment
91
- if node.type
92
- type_defn = schema.types[node.type.name]
93
- type_defn = type_defn.metadata[:type_class]
94
- # Faster than .map{}.include?()
95
- query.warden.possible_types(type_defn).each do |t|
96
- if t.metadata[:type_class] == owner_type
97
- gather_selections(owner_object, owner_type, node.selections, selections_by_name)
98
- break
99
- end
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
100
343
  end
101
344
  else
102
- # it's an untyped fragment, definitely continue
103
- gather_selections(owner_object, owner_type, node.selections, selections_by_name)
345
+ next_selections = selections_by_name
104
346
  end
105
- when GraphQL::Language::Nodes::FragmentSpread
106
- fragment_def = query.fragments[node.name]
107
- type_defn = schema.types[fragment_def.type.name]
108
- type_defn = type_defn.metadata[:type_class]
109
- schema.possible_types(type_defn).each do |t|
110
- if t.metadata[:type_class] == owner_type
111
- gather_selections(owner_object, owner_type, fragment_def.selections, selections_by_name)
112
- break
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)
113
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|
369
+ if t == owner_type
370
+ gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections)
371
+ break
372
+ end
373
+ end
374
+ else
375
+ raise "Invariant: unexpected selection class: #{node.class}"
114
376
  end
115
- else
116
- raise "Invariant: unexpected selection class: #{node.class}"
117
377
  end
118
378
  end
379
+ selections_to_run || selections_by_name
119
380
  end
120
381
 
121
- def evaluate_selections(path, scoped_context, owner_object, owner_type, selections, root_operation_type: nil)
122
- @interpreter_context[:current_object] = owner_object
123
- @interpreter_context[:current_path] = path
124
- selections_by_name = {}
125
- gather_selections(owner_object, owner_type, selections, selections_by_name)
126
- selections_by_name.each do |result_name, field_ast_nodes_or_ast_node|
127
- # As a performance optimization, the hash key will be a `Node` if
128
- # there's only one selection of the field. But if there are multiple
129
- # selections of the field, it will be an Array of nodes
130
- if field_ast_nodes_or_ast_node.is_a?(Array)
131
- field_ast_nodes = field_ast_nodes_or_ast_node
132
- ast_node = field_ast_nodes.first
133
- else
134
- field_ast_nodes = nil
135
- ast_node = field_ast_nodes_or_ast_node
136
- end
137
- field_name = ast_node.name
138
- field_defn = @fields_cache[owner_type][field_name] ||= owner_type.get_field(field_name)
139
- is_introspection = false
140
- if field_defn.nil?
141
- field_defn = if owner_type == schema.query.metadata[:type_class] && (entry_point_field = schema.introspection_system.entry_point(name: field_name))
142
- is_introspection = true
143
- entry_point_field.metadata[:type_class]
144
- elsif (dynamic_field = schema.introspection_system.dynamic_field(name: field_name))
145
- is_introspection = true
146
- dynamic_field.metadata[:type_class]
147
- else
148
- 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)
149
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}"
150
435
  end
436
+ end
151
437
 
152
- return_type = resolve_if_late_bound_type(field_defn.type)
438
+ return_type = field_defn.type
153
439
 
154
- next_path = path.dup
155
- next_path << result_name
156
- 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
157
452
 
158
- # This seems janky, but we need to know
159
- # the field's return type at this path in order
160
- # to propagate `null`
161
- set_type_at_path(next_path, return_type)
162
- # Set this before calling `run_with_directives`, so that the directive can have the latest path
163
- @interpreter_context[:current_path] = next_path
164
- @interpreter_context[:current_field] = field_defn
453
+ object = owner_object
165
454
 
166
- context.scoped_context = scoped_context
167
- object = owner_object
455
+ if is_introspection
456
+ object = authorized_new(field_defn.owner, object, context)
457
+ end
168
458
 
169
- if is_introspection
170
- object = field_defn.owner.authorized_new(object, context)
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)
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)
171
472
  end
473
+ end
474
+ end
172
475
 
173
- begin
174
- kwarg_arguments = arguments(object, field_defn, ast_node)
175
- rescue GraphQL::ExecutionError => e
176
- 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)
177
480
  next
178
481
  end
179
482
 
180
- # It might turn out that making arguments for every field is slow.
181
- # If we have to cache them, we'll need a more subtle approach here.
182
- field_defn.extras.each do |extra|
183
- case extra
184
- when :ast_node
185
- kwarg_arguments[:ast_node] = ast_node
186
- when :execution_errors
187
- kwarg_arguments[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
188
- when :path
189
- kwarg_arguments[:path] = next_path
190
- when :lookahead
191
- if !field_ast_nodes
192
- field_ast_nodes = [ast_node]
193
- end
194
- kwarg_arguments[:lookahead] = Execution::Lookahead.new(
195
- query: query,
196
- ast_nodes: field_ast_nodes,
197
- field: field_defn,
198
- )
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
199
487
  else
200
- kwarg_arguments[extra] = field_defn.fetch_extra(extra, context)
488
+ resolved_arguments.keyword_arguments
201
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)
524
+ end
525
+ resolved_arguments.keyword_arguments
202
526
  end
203
527
 
204
- @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
205
531
 
206
- # Optimize for the case that field is selected only once
207
- if field_ast_nodes.nil? || field_ast_nodes.size == 1
208
- next_selections = ast_node.selections
209
- else
210
- next_selections = []
211
- field_ast_nodes.each { |f| next_selections.concat(f.selections) }
212
- 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
213
551
 
214
- field_result = resolve_with_directives(object, ast_node) do
215
- # Actually call the field resolver and capture the result
216
- app_result = begin
217
- query.with_error_handling do
218
- query.trace("execute_field", {owner: owner_type, field: field_defn, path: next_path, query: query, object: object, arguments: kwarg_arguments}) do
219
- field_defn.resolve(object, kwarg_arguments, context)
220
- end
221
- end
222
- rescue GraphQL::ExecutionError => err
223
- 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)
224
557
  end
225
- 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|
226
- continue_value = continue_value(next_path, inner_result, field_defn, return_type.non_null?, ast_node)
227
- if HALT != continue_value
228
- continue_field(next_path, continue_value, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments)
229
- 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
565
+ end
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)
230
571
  end
231
572
  end
573
+ end
574
+
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
232
590
 
233
- # If this field is a root mutation field, immediately resolve
234
- # all of its child fields before moving on to the next root mutation field.
235
- # (Subselections of this mutation will still be resolved level-by-level.)
236
- if root_operation_type == "mutation"
237
- Interpreter::Resolve.resolve_all([field_result])
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)
238
616
  else
239
- field_result
617
+ selection_result.set_leaf(result_name, value)
240
618
  end
241
619
  end
242
620
  end
243
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
+
244
648
  HALT = Object.new
245
- def continue_value(path, value, field, is_non_null, ast_node)
246
- 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
247
652
  if is_non_null
248
- err = GraphQL::InvalidNullError.new(field.owner, field, value)
249
- 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
250
658
  else
251
- write_in_response(path, nil)
659
+ set_result(selection_result, result_name, nil, false)
252
660
  end
253
661
  HALT
254
- elsif value.is_a?(GraphQL::ExecutionError)
255
- value.path ||= path
256
- value.ast_node ||= ast_node
257
- write_execution_errors_in_response(path, [value])
258
- HALT
259
- elsif value.is_a?(Array) && value.any? && value.all? { |v| v.is_a?(GraphQL::ExecutionError) }
260
- value.each_with_index do |error, index|
261
- error.ast_node ||= ast_node
262
- 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
263
712
  end
264
- write_execution_errors_in_response(path, value)
265
- HALT
266
- elsif value.is_a?(GraphQL::UnauthorizedError)
267
- # this hook might raise & crash, or it might return
268
- # a replacement value
269
- next_value = begin
270
- schema.unauthorized_object(value)
271
- rescue GraphQL::ExecutionError => err
272
- 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
273
735
  end
274
-
275
- continue_value(path, next_value, field, is_non_null, ast_node)
276
- 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)
277
739
  HALT
278
740
  else
279
741
  value
@@ -288,93 +750,184 @@ module GraphQL
288
750
  # Location information from `path` and `ast_node`.
289
751
  #
290
752
  # @return [Lazy, Array, Hash, Object] Lazy, Array, and Hash are all traversed to resolve lazy values later
291
- def continue_field(path, value, field, type, ast_node, next_selections, is_non_null, owner_object, arguments) # rubocop:disable Metrics/ParameterLists
292
- 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
293
760
  when "SCALAR", "ENUM"
294
- r = type.coerce_result(value, context)
295
- 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)
296
767
  r
297
768
  when "UNION", "INTERFACE"
298
- resolved_type_or_lazy = query.resolve_type(type, value)
299
- after_lazy(resolved_type_or_lazy, owner: type, path: path, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |resolved_type|
300
- 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
301
777
 
778
+ possible_types = query.possible_types(current_type)
302
779
  if !possible_types.include?(resolved_type)
303
- parent_type = field.owner
304
- 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)
305
783
  schema.type_error(type_error, context)
306
- write_in_response(path, nil)
784
+ set_result(selection_result, result_name, nil, false)
307
785
  nil
308
786
  else
309
- resolved_type = resolved_type.metadata[:type_class]
310
- 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)
311
788
  end
312
789
  end
313
790
  when "OBJECT"
314
791
  object_proxy = begin
315
- type.authorized_new(value, context)
792
+ authorized_new(current_type, value, context)
316
793
  rescue GraphQL::ExecutionError => err
317
794
  err
318
795
  end
319
- after_lazy(object_proxy, owner: type, path: path, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |inner_object|
320
- 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)
321
798
  if HALT != continue_value
322
- response_hash = {}
323
- write_in_response(path, response_hash)
324
- evaluate_selections(path, context.scoped_context, continue_value, type, next_selections)
325
- 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
326
838
  end
327
839
  end
328
840
  when "LIST"
329
- response_list = []
330
- write_in_response(path, response_list)
331
- 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)
332
848
  idx = 0
333
- scoped_context = context.scoped_context
334
- value.each do |inner_value|
335
- next_path = path.dup
336
- next_path << idx
337
- next_path.freeze
338
- idx += 1
339
- set_type_at_path(next_path, inner_type)
340
- # This will update `response_list` with the lazy
341
- 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|
342
- # reset `is_non_null` here and below, because the inner type will have its own nullability constraint
343
- continue_value = continue_value(next_path, inner_inner_value, field, false, ast_node)
344
- if HALT != continue_value
345
- 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)
346
859
  end
347
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
348
880
  end
349
- response_list
350
- when "NON_NULL"
351
- inner_type = type.of_type
352
- # For fields like `__schema: __Schema!`
353
- inner_type = resolve_if_late_bound_type(inner_type)
354
- # Don't `set_type_at_path` because we want the static type,
355
- # we're going to use that to determine whether a `nil` should be propagated or not.
356
- 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)
357
883
  else
358
- raise "Invariant: Unhandled type kind #{type.kind} (#{type})"
884
+ raise "Invariant: Unhandled type kind #{current_type.kind} (#{current_type})"
885
+ end
886
+ end
887
+
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
359
900
  end
360
901
  end
361
902
 
362
- def resolve_with_directives(object, ast_node)
363
- run_directive(object, ast_node, 0) { yield }
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)
364
906
  end
365
907
 
366
- def run_directive(object, ast_node, idx)
367
- dir_node = ast_node.directives[idx]
908
+ def run_directive(method_name, object, directives, idx, &block)
909
+ dir_node = directives[idx]
368
910
  if !dir_node
369
911
  yield
370
912
  else
371
- dir_defn = schema.directives.fetch(dir_node.name)
372
- if !dir_defn.is_a?(Class)
373
- dir_defn = dir_defn.metadata[:type_class] || raise("Only class-based directives are supported (not `@#{dir_node.name}`)")
374
- end
375
- dir_args = arguments(nil, dir_defn, dir_node)
376
- dir_defn.resolve(object, dir_args, context) do
377
- 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
378
931
  end
379
932
  end
380
933
  end
@@ -382,7 +935,7 @@ module GraphQL
382
935
  # Check {Schema::Directive.include?} for each directive that's present
383
936
  def directives_include?(node, graphql_object, parent_type)
384
937
  node.directives.each do |dir_node|
385
- dir_defn = schema.directives.fetch(dir_node.name).metadata[:type_class] || raise("Only class-based directives are supported (not #{dir_node.name.inspect})")
938
+ dir_defn = @schema_directives.fetch(dir_node.name)
386
939
  args = arguments(graphql_object, dir_defn, dir_node)
387
940
  if !dir_defn.include?(graphql_object, args, context)
388
941
  return false
@@ -391,242 +944,102 @@ module GraphQL
391
944
  true
392
945
  end
393
946
 
394
- def resolve_if_late_bound_type(type)
395
- if type.is_a?(GraphQL::Schema::LateBoundType)
396
- query.warden.get_type(type.name).metadata[:type_class]
397
- else
398
- type
399
- end
947
+ def get_current_runtime_state
948
+ Thread.current[:__graphql_runtime_info] ||= CurrentState.new
400
949
  end
401
950
 
402
951
  # @param obj [Object] Some user-returned value that may want to be batched
403
- # @param path [Array<String>]
404
952
  # @param field [GraphQL::Schema::Field]
405
953
  # @param eager [Boolean] Set to `true` for mutation root fields only
954
+ # @param trace [Boolean] If `false`, don't wrap this with field tracing
406
955
  # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
407
- def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, eager: false)
408
- @interpreter_context[:current_object] = owner_object
409
- @interpreter_context[:current_arguments] = arguments
410
- @interpreter_context[:current_path] = path
411
- @interpreter_context[:current_field] = field
412
- if schema.lazy?(lazy_obj)
413
- lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do
414
- @interpreter_context[:current_path] = path
415
- @interpreter_context[:current_field] = field
416
- @interpreter_context[:current_object] = owner_object
417
- @interpreter_context[:current_arguments] = arguments
418
- 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
419
966
  # Wrap the execution of _this_ method with tracing,
420
967
  # but don't wrap the continuation below
421
968
  inner_obj = begin
422
- query.with_error_handling do
423
- query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments}) do
969
+ if trace
970
+ query.current_trace.execute_field_lazy(field: field, query: query, object: owner_object, arguments: arguments, ast_node: ast_node) do
424
971
  schema.sync_lazy(lazy_obj)
425
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
426
983
  end
427
- rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
428
- yield(err)
429
- end
430
- 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|
431
- yield(really_inner_obj)
432
984
  end
985
+ yield(inner_obj)
433
986
  end
434
987
 
435
988
  if eager
436
989
  lazy.value
437
990
  else
438
- 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
439
998
  lazy
440
999
  end
441
1000
  else
1001
+ # Don't need to reset state here because it _wasn't_ lazy.
442
1002
  yield(lazy_obj)
443
1003
  end
444
1004
  end
445
1005
 
446
- def each_argument_pair(ast_args_or_hash)
447
- case ast_args_or_hash
448
- when GraphQL::Language::Nodes::Field, GraphQL::Language::Nodes::InputObject, GraphQL::Language::Nodes::Directive
449
- ast_args_or_hash.arguments.each do |arg|
450
- yield(arg.name, arg.value)
451
- end
452
- when Hash
453
- ast_args_or_hash.each do |key, value|
454
- normalized_name = GraphQL::Schema::Member::BuildType.camelize(key.to_s)
455
- yield(normalized_name, value)
456
- end
457
- else
458
- raise "Invariant, unexpected #{ast_args_or_hash.inspect}"
459
- end
460
- end
461
-
462
- def arguments(graphql_object, arg_owner, ast_node_or_hash)
463
- kwarg_arguments = {}
464
- arg_defns = arg_owner.arguments
465
- each_argument_pair(ast_node_or_hash) do |arg_name, arg_value|
466
- arg_defn = arg_defns[arg_name]
467
- # Need to distinguish between client-provided `nil`
468
- # and nothing-at-all
469
- is_present, value = arg_to_value(graphql_object, arg_defn.type, arg_value)
470
- if is_present
471
- # This doesn't apply to directives, which are legacy
472
- # Can remove this when Skip and Include use classes or something.
473
- if graphql_object
474
- value = arg_defn.prepare_value(graphql_object, value)
475
- end
476
- kwarg_arguments[arg_defn.keyword] = value
477
- end
478
- end
479
- arg_defns.each do |name, arg_defn|
480
- if arg_defn.default_value? && !kwarg_arguments.key?(arg_defn.keyword)
481
- _is_present, value = arg_to_value(graphql_object, arg_defn.type, arg_defn.default_value)
482
- kwarg_arguments[arg_defn.keyword] = value
483
- end
484
- end
485
- kwarg_arguments
486
- end
487
-
488
- # Get a Ruby-ready value from a client query.
489
- # @param graphql_object [Object] The owner of the field whose argument this is
490
- # @param arg_type [Class, GraphQL::Schema::NonNull, GraphQL::Schema::List]
491
- # @param ast_value [GraphQL::Language::Nodes::VariableIdentifier, String, Integer, Float, Boolean]
492
- # @return [Array(is_present, value)]
493
- def arg_to_value(graphql_object, arg_type, ast_value)
494
- if ast_value.is_a?(GraphQL::Language::Nodes::VariableIdentifier)
495
- # If it's not here, it will get added later
496
- if query.variables.key?(ast_value.name)
497
- return true, query.variables[ast_value.name]
498
- else
499
- return false, nil
500
- end
501
- elsif ast_value.is_a?(GraphQL::Language::Nodes::NullValue)
502
- return true, nil
503
- elsif arg_type.is_a?(GraphQL::Schema::NonNull)
504
- arg_to_value(graphql_object, arg_type.of_type, ast_value)
505
- elsif arg_type.is_a?(GraphQL::Schema::List)
506
- # Treat a single value like a list
507
- arg_value = Array(ast_value)
508
- list = []
509
- arg_value.map do |inner_v|
510
- _present, value = arg_to_value(graphql_object, arg_type.of_type, inner_v)
511
- list << value
512
- end
513
- return true, list
514
- elsif arg_type.is_a?(Class) && arg_type < GraphQL::Schema::InputObject
515
- # For these, `prepare` is applied during `#initialize`.
516
- # Pass `nil` so it will be skipped in `#arguments`.
517
- # What a mess.
518
- args = arguments(nil, arg_type, ast_value)
519
- # We're not tracking defaults_used, but for our purposes
520
- # we compare the value to the default value.
521
-
522
- input_obj = query.with_error_handling do
523
- arg_type.new(ruby_kwargs: args, context: context, defaults_used: nil)
524
- end
525
- return true, input_obj
1006
+ def arguments(graphql_object, arg_owner, ast_node)
1007
+ if arg_owner.arguments_statically_coercible?
1008
+ query.arguments_for(ast_node, arg_owner)
526
1009
  else
527
- flat_value = flatten_ast_value(ast_value)
528
- return true, arg_type.coerce_input(flat_value, context)
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)
529
1012
  end
530
1013
  end
531
1014
 
532
- def flatten_ast_value(v)
533
- case v
534
- when GraphQL::Language::Nodes::Enum
535
- v.name
536
- when GraphQL::Language::Nodes::InputObject
537
- h = {}
538
- v.arguments.each do |arg|
539
- h[arg.name] = flatten_ast_value(arg.value)
540
- end
541
- h
542
- when Array
543
- v.map { |v2| flatten_ast_value(v2) }
544
- when GraphQL::Language::Nodes::VariableIdentifier
545
- flatten_ast_value(query.variables[v.name])
546
- else
547
- v
548
- end
1015
+ def delete_all_interpreter_context
1016
+ Thread.current[:__graphql_runtime_info] = nil
549
1017
  end
550
1018
 
551
- def write_invalid_null_in_response(path, invalid_null_error)
552
- if !dead_path?(path)
553
- schema.type_error(invalid_null_error, context)
554
- write_in_response(path, nil)
555
- add_dead_path(path)
1019
+ def resolve_type(type, value)
1020
+ resolved_type, resolved_value = query.current_trace.resolve_type(query: query, type: type, object: value) do
1021
+ query.resolve_type(type, value)
556
1022
  end
557
- end
558
1023
 
559
- def write_execution_errors_in_response(path, errors)
560
- if !dead_path?(path)
561
- errors.each do |v|
562
- context.errors << v
1024
+ if lazy?(resolved_type)
1025
+ GraphQL::Execution::Lazy.new do
1026
+ query.current_trace.resolve_type_lazy(query: query, type: type, object: value) do
1027
+ schema.sync_lazy(resolved_type)
1028
+ end
563
1029
  end
564
- write_in_response(path, nil)
565
- add_dead_path(path)
566
- end
567
- end
568
-
569
- def write_in_response(path, value)
570
- if dead_path?(path)
571
- return
572
1030
  else
573
- if value.nil? && path.any? && type_at(path).non_null?
574
- # This nil is invalid, try writing it at the previous spot
575
- propagate_path = path[0..-2]
576
- write_in_response(propagate_path, value)
577
- add_dead_path(propagate_path)
578
- else
579
- @response.write(path, value)
580
- end
581
- end
582
- end
583
-
584
- # To propagate nulls, we have to know what the field type was
585
- # at previous parts of the response.
586
- # This hash matches the response
587
- def type_at(path)
588
- t = @types_at_paths
589
- path.each do |part|
590
- t = t[part] || (raise("Invariant: #{part.inspect} not found in #{t}"))
1031
+ [resolved_type, resolved_value]
591
1032
  end
592
- t = t[:__type]
593
- t
594
1033
  end
595
1034
 
596
- def set_type_at_path(path, type)
597
- types = @types_at_paths
598
- path.each do |part|
599
- types = types[part] ||= {}
600
- end
601
- # Use this magic key so that the hash contains:
602
- # - string keys for nested fields
603
- # - :__type for the object type of a selection
604
- types[:__type] ||= type
605
- nil
1035
+ def authorized_new(type, value, context)
1036
+ type.authorized_new(value, context)
606
1037
  end
607
1038
 
608
- # Mark `path` as having been permanently nulled out.
609
- # No values will be added beyond that path.
610
- def add_dead_path(path)
611
- dead = @dead_paths
612
- path.each do |part|
613
- dead = dead[part] ||= {}
614
- end
615
- dead[:__dead] = true
616
- end
617
-
618
- def dead_path?(path)
619
- res = @dead_paths
620
- path.each do |part|
621
- if res
622
- if res[:__dead]
623
- break
624
- else
625
- res = res[part]
626
- end
627
- end
628
- end
629
- res && res[:__dead]
1039
+ def lazy?(object)
1040
+ @lazy_cache.fetch(object.class) {
1041
+ @lazy_cache[object.class] = @schema.lazy?(object)
1042
+ }
630
1043
  end
631
1044
  end
632
1045
  end