graphql 2.4.3 → 2.5.3

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 (171) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/analyzer.rb +2 -1
  3. data/lib/graphql/analysis/query_complexity.rb +87 -7
  4. data/lib/graphql/analysis/visitor.rb +38 -41
  5. data/lib/graphql/analysis.rb +15 -12
  6. data/lib/graphql/autoload.rb +38 -0
  7. data/lib/graphql/backtrace/table.rb +118 -55
  8. data/lib/graphql/backtrace.rb +1 -19
  9. data/lib/graphql/current.rb +7 -2
  10. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  11. data/lib/graphql/dashboard/installable.rb +22 -0
  12. data/lib/graphql/dashboard/limiters.rb +93 -0
  13. data/lib/graphql/dashboard/operation_store.rb +199 -0
  14. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  15. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  16. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  17. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  18. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  19. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  20. data/lib/graphql/dashboard/statics/icon.png +0 -0
  21. data/lib/graphql/dashboard/subscriptions.rb +96 -0
  22. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  23. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  24. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  25. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  26. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +23 -0
  27. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  28. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  29. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  30. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  31. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  32. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  33. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  34. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  35. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  36. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  37. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  38. data/lib/graphql/dashboard.rb +158 -0
  39. data/lib/graphql/dataloader/active_record_association_source.rb +64 -0
  40. data/lib/graphql/dataloader/active_record_source.rb +26 -0
  41. data/lib/graphql/dataloader/async_dataloader.rb +21 -9
  42. data/lib/graphql/dataloader/null_dataloader.rb +1 -1
  43. data/lib/graphql/dataloader/source.rb +3 -3
  44. data/lib/graphql/dataloader.rb +43 -14
  45. data/lib/graphql/dig.rb +2 -1
  46. data/lib/graphql/execution/interpreter/resolve.rb +3 -3
  47. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +34 -4
  48. data/lib/graphql/execution/interpreter/runtime.rb +96 -52
  49. data/lib/graphql/execution/interpreter.rb +16 -7
  50. data/lib/graphql/execution/multiplex.rb +6 -5
  51. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  52. data/lib/graphql/invalid_name_error.rb +1 -1
  53. data/lib/graphql/invalid_null_error.rb +19 -16
  54. data/lib/graphql/language/cache.rb +13 -0
  55. data/lib/graphql/language/document_from_schema_definition.rb +8 -7
  56. data/lib/graphql/language/lexer.rb +11 -4
  57. data/lib/graphql/language/nodes.rb +3 -0
  58. data/lib/graphql/language/parser.rb +15 -8
  59. data/lib/graphql/language/printer.rb +8 -8
  60. data/lib/graphql/language/static_visitor.rb +37 -33
  61. data/lib/graphql/language/visitor.rb +59 -55
  62. data/lib/graphql/pagination/connection.rb +1 -1
  63. data/lib/graphql/query/context/scoped_context.rb +1 -1
  64. data/lib/graphql/query/context.rb +7 -5
  65. data/lib/graphql/query/variable_validation_error.rb +1 -1
  66. data/lib/graphql/query.rb +22 -32
  67. data/lib/graphql/railtie.rb +7 -0
  68. data/lib/graphql/schema/addition.rb +1 -1
  69. data/lib/graphql/schema/always_visible.rb +1 -0
  70. data/lib/graphql/schema/argument.rb +7 -8
  71. data/lib/graphql/schema/build_from_definition.rb +99 -53
  72. data/lib/graphql/schema/directive/flagged.rb +3 -1
  73. data/lib/graphql/schema/directive.rb +2 -2
  74. data/lib/graphql/schema/enum.rb +36 -1
  75. data/lib/graphql/schema/enum_value.rb +1 -1
  76. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  77. data/lib/graphql/schema/field.rb +27 -13
  78. data/lib/graphql/schema/field_extension.rb +1 -1
  79. data/lib/graphql/schema/has_single_input_argument.rb +3 -1
  80. data/lib/graphql/schema/input_object.rb +77 -40
  81. data/lib/graphql/schema/interface.rb +3 -2
  82. data/lib/graphql/schema/list.rb +1 -1
  83. data/lib/graphql/schema/loader.rb +1 -1
  84. data/lib/graphql/schema/member/has_arguments.rb +25 -17
  85. data/lib/graphql/schema/member/has_dataloader.rb +62 -0
  86. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  87. data/lib/graphql/schema/member/has_directives.rb +4 -4
  88. data/lib/graphql/schema/member/has_fields.rb +19 -1
  89. data/lib/graphql/schema/member/has_interfaces.rb +5 -5
  90. data/lib/graphql/schema/member/has_validators.rb +1 -1
  91. data/lib/graphql/schema/member/scoped.rb +1 -1
  92. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  93. data/lib/graphql/schema/member.rb +1 -0
  94. data/lib/graphql/schema/object.rb +25 -8
  95. data/lib/graphql/schema/relay_classic_mutation.rb +0 -1
  96. data/lib/graphql/schema/resolver.rb +12 -10
  97. data/lib/graphql/schema/subscription.rb +52 -6
  98. data/lib/graphql/schema/union.rb +1 -1
  99. data/lib/graphql/schema/validator/required_validator.rb +23 -6
  100. data/lib/graphql/schema/validator.rb +1 -1
  101. data/lib/graphql/schema/visibility/migration.rb +1 -0
  102. data/lib/graphql/schema/visibility/profile.rb +98 -244
  103. data/lib/graphql/schema/visibility/visit.rb +190 -0
  104. data/lib/graphql/schema/visibility.rb +178 -38
  105. data/lib/graphql/schema/warden.rb +18 -5
  106. data/lib/graphql/schema.rb +266 -54
  107. data/lib/graphql/static_validation/all_rules.rb +1 -1
  108. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
  109. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +47 -13
  110. data/lib/graphql/static_validation/rules/fields_will_merge.rb +79 -17
  111. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
  112. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  113. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  114. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  115. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  116. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
  117. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  118. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +1 -1
  119. data/lib/graphql/static_validation/validation_context.rb +1 -0
  120. data/lib/graphql/static_validation/validator.rb +6 -1
  121. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -1
  122. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  123. data/lib/graphql/subscriptions/event.rb +12 -1
  124. data/lib/graphql/subscriptions/serialize.rb +1 -1
  125. data/lib/graphql/subscriptions.rb +1 -1
  126. data/lib/graphql/testing/helpers.rb +7 -4
  127. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  128. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  129. data/lib/graphql/tracing/appoptics_trace.rb +9 -1
  130. data/lib/graphql/tracing/appoptics_tracing.rb +7 -0
  131. data/lib/graphql/tracing/appsignal_trace.rb +32 -55
  132. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  133. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  134. data/lib/graphql/tracing/data_dog_trace.rb +46 -158
  135. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  136. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  137. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  138. data/lib/graphql/tracing/detailed_trace.rb +93 -0
  139. data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
  140. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  141. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  142. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  143. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  144. data/lib/graphql/tracing/notifications_trace.rb +182 -34
  145. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  146. data/lib/graphql/tracing/null_trace.rb +9 -0
  147. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  148. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  149. data/lib/graphql/tracing/perfetto_trace.rb +734 -0
  150. data/lib/graphql/tracing/platform_trace.rb +5 -0
  151. data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
  152. data/lib/graphql/tracing/prometheus_trace.rb +72 -68
  153. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  154. data/lib/graphql/tracing/scout_trace.rb +32 -55
  155. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  156. data/lib/graphql/tracing/sentry_trace.rb +62 -94
  157. data/lib/graphql/tracing/statsd_trace.rb +33 -41
  158. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  159. data/lib/graphql/tracing/trace.rb +111 -1
  160. data/lib/graphql/tracing.rb +31 -30
  161. data/lib/graphql/types/relay/connection_behaviors.rb +3 -3
  162. data/lib/graphql/types/relay/edge_behaviors.rb +2 -2
  163. data/lib/graphql/types.rb +18 -11
  164. data/lib/graphql/version.rb +1 -1
  165. data/lib/graphql.rb +55 -47
  166. metadata +146 -11
  167. data/lib/graphql/backtrace/inspect_result.rb +0 -38
  168. data/lib/graphql/backtrace/trace.rb +0 -93
  169. data/lib/graphql/backtrace/tracer.rb +0 -80
  170. data/lib/graphql/schema/null_mask.rb +0 -11
  171. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0dcc846071132cf2d084ff65347dab7c97d0b25b60c1cdb6dfcbe37c55f278e9
