graphql 2.0.30 → 2.3.6

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 (157) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install/mutation_root_generator.rb +2 -2
  3. data/lib/generators/graphql/install/templates/base_mutation.erb +2 -0
  4. data/lib/generators/graphql/install/templates/mutation_type.erb +2 -0
  5. data/lib/generators/graphql/install_generator.rb +3 -0
  6. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  7. data/lib/generators/graphql/templates/base_connection.erb +2 -0
  8. data/lib/generators/graphql/templates/base_edge.erb +2 -0
  9. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  10. data/lib/generators/graphql/templates/base_field.erb +2 -0
  11. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  12. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  13. data/lib/generators/graphql/templates/base_object.erb +2 -0
  14. data/lib/generators/graphql/templates/base_resolver.erb +6 -0
  15. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  16. data/lib/generators/graphql/templates/base_union.erb +2 -0
  17. data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
  18. data/lib/generators/graphql/templates/loader.erb +2 -0
  19. data/lib/generators/graphql/templates/mutation.erb +2 -0
  20. data/lib/generators/graphql/templates/node_type.erb +2 -0
  21. data/lib/generators/graphql/templates/query_type.erb +2 -0
  22. data/lib/generators/graphql/templates/schema.erb +5 -0
  23. data/lib/graphql/analysis/analyzer.rb +89 -0
  24. data/lib/graphql/analysis/field_usage.rb +82 -0
  25. data/lib/graphql/analysis/max_query_complexity.rb +20 -0
  26. data/lib/graphql/analysis/max_query_depth.rb +20 -0
  27. data/lib/graphql/analysis/query_complexity.rb +183 -0
  28. data/lib/graphql/analysis/query_depth.rb +58 -0
  29. data/lib/graphql/analysis/visitor.rb +282 -0
  30. data/lib/graphql/analysis.rb +92 -1
  31. data/lib/graphql/backtrace/inspect_result.rb +0 -12
  32. data/lib/graphql/backtrace/trace.rb +12 -15
  33. data/lib/graphql/coercion_error.rb +1 -9
  34. data/lib/graphql/dataloader/async_dataloader.rb +88 -0
  35. data/lib/graphql/dataloader/null_dataloader.rb +1 -1
  36. data/lib/graphql/dataloader/request.rb +5 -0
  37. data/lib/graphql/dataloader/source.rb +11 -3
  38. data/lib/graphql/dataloader.rb +112 -142
  39. data/lib/graphql/duration_encoding_error.rb +16 -0
  40. data/lib/graphql/execution/interpreter/argument_value.rb +5 -1
  41. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +175 -0
  42. data/lib/graphql/execution/interpreter/runtime.rb +163 -365
  43. data/lib/graphql/execution/interpreter.rb +92 -158
  44. data/lib/graphql/execution/lookahead.rb +88 -21
  45. data/lib/graphql/introspection/dynamic_fields.rb +1 -1
  46. data/lib/graphql/introspection/entry_points.rb +11 -5
  47. data/lib/graphql/introspection/schema_type.rb +3 -1
  48. data/lib/graphql/language/block_string.rb +34 -18
  49. data/lib/graphql/language/definition_slice.rb +1 -1
  50. data/lib/graphql/language/document_from_schema_definition.rb +38 -38
  51. data/lib/graphql/language/lexer.rb +305 -193
  52. data/lib/graphql/language/nodes.rb +113 -66
  53. data/lib/graphql/language/parser.rb +787 -1986
  54. data/lib/graphql/language/printer.rb +303 -146
  55. data/lib/graphql/language/sanitized_printer.rb +20 -22
  56. data/lib/graphql/language/static_visitor.rb +167 -0
  57. data/lib/graphql/language/visitor.rb +20 -81
  58. data/lib/graphql/language.rb +61 -0
  59. data/lib/graphql/load_application_object_failed_error.rb +5 -1
  60. data/lib/graphql/pagination/array_connection.rb +6 -6
  61. data/lib/graphql/pagination/connection.rb +28 -1
  62. data/lib/graphql/pagination/mongoid_relation_connection.rb +1 -2
  63. data/lib/graphql/query/context/scoped_context.rb +101 -0
  64. data/lib/graphql/query/context.rb +66 -131
  65. data/lib/graphql/query/null_context.rb +4 -11
  66. data/lib/graphql/query/validation_pipeline.rb +4 -4
  67. data/lib/graphql/query/variables.rb +3 -3
  68. data/lib/graphql/query.rb +17 -26
  69. data/lib/graphql/railtie.rb +9 -6
  70. data/lib/graphql/rake_task.rb +3 -12
  71. data/lib/graphql/rubocop/graphql/base_cop.rb +1 -1
  72. data/lib/graphql/schema/addition.rb +21 -11
  73. data/lib/graphql/schema/argument.rb +43 -8
  74. data/lib/graphql/schema/base_64_encoder.rb +3 -5
  75. data/lib/graphql/schema/build_from_definition.rb +9 -12
  76. data/lib/graphql/schema/directive/one_of.rb +12 -0
  77. data/lib/graphql/schema/directive/specified_by.rb +14 -0
  78. data/lib/graphql/schema/directive.rb +3 -1
  79. data/lib/graphql/schema/enum.rb +3 -3
  80. data/lib/graphql/schema/field/connection_extension.rb +1 -15
  81. data/lib/graphql/schema/field/scope_extension.rb +8 -1
  82. data/lib/graphql/schema/field.rb +49 -35
  83. data/lib/graphql/schema/has_single_input_argument.rb +157 -0
  84. data/lib/graphql/schema/input_object.rb +4 -4
  85. data/lib/graphql/schema/interface.rb +10 -10
  86. data/lib/graphql/schema/introspection_system.rb +4 -2
  87. data/lib/graphql/schema/late_bound_type.rb +4 -0
  88. data/lib/graphql/schema/list.rb +2 -2
  89. data/lib/graphql/schema/loader.rb +2 -3
  90. data/lib/graphql/schema/member/base_dsl_methods.rb +2 -1
  91. data/lib/graphql/schema/member/has_arguments.rb +63 -73
  92. data/lib/graphql/schema/member/has_directives.rb +1 -1
  93. data/lib/graphql/schema/member/has_fields.rb +8 -5
  94. data/lib/graphql/schema/member/has_interfaces.rb +23 -9
  95. data/lib/graphql/schema/member/relay_shortcuts.rb +1 -1
  96. data/lib/graphql/schema/member/scoped.rb +19 -0
  97. data/lib/graphql/schema/member/type_system_helpers.rb +1 -2
  98. data/lib/graphql/schema/member/validates_input.rb +3 -3
  99. data/lib/graphql/schema/mutation.rb +7 -0
  100. data/lib/graphql/schema/object.rb +8 -0
  101. data/lib/graphql/schema/printer.rb +8 -7
  102. data/lib/graphql/schema/relay_classic_mutation.rb +6 -128
  103. data/lib/graphql/schema/resolver.rb +27 -13
  104. data/lib/graphql/schema/scalar.rb +3 -3
  105. data/lib/graphql/schema/subscription.rb +11 -4
  106. data/lib/graphql/schema/union.rb +1 -1
  107. data/lib/graphql/schema/unique_within_type.rb +1 -1
  108. data/lib/graphql/schema/warden.rb +96 -95
  109. data/lib/graphql/schema.rb +323 -102
  110. data/lib/graphql/static_validation/all_rules.rb +1 -1
  111. data/lib/graphql/static_validation/base_visitor.rb +1 -1
  112. data/lib/graphql/static_validation/literal_validator.rb +2 -3
  113. data/lib/graphql/static_validation/rules/fields_will_merge.rb +2 -2
  114. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  115. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +2 -2
  116. data/lib/graphql/static_validation/validation_context.rb +5 -5
  117. data/lib/graphql/static_validation/validator.rb +3 -0
  118. data/lib/graphql/static_validation.rb +0 -1
  119. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +4 -3
  120. data/lib/graphql/subscriptions/broadcast_analyzer.rb +1 -1
  121. data/lib/graphql/subscriptions/event.rb +8 -2
  122. data/lib/graphql/subscriptions/serialize.rb +2 -0
  123. data/lib/graphql/subscriptions.rb +15 -13
  124. data/lib/graphql/testing/helpers.rb +151 -0
  125. data/lib/graphql/testing.rb +2 -0
  126. data/lib/graphql/tracing/appoptics_trace.rb +2 -2
  127. data/lib/graphql/tracing/appoptics_tracing.rb +2 -2
  128. data/lib/graphql/tracing/legacy_hooks_trace.rb +74 -0
  129. data/lib/graphql/tracing/platform_tracing.rb +3 -1
  130. data/lib/graphql/tracing/{prometheus_tracing → prometheus_trace}/graphql_collector.rb +3 -1
  131. data/lib/graphql/tracing/prometheus_trace.rb +9 -9
  132. data/lib/graphql/tracing/sentry_trace.rb +112 -0
  133. data/lib/graphql/tracing/trace.rb +1 -0
  134. data/lib/graphql/tracing.rb +3 -1
  135. data/lib/graphql/type_kinds.rb +1 -1
  136. data/lib/graphql/types/iso_8601_duration.rb +77 -0
  137. data/lib/graphql/types/relay/connection_behaviors.rb +32 -2
  138. data/lib/graphql/types/relay/edge_behaviors.rb +7 -0
  139. data/lib/graphql/types.rb +1 -0
  140. data/lib/graphql/version.rb +1 -1
  141. data/lib/graphql.rb +13 -13
  142. data/readme.md +12 -2
  143. metadata +33 -26
  144. data/lib/graphql/analysis/ast/analyzer.rb +0 -84
  145. data/lib/graphql/analysis/ast/field_usage.rb +0 -57
  146. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -22
  147. data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
  148. data/lib/graphql/analysis/ast/query_complexity.rb +0 -230
  149. data/lib/graphql/analysis/ast/query_depth.rb +0 -55
  150. data/lib/graphql/analysis/ast/visitor.rb +0 -276
  151. data/lib/graphql/analysis/ast.rb +0 -81
  152. data/lib/graphql/deprecation.rb +0 -9
  153. data/lib/graphql/filter.rb +0 -59
  154. data/lib/graphql/language/parser.y +0 -560
  155. data/lib/graphql/schema/base_64_bp.rb +0 -26
  156. data/lib/graphql/static_validation/type_stack.rb +0 -216
  157. data/lib/graphql/subscriptions/instrumentation.rb +0 -28
