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
@@ -0,0 +1,308 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "graphql/dataloader/null_dataloader"
4
+ require "graphql/dataloader/request"
5
+ require "graphql/dataloader/request_all"
6
+ require "graphql/dataloader/source"
7
+
8
+ module GraphQL
9
+ # This plugin supports Fiber-based concurrency, along with {GraphQL::Dataloader::Source}.
10
+ #
11
+ # @example Installing Dataloader
12
+ #
13
+ # class MySchema < GraphQL::Schema
14
+ # use GraphQL::Dataloader
15
+ # end
16
+ #
17
+ # @example Waiting for batch-loaded data in a GraphQL field
18
+ #
19
+ # field :team, Types::Team, null: true
20
+ #
21
+ # def team
22
+ # dataloader.with(Sources::Record, Team).load(object.team_id)
23
+ # end
24
+ #
25
+ class Dataloader
26
+ class << self
27
+ attr_accessor :default_nonblocking
28
+ end
29
+
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
35
+ else
36
+ self
37
+ end
38
+ end
39
+
40
+ # Call the block with a Dataloader instance,
41
+ # then run all enqueued jobs and return the result of the block.
42
+ def self.with_dataloading(&block)
43
+ dataloader = self.new
44
+ result = nil
45
+ dataloader.append_job {
46
+ result = block.call(dataloader)
47
+ }
48
+ dataloader.run
49
+ result
50
+ end
51
+
52
+ def initialize(nonblocking: self.class.default_nonblocking)
53
+ @source_cache = Hash.new { |h, k| h[k] = {} }
54
+ @pending_jobs = []
55
+ if !nonblocking.nil?
56
+ @nonblocking = nonblocking
57
+ end
58
+ end
59
+
60
+ def nonblocking?
61
+ @nonblocking
62
+ end
63
+
64
+ # Get a Source instance from this dataloader, for calling `.load(...)` or `.request(...)` on.
65
+ #
66
+ # @param source_class [Class<GraphQL::Dataloader::Source]
67
+ # @param batch_parameters [Array<Object>]
68
+ # @return [GraphQL::Dataloader::Source] An instance of {source_class}, initialized with `self, *batch_parameters`,
69
+ # and cached for the lifetime of this {Multiplex}.
70
+ if RUBY_VERSION < "3" || RUBY_ENGINE != "ruby" # truffle-ruby wasn't doing well with the implementation below
71
+ def with(source_class, *batch_args)
72
+ batch_key = source_class.batch_key_for(*batch_args)
73
+ @source_cache[source_class][batch_key] ||= begin
74
+ source = source_class.new(*batch_args)
75
+ source.setup(self)
76
+ source
77
+ end
78
+ end
79
+ else
80
+ def with(source_class, *batch_args, **batch_kwargs)
81
+ batch_key = source_class.batch_key_for(*batch_args, **batch_kwargs)
82
+ @source_cache[source_class][batch_key] ||= begin
83
+ source = source_class.new(*batch_args, **batch_kwargs)
84
+ source.setup(self)
85
+ source
86
+ end
87
+ end
88
+ end
89
+ # Tell the dataloader that this fiber is waiting for data.
90
+ #
91
+ # Dataloader will resume the fiber after the requested data has been loaded (by another Fiber).
92
+ #
93
+ # @return [void]
94
+ def yield
95
+ Fiber.yield
96
+ nil
97
+ end
98
+
99
+ # @api private Nothing to see here
100
+ def append_job(&job)
101
+ # 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)
104
+ nil
105
+ end
106
+
107
+ # Use a self-contained queue for the work in the block.
108
+ def run_isolated
109
+ prev_queue = @pending_jobs
110
+ prev_pending_keys = {}
111
+ @source_cache.each do |source_class, batched_sources|
112
+ batched_sources.each do |batch_args, batched_source_instance|
113
+ if batched_source_instance.pending?
114
+ prev_pending_keys[batched_source_instance] = batched_source_instance.pending_keys.dup
115
+ batched_source_instance.pending_keys.clear
116
+ end
117
+ end
118
+ end
119
+
120
+ @pending_jobs = []
121
+ res = nil
122
+ # Make sure the block is inside a Fiber, so it can `Fiber.yield`
123
+ append_job {
124
+ res = yield
125
+ }
126
+ run
127
+ res
128
+ ensure
129
+ @pending_jobs = prev_queue
130
+ prev_pending_keys.each do |source_instance, pending_keys|
131
+ source_instance.pending_keys.concat(pending_keys)
132
+ end
133
+ end
134
+
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 = []
161
+ next_source_fibers = []
162
+ first_pass = true
163
+
164
+ while first_pass || (f = pending_fibers.shift)
165
+ if first_pass
166
+ 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
+
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
182
+ 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
+ end
190
+ end
191
+
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
202
+
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
222
+ end
223
+
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
+ end
231
+ nil
232
+ end
233
+
234
+ def join_queues(previous_queue, next_queue)
235
+ if @nonblocking
236
+ Fiber.scheduler.run
237
+ next_queue.select!(&:alive?)
238
+ end
239
+ previous_queue.concat(next_queue)
240
+ end
241
+
242
+ private
243
+
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
255
+ end
256
+ end
257
+ end
258
+
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)
270
+ end
271
+ end
272
+
273
+ source_fiber
274
+ end
275
+
276
+ def resume(fiber)
277
+ fiber.resume
278
+ rescue UncaughtThrowError => e
279
+ throw e.tag, e.value
280
+ end
281
+
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 = {}
290
+
291
+ Thread.current.keys.each do |fiber_var_key|
292
+ fiber_locals[fiber_var_key] = Thread.current[fiber_var_key]
293
+ end
294
+
295
+ if @nonblocking
296
+ Fiber.new(blocking: false) do
297
+ fiber_locals.each { |k, v| Thread.current[k] = v }
298
+ yield
299
+ end
300
+ else
301
+ Fiber.new do
302
+ fiber_locals.each { |k, v| Thread.current[k] = v }
303
+ yield
304
+ end
305
+ end
306
+ end
307
+ end
308
+ end
@@ -2,9 +2,9 @@
2
2
  module GraphQL