4
- data.tar.gz: afc95cb5ba89e33974b8b38616bc36c14dcbfd3c9f121b8e65437e7cfc390770
3
+ metadata.gz: d2e4ec1acc9810683bb2628dc5403c74d729742612655f1be275267a89b107b3
4
+ data.tar.gz: fb092108f803095aea29b325859fc810feaa8e5fdb6c873c431f9824428fbc15
5
5
  SHA512:
6
- metadata.gz: d1b6ad8fba792ca671246b2d6531f981a2c184adde3e4efc7522a7f38ea659c563dadd50ee208ab406f0ce4c37fc6625c2d59e020b0334cdefd8ea7f5d5eed8a
7
- data.tar.gz: 74c1156b7b407b2d687cc74886198d9f2705d7d82e3672e5d5b9b61f2811fa53dfe7b2bdb61ac0dae8e5e83242415ff7d7d1f6e2721f77b90dcda61dff88c815
6
+ metadata.gz: 603e3da13be680884399546074dae65f44635382e13311758e237f7973bbdd96f4a1a46f6b17476af77d88177f5e82f3c76b227633e2e15a9c909365a818f97e
7
+ data.tar.gz: d631c5a9e1c91fb815d98e1981641405c344b589c6e823ae5609b25fd819a2d5dc9032f366c94a3da69c56a48aa7d8e9ee082cbfa217807e6cbf313d16cbd598
@@ -42,6 +42,7 @@ module GraphQL
42
42
  raise GraphQL::RequiredImplementationMissingError
