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