graphql 1.9.21 → 2.0.16

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