graphql 2.0.32 → 2.5.22

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.
Files changed (308) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/detailed_trace_generator.rb +77 -0
  3. data/lib/generators/graphql/install/mutation_root_generator.rb +2 -2
  4. data/lib/generators/graphql/install/templates/base_mutation.erb +2 -0
  5. data/lib/generators/graphql/install/templates/mutation_type.erb +2 -0
  6. data/lib/generators/graphql/install_generator.rb +49 -0
  7. data/lib/generators/graphql/orm_mutations_base.rb +1 -1
  8. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  9. data/lib/generators/graphql/templates/base_connection.erb +2 -0
  10. data/lib/generators/graphql/templates/base_edge.erb +2 -0
  11. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  12. data/lib/generators/graphql/templates/base_field.erb +2 -0
  13. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  14. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  15. data/lib/generators/graphql/templates/base_object.erb +2 -0
  16. data/lib/generators/graphql/templates/base_resolver.erb +8 -0
  17. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  18. data/lib/generators/graphql/templates/base_union.erb +2 -0
  19. data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
  20. data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
  21. data/lib/generators/graphql/templates/loader.erb +2 -0
  22. data/lib/generators/graphql/templates/mutation.erb +2 -0
  23. data/lib/generators/graphql/templates/node_type.erb +2 -0
  24. data/lib/generators/graphql/templates/query_type.erb +2 -0
  25. data/lib/generators/graphql/templates/schema.erb +5 -0
  26. data/lib/generators/graphql/type_generator.rb +1 -1
  27. data/lib/graphql/analysis/analyzer.rb +90 -0
  28. data/lib/graphql/analysis/field_usage.rb +82 -0
  29. data/lib/graphql/analysis/max_query_complexity.rb +20 -0
  30. data/lib/graphql/analysis/max_query_depth.rb +20 -0
  31. data/lib/graphql/analysis/query_complexity.rb +263 -0
  32. data/lib/graphql/analysis/query_depth.rb +58 -0
  33. data/lib/graphql/analysis/visitor.rb +280 -0
  34. data/lib/graphql/analysis.rb +95 -1
  35. data/lib/graphql/autoload.rb +38 -0
  36. data/lib/graphql/backtrace/table.rb +118 -55
  37. data/lib/graphql/backtrace.rb +1 -19
  38. data/lib/graphql/coercion_error.rb +1 -9
  39. data/lib/graphql/current.rb +57 -0
  40. data/lib/graphql/dashboard/application_controller.rb +41 -0
  41. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  42. data/lib/graphql/dashboard/installable.rb +22 -0
  43. data/lib/graphql/dashboard/landings_controller.rb +9 -0
  44. data/lib/graphql/dashboard/limiters.rb +93 -0
  45. data/lib/graphql/dashboard/operation_store.rb +199 -0
  46. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  47. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  48. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  49. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  50. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  51. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  52. data/lib/graphql/dashboard/statics/icon.png +0 -0
  53. data/lib/graphql/dashboard/statics_controller.rb +31 -0
  54. data/lib/graphql/dashboard/subscriptions.rb +97 -0
  55. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  56. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  57. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  58. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  59. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +24 -0
  60. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  61. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  62. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  63. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  64. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  65. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  66. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  67. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  68. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  69. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  70. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  71. data/lib/graphql/dashboard.rb +96 -0
  72. data/lib/graphql/dataloader/active_record_association_source.rb +84 -0
  73. data/lib/graphql/dataloader/active_record_source.rb +47 -0
  74. data/lib/graphql/dataloader/async_dataloader.rb +112 -0
  75. data/lib/graphql/dataloader/null_dataloader.rb +55 -10
  76. data/lib/graphql/dataloader/request.rb +5 -0
  77. data/lib/graphql/dataloader/source.rb +35 -12
  78. data/lib/graphql/dataloader.rb +224 -149
  79. data/lib/graphql/date_encoding_error.rb +1 -1
  80. data/lib/graphql/dig.rb +2 -1
  81. data/lib/graphql/duration_encoding_error.rb +16 -0
  82. data/lib/graphql/execution/interpreter/argument_value.rb +5 -1
  83. data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
  84. data/lib/graphql/execution/interpreter/resolve.rb +23 -25
  85. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +228 -0
  86. data/lib/graphql/execution/interpreter/runtime.rb +363 -434
  87. data/lib/graphql/execution/interpreter.rb +91 -164
  88. data/lib/graphql/execution/lookahead.rb +105 -31
  89. data/lib/graphql/execution/multiplex.rb +7 -6
  90. data/lib/graphql/execution/next/field_resolve_step.rb +711 -0
  91. data/lib/graphql/execution/next/load_argument_step.rb +60 -0
  92. data/lib/graphql/execution/next/prepare_object_step.rb +129 -0
  93. data/lib/graphql/execution/next/runner.rb +389 -0
  94. data/lib/graphql/execution/next/selections_step.rb +37 -0
  95. data/lib/graphql/execution/next.rb +70 -0
  96. data/lib/graphql/execution.rb +1 -0
  97. data/lib/graphql/execution_error.rb +13 -10
  98. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  99. data/lib/graphql/introspection/directive_type.rb +7 -3
  100. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  101. data/lib/graphql/introspection/entry_points.rb +20 -6
  102. data/lib/graphql/introspection/enum_value_type.rb +5 -5
  103. data/lib/graphql/introspection/field_type.rb +13 -5
  104. data/lib/graphql/introspection/input_value_type.rb +21 -13
  105. data/lib/graphql/introspection/schema_type.rb +8 -11
  106. data/lib/graphql/introspection/type_type.rb +64 -28
  107. data/lib/graphql/invalid_name_error.rb +1 -1
  108. data/lib/graphql/invalid_null_error.rb +26 -17
  109. data/lib/graphql/language/block_string.rb +34 -18
  110. data/lib/graphql/language/cache.rb +13 -0
  111. data/lib/graphql/language/comment.rb +18 -0
  112. data/lib/graphql/language/definition_slice.rb +1 -1
  113. data/lib/graphql/language/document_from_schema_definition.rb +90 -61
  114. data/lib/graphql/language/lexer.rb +319 -193
  115. data/lib/graphql/language/nodes.rb +136 -77
  116. data/lib/graphql/language/parser.rb +807 -1985
  117. data/lib/graphql/language/printer.rb +324 -151
  118. data/lib/graphql/language/sanitized_printer.rb +21 -23
  119. data/lib/graphql/language/static_visitor.rb +171 -0
  120. data/lib/graphql/language/visitor.rb +23 -83
  121. data/lib/graphql/language.rb +71 -1
  122. data/lib/graphql/load_application_object_failed_error.rb +5 -1
  123. data/lib/graphql/pagination/array_connection.rb +6 -6
  124. data/lib/graphql/pagination/connection.rb +30 -1
  125. data/lib/graphql/pagination/connections.rb +32 -0
  126. data/lib/graphql/pagination/mongoid_relation_connection.rb +1 -2
  127. data/lib/graphql/query/context/scoped_context.rb +101 -0
  128. data/lib/graphql/query/context.rb +82 -144
  129. data/lib/graphql/query/null_context.rb +15 -18
  130. data/lib/graphql/query/partial.rb +179 -0
  131. data/lib/graphql/query/validation_pipeline.rb +4 -4
  132. data/lib/graphql/query/variable_validation_error.rb +1 -1
  133. data/lib/graphql/query/variables.rb +3 -3
  134. data/lib/graphql/query.rb +126 -81
  135. data/lib/graphql/railtie.rb +16 -6
  136. data/lib/graphql/rake_task.rb +3 -12
  137. data/lib/graphql/rubocop/graphql/base_cop.rb +1 -1
  138. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +144 -0
  139. data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
  140. data/lib/graphql/rubocop.rb +2 -0
  141. data/lib/graphql/schema/addition.rb +26 -13
  142. data/lib/graphql/schema/always_visible.rb +7 -2
  143. data/lib/graphql/schema/argument.rb +75 -9
  144. data/lib/graphql/schema/base_64_encoder.rb +3 -5
  145. data/lib/graphql/schema/build_from_definition.rb +123 -60
  146. data/lib/graphql/schema/directive/flagged.rb +4 -2
  147. data/lib/graphql/schema/directive/one_of.rb +12 -0
  148. data/lib/graphql/schema/directive/specified_by.rb +14 -0
  149. data/lib/graphql/schema/directive.rb +54 -2
  150. data/lib/graphql/schema/enum.rb +110 -27
  151. data/lib/graphql/schema/enum_value.rb +10 -2
  152. data/lib/graphql/schema/field/connection_extension.rb +15 -49
  153. data/lib/graphql/schema/field/scope_extension.rb +23 -7
  154. data/lib/graphql/schema/field.rb +245 -118
  155. data/lib/graphql/schema/field_extension.rb +34 -1
  156. data/lib/graphql/schema/has_single_input_argument.rb +160 -0
  157. data/lib/graphql/schema/input_object.rb +116 -60
  158. data/lib/graphql/schema/interface.rb +34 -16
  159. data/lib/graphql/schema/introspection_system.rb +8 -17
  160. data/lib/graphql/schema/late_bound_type.rb +4 -0
  161. data/lib/graphql/schema/list.rb +3 -3
  162. data/lib/graphql/schema/loader.rb +3 -4
  163. data/lib/graphql/schema/member/base_dsl_methods.rb +18 -2
  164. data/lib/graphql/schema/member/has_arguments.rb +132 -100
  165. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  166. data/lib/graphql/schema/member/has_dataloader.rb +99 -0
  167. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  168. data/lib/graphql/schema/member/has_directives.rb +4 -4
  169. data/lib/graphql/schema/member/has_fields.rb +115 -15
  170. data/lib/graphql/schema/member/has_interfaces.rb +26 -12
  171. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  172. data/lib/graphql/schema/member/has_validators.rb +1 -1
  173. data/lib/graphql/schema/member/relay_shortcuts.rb +1 -1
  174. data/lib/graphql/schema/member/scoped.rb +19 -0
  175. data/lib/graphql/schema/member/type_system_helpers.rb +17 -4
  176. data/lib/graphql/schema/member/validates_input.rb +3 -3
  177. data/lib/graphql/schema/member.rb +6 -0
  178. data/lib/graphql/schema/mutation.rb +7 -0
  179. data/lib/graphql/schema/object.rb +34 -8
  180. data/lib/graphql/schema/printer.rb +9 -7
  181. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  182. data/lib/graphql/schema/relay_classic_mutation.rb +6 -129
  183. data/lib/graphql/schema/resolver.rb +90 -32
  184. data/lib/graphql/schema/scalar.rb +4 -9
  185. data/lib/graphql/schema/subscription.rb +63 -10
  186. data/lib/graphql/schema/timeout.rb +19 -2
  187. data/lib/graphql/schema/type_expression.rb +2 -2
  188. data/lib/graphql/schema/union.rb +2 -2
  189. data/lib/graphql/schema/unique_within_type.rb +1 -1
  190. data/lib/graphql/schema/validator/all_validator.rb +62 -0
  191. data/lib/graphql/schema/validator/required_validator.rb +92 -11
  192. data/lib/graphql/schema/validator.rb +3 -1
  193. data/lib/graphql/schema/visibility/migration.rb +188 -0
  194. data/lib/graphql/schema/visibility/profile.rb +445 -0
  195. data/lib/graphql/schema/visibility/visit.rb +190 -0
  196. data/lib/graphql/schema/visibility.rb +311 -0
  197. data/lib/graphql/schema/warden.rb +275 -103
  198. data/lib/graphql/schema.rb +950 -210
  199. data/lib/graphql/static_validation/all_rules.rb +3 -3
  200. data/lib/graphql/static_validation/base_visitor.rb +7 -6
  201. data/lib/graphql/static_validation/literal_validator.rb +6 -7
  202. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  203. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
  204. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +3 -2
  205. data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
  206. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
  207. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +12 -2
  208. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +47 -13
  209. data/lib/graphql/static_validation/rules/fields_will_merge.rb +88 -25
  210. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
  211. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  212. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +12 -2
  213. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  214. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  215. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  216. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  217. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  218. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +5 -5
  219. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +5 -5
  220. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  221. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +7 -3
  222. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  223. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  224. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
  225. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +11 -2
  226. data/lib/graphql/static_validation/validation_context.rb +21 -5
  227. data/lib/graphql/static_validation/validator.rb +9 -1
  228. data/lib/graphql/static_validation.rb +0 -1
  229. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +8 -5
  230. data/lib/graphql/subscriptions/broadcast_analyzer.rb +11 -5
  231. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  232. data/lib/graphql/subscriptions/event.rb +21 -4
  233. data/lib/graphql/subscriptions/serialize.rb +3 -1
  234. data/lib/graphql/subscriptions.rb +21 -17
  235. data/lib/graphql/testing/helpers.rb +161 -0
  236. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  237. data/lib/graphql/testing.rb +3 -0
  238. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  239. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  240. data/lib/graphql/tracing/appoptics_trace.rb +7 -3
  241. data/lib/graphql/tracing/appoptics_tracing.rb +9 -2
  242. data/lib/graphql/tracing/appsignal_trace.rb +32 -59
  243. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  244. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  245. data/lib/graphql/tracing/data_dog_trace.rb +46 -162
  246. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  247. data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
  248. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  249. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  250. data/lib/graphql/tracing/detailed_trace.rb +156 -0
  251. data/lib/graphql/tracing/legacy_hooks_trace.rb +75 -0
  252. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  253. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  254. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  255. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  256. data/lib/graphql/tracing/notifications_trace.rb +183 -37
  257. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  258. data/lib/graphql/tracing/null_trace.rb +9 -0
  259. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  260. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  261. data/lib/graphql/tracing/perfetto_trace.rb +864 -0
  262. data/lib/graphql/tracing/platform_tracing.rb +3 -1
  263. data/lib/graphql/tracing/{prometheus_tracing → prometheus_trace}/graphql_collector.rb +5 -1
  264. data/lib/graphql/tracing/prometheus_trace.rb +73 -73
  265. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  266. data/lib/graphql/tracing/scout_trace.rb +32 -58
  267. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  268. data/lib/graphql/tracing/sentry_trace.rb +82 -0
  269. data/lib/graphql/tracing/statsd_trace.rb +33 -45
  270. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  271. data/lib/graphql/tracing/trace.rb +112 -1
  272. data/lib/graphql/tracing.rb +31 -28
  273. data/lib/graphql/type_kinds.rb +2 -1
  274. data/lib/graphql/types/iso_8601_duration.rb +77 -0
  275. data/lib/graphql/types/relay/connection_behaviors.rb +44 -2
  276. data/lib/graphql/types/relay/edge_behaviors.rb +18 -0
  277. data/lib/graphql/types/relay/has_node_field.rb +13 -8
  278. data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
  279. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  280. data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
  281. data/lib/graphql/types.rb +18 -10
  282. data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
  283. data/lib/graphql/unauthorized_error.rb +5 -1
  284. data/lib/graphql/version.rb +1 -1
  285. data/lib/graphql.rb +71 -54
  286. data/readme.md +12 -2
  287. metadata +233 -37
  288. data/lib/graphql/analysis/ast/analyzer.rb +0 -84
  289. data/lib/graphql/analysis/ast/field_usage.rb +0 -57
  290. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -22
  291. data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
  292. data/lib/graphql/analysis/ast/query_complexity.rb +0 -230
  293. data/lib/graphql/analysis/ast/query_depth.rb +0 -55
  294. data/lib/graphql/analysis/ast/visitor.rb +0 -276
  295. data/lib/graphql/analysis/ast.rb +0 -81
  296. data/lib/graphql/backtrace/inspect_result.rb +0 -50
  297. data/lib/graphql/backtrace/trace.rb +0 -96
  298. data/lib/graphql/backtrace/tracer.rb +0 -80
  299. data/lib/graphql/deprecation.rb +0 -9
  300. data/lib/graphql/filter.rb +0 -59
  301. data/lib/graphql/language/parser.y +0 -560
  302. data/lib/graphql/language/token.rb +0 -34
  303. data/lib/graphql/schema/base_64_bp.rb +0 -26
  304. data/lib/graphql/schema/invalid_type_error.rb +0 -7
  305. data/lib/graphql/schema/null_mask.rb +0 -11
  306. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
  307. data/lib/graphql/static_validation/type_stack.rb +0 -216
  308. data/lib/graphql/subscriptions/instrumentation.rb +0 -28