3
3
  module Define
4
4
  module AssignGlobalIdField
5
- def self.call(type_defn, field_name)
5
+ def self.call(type_defn, field_name, **field_kwargs)
6
6
  resolve = GraphQL::Relay::GlobalIdResolve.new(type: type_defn)
7
- GraphQL::Define::AssignObjectField.call(type_defn, field_name, type: GraphQL::ID_TYPE.to_non_null_type, resolve: resolve)
7
+ GraphQL::Define::AssignObjectField.call(type_defn, field_name, **field_kwargs, type: GraphQL::DEPRECATED_ID_TYPE.to_non_null_type, resolve: resolve)
8
8
  end
9
9
  end
10
10
  end
@@ -34,7 +34,6 @@ module GraphQL
34
34
  end
35
35
 
36
36
  # Lookup a function from the dictionary and call it if it's found.
37
- ruby2_keywords
38
37
  def method_missing(name, *args, &block)
39
38
  definition = @dictionary[name]
40
39
  if definition
@@ -44,6 +43,7 @@ module GraphQL
44
43
  raise NoDefinitionError, msg, caller
45
44
  end
46
45
  end
46
+ ruby2_keywords :method_missing
47
47
 
48
48
  def respond_to_missing?(name, include_private = false)
49
49
  @dictionary[name] || super
@@ -3,6 +3,23 @@ module GraphQL
3
3
  module Define
4
4
  # @api deprecated
5
5
  module InstanceDefinable
6
+ module DeprecatedDefine
7
+ def define(**kwargs, &block)
8
+ deprecated_caller = caller(1, 1).first
9
+ if deprecated_caller.include?("lib/graphql")
10
+ deprecated_caller = caller(2, 10).find { |c| !c.include?("lib/graphql") }
11
+ end
12
+
13
+ if deprecated_caller
14
+ GraphQL::Deprecation.warn <<-ERR
15
+ #{self}.define will be removed in GraphQL-Ruby 2.0; use a class-based definition instead. See https://graphql-ruby.org/schema/class_based_api.html.
16
+ -> called from #{deprecated_caller}
17
+ ERR
18
+ end
19
+ deprecated_define(**kwargs, &block)
20
+ end
21
+ end
22
+
6
23
  def self.included(base)
7
24
  base.extend(ClassMethods)
8
25
  base.ensure_defined(:metadata)
@@ -14,7 +31,7 @@ module GraphQL
14
31
  end
15
32
 
16
33
  # @api deprecated
17
- def define(**kwargs, &block)
34
+ def deprecated_define(**kwargs, &block)
18
35
  # make sure the previous definition_proc was executed:
19
36
  ensure_defined
20
37
  stash_dependent_methods