43
43
  end
44
44
 
45
+ # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
45
46
  class << self
46
47
  private
47
48
 
@@ -72,7 +73,7 @@ module GraphQL
72
73
  build_visitor_hooks :variable_definition
73
74
  build_visitor_hooks :variable_identifier
74
75
  build_visitor_hooks :abstract_node
75
-
76
+ # rubocop:enable Development/NoEvalCop
76
77
  protected
77
78
 
78
79
  # @return [GraphQL::Query, GraphQL::Execution::Multiplex] Whatever this analyzer is analyzing
@@ -13,7 +13,32 @@ module GraphQL
13
13
 
14
14
  # Override this method to use the complexity result
15
15
  def result
16
- max_possible_complexity
16
+ case subject.schema.complexity_cost_calculation_mode_for(subject.context)
17
+ when :future
18
+ max_possible_complexity
19
+ when :legacy
20
+ max_possible_complexity(mode: :legacy)
21
+ when :compare
22
+ future_complexity = max_possible_complexity
23
+ legacy_complexity = max_possible_complexity(mode: :legacy)
24
+ if future_complexity != legacy_complexity
25
+ subject.schema.legacy_complexity_cost_calculation_mismatch(subject, future_complexity, legacy_complexity)
26
+ else
27
+ future_complexity
28
+ end
29
+ when nil
30
+ subject.logger.warn <<~GRAPHQL
31
+ GraphQL-Ruby's complexity cost system is getting some "breaking fixes" in a future version. See the migration notes at https://graphql-ruby.org/api-docs/#{GraphQL::VERSION}/Schema.html#complexity_cost_cacluation_mode-class_method
32
+
33
+ To opt into the future behavior, configure your schema (#{subject.schema.name ? subject.schema.name : subject.schema.ancestors}) with:
34
+
35
+ complexity_cost_calculation_mode(:future) # or `:legacy`, `:compare`
36
+
37
+ GRAPHQL
38
+ max_possible_complexity
39
+ else
40
+ raise ArgumentError, "Expected `:future`, `:legacy`, `:compare`, or `nil` from `#{query.schema}.complexity_cost_calculation_mode_for` but got: #{query.schema.complexity_cost_calculation_mode.inspect}"
41
+ end
17
42
  end
18
43
 
19
44
  # ScopedTypeComplexity models a tree of GraphQL types mapped to inner selections, ie:
@@ -44,6 +69,10 @@ module GraphQL
44
69
  def own_complexity(child_complexity)
45
70
  @field_definition.calculate_complexity(query: @query, nodes: @nodes, child_complexity: child_complexity)
46
71
  end
72
+
73
+ def composite?
74
+ !empty?
75
+ end
47
76
  end
48
77
 
49
78
  def on_enter_field(node, parent, visitor)
@@ -77,16 +106,17 @@ module GraphQL
77
106
  private
78
107
 
79
108
  # @return [Integer]
80
- def max_possible_complexity
109
+ def max_possible_complexity(mode: :future)
81
110
  @complexities_on_type_by_query.reduce(0) do |total, (query, scopes_stack)|
82
- total + merged_max_complexity_for_scopes(query, [scopes_stack.first])
111
+ total + merged_max_complexity_for_scopes(query, [scopes_stack.first], mode)
83
112
  end
84
113
  end
85
114
 
86
115
  # @param query [GraphQL::Query] Used for `query.possible_types`
87
116
  # @param scopes [Array<ScopedTypeComplexity>] Array of scoped type complexities
117
+ # @param mode [:future, :legacy]
88
118
  # @return [Integer]
89
- def merged_max_complexity_for_scopes(query, scopes)
119
+ def merged_max_complexity_for_scopes(query, scopes, mode)
90
120
  # Aggregate a set of all possible scope types encountered (scope keys).
91
121
  # Use a hash, but ignore the values; it's just a fast way to work with the keys.
92
122
  possible_scope_types = scopes.each_with_object({}) do |scope, memo|
@@ -115,14 +145,20 @@ module GraphQL
115
145
  end
116
146
 
117
147
  # Find the maximum complexity for the scope type among possible lexical branches.
118
- complexity = merged_max_complexity(query, all_inner_selections)
148
+ complexity = case mode
149
+ when :legacy
150
+ legacy_merged_max_complexity(query, all_inner_selections)
151
+ when :future
152
+ merged_max_complexity(query, all_inner_selections)
153
+ else
154
+ raise ArgumentError, "Expected :legacy or :future, not: #{mode.inspect}"
155
+ end
119
156
  complexity > max ? complexity : max
120
157
  end
121
158
  end
122
159
 
123
160
  def types_intersect?(query, a, b)
124
161
  return true if a == b
125
-
126
162
  a_types = query.types.possible_types(a)
127
163
  query.types.possible_types(b).any? { |t| a_types.include?(t) }
128
164
  end
@@ -145,6 +181,50 @@ module GraphQL
145
181
  memo.merge!(inner_selection)
146
182
  end
147
183
 
184
+ # Add up the total cost for each unique field name's coalesced selections
185
+ unique_field_keys.each_key.reduce(0) do |total, field_key|
186
+ # Collect all child scopes for this field key;
187
+ # all keys come with at least one scope.
188
+ child_scopes = inner_selections.filter_map { _1[field_key] }
189
+
190
+ # Compute maximum possible cost of child selections;
191
+ # composites merge their maximums, while leaf scopes are always zero.
192
+ # FieldsWillMerge validation assures all scopes are uniformly composite or leaf.
193
+ maximum_children_cost = if child_scopes.any?(&:composite?)
194
+ merged_max_complexity_for_scopes(query, child_scopes, :future)
195
+ else
196
+ 0
197
+ end
198
+
199
+ # Identify the maximum cost and scope among possibilities
200
+ maximum_cost = 0
201
+ maximum_scope = child_scopes.reduce(child_scopes.last) do |max_scope, possible_scope|
202
+ scope_cost = possible_scope.own_complexity(maximum_children_cost)
203
+ if scope_cost > maximum_cost
204
+ maximum_cost = scope_cost
205
+ possible_scope
206
+ else
207
+ max_scope
208
+ end
209
+ end
210
+
211
+ field_complexity(
212
+ maximum_scope,
213
+ max_complexity: maximum_cost,
214
+ child_complexity: maximum_children_cost,
215
+ )
216
+
217
+ total + maximum_cost
218
+ end
219
+ end
220
+
221
+ def legacy_merged_max_complexity(query, inner_selections)
222
+ # Aggregate a set of all unique field selection keys across all scopes.
223
+ # Use a hash, but ignore the values; it's just a fast way to work with the keys.
224
+ unique_field_keys = inner_selections.each_with_object({}) do |inner_selection, memo|
225
+ memo.merge!(inner_selection)
226
+ end
227
+
148
228
  # Add up the total cost for each unique field name's coalesced selections
149
229
  unique_field_keys.each_key.reduce(0) do |total, field_key|
150
230
  composite_scopes = nil
@@ -167,7 +247,7 @@ module GraphQL
167
247
  end
168
248
 
169
249
  if composite_scopes
170
- child_complexity = merged_max_complexity_for_scopes(query, composite_scopes)
250
+ child_complexity = merged_max_complexity_for_scopes(query, composite_scopes, :legacy)
171
251
 
172
252
  # This is the last composite scope visited; assume it's representative (for backwards compatibility).
173
253
  # Note: it would be more correct to score each composite scope and use the maximum possibility.
@@ -10,7 +10,7 @@ module GraphQL
10
10
  #
11
11
  # @see {GraphQL::Analysis::Analyzer} AST Analyzers for queries
12
12
  class Visitor < GraphQL::Language::StaticVisitor
13
- def initialize(query:, analyzers:)
13
+ def initialize(query:, analyzers:, timeout:)
14
14
  @analyzers = analyzers
15
15
  @path = []
16
16
  @object_types = []
@@ -24,6 +24,11 @@ module GraphQL
24
24
  @types = query.types
25
25
  @response_path = []
26
26
  @skip_stack = [false]
27
+ @timeout_time = if timeout
28
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second) + timeout
29
+ else
30
+ Float::INFINITY
31
+ end
27
32
  super(query.selected_operation)