@@ -21,7 +21,7 @@ module GraphQL
21
21
  def request(value)
22
22
  res_key = result_key_for(value)
23
23
  if !@results.key?(res_key)
24
- @pending[res_key] ||= value
24
+ @pending[res_key] ||= normalize_fetch_key(value)
25
25
  end
26
26
  Dataloader::Request.new(self, value)
27
27
  end
@@ -35,12 +35,24 @@ module GraphQL
35
35
  value
36
36
  end
37
37
 
38
+ # Implement this method if varying values given to {load} (etc) should be consolidated
39
+ # or normalized before being handed off to your {fetch} implementation.
40
+ #
41
+ # This is different than {result_key_for} because _that_ method handles unification inside Dataloader's cache,
42
+ # but this method changes the value passed into {fetch}.
43
+ #
44
+ # @param value [Object] The value passed to {load}, {load_all}, {request}, or {request_all}
45
+ # @return [Object] The value given to {fetch}
46
+ def normalize_fetch_key(value)
47
+ value
48
+ end
49
+
38
50
  # @return [Dataloader::Request] a pending request for a values from `keys`. Call `.load` on that object to wait for the results.
39
51
  def request_all(values)
40
52
  values.each do |v|
41
53
  res_key = result_key_for(v)
42
54
  if !@results.key?(res_key)