@@ -0,0 +1,282 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Analysis
4
+ # Depth first traversal through a query AST, calling AST analyzers
5
+ # along the way.
6
+ #
7
+ # The visitor is a special case of GraphQL::Language::StaticVisitor, visiting
8
+ # only the selected operation, providing helpers for common use cases such
9
+ # as skipped fields and visiting fragment spreads.
10
+ #
11
+ # @see {GraphQL::Analysis::Analyzer} AST Analyzers for queries
12
+ class Visitor < GraphQL::Language::StaticVisitor
13
+ def initialize(query:, analyzers:)
14
+ @analyzers = analyzers
15
+ @path = []
16
+ @object_types = []
17
+ @directives = []
18
+ @field_definitions = []
19
+ @argument_definitions = []
20
+ @directive_definitions = []
21
+ @rescued_errors = []
22
+ @query = query
23
+ @schema = query.schema
24
+ @response_path = []
25
+ @skip_stack = [false]
26
+ super(query.selected_operation)
27
+ end
28
+
29
+ # @return [GraphQL::Query] the query being visited
30
+ attr_reader :query
31
+
32
+ # @return [Array<GraphQL::ObjectType>] Types whose scope we've entered
33
+ attr_reader :object_types
34
+
35
+ # @return [Array<GraphQL::AnalysisError]
36
+ attr_reader :rescued_errors
37
+
38
+ def visit
39
+ return unless @document
40
+ super
41
+ end
42
+
43
+ # Visit Helpers
44
+
45
+ # @return [GraphQL::Execution::Interpreter::Arguments] Arguments for this node, merging default values, literal values and query variables
46
+ # @see {GraphQL::Query#arguments_for}
47
+ def arguments_for(ast_node, field_definition)
48
+ @query.arguments_for(ast_node, field_definition)
49
+ end
50
+
51
+ # @return [Boolean] If the visitor is currently inside a fragment definition
52
+ def visiting_fragment_definition?
53
+ @in_fragment_def
54
+ end
55
+
56
+ # @return [Boolean] If the current node should be skipped because of a skip or include directive
57
+ def skipping?
58
+ @skipping
59
+ end
60
+
61
+ # @return [Array<String>] The path to the response key for the current field
62
+ def response_path
63
+ @response_path.dup
64
+ end
65
+
66
+ # Visitor Hooks
67
+ [
68
+ :operation_definition, :fragment_definition,
69
+ :inline_fragment, :field, :directive, :argument, :fragment_spread
70
+ ].each do |node_type|
71
+ module_eval <<-RUBY, __FILE__, __LINE__
72
+ def call_on_enter_#{node_type}(node, parent)
73
+ @analyzers.each do |a|
74
+ begin
75
+ a.on_enter_#{node_type}(node, parent, self)
76
+ rescue AnalysisError => err
77
+ @rescued_errors << err
78
+ end
79
+ end
80
+ end
81
+
82
+ def call_on_leave_#{node_type}(node, parent)
83
+ @analyzers.each do |a|
84
+ begin
85
+ a.on_leave_#{node_type}(node, parent, self)
86
+ rescue AnalysisError => err
87
+ @rescued_errors << err
88
+ end
89
+ end
90
+ end
91
+
92
+ RUBY
93
+ end
94
+
95
+ def on_operation_definition(node, parent)
96
+ object_type = @schema.root_type_for_operation(node.operation_type)
97
+ @object_types.push(object_type)
98
+ @path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}")
99
+ call_on_enter_operation_definition(node, parent)
100
+ super
101
+ call_on_leave_operation_definition(node, parent)
102
+ @object_types.pop
103
+ @path.pop
104
+ end
105
+
106
+ def on_fragment_definition(node, parent)
107
+ on_fragment_with_type(node) do
108
+ @path.push("fragment #{node.name}")
109
+ @in_fragment_def = false
110
+ call_on_enter_fragment_definition(node, parent)
111
+ super
112
+ @in_fragment_def = false
113
+ call_on_leave_fragment_definition(node, parent)
114
+ end
115
+ end
116
+
117
+ def on_inline_fragment(node, parent)
118
+ on_fragment_with_type(node) do
119
+ @path.push("...#{node.type ? " on #{node.type.name}" : ""}")
120
+ @skipping = @skip_stack.last || skip?(node)
121
+ @skip_stack << @skipping
122
+
123
+ call_on_enter_inline_fragment(node, parent)
124
+ super
125
+ @skipping = @skip_stack.pop
126
+ call_on_leave_inline_fragment(node, parent)
127
+ end
128
+ end
129
+
130
+ def on_field(node, parent)
131
+ @response_path.push(node.alias || node.name)
132
+ parent_type = @object_types.last
133
+ # This could be nil if the previous field wasn't found:
134
+ field_definition = parent_type && @schema.get_field(parent_type, node.name, @query.context)
135
+ @field_definitions.push(field_definition)
136
+ if !field_definition.nil?
137
+ next_object_type = field_definition.type.unwrap
138
+ @object_types.push(next_object_type)
139
+ else
140
+ @object_types.push(nil)
141
+ end
142
+ @path.push(node.alias || node.name)
143
+
144
+ @skipping = @skip_stack.last || skip?(node)
145
+ @skip_stack << @skipping
146
+
147
+ call_on_enter_field(node, parent)
148
+ super
149
+ @skipping = @skip_stack.pop
150
+ call_on_leave_field(node, parent)
151
+ @response_path.pop
152
+ @field_definitions.pop
153
+ @object_types.pop
154
+ @path.pop
155
+ end
156
+
157
+ def on_directive(node, parent)
158
+ directive_defn = @schema.directives[node.name]
159
+ @directive_definitions.push(directive_defn)
160
+ call_on_enter_directive(node, parent)
161
+ super
162
+ call_on_leave_directive(node, parent)
163
+ @directive_definitions.pop
164
+ end
165
+
166
+ def on_argument(node, parent)
167
+ argument_defn = if (arg = @argument_definitions.last)
168
+ arg_type = arg.type.unwrap
169
+ if arg_type.kind.input_object?
170
+ arg_type.get_argument(node.name, @query.context)
171
+ else
172
+ nil
173
+ end
174
+ elsif (directive_defn = @directive_definitions.last)
175
+ directive_defn.get_argument(node.name, @query.context)
176
+ elsif (field_defn = @field_definitions.last)
177
+ field_defn.get_argument(node.name, @query.context)
178
+ else
179
+ nil
180
+ end
181
+
182
+ @argument_definitions.push(argument_defn)
183
+ @path.push(node.name)
184
+ call_on_enter_argument(node, parent)
185
+ super
186
+ call_on_leave_argument(node, parent)
187
+ @argument_definitions.pop
188
+ @path.pop
189
+ end
190
+
191
+ def on_fragment_spread(node, parent)
192
+ @path.push("... #{node.name}")
193
+ @skipping = @skip_stack.last || skip?(node)
194
+ @skip_stack << @skipping
195
+
196
+ call_on_enter_fragment_spread(node, parent)
197
+ enter_fragment_spread_inline(node)
198
+ super
199
+ @skipping = @skip_stack.pop
200
+ leave_fragment_spread_inline(node)
201
+ call_on_leave_fragment_spread(node, parent)
202
+ @path.pop
203
+ end
204
+
205
+ # @return [GraphQL::BaseType] The current object type
206
+ def type_definition
207
+ @object_types.last
208
+ end
209
+
210
+ # @return [GraphQL::BaseType] The type which the current type came from
211
+ def parent_type_definition
212
+ @object_types[-2]
213
+ end
214
+
215
+ # @return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one
216
+ def field_definition
217
+ @field_definitions.last
218
+ end
219
+
220
+ # @return [GraphQL::Field, nil] The GraphQL field which returned the object that the current field belongs to
221
+ def previous_field_definition
222
+ @field_definitions[-2]
223
+ end
224
+
225
+ # @return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one
226
+ def directive_definition
227
+ @directive_definitions.last
228
+ end
229
+
230
+ # @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one
231
+ def argument_definition
232
+ @argument_definitions.last
233
+ end
234
+
235
+ # @return [GraphQL::Argument, nil] The previous GraphQL argument
236
+ def previous_argument_definition
237
+ @argument_definitions[-2]
238
+ end
239
+
240
+ private
241
+
242
+ # Visit a fragment spread inline instead of visiting the definition
243
+ # by itself.
244
+ def enter_fragment_spread_inline(fragment_spread)
245
+ fragment_def = query.fragments[fragment_spread.name]
246
+
247
+ object_type = if fragment_def.type
248
+ @query.warden.get_type(fragment_def.type.name)
249
+ else
250
+ object_types.last
251
+ end
252
+
253
+ object_types << object_type
254
+
255
+ on_fragment_definition_children(fragment_def)
256
+ end
257
+
258
+ # Visit a fragment spread inline instead of visiting the definition
259
+ # by itself.
260
+ def leave_fragment_spread_inline(_fragment_spread)
261
+ object_types.pop
262
+ end
263
+
264
+ def skip?(ast_node)
265
+ dir = ast_node.directives
266
+ dir.any? && !GraphQL::Execution::DirectiveChecks.include?(dir, query)
267
+ end
268
+
269
+ def on_fragment_with_type(node)
270
+ object_type = if node.type
271
+ @query.warden.get_type(node.type.name)
272
+ else
273
+ @object_types.last
274
+ end
275
+ @object_types.push(object_type)
276
+ yield(node)
277
+ @object_types.pop
278
+ @path.pop
279
+ end
280
+ end
281
+ end
282
+ end
@@ -1,2 +1,93 @@
1
1
  # frozen_string_literal: true
2
- require "graphql/analysis/ast"
2
+ require "graphql/analysis/visitor"
3
+ require "graphql/analysis/analyzer"
4
+ require "graphql/analysis/field_usage"
5
+ require "graphql/analysis/query_complexity"
6
+ require "graphql/analysis/max_query_complexity"
7
+ require "graphql/analysis/query_depth"
8
+ require "graphql/analysis/max_query_depth"
9
+ require "timeout"
10
+
11
+ module GraphQL
12
+ module Analysis
13
+ AST = self
14
+ module_function
15
+ # Analyze a multiplex, and all queries within.
16
+ # Multiplex analyzers are ran for all queries, keeping state.
17
+ # Query analyzers are ran per query, without carrying state between queries.
18
+ #
19
+ # @param multiplex [GraphQL::Execution::Multiplex]
20
+ # @param analyzers [Array<GraphQL::Analysis::Analyzer>]
21
+ # @return [Array<Any>] Results from multiplex analyzers
22
+ def analyze_multiplex(multiplex, analyzers)
23
+ multiplex_analyzers = analyzers.map { |analyzer| analyzer.new(multiplex) }
24
+
25
+ multiplex.current_trace.analyze_multiplex(multiplex: multiplex) do
26
+ query_results = multiplex.queries.map do |query|
27
+ if query.valid?
28
+ analyze_query(
29
+ query,
30
+ query.analyzers,
31
+ multiplex_analyzers: multiplex_analyzers
32
+ )
33
+ else
34
+ []
35
+ end
36
+ end
37
+
38
+ multiplex_results = multiplex_analyzers.map(&:result)
39
+ multiplex_errors = analysis_errors(multiplex_results)
40
+
41
+ multiplex.queries.each_with_index do |query, idx|
42
+ query.analysis_errors = multiplex_errors + analysis_errors(query_results[idx])
43
+ end
44
+ multiplex_results
45
+ end
46
+ end
47
+
48
+ # @param query [GraphQL::Query]
49
+ # @param analyzers [Array<GraphQL::Analysis::Analyzer>]
50
+ # @return [Array<Any>] Results from those analyzers
51
+ def analyze_query(query, analyzers, multiplex_analyzers: [])
52
+ query.current_trace.analyze_query(query: query) do
53
+ query_analyzers = analyzers
54
+ .map { |analyzer| analyzer.new(query) }
55
+ .tap { _1.select!(&:analyze?) }
56
+
57
+ analyzers_to_run = query_analyzers + multiplex_analyzers
58
+ if analyzers_to_run.any?
59
+
60
+ analyzers_to_run.select!(&:visit?)
61
+ if analyzers_to_run.any?
62
+ visitor = GraphQL::Analysis::Visitor.new(
63
+ query: query,
64
+ analyzers: analyzers_to_run
65
+ )
66
+
67
+ # `nil` or `0` causes no timeout
68
+ Timeout::timeout(query.validate_timeout_remaining) do
69
+ visitor.visit
70
+ end
71
+
72
+ if visitor.rescued_errors.any?
73
+ return visitor.rescued_errors
74
+ end
75
+ end
76
+
77
+ query_analyzers.map(&:result)
78
+ else
79
+ []
80
+ end
81
+ end
82
+ rescue Timeout::Error
83
+ [GraphQL::AnalysisError.new("Timeout on validation of query")]
84
+ rescue GraphQL::UnauthorizedError
85
+ # This error was raised during analysis and will be returned the client before execution
86
+ []
87
+ end
88
+
89
+ def analysis_errors(results)
90
+ results.flatten.tap { _1.select! { |r| r.is_a?(GraphQL::AnalysisError) } }
91
+ end
92
+ end
93
+ end
@@ -16,12 +16,6 @@ module GraphQL
16
16
  "[" +
17
17
  obj.map { |v| inspect_truncated(v) }.join(", ") +
18
18
  "]"
19
- when Query::Context::SharedMethods
20
- if obj.invalid_null?
21
- "nil"
22
- else
23
- inspect_truncated(obj.value)
24
- end
25
19
  else
26
20
  inspect_truncated(obj)
27
21
  end
@@ -33,12 +27,6 @@ module GraphQL
33
27
  "{...}"
34
28
  when Array
35
29
  "[...]"
36
- when Query::Context::SharedMethods
37
- if obj.invalid_null?
38
- "nil"
39
- else
40
- inspect_truncated(obj.value)
41
- end
42
30
  when GraphQL::Execution::Lazy
43
31
  "(unresolved)"
44
32
  else
@@ -2,6 +2,12 @@
2
2
  module GraphQL
3
3
  class Backtrace
4
4
  module Trace
5
+ def initialize(*args, **kwargs, &block)
6
+ @__backtrace_contexts = {}
7
+ @__backtrace_last_context = nil
8
+ super
9
+ end
10
+
5
11
  def validate(query:, validate:)
6
12
  if query.multiplex
7
13
  push_query_backtrace_context(query)
@@ -42,36 +48,27 @@ module GraphQL
42
48
  rescue StandardError => err
43
49
  # This is an unhandled error from execution,
44
50
  # Re-raise it with a GraphQL trace.
45
- multiplex_context = multiplex.context
46
- potential_context = multiplex_context[:last_graphql_backtrace_context]
47
-
51
+ potential_context = @__backtrace_last_context
48
52
  if potential_context.is_a?(GraphQL::Query::Context) ||
49
53
  potential_context.is_a?(Backtrace::Frame)
50
54
  raise TracedError.new(err, potential_context)
51
55
  else
52
56
  raise
53
57
  end
54
- ensure
55
- multiplex_context = multiplex.context
56
- multiplex_context.delete(:graphql_backtrace_contexts)
57
- multiplex_context.delete(:last_graphql_backtrace_context)
58
58
  end
59
59
 
60
60
  private
61
61
 
62
62
  def push_query_backtrace_context(query)
63
63
  push_data = query
64
- multiplex = query.multiplex
65
64
  push_key = []
66
- push_storage = multiplex.context[:graphql_backtrace_contexts] ||= {}
67
- push_storage[push_key] = push_data
68
- multiplex.context[:last_graphql_backtrace_context] = push_data
65
+ @__backtrace_contexts[push_key] = push_data
66
+ @__backtrace_last_context = push_data
69
67
  end
70
68
 
71
69
  def push_field_backtrace_context(field, query, ast_node, arguments, object)
72
- multiplex = query.multiplex
73
70
  push_key = query.context[:current_path]
74
- push_storage = multiplex.context[:graphql_backtrace_contexts]
71
+ push_storage = @__backtrace_contexts
75
72
  parent_frame = push_storage[push_key[0..-2]]
76
73
 
77
74
  if parent_frame.is_a?(GraphQL::Query)
@@ -87,10 +84,10 @@ module GraphQL
87
84
  arguments: arguments,
88
85
  parent_frame: parent_frame,
89
86
  )