@@ -22,11 +39,16 @@ module GraphQL
22
39
  nil
23
40
  end
24
41
 
42
+ # @api deprecated
43
+ def define(**kwargs, &block)
44
+ deprecated_define(**kwargs, &block)
45
+ end
46
+
25
47
  # @api deprecated
26
48
  def redefine(**kwargs, &block)
27
49
  ensure_defined
28
50
  new_inst = self.dup
29
- new_inst.define(**kwargs, &block)
51
+ new_inst.deprecated_define(**kwargs, &block)
30
52
  new_inst
31
53
  end
32
54
 
@@ -54,7 +76,7 @@ module GraphQL
54
76
  # Apply definition from `define(...)` kwargs
55
77
  defn.define_keywords.each do |keyword, value|
56
78
  # Don't splat string hashes, which blows up on Rubies before 2.7
57
- if value.is_a?(Hash) && value.each_key.all? { |k| k.is_a?(Symbol) }
79
+ if value.is_a?(Hash) && !value.empty? && value.each_key.all? { |k| k.is_a?(Symbol) }
58
80
  defn_proxy.public_send(keyword, **value)
59
81
  else
60
82
  defn_proxy.public_send(keyword, value)
@@ -125,8 +147,16 @@ module GraphQL
125
147
  module ClassMethods
126
148
  # Create a new instance
127
149
  # and prepare a definition using its {.definitions}.
150
+ # @api deprecated
128
151
  # @param kwargs [Hash] Key-value pairs corresponding to defininitions from `accepts_definitions`
129
152
  # @param block [Proc] Block which calls helper methods from `accepts_definitions`
153
+ def deprecated_define(**kwargs, &block)
154
+ instance = self.new
155
+ instance.deprecated_define(**kwargs, &block)
156
+ instance
157
+ end
158
+
159
+ # @api deprecated
130
160
  def define(**kwargs, &block)
131
161
  instance = self.new
132
162
  instance.define(**kwargs, &block)
@@ -200,10 +230,10 @@ module GraphQL
200
230
  # Even though we're just using the first value here,
201
231
  # We have to add a splat here to use `ruby2_keywords`,
202
232
  # so that it will accept a `[{}]` input from the caller.
203
- ruby2_keywords
204
233
  def call(defn, *value)
205
234
  defn.public_send(@attr_assign_method, value.first)
206
235
  end
236
+ ruby2_keywords :call
207
237
  end
208
238
  end
209
239
  end
@@ -7,11 +7,11 @@ module GraphQL
7
7
  class TypeDefiner
8
8
  include Singleton
9
9
  # rubocop:disable Naming/MethodName
10
- def Int; GraphQL::INT_TYPE; end
11
- def String; GraphQL::STRING_TYPE; end
12
- def Float; GraphQL::FLOAT_TYPE; end
13
- def Boolean; GraphQL::BOOLEAN_TYPE; end
14
- def ID; GraphQL::ID_TYPE; end
10
+ def Int; GraphQL::DEPRECATED_INT_TYPE; end
11
+ def String; GraphQL::DEPRECATED_STRING_TYPE; end
12
+ def Float; GraphQL::DEPRECATED_FLOAT_TYPE; end
13
+ def Boolean; GraphQL::DEPRECATED_BOOLEAN_TYPE; end
14
+ def ID; GraphQL::DEPRECATED_ID_TYPE; end
15
15
  # rubocop:enable Naming/MethodName
16
16
 
17
17
  # Make a {ListType} which wraps the input type
@@ -4,13 +4,13 @@ module GraphQL
4
4
  #
5
5
  # 1. Scoped by file (CRuby only), add to the top of the file:
6
6
  #
7
- # using GraphQL::DeprecatedDSL
7
+ # using GraphQL::DeprecationDSL
8
8
  #
9
9
  # (This is a "refinement", there are also other ways to scope it.)
10
10
  #
11
11
  # 2. Global application, add before schema definition:
12
12
  #
13
- # GraphQL::DeprecatedDSL.activate
13
+ # GraphQL::DeprecationDSL.activate
14
14
  #
15
15
  module DeprecatedDSL
16
16
  TYPE_CLASSES = [
@@ -23,19 +23,32 @@ module GraphQL
23
23
  ]
24
24
 
25
25
  def self.activate