43
- @pending[res_key] ||= v
55
+ @pending[res_key] ||= normalize_fetch_key(v)
44
56
  end
45
57
  end
46
58
  Dataloader::RequestAll.new(self, values)
@@ -53,7 +65,7 @@ module GraphQL
53
65
  if @results.key?(result_key)
54
66
  result_for(result_key)
55
67
  else
56
- @pending[result_key] ||= value
68
+ @pending[result_key] ||= normalize_fetch_key(value)
57
69
  sync([result_key])
58
70
  result_for(result_key)
59
71
  end
@@ -68,12 +80,12 @@ module GraphQL
68
80
  k = result_key_for(v)
69
81
  result_keys << k
70
82
  if !@results.key?(k)
71
- @pending[k] ||= v
83
+ @pending[k] ||= normalize_fetch_key(v)
72
84
  pending_keys << k
73
85
  end
74
86
  }
75
87
 
76
- if pending_keys.any?
88
+ if !pending_keys.empty?
77
89
  sync(pending_keys)
78
90
  end
79
91
 
@@ -88,18 +100,19 @@ module GraphQL
88
100
  raise "Implement `#{self.class}#fetch(#{keys.inspect}) to return a record for each of the keys"
89
101
  end
90
102
 
103
+ MAX_ITERATIONS = 1000
91
104
  # Wait for a batch, if there's anything to batch.