90
-
91
87
  push_storage[push_key] = push_data
92
- multiplex.context[:last_graphql_backtrace_context] = push_data
88
+ @__backtrace_last_context = push_data
93
89
  end
90
+
94
91
  end
95
92
  end
96
93
  end
@@ -1,13 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- class CoercionError < GraphQL::Error
4
- # @return [Hash] Optional custom data for error objects which will be added
5
- # under the `extensions` key.
6
- attr_accessor :extensions
7
-
8
- def initialize(message, extensions: nil)
9
- @extensions = extensions
10
- super(message)
11
- end
3
+ class CoercionError < GraphQL::ExecutionError
12
4
  end
13
5
  end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Dataloader
4
+ class AsyncDataloader < Dataloader
5
+ def yield
6
+ if (condition = Thread.current[:graphql_dataloader_next_tick])
7
+ condition.wait
8
+ else
9
+ Fiber.yield
10
+ end
11
+ nil
12
+ end
13
+
14
+ def run
15
+ job_fibers = []
16
+ next_job_fibers = []
17
+ source_tasks = []
18
+ next_source_tasks = []
19
+ first_pass = true
20
+ sources_condition = Async::Condition.new
21
+ manager = spawn_fiber do
22
+ while first_pass || job_fibers.any?
23
+ first_pass = false
24
+ fiber_vars = get_fiber_variables
25
+
26
+ while (f = (job_fibers.shift || spawn_job_fiber))
27
+ if f.alive?
28
+ finished = run_fiber(f)
29
+ if !finished
30
+ next_job_fibers << f
31
+ end
32
+ end
33
+ end
34
+ job_fibers.concat(next_job_fibers)
35
+ next_job_fibers.clear
36
+
37
+ Sync do |root_task|
38
+ set_fiber_variables(fiber_vars)
39
+ while source_tasks.any? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) }
40
+ while (task = source_tasks.shift || spawn_source_task(root_task, sources_condition))
41
+ if task.alive?
42
+ root_task.yield # give the source task a chance to run
43
+ next_source_tasks << task
44
+ end
45
+ end
46
+ sources_condition.signal
47
+ source_tasks.concat(next_source_tasks)
48
+ next_source_tasks.clear
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ manager.resume
55
+ if manager.alive?
56
+ raise "Invariant: Manager didn't terminate successfully: #{manager}"
57
+ end
58
+
59
+ rescue UncaughtThrowError => e
60
+ throw e.tag, e.value
61
+ end
62
+
63
+ private
64
+
65
+ def spawn_source_task(parent_task, condition)
66
+ pending_sources = nil
67
+ @source_cache.each_value do |source_by_batch_params|
68
+ source_by_batch_params.each_value do |source|
69
+ if source.pending?
70
+ pending_sources ||= []
71
+ pending_sources << source
72
+ end
73
+ end
74
+ end
75
+
76
+ if pending_sources
77
+ fiber_vars = get_fiber_variables
78
+ parent_task.async do
79
+ set_fiber_variables(fiber_vars)
80
+ Thread.current[:graphql_dataloader_next_tick] = condition
81
+ pending_sources.each(&:run_pending_keys)
82
+ cleanup_fiber
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -8,7 +8,7 @@ module GraphQL
8
8
  # simple internal code while adding the option to add Dataloader.
