graphql 2.0.30 → 2.3.6

Sign up to get free protection for your applications and to get access to all the features.
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