92
105
  # Then run the batch and update the cache.
93
106
  # @return [void]
94
107
  def sync(pending_result_keys)
95
- @dataloader.yield
108
+ @dataloader.yield(self)
96
109
  iterations = 0
97
110
  while pending_result_keys.any? { |key| !@results.key?(key) }
98
111
  iterations += 1
99
- if iterations > 1000
100
- raise "#{self.class}#sync tried 1000 times to load pending keys (#{pending_result_keys}), but they still weren't loaded. There is likely a circular dependency."
112
+ if iterations > MAX_ITERATIONS
113
+ raise "#{self.class}#sync tried #{MAX_ITERATIONS} times to load pending keys (#{pending_result_keys}), but they still weren't loaded. There is likely a circular dependency#{@dataloader.fiber_limit ? " or `fiber_limit: #{@dataloader.fiber_limit}` is set too low" : ""}."
101
114
  end
102
- @dataloader.yield
115
+ @dataloader.yield(self)
103
116
  end
104
117
  nil
105
118
  end
@@ -161,7 +174,14 @@ module GraphQL
161
174
  [*batch_args, **batch_kwargs]
162
175
  end
163
176
 
164
- attr_reader :pending
177
+ # Clear any already-loaded objects for this source
178
+ # @return [void]
179
+ def clear_cache
180
+ @results.clear
181
+ nil
182
+ end
183
+
184
+ attr_reader :pending, :results
165
185
 
166
186
  private
167
187
 
@@ -178,8 +198,11 @@ This key should have been loaded already. This is a bug in GraphQL::Dataloader,
178
198
  ERR
179
199
  end
180
200
  result = @results[key]
181
-
182
- raise result if result.class <= StandardError
201
+ if result.is_a?(StandardError)
202
+ # Dup it because the rescuer may modify it.
203
+ # (This happens for GraphQL::ExecutionErrors, at least)
204
+ raise result.dup
205
+ end
183
206
 
184
207
  result
185
208
  end
@@ -4,6 +4,8 @@ require "graphql/dataloader/null_dataloader"
4
4
  require "graphql/dataloader/request"
5
5
  require "graphql/dataloader/request_all"
6
6
  require "graphql/dataloader/source"
7
+ require "graphql/dataloader/active_record_association_source"
8
+ require "graphql/dataloader/active_record_source"
7
9
 
8
10
  module GraphQL
9
11
  # This plugin supports Fiber-based concurrency, along with {GraphQL::Dataloader::Source}.
@@ -24,17 +26,23 @@ module GraphQL
24
26
  #
25
27
  class Dataloader
26
28
  class << self
27
- attr_accessor :default_nonblocking
29
+ attr_accessor :default_nonblocking, :default_fiber_limit
28
30
  end
29
31
 
30
- AsyncDataloader = Class.new(self) { self.default_nonblocking = true }
31
-
32
- def self.use(schema, nonblocking: nil)
33
- schema.dataloader_class = if nonblocking
34
- AsyncDataloader
32
+ def self.use(schema, nonblocking: nil, fiber_limit: nil)
33
+ dataloader_class = if nonblocking
34
+ warn("`nonblocking: true` is deprecated from `GraphQL::Dataloader`, please use `GraphQL::Dataloader::AsyncDataloader` instead. Docs: https://graphql-ruby.org/dataloader/async_dataloader.")
35
+ Class.new(self) { self.default_nonblocking = true }
35
36
  else
36
37
  self
37
38
  end
39
+
40
+ if fiber_limit
41
+ dataloader_class = Class.new(dataloader_class)
42
+ dataloader_class.default_fiber_limit = fiber_limit
43
+ end
44
+
45
+ schema.dataloader_class = dataloader_class
38
46
  end