9
9
  class NullDataloader < Dataloader
10
10
  # These are all no-ops because code was
11
- # executed sychronously.
11
+ # executed synchronously.
12
12
  def run; end
13
13
  def run_isolated; yield; end
14
14
  def yield
@@ -14,6 +14,11 @@ module GraphQL
14
14
  def load
15
15
  @source.load(@key)
16
16
  end
17
+
18
+ def load_with_deprecation_warning
19
+ warn("Returning `.request(...)` from GraphQL::Dataloader is deprecated, use `.load(...)` instead. (See usage of #{@source} with #{@key.inspect}).")
20
+ load
21
+ end
17
22
  end
18
23
  end
19
24
  end
@@ -88,6 +88,7 @@ module GraphQL
88
88
  raise "Implement `#{self.class}#fetch(#{keys.inspect}) to return a record for each of the keys"
89
89
  end
90
90
 
91
+ MAX_ITERATIONS = 1000
91
92
  # Wait for a batch, if there's anything to batch.
92
93
  # Then run the batch and update the cache.
93
94
  # @return [void]
@@ -96,8 +97,8 @@ module GraphQL
96
97
  iterations = 0
97
98
  while pending_result_keys.any? { |key| !@results.key?(key) }
98
99
  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."
100
+ if iterations > MAX_ITERATIONS
101
+ 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."
101
102
  end
102
103
  @dataloader.yield
103
104
  end
@@ -161,7 +162,14 @@ module GraphQL
161
162
  [*batch_args, **batch_kwargs]
162
163
  end
163
164
 
164
- attr_reader :pending
165
+ # Clear any already-loaded objects for this source
166
+ # @return [void]
167
+ def clear_cache
168
+ @results.clear
169
+ nil
170
+ end
171
+
172
+ attr_reader :pending, :results
165
173
 
166
174
  private
167
175