28
33
  end
29
34
 
@@ -64,6 +69,7 @@ module GraphQL
64
69
  @response_path.dup
65
70
  end
66
71
 
72
+ # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
67
73
  # Visitor Hooks
68
74
  [
69
75
  :operation_definition, :fragment_definition,
@@ -72,28 +78,26 @@ module GraphQL
72
78
  module_eval <<-RUBY, __FILE__, __LINE__
73
79
  def call_on_enter_#{node_type}(node, parent)
74
80
  @analyzers.each do |a|
75
- begin
76
- a.on_enter_#{node_type}(node, parent, self)
77
- rescue AnalysisError => err
78
- @rescued_errors << err
79
- end
81
+ a.on_enter_#{node_type}(node, parent, self)
82
+ rescue AnalysisError => err
83
+ @rescued_errors << err
80
84
  end
81
85
  end
82
86
 
83
87
  def call_on_leave_#{node_type}(node, parent)
84
88
  @analyzers.each do |a|
85
- begin
86
- a.on_leave_#{node_type}(node, parent, self)
87
- rescue AnalysisError => err
88
- @rescued_errors << err
89
- end
89
+ a.on_leave_#{node_type}(node, parent, self)
90
+ rescue AnalysisError => err
91
+ @rescued_errors << err
90
92
  end
91
93
  end
92
94
 
93
95
  RUBY
94
96
  end
97
+ # rubocop:enable Development/NoEvalCop
95
98
 
96
99
  def on_operation_definition(node, parent)
100
+ check_timeout
97
101
  object_type = @schema.root_type_for_operation(node.operation_type)
98
102
  @object_types.push(object_type)
99
103
  @path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}")