39
47
 
40
48
  # Call the block with a Dataloader instance,
@@ -49,18 +57,51 @@ module GraphQL
49
57
  result
50
58
  end
51
59
 
52
- def initialize(nonblocking: self.class.default_nonblocking)
60
+ def initialize(nonblocking: self.class.default_nonblocking, fiber_limit: self.class.default_fiber_limit)
53
61
  @source_cache = Hash.new { |h, k| h[k] = {} }
54
62
  @pending_jobs = []
55
63
  if !nonblocking.nil?
56
64
  @nonblocking = nonblocking
57
65
  end
66
+ @fiber_limit = fiber_limit
67
+ @lazies_at_depth = Hash.new { |h, k| h[k] = [] }
58
68
  end
59
69
 
70
+ # @return [Integer, nil]
71
+ attr_reader :fiber_limit
72
+
60
73
  def nonblocking?
61
74
  @nonblocking
62
75
  end
63
76
 
77
+ # This is called before the fiber is spawned, from the parent context (i.e. from
78
+ # the thread or fiber that it is scheduled from).
79
+ #
80
+ # @return [Hash<Symbol, Object>] Current fiber-local variables
81
+ def get_fiber_variables
82
+ fiber_vars = {}
83
+ Thread.current.keys.each do |fiber_var_key|
84
+ fiber_vars[fiber_var_key] = Thread.current[fiber_var_key]
85
+ end
86
+ fiber_vars
87
+ end
88
+
89
+ # Set up the fiber variables in a new fiber.
90
+ #
91
+ # This is called within the fiber, right after it is spawned.
92
+ #
93
+ # @param vars [Hash<Symbol, Object>] Fiber-local variables from {get_fiber_variables}
94
+ # @return [void]
95
+ def set_fiber_variables(vars)
96
+ vars.each { |k, v| Thread.current[k] = v }
97
+ nil
98
+ end
99
+
100
+ # This method is called when Dataloader is finished using a fiber.
101
+ # Use it to perform any cleanup, such as releasing database connections (if required manually)
102
+ def cleanup_fiber
103
+ end
104
+
64
105
  # Get a Source instance from this dataloader, for calling `.load(...)` or `.request(...)` on.
65
106
  #
66
107
  # @param source_class [Class<GraphQL::Dataloader::Source]
@@ -91,16 +132,28 @@ module GraphQL
91
132
  # Dataloader will resume the fiber after the requested data has been loaded (by another Fiber).
92
133
  #
93
134
  # @return [void]
94
- def yield
135
+ def yield(source = Fiber[:__graphql_current_dataloader_source])
136
+ trace = Fiber[:__graphql_current_multiplex]&.current_trace
137
+ trace&.dataloader_fiber_yield(source)
95
138
  Fiber.yield
139
+ trace&.dataloader_fiber_resume(source)
96
140
  nil
97
141
  end
98
142
 
99
143
  # @api private Nothing to see here
100
- def append_job(&job)
144
+ def append_job(callable = nil, &job)
101
145
  # Given a block, queue it up to be worked through when `#run` is called.
102
- # (If the dataloader is already running, than a Fiber will pick this up later.)
103
- @pending_jobs.push(job)
146
+ # (If the dataloader is already running, then a Fiber will pick this up later.)
147
+ @pending_jobs.push(callable || job)
148
+ nil
149
+ end
150
+
151
+ # Clear any already-loaded objects from {Source} caches
152
+ # @return [void]
153
+ def clear_cache
154
+ @source_cache.each do |_source_class, batched_sources|
155
+ batched_sources.each_value(&:clear_cache)
156
+ end
104
157
  nil
105
158
  end
106
159
 
@@ -108,6 +161,10 @@ module GraphQL
108
161
  def run_isolated
109
162
  prev_queue = @pending_jobs
110
163
  prev_pending_keys = {}
164
+ prev_lazies_at_depth = @lazies_at_depth
165
+ @lazies_at_depth = @lazies_at_depth.dup.clear
166
+ # Clear pending loads but keep already-cached records
167
+ # in case they are useful to the given block.
111
168
  @source_cache.each do |source_class, batched_sources|
112
169
  batched_sources.each do |batch_args, batched_source_instance|
113
170
  if batched_source_instance.pending?
@@ -127,185 +184,203 @@ module GraphQL
127
184
  res
128
185
  ensure
129
186
  @pending_jobs = prev_queue
187
+ @lazies_at_depth = prev_lazies_at_depth
130
188
  prev_pending_keys.each do |source_instance, pending|
131
- source_instance.pending.merge!(pending)
189
+ pending.each do |key, value|
190
+ if !source_instance.results.key?(key)
191
+ source_instance.pending[key] = value
192
+ end
193
+ end
132
194
  end
133
195
  end
134
196
 