26
+ deprecated_caller = caller(1, 1).first
27
+ GraphQL::Deprecation.warn "DeprecatedDSL will be removed from GraphQL-Ruby 2.0, use `.to_non_null_type` instead of `!` and remove `.activate` from #{deprecated_caller}"
26
28
  TYPE_CLASSES.each { |c| c.extend(Methods) }
27
29
  GraphQL::Schema::List.include(Methods)
28
30
  GraphQL::Schema::NonNull.include(Methods)
29
31
  end
32
+
30
33
  module Methods
31
34
  def !
35
+ deprecated_caller = caller(1, 1).first
36
+ GraphQL::Deprecation.warn "DeprecatedDSL will be removed from GraphQL-Ruby 2.0, use `.to_non_null_type` instead of `!` at #{deprecated_caller}"
32
37
  to_non_null_type
33
38
  end
34
39
  end
35
40
 
36
- TYPE_CLASSES.each do |type_class|
37
- refine type_class.singleton_class do
38
- include Methods
41
+ if defined?(::Refinement) && Refinement.private_method_defined?(:import_methods)
42
+ TYPE_CLASSES.each do |type_class|
43
+ refine type_class.singleton_class do
44
+ import_methods Methods
45
+ end
46
+ end
47
+ else
48
+ TYPE_CLASSES.each do |type_class|
49
+ refine type_class.singleton_class do
50
+ include Methods
51
+ end
39
52
  end
40
53
  end
41
54
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Deprecation
5
+ def self.warn(message)
6
+ Kernel.warn(message)
7
+ end
8
+ end
9
+ end
@@ -99,9 +99,9 @@ module GraphQL
99
99
  def type_class
100
100
  metadata[:type_class]
101
101
  end
102
+
103
+ def get_argument(argument_name)
104
+ arguments[argument_name]
105
+ end
102
106
  end
103
107
  end
104
-
105
- require "graphql/directive/include_directive"
106
- require "graphql/directive/skip_directive"
107
- require "graphql/directive/deprecated_directive"
@@ -2,6 +2,8 @@
2
2
  module GraphQL
3
3
  # @api deprecated
4
4
  class EnumType < GraphQL::BaseType
5
+ extend Define::InstanceDefinable::DeprecatedDefine
6
+
5
7
  accepts_definitions :values, value: GraphQL::Define::AssignEnumValue
6
8
  ensure_defined(:values, :validate_non_null_input, :coerce_non_null_input, :coerce_result)
7
9
  attr_accessor :ast_node
@@ -32,10 +34,14 @@ module GraphQL
32
34
  end
33
35
 
34
36
  # @return [Hash<String => EnumValue>] `{name => value}` pairs contained in this type
35
- def values
37
+ def values(_context = nil)
36
38
  @values_by_name
37
39
  end
38
40
 
41
+ def enum_values(_context = nil)
42
+ values.values
43
+ end
44
+
39
45
  def kind
40
46
  GraphQL::TypeKinds::ENUM
41
47
  end
@@ -18,17 +18,83 @@ module GraphQL
18
18
  #
19
19
  class Errors
20
20
  def self.use(schema)
21
- schema.error_handler = self.new(schema)
21
+ definition_line = caller(2, 1).first
22
+ GraphQL::Deprecation.warn("GraphQL::Execution::Errors is now installed by default, remove `use GraphQL::Execution::Errors` from #{definition_line}")
22
23
  end
23
24
 
25
+ NEW_HANDLER_HASH = ->(h, k) {
26
+ h[k] = {
27
+ class: k,
28
+ handler: nil,
29
+ subclass_handlers: Hash.new(&NEW_HANDLER_HASH),
30
+ }
31
+ }
32
+
24
33
  def initialize(schema)
25
34
  @schema = schema
35
+ @handlers = {
36
+ class: nil,
37
+ handler: nil,
38
+ subclass_handlers: Hash.new(&NEW_HANDLER_HASH),
39
+ }
26
40
  end
27
41
 
