graphql 1.9.21 → 2.0.0

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