135
- # @api private Move along, move along
136
- def run
137
- if @nonblocking && !Fiber.scheduler
138
- raise "`nonblocking: true` requires `Fiber.scheduler`, assign one with `Fiber.set_scheduler(...)` before executing GraphQL."
139
- end
140
- # At a high level, the algorithm is:
141
- #
142
- # A) Inside Fibers, run jobs from the queue one-by-one
143
- # - When one of the jobs yields to the dataloader (`Fiber.yield`), then that fiber will pause
144
- # - In that case, if there are still pending jobs, a new Fiber will be created to run jobs
145
- # - Continue until all jobs have been _started_ by a Fiber. (Any number of those Fibers may be waiting to be resumed, after their data is loaded)
146
- # B) Once all known jobs have been run until they are complete or paused for data, run all pending data sources.
147
- # - Similarly, create a Fiber to consume pending sources and tell them to load their data.
148
- # - If one of those Fibers pauses, then create a new Fiber to continue working through remaining pending sources.
149
- # - When a source causes another source to become pending, run the newly-pending source _first_, since it's a dependency of the previous one.
150
- # C) After all pending sources have been completely loaded (there are no more pending sources), resume any Fibers that were waiting for data.
151
- # - Those Fibers assume that source caches will have been populated with the data they were waiting for.
152
- # - Those Fibers may request data from a source again, in which case they will yeilded and be added to a new pending fiber list.
153
- # D) Once all pending fibers have been resumed once, return to `A` above.
154
- #
155
- # For whatever reason, the best implementation I could find was to order the steps `[D, A, B, C]`, with a special case for skipping `D`
156
- # on the first pass. I just couldn't find a better way to write the loops in a way that was DRY and easy to read.
157
- #
158
- pending_fibers = []
159
- next_fibers = []
160
- pending_source_fibers = []
197
+ # @param trace_query_lazy [nil, Execution::Multiplex]
198
+ def run(trace_query_lazy: nil)
199
+ trace = Fiber[:__graphql_current_multiplex]&.current_trace
200
+ jobs_fiber_limit, total_fiber_limit = calculate_fiber_limit
201
+ job_fibers = []
202
+ next_job_fibers = []
203
+ source_fibers = []
161
204
  next_source_fibers = []
162
205
  first_pass = true
163
-
164
- while first_pass || (f = pending_fibers.shift)
165
- if first_pass
206
+ manager = spawn_fiber do
207
+ trace&.begin_dataloader(self)
208
+ while first_pass || !job_fibers.empty?
166
209
  first_pass = false
167
- else
168
- # These fibers were previously waiting for sources to load data,
169
- # resume them. (They might wait again, in which case, re-enqueue them.)
170
- resume(f)
171
- if f.alive?
172
- next_fibers << f
173
- end
174
- end
175
210
 
176
- while @pending_jobs.any?
177
- # Create a Fiber to consume jobs until one of the jobs yields
178
- # or jobs run out
179
- f = spawn_fiber {
180
- while (job = @pending_jobs.shift)
181
- job.call
211
+ run_pending_steps(trace, job_fibers, next_job_fibers, jobs_fiber_limit, source_fibers, next_source_fibers, total_fiber_limit)
212
+
213
+ if !@lazies_at_depth.empty?
214
+ with_trace_query_lazy(trace_query_lazy) do
215
+ run_next_pending_lazies(job_fibers, trace)
216
+ run_pending_steps(trace, job_fibers, next_job_fibers, jobs_fiber_limit, source_fibers, next_source_fibers, total_fiber_limit)
182
217
  end
183
- }
184
- resume(f)
185
- # In this case, the job yielded. Queue it up to run again after
186
- # we load whatever it's waiting for.
187
- if f.alive?
188
- next_fibers << f
189
218
  end
190
219
  end
191
220
 
192
- if pending_fibers.empty?
193
- # Now, run all Sources which have become pending _before_ resuming GraphQL execution.
194
- # Sources might queue up other Sources, which is fine -- those will also run before resuming execution.
195
- #
196
- # This is where an evented approach would be even better -- can we tell which
197
- # fibers are ready to continue, and continue execution there?
198
- #
199
- if (first_source_fiber = create_source_fiber)
200
- pending_source_fibers << first_source_fiber
201
- end
221
+ trace&.end_dataloader(self)
222
+ end
202
223
 
203
- while pending_source_fibers.any?
204
- while (outer_source_fiber = pending_source_fibers.pop)
205
- resume(outer_source_fiber)
206
- if outer_source_fiber.alive?
207
- next_source_fibers << outer_source_fiber
208
- end
209
- if (next_source_fiber = create_source_fiber)
210
- pending_source_fibers << next_source_fiber
211
- end
212
- end
213
- join_queues(pending_source_fibers, next_source_fibers)
214
- next_source_fibers.clear
215
- end
216
- # Move newly-enqueued Fibers on to the list to be resumed.
217
- # Clear out the list of next-round Fibers, so that
218
- # any Fibers that pause can be put on it.
219
- join_queues(pending_fibers, next_fibers)
220
- next_fibers.clear
221
- end
224
+ run_fiber(manager)
225
+
226
+ if manager.alive?
227
+ raise "Invariant: Manager fiber didn't terminate properly."
222
228
  end
223
229
 
224
- if @pending_jobs.any?
225
- raise "Invariant: #{@pending_jobs.size} pending jobs"
226
- elsif pending_fibers.any?
227
- raise "Invariant: #{pending_fibers.size} pending fibers"
228
- elsif next_fibers.any?
229
- raise "Invariant: #{next_fibers.size} next fibers"
230
+ if !job_fibers.empty?
231
+ raise "Invariant: job fibers should have exited but #{job_fibers.size} remained"
230
232
  end
231
- nil
233
+ if !source_fibers.empty?
234
+ raise "Invariant: source fibers should have exited but #{source_fibers.size} remained"
235
+ end
236
+
237
+ rescue UncaughtThrowError => e
238
+ throw e.tag, e.value
232
239
  end
233
240
 