28
- class NullErrorHandler
29
- def self.with_error_handling(_ctx)
30
- yield
42
+ # @api private
43
+ def each_rescue
44
+ handlers = @handlers.values
45
+ while (handler = handlers.shift) do
46
+ yield(handler[:class], handler[:handler])
47
+ handlers.concat(handler[:subclass_handlers].values)
48
+ end
49
+ end
50
+
51
+ # Register this handler, updating the
52
+ # internal handler index to maintain least-to-most specific.
53
+ #
54
+ # @param error_class [Class<Exception>]
55
+ # @param error_handler [Proc]
56
+ # @return [void]
57
+ def rescue_from(error_class, error_handler)
58
+ subclasses_handlers = {}
59
+ this_level_subclasses = []
60
+ # During this traversal, do two things:
61
+ # - Identify any already-registered subclasses of this error class
62
+ # and gather them up to be inserted _under_ this class
63
+ # - Find the point in the index where this handler should be inserted
64
+ # (That is, _under_ any superclasses, or at top-level, if there are no superclasses registered)
65
+ handlers = @handlers[:subclass_handlers]
66
+ while (handlers) do
67
+ this_level_subclasses.clear
68
+ # First, identify already-loaded handlers that belong
69
+ # _under_ this one. (That is, they're handlers
70
+ # for subclasses of `error_class`.)
71
+ handlers.each do |err_class, handler|
72
+ if err_class < error_class
73
+ subclasses_handlers[err_class] = handler
74
+ this_level_subclasses << err_class
75
+ end
76
+ end
77
+ # Any handlers that we'll be moving, delete them from this point in the index
78
+ this_level_subclasses.each do |err_class|
79
+ handlers.delete(err_class)
80
+ end
81
+
82
+ # See if any keys in this hash are superclasses of this new class:
83
+ next_index_point = handlers.find { |err_class, handler| error_class < err_class }
84
+ if next_index_point
85
+ handlers = next_index_point[1][:subclass_handlers]
86
+ else
87
+ # this new handler doesn't belong to any sub-handlers,
88
+ # so insert it in the current set of `handlers`
89
+ break
90
+ end
31
91
  end
92
+ # Having found the point at which to insert this handler,
93
+ # register it and merge any subclass handlers back in at this point.
94
+ this_class_handlers = handlers[error_class]
95
+ this_class_handlers[:handler] = error_handler
96
+ this_class_handlers[:subclass_handlers].merge!(subclasses_handlers)
97
+ nil
32
98
  end
33
99
 
34
100
  # Call the given block with the schema's configured error handlers.
@@ -40,21 +106,58 @@ module GraphQL
40
106
  def with_error_handling(ctx)
41
107
  yield
42
108
  rescue StandardError => err
43
- rescues = ctx.schema.rescues
44
- _err_class, handler = rescues.find { |err_class, handler| err.is_a?(err_class) }
109
+ handler = find_handler_for(err.class)
45
110
  if handler
46
111
  runtime_info = ctx.namespace(:interpreter) || {}
47
112
  obj = runtime_info[:current_object]
48
113
  args = runtime_info[:current_arguments]
114
+ args = args && args.keyword_arguments
49
115
  field = runtime_info[:current_field]
50
116
  if obj.is_a?(GraphQL::Schema::Object)
51
117
  obj = obj.object
52
118
  end
53
- handler.call(err, obj, args, ctx, field)
119
+ handler[:handler].call(err, obj, args, ctx, field)
54
120
  else
55
121
  raise err
56
122
  end
57
123
  end
124
+
125
+ # @return [Proc, nil] The handler for `error_class`, if one was registered on this schema or inherited
126
+ def find_handler_for(error_class)
127
+ handlers = @handlers[:subclass_handlers]
128
+ handler = nil
129
+ while (handlers) do
130
+ _err_class, next_handler = handlers.find { |err_class, handler| error_class <= err_class }
131
+ if next_handler
132
+ handlers = next_handler[:subclass_handlers]
133
+ handler = next_handler
134
+ else
135
+ # Don't reassign `handler` --
136
+ # let the previous assignment carry over outside this block.
137
+ break
138
+ end
139
+ end
140
+
141
+ # check for a handler from a parent class:
142
+ if @schema.superclass.respond_to?(:error_handler) && (parent_errors = @schema.superclass.error_handler)
143
+ parent_handler = parent_errors.find_handler_for(error_class)
144
+ end
145
+
146
+ # If the inherited handler is more specific than the one defined here,
147
+ # use it.
148
+ # If it's a tie (or there is no parent handler), use the one defined here.
149
+ # If there's an inherited one, but not one defined here, use the inherited one.
150
+ # Otherwise, there's no handler for this error, return `nil`.
151
+ if parent_handler && handler && parent_handler[:class] < handler[:class]
152
+ parent_handler
153
+ elsif handler
154
+ handler
155
+ elsif parent_handler
156
+ parent_handler
157
+ else
158
+ nil
159
+ end
160
+ end
58
161
  end
59
162
  end
60
163
  end