graphql 1.9.17 → 2.0.20

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