234
- def join_queues(previous_queue, next_queue)
235
- if @nonblocking
236
- Fiber.scheduler.run
237
- next_queue.select!(&:alive?)
241
+ def run_fiber(f)
242
+ f.resume
243
+ end
244
+
245
+ # @api private
246
+ def lazy_at_depth(depth, lazy)
247
+ @lazies_at_depth[depth] << lazy
248
+ end
249
+
250
+ def spawn_fiber
251
+ fiber_vars = get_fiber_variables
252
+ Fiber.new(blocking: !@nonblocking) {
253
+ set_fiber_variables(fiber_vars)
254
+ yield
255
+ cleanup_fiber
256
+ }
257
+ end
258
+
259
+ # Pre-warm the Dataloader cache with ActiveRecord objects which were loaded elsewhere.
260
+ # These will be used by {Dataloader::ActiveRecordSource}, {Dataloader::ActiveRecordAssociationSource} and their helper
261
+ # methods, `dataload_record` and `dataload_association`.
262
+ # @param records [Array<ActiveRecord::Base>] Already-loaded records to warm the cache with
263
+ # @param index_by [Symbol] The attribute to use as the cache key. (Should match `find_by:` when using {ActiveRecordSource})
264
+ # @return [void]
265
+ def merge_records(records, index_by: :id)
266
+ records_by_class = Hash.new { |h, k| h[k] = {} }
267
+ records.each do |r|
268
+ records_by_class[r.class][r.public_send(index_by)] = r
269
+ end
270
+ records_by_class.each do |r_class, records|
271
+ with(ActiveRecordSource, r_class).merge(records)
238
272
  end
239
- previous_queue.concat(next_queue)
240
273
  end
241
274
 
242
275
  private
243
276
 
244
- # If there are pending sources, return a fiber for running them.
245
- # Otherwise, return `nil`.
246
- #
247
- # @return [Fiber, nil]
248
- def create_source_fiber
249
- pending_sources = nil
250
- @source_cache.each_value do |source_by_batch_params|
251
- source_by_batch_params.each_value do |source|
252
- if source.pending?
253
- pending_sources ||= []
254
- pending_sources << source
277
+ def run_next_pending_lazies(job_fibers, trace)
278
+ smallest_depth = nil
279
+ @lazies_at_depth.each_key do |depth_key|
280
+ smallest_depth ||= depth_key
281
+ if depth_key < smallest_depth
282
+ smallest_depth = depth_key
283
+ end
284
+ end
285
+
286
+ if smallest_depth
287
+ lazies = @lazies_at_depth.delete(smallest_depth)
288
+ if !lazies.empty?
289
+ lazies.each_with_index do |l, idx|
290
+ append_job { l.value }
255
291
  end
292
+ job_fibers.unshift(spawn_job_fiber(trace))
256
293
  end
257
294
  end
295
+ end
258
296
 
259
- if pending_sources
260
- # By passing the whole array into this Fiber, it's possible that we set ourselves up for a bunch of no-ops.
261
- # For example, if you have sources `[a, b, c]`, and `a` is loaded, then `b` yields to wait for `d`, then
262
- # the next fiber would be dispatched with `[c, d]`. It would fulfill `c`, then `d`, then eventually
263
- # the previous fiber would start up again. `c` would no longer be pending, but it would still receive `.run_pending_keys`.
264
- # That method is short-circuited since it isn't pending any more, but it's still a waste.
265
- #
266
- # This design could probably be improved by maintaining a `@pending_sources` queue which is shared by the fibers,
267
- # similar to `@pending_jobs`. That way, when a fiber is resumed, it would never pick up work that was finished by a different fiber.
268
- source_fiber = spawn_fiber do
269
- pending_sources.each(&:run_pending_keys)
297
+ def run_pending_steps(trace, job_fibers, next_job_fibers, jobs_fiber_limit, source_fibers, next_source_fibers, total_fiber_limit)
298
+ while (f = (job_fibers.shift || (((next_job_fibers.size + job_fibers.size) < jobs_fiber_limit) && spawn_job_fiber(trace))))
299
+ if f.alive?
300
+ finished = run_fiber(f)
301
+ if !finished
302
+ next_job_fibers << f
303
+ end
270
304
  end
271
305
  end
306
+ join_queues(job_fibers, next_job_fibers)
272
307
 
273
- source_fiber
308
+ while (!source_fibers.empty? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) })
309
+ while (f = source_fibers.shift || (((job_fibers.size + source_fibers.size + next_source_fibers.size + next_job_fibers.size) < total_fiber_limit) && spawn_source_fiber(trace)))
310
+ if f.alive?
311
+ finished = run_fiber(f)
312
+ if !finished
313
+ next_source_fibers << f
314
+ end
315
+ end
316
+ end
317
+ join_queues(source_fibers, next_source_fibers)
318
+ end
274
319
  end
275
320
 
276
- def resume(fiber)
277
- fiber.resume
278
- rescue UncaughtThrowError => e
279
- throw e.tag, e.value
321
+ def with_trace_query_lazy(multiplex_or_nil, &block)
322
+ if (multiplex = multiplex_or_nil)
323
+ query = multiplex.queries.length == 1 ? multiplex.queries[0] : nil
324
+ multiplex.current_trace.execute_query_lazy(query: query, multiplex: multiplex, &block)
325
+ else
326
+ yield
327
+ end
280
328
  end
281
329
 
282
- # Copies the thread local vars into the fiber thread local vars. Many
283
- # gems (such as RequestStore, MiniRacer, etc.) rely on thread local vars
284
- # to keep track of execution context, and without this they do not
285
- # behave as expected.
286
- #
287
- # @see https://github.com/rmosolgo/graphql-ruby/issues/3449
288
- def spawn_fiber
289
- fiber_locals = {}
330
+ def calculate_fiber_limit
331
+ total_fiber_limit = @fiber_limit || Float::INFINITY
332
+ if total_fiber_limit < 4
333
+ raise ArgumentError, "Dataloader fiber limit is too low (#{total_fiber_limit}), it must be at least 4"
334
+ end
335
+ total_fiber_limit -= 1 # deduct one fiber for `manager`
336
+ # Deduct at least one fiber for sources
337
+ jobs_fiber_limit = total_fiber_limit - 2
338
+ return jobs_fiber_limit, total_fiber_limit
339
+ end
290
340
 