@@ -104,31 +108,27 @@ module GraphQL
104
108
  @path.pop
105
109
  end
106
110
 
107
- def on_fragment_definition(node, parent)
108
- on_fragment_with_type(node) do
109
- @path.push("fragment #{node.name}")
110
- @in_fragment_def = false
111
- call_on_enter_fragment_definition(node, parent)
112
- super
113
- @in_fragment_def = false
114
- call_on_leave_fragment_definition(node, parent)
115
- end
116
- end
117
-
118
111
  def on_inline_fragment(node, parent)
119
- on_fragment_with_type(node) do
120
- @path.push("...#{node.type ? " on #{node.type.name}" : ""}")
121
- @skipping = @skip_stack.last || skip?(node)
122
- @skip_stack << @skipping
123
-
124
- call_on_enter_inline_fragment(node, parent)
125
- super
126
- @skipping = @skip_stack.pop
127
- call_on_leave_inline_fragment(node, parent)
112
+ check_timeout
113
+ object_type = if node.type
114
+ @types.type(node.type.name)
115
+ else
116
+ @object_types.last
128
117
  end
118
+ @object_types.push(object_type)
119
+ @path.push("...#{node.type ? " on #{node.type.name}" : ""}")
120
+ @skipping = @skip_stack.last || skip?(node)
121
+ @skip_stack << @skipping
122
+ call_on_enter_inline_fragment(node, parent)
123
+ super
124
+ @skipping = @skip_stack.pop
125
+ call_on_leave_inline_fragment(node, parent)
126
+ @object_types.pop
127
+ @path.pop
129
128
  end
130
129
 
131
130
  def on_field(node, parent)
131
+ check_timeout
132
132
  @response_path.push(node.alias || node.name)
133
133
  parent_type = @object_types.last
134
134
  # This could be nil if the previous field wasn't found:
@@ -156,6 +156,7 @@ module GraphQL
156
156
  end
157
157
 
158
158
  def on_directive(node, parent)
159
+ check_timeout
159
160
  directive_defn = @schema.directives[node.name]
160
161
  @directive_definitions.push(directive_defn)
161
162
  call_on_enter_directive(node, parent)
@@ -165,6 +166,7 @@ module GraphQL
165
166
  end
166
167
 
167
168
  def on_argument(node, parent)
169
+ check_timeout
168
170
  argument_defn = if (arg = @argument_definitions.last)
169
171
  arg_type = arg.type.unwrap
170
172
  if arg_type.kind.input_object?
@@ -190,6 +192,7 @@ module GraphQL
190
192
  end
191
193
 
192
194
  def on_fragment_spread(node, parent)
195
+ check_timeout
193
196
  @path.push("... #{node.name}")
194
197
  @skipping = @skip_stack.last || skip?(node)
195
198
  @skip_stack << @skipping
@@ -264,19 +267,13 @@ module GraphQL
264
267
 
265
268
  def skip?(ast_node)
266
269
  dir = ast_node.directives
267
- dir.any? && !GraphQL::Execution::DirectiveChecks.include?(dir, query)
270
+ !dir.empty? && !GraphQL::Execution::DirectiveChecks.include?(dir, query)
268
271
  end
269
272
 
270
- def on_fragment_with_type(node)
271
- object_type = if node.type
272
- @types.type(node.type.name)
273
- else
274
- @object_types.last
273
+ def check_timeout
274
+ if Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second) > @timeout_time
275
+ raise GraphQL::Analysis::TimeoutError
275
276
  end
276
- @object_types.push(object_type)
277
- yield(node)
278
- @object_types.pop
279
- @path.pop
280
277
  end
281
278
  end
282
279
  end
@@ -6,11 +6,16 @@ require "graphql/analysis/query_complexity"
6
6
  require "graphql/analysis/max_query_complexity"
7
7
  require "graphql/analysis/query_depth"
8
8
  require "graphql/analysis/max_query_depth"
9
- require "timeout"
10
-
11
9
  module GraphQL
12
10
  module Analysis
13
11
  AST = self
12
+
13
+ class TimeoutError < AnalysisError
14
+ def initialize(...)
15
+ super("Timeout on validation of query")
16
+ end
17
+ end
18
+
14
19
  module_function
15
20
  # Analyze a multiplex, and all queries within.
16
21
  # Multiplex analyzers are ran for all queries, keeping state.
@@ -55,21 +60,19 @@ module GraphQL
55
60
  .tap { _1.select!(&:analyze?) }
56
61
 
57
62
  analyzers_to_run = query_analyzers + multiplex_analyzers
58
- if analyzers_to_run.any?
63
+ if !analyzers_to_run.empty?
59
64
 
60
65
  analyzers_to_run.select!(&:visit?)
61
- if analyzers_to_run.any?
66
+ if !analyzers_to_run.empty?
62
67
  visitor = GraphQL::Analysis::Visitor.new(
63
68
  query: query,
64
- analyzers: analyzers_to_run
69
+ analyzers: analyzers_to_run,
70
+ timeout: query.validate_timeout_remaining,
65
71
  )
66
72
 
67
- # `nil` or `0` causes no timeout
68
- Timeout::timeout(query.validate_timeout_remaining) do
69
- visitor.visit
70
- end
73
+ visitor.visit
71
74
 
72
- if visitor.rescued_errors.any?
75
+ if !visitor.rescued_errors.empty?
73
76
  return visitor.rescued_errors
74
77
  end
75
78
  end
@@ -79,8 +82,8 @@ module GraphQL
79
82
  []
80
83
  end
81
84
  end
82
- rescue Timeout::Error
83
- [GraphQL::AnalysisError.new("Timeout on validation of query")]
85
+ rescue TimeoutError => err
86
+ [err]
84
87
  rescue GraphQL::UnauthorizedError, GraphQL::ExecutionError
85
88
  # This error was raised during analysis and will be returned the client before execution
86
89
  []
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ # @see GraphQL::Railtie for automatic Rails integration
5
+ module Autoload
6
+ # Register a constant named `const_name` to be loaded from `path`.
7
+ # This is like `Kernel#autoload` but it tracks the constants so they can be eager-loaded with {#eager_load!}
8
+ # @param const_name [Symbol]
9
+ # @param path [String]
10
+ # @return [void]
11
+ def autoload(const_name, path)
12
+ @_eagerloaded_constants ||= []
13
+ @_eagerloaded_constants << const_name
14
+
15
+ super const_name, path
16
+ end
17
+
18
+ # Call this to load this constant's `autoload` dependents and continue calling recursively
19
+ # @return [void]
20
+ def eager_load!
21
+ @_eager_loading = true
22
+ if @_eagerloaded_constants
23
+ @_eagerloaded_constants.each { |const_name| const_get(const_name) }
24
+ @_eagerloaded_constants = nil
25
+ end
26
+ nil
27
+ ensure
28
+ @_eager_loading = false
29
+ end
30
+
31
+ private
32
+
33
+ # @return [Boolean] `true` if GraphQL-Ruby is currently eager-loading its constants
34
+ def eager_loading?
35
+ @_eager_loading ||= false
36
+ end
37
+ end
38
+ end
@@ -36,7 +36,93 @@ module GraphQL
36
36
  private
37
37
 
38
38
  def rows
39
- @rows ||= build_rows(@context, rows: [HEADERS], top: true)
39
+ @rows ||= begin
40
+ query = @context.query
41
+ query_ctx = @context
42
+ runtime_inst = query_ctx.namespace(:interpreter_runtime)[:runtime]
43
+ result = runtime_inst.instance_variable_get(:@response)
44
+ rows = []
45
+ result_path = []
46
+ last_part = nil
47
+ path = @context.current_path
48
+ path.each do |path_part|
49
+ value = value_at(runtime_inst, result_path)
50
+
51
+ if result_path.empty?
52
+ name = query.selected_operation.operation_type || "query"
53
+ if (n = query.selected_operation_name)
54
+ name += " #{n}"
55
+ end
56
+ args = query.variables
57
+ else
58
+ name = result.graphql_field.path
59
+ args = result.graphql_arguments
60
+ end
61
+
62
+ object = result.graphql_parent ? result.graphql_parent.graphql_application_value : result.graphql_application_value
63
+ object = object.object.inspect
64
+
65
+ rows << [
66
+ result.ast_node.position.join(":"),
67
+ name,
68
+ "#{object}",
69
+ args.to_h.inspect,
70
+ inspect_result(value),
71
+ ]
72
+
73
+ result_path << path_part
74
+ if path_part == path.last
75
+ last_part = path_part
76
+ else
77
+ result = result[path_part]
78
+ end
79
+ end
80
+
81
+ object = result.graphql_application_value.object.inspect
82
+ ast_node = nil
83
+ result.graphql_selections.each do |s|
84
+ found_ast_node = find_ast_node(s, last_part)
85
+ if found_ast_node
86
+ ast_node = found_ast_node
87
+ break
88
+ end
89
+ end
90
+
91
+ if ast_node
92
+ field_defn = query.get_field(result.graphql_result_type, ast_node.name)
93
+ args = query.arguments_for(ast_node, field_defn).to_h
94
+ field_path = field_defn.path
95
+ if ast_node.alias
96
+ field_path += " as #{ast_node.alias}"
97
+ end
98
+
99
+ rows << [
100
+ ast_node.position.join(":"),
101
+ field_path,
102
+ "#{object}",
103
+ args.inspect,
104
+ inspect_result(@override_value)
105
+ ]
106
+ end
107
+
108
+ rows << HEADERS
109
+ rows.reverse!
110
+ rows
111
+ end
112
+ end
113
+
114
+ def find_ast_node(node, last_part)
115
+ return nil unless node
116
+ return node if node.respond_to?(:alias) && node.respond_to?(:name) && (node.alias == last_part || node.name == last_part)
117
+ return nil unless node.respond_to?(:selections)
118
+ return nil if node.selections.nil? || node.selections.empty?
119
+
120
+ node.selections.each do |child|
121
+ child_ast_node = find_ast_node(child, last_part)
122
+ return child_ast_node if child_ast_node
123
+ end
124
+
125
+ nil
40
126
  end
41
127
 
42
128
  # @return [String]
@@ -75,67 +161,44 @@ module GraphQL
75
161
  table
76
162
  end
77
163
 
78
- # @return [Array] 5 items for a backtrace table (not `key`)
79
- def build_rows(context_entry, rows:, top: false)
80
- case context_entry
81
- when Backtrace::Frame
82
- field_alias = context_entry.ast_node.respond_to?(:alias) && context_entry.ast_node.alias
83
- value = if top && @override_value
84
- @override_value
85
- else
86
- value_at(@context.query.context.namespace(:interpreter_runtime)[:runtime], context_entry.path)
87
- end
88
- rows << [
89
- "#{context_entry.ast_node ? context_entry.ast_node.position.join(":") : ""}",
90
- "#{context_entry.field.path}#{field_alias ? " as #{field_alias}" : ""}",
91
- "#{context_entry.object.object.inspect}",
92
- context_entry.arguments.to_h.inspect, # rubocop:disable Development/ContextIsPassedCop -- unrelated method
93
- Backtrace::InspectResult.inspect_result(value),
94
- ]
95
- if (parent = context_entry.parent_frame)
96
- build_rows(parent, rows: rows)
97
- else
98
- rows
99
- end
100
- when GraphQL::Query::Context
101
- query = context_entry.query
102
- op = query.selected_operation
103
- if op
104
- op_type = op.operation_type
105
- position = "#{op.line}:#{op.col}"
106
- else
107
- op_type = "query"
108
- position = "?:?"
109
- end
110
- op_name = query.selected_operation_name
111
- object = query.root_value
112
- if object.is_a?(GraphQL::Schema::Object)
113
- object = object.object
114
- end
115
- value = value_at(context_entry.namespace(:interpreter_runtime)[:runtime], [])
116
- rows << [
117
- "#{position}",
118
- "#{op_type}#{op_name ? " #{op_name}" : ""}",
119
- "#{object.inspect}",
120
- query.variables.to_h.inspect,
121
- Backtrace::InspectResult.inspect_result(value),
122
- ]
123
- else
124
- raise "Unexpected get_rows subject #{context_entry.class} (#{context_entry.inspect})"
125
- end
126
- end
127
164
 
128
165
  def value_at(runtime, path)
129
166
  response = runtime.final_result
130
167
  path.each do |key|
131
- if response && (response = response[key])
132
- next
133
- else
134
- break
135
- end
168
+ response && (response = response[key])
136
169
  end
137
170
  response
138
171
  end
172
+
173
+ def inspect_result(obj)
174
+ case obj
175
+ when Hash
176
+ "{" +
177
+ obj.map do |key, val|
178
+ "#{key}: #{inspect_truncated(val)}"
179
+ end.join(", ") +
180
+ "}"
181
+ when Array
182
+ "[" +
183
+ obj.map { |v| inspect_truncated(v) }.join(", ") +
184
+ "]"
185
+ else
186
+ inspect_truncated(obj)
187
+ end
188
+ end
189
+
190
+ def inspect_truncated(obj)
191
+ case obj
192
+ when Hash
193
+ "{...}"
194
+ when Array
195
+ "[...]"
196
+ when GraphQL::Execution::Lazy
197
+ "(unresolved)"
198
+ else
199
+ "#{obj.inspect}"
200
+ end
201
+ end
139
202
  end
140
203
  end
141
204
  end
@@ -1,9 +1,6 @@
1
1
  # frozen_string_literal: true
2
- require "graphql/backtrace/inspect_result"
3
2
  require "graphql/backtrace/table"
4
3
  require "graphql/backtrace/traced_error"
5
- require "graphql/backtrace/tracer"
6
- require "graphql/backtrace/trace"
7
4
  module GraphQL
8
5
  # Wrap unhandled errors with {TracedError}.
9
6
  #
@@ -24,7 +21,7 @@ module GraphQL
24
21
  def_delegators :to_a, :each, :[]
25
22
 
26
23
  def self.use(schema_defn)
27
- schema_defn.trace_with(self::Trace)
24
+ schema_defn.using_backtrace = true
28
25
  end
29
26
 
30
27
  def initialize(context, value: nil)
@@ -40,20 +37,5 @@ module GraphQL
40
37
  def to_a
41
38
  @table.to_backtrace
42
39
  end
43
-
44
- # Used for internal bookkeeping
45
- # @api private
46
- class Frame
47
- attr_reader :path, :query, :ast_node, :object, :field, :arguments, :parent_frame
48
- def initialize(path:, query:, ast_node:, object:, field:, arguments:, parent_frame:)
49
- @path = path
50
- @query = query
51
- @ast_node = ast_node
52
- @field = field
53
- @object = object
54
- @arguments = arguments
55
- @parent_frame = parent_frame
56
- end
57
- end
58
40
  end
59
41
  end