291
- Thread.current.keys.each do |fiber_var_key|
292
- # This variable should be fresh in each new fiber
293
- if fiber_var_key != :__graphql_runtime_info
294
- fiber_locals[fiber_var_key] = Thread.current[fiber_var_key]
341
+ def join_queues(prev_queue, new_queue)
342
+ @nonblocking && Fiber.scheduler.run
343
+ prev_queue.concat(new_queue)
344
+ new_queue.clear
345
+ end
346
+
347
+ def spawn_job_fiber(trace)
348
+ if !@pending_jobs.empty?
349
+ spawn_fiber do
350
+ trace&.dataloader_spawn_execution_fiber(@pending_jobs)
351
+ while job = @pending_jobs.shift
352
+ job.call
353
+ end
354
+ trace&.dataloader_fiber_exit
295
355
  end
296
356
  end
357
+ end
297
358
 
298
- if @nonblocking
299
- Fiber.new(blocking: false) do
300
- fiber_locals.each { |k, v| Thread.current[k] = v }
301
- yield
359
+ def spawn_source_fiber(trace)
360
+ pending_sources = nil
361
+ @source_cache.each_value do |source_by_batch_params|
362
+ source_by_batch_params.each_value do |source|
363
+ if source.pending?
364
+ pending_sources ||= []
365
+ pending_sources << source
366
+ end
302
367
  end
303
- else
304
- Fiber.new do
305
- fiber_locals.each { |k, v| Thread.current[k] = v }
306
- yield
368
+ end
369
+
370
+ if pending_sources
371
+ spawn_fiber do
372
+ trace&.dataloader_spawn_source_fiber(pending_sources)
373
+ pending_sources.each do |source|
374
+ Fiber[:__graphql_current_dataloader_source] = source
375
+ trace&.begin_dataloader_source(source)
376
+ source.run_pending_keys
377
+ trace&.end_dataloader_source(source)
378
+ end
379
+ trace&.dataloader_fiber_exit
307
380
  end
308
381
  end
309
382
  end
310
383
  end
311
384
  end
385
+
386
+ require "graphql/dataloader/async_dataloader"
@@ -10,7 +10,7 @@ module GraphQL
10
10
 
11
11
  def initialize(value)
12
12
  @date_value = value
13
- super("Date cannot be parsed: #{value}. \nDate must be be able to be parsed as a Ruby Date object.")
13
+ super("Date cannot be parsed: #{value}. \nDate must be able to be parsed as a Ruby Date object.")
14
14
  end
15
15
  end
16
16
  end
data/lib/graphql/dig.rb CHANGED
@@ -5,7 +5,8 @@ module GraphQL
5
5
  # so we can use some of the magic in Schema::InputObject and Interpreter::Arguments
6
6
  # to handle stringified/symbolized keys.
7
7
  #
8
- # @param args [Array<[String, Symbol>] Retrieves the value object corresponding to the each key objects repeatedly
8
+ # @param own_key [String, Symbol] A key to retrieve
9
+ # @param rest_keys [Array<[String, Symbol>] Retrieves the value object corresponding to the each key objects repeatedly
9
10
  # @return [Object]
10
11
  def dig(own_key, *rest_keys)
11
12
  val = self[own_key]
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ # This error is raised when `Types::ISO8601Duration` is asked to return a value
4
+ # that cannot be parsed as an ISO8601-formatted duration by ActiveSupport::Duration.
5
+ #
6
+ # @see GraphQL::Types::ISO8601Duration which raises this error
7
+ class DurationEncodingError < GraphQL::RuntimeTypeError
8
+ # The value which couldn't be encoded
9
+ attr_reader :duration_value
10
+
11
+ def initialize(value)
12
+ @duration_value = value
13
+ super("Duration cannot be parsed: #{value}. \nDuration must be an ISO8601-formatted duration.")
14
+ end
15
+ end
16
+ end
@@ -6,15 +6,19 @@ module GraphQL
6
6
  # A container for metadata regarding arguments present in a GraphQL query.
7
7
  # @see Interpreter::Arguments#argument_values for a hash of these objects.
8
8
  class ArgumentValue
9
- def initialize(definition:, value:, default_used:)
9
+ def initialize(definition:, value:, original_value:, default_used:)
10
10
  @definition = definition
11
11
  @value = value
12
+ @original_value = original_value
12
13
  @default_used = default_used
13
14
  end
14
15
 
15
16
  # @return [Object] The Ruby-ready value for this Argument
16
17
  attr_reader :value
17
18
 
19
+ # @return [Object] The value of this argument _before_ `prepare` is applied.
20
+ attr_reader :original_value
21
+
18
22
  # @return [GraphQL::Schema::Argument] The definition instance for this argument
19
23
  attr_reader :definition
20
24
 
@@ -8,22 +8,17 @@ module GraphQL
8
8
  @query = query
9
9
  @dataloader = query.context.dataloader
10
10
  @storage = Hash.new do |h, argument_owner|
11
- args_by_parent = if argument_owner.arguments_statically_coercible?
11
+ h[argument_owner] = if argument_owner.arguments_statically_coercible?
12
12
  shared_values_cache = {}
13
13
  Hash.new do |h2, ignored_parent_object|
14
14
  h2[ignored_parent_object] = shared_values_cache
15
- end
15
+ end.compare_by_identity
16
16
  else
17
17
  Hash.new do |h2, parent_object|
18
- args_by_node = {}
19
- args_by_node.compare_by_identity
20
- h2[parent_object] = args_by_node
21
- end
18
+ h2[parent_object] = {}.compare_by_identity
19
+ end.compare_by_identity
22
20
  end
23
- args_by_parent.compare_by_identity
24
- h[argument_owner] = args_by_parent
25
- end
26
- @storage.compare_by_identity
21
+ end.compare_by_identity
27
22
  end
28
23
 
29
24
  def fetch(ast_node, argument_owner, parent_object)