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.
- checksums.yaml +4 -4
- data/lib/graphql/analysis/analyzer.rb +2 -1
- data/lib/graphql/analysis/query_complexity.rb +87 -7
- data/lib/graphql/analysis/visitor.rb +38 -41
- data/lib/graphql/analysis.rb +15 -12
- data/lib/graphql/autoload.rb +38 -0
- data/lib/graphql/backtrace/table.rb +118 -55
- data/lib/graphql/backtrace.rb +1 -19
- data/lib/graphql/current.rb +7 -2
- data/lib/graphql/dashboard/detailed_traces.rb +47 -0
- data/lib/graphql/dashboard/installable.rb +22 -0
- data/lib/graphql/dashboard/limiters.rb +93 -0
- data/lib/graphql/dashboard/operation_store.rb +199 -0
- data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
- data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
- data/lib/graphql/dashboard/statics/charts.min.css +1 -0
- data/lib/graphql/dashboard/statics/dashboard.css +30 -0
- data/lib/graphql/dashboard/statics/dashboard.js +143 -0
- data/lib/graphql/dashboard/statics/header-icon.png +0 -0
- data/lib/graphql/dashboard/statics/icon.png +0 -0
- data/lib/graphql/dashboard/subscriptions.rb +96 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +23 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
- data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
- data/lib/graphql/dashboard.rb +158 -0
- data/lib/graphql/dataloader/active_record_association_source.rb +64 -0
- data/lib/graphql/dataloader/active_record_source.rb +26 -0
- data/lib/graphql/dataloader/async_dataloader.rb +21 -9
- data/lib/graphql/dataloader/null_dataloader.rb +1 -1
- data/lib/graphql/dataloader/source.rb +3 -3
- data/lib/graphql/dataloader.rb +43 -14
- data/lib/graphql/dig.rb +2 -1
- data/lib/graphql/execution/interpreter/resolve.rb +3 -3
- data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +34 -4
- data/lib/graphql/execution/interpreter/runtime.rb +96 -52
- data/lib/graphql/execution/interpreter.rb +16 -7
- data/lib/graphql/execution/multiplex.rb +6 -5
- data/lib/graphql/introspection/directive_location_enum.rb +1 -1
- data/lib/graphql/invalid_name_error.rb +1 -1
- data/lib/graphql/invalid_null_error.rb +19 -16
- data/lib/graphql/language/cache.rb +13 -0
- data/lib/graphql/language/document_from_schema_definition.rb +8 -7
- data/lib/graphql/language/lexer.rb +11 -4
- data/lib/graphql/language/nodes.rb +3 -0
- data/lib/graphql/language/parser.rb +15 -8
- data/lib/graphql/language/printer.rb +8 -8
- data/lib/graphql/language/static_visitor.rb +37 -33
- data/lib/graphql/language/visitor.rb +59 -55
- data/lib/graphql/pagination/connection.rb +1 -1
- data/lib/graphql/query/context/scoped_context.rb +1 -1
- data/lib/graphql/query/context.rb +7 -5
- data/lib/graphql/query/variable_validation_error.rb +1 -1
- data/lib/graphql/query.rb +22 -32
- data/lib/graphql/railtie.rb +7 -0
- data/lib/graphql/schema/addition.rb +1 -1
- data/lib/graphql/schema/always_visible.rb +1 -0
- data/lib/graphql/schema/argument.rb +7 -8
- data/lib/graphql/schema/build_from_definition.rb +99 -53
- data/lib/graphql/schema/directive/flagged.rb +3 -1
- data/lib/graphql/schema/directive.rb +2 -2
- data/lib/graphql/schema/enum.rb +36 -1
- data/lib/graphql/schema/enum_value.rb +1 -1
- data/lib/graphql/schema/field/scope_extension.rb +1 -1
- data/lib/graphql/schema/field.rb +27 -13
- data/lib/graphql/schema/field_extension.rb +1 -1
- data/lib/graphql/schema/has_single_input_argument.rb +3 -1
- data/lib/graphql/schema/input_object.rb +77 -40
- data/lib/graphql/schema/interface.rb +3 -2
- data/lib/graphql/schema/list.rb +1 -1
- data/lib/graphql/schema/loader.rb +1 -1
- data/lib/graphql/schema/member/has_arguments.rb +25 -17
- data/lib/graphql/schema/member/has_dataloader.rb +62 -0
- data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
- data/lib/graphql/schema/member/has_directives.rb +4 -4
- data/lib/graphql/schema/member/has_fields.rb +19 -1
- data/lib/graphql/schema/member/has_interfaces.rb +5 -5
- data/lib/graphql/schema/member/has_validators.rb +1 -1
- data/lib/graphql/schema/member/scoped.rb +1 -1
- data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
- data/lib/graphql/schema/member.rb +1 -0
- data/lib/graphql/schema/object.rb +25 -8
- data/lib/graphql/schema/relay_classic_mutation.rb +0 -1
- data/lib/graphql/schema/resolver.rb +12 -10
- data/lib/graphql/schema/subscription.rb +52 -6
- data/lib/graphql/schema/union.rb +1 -1
- data/lib/graphql/schema/validator/required_validator.rb +23 -6
- data/lib/graphql/schema/validator.rb +1 -1
- data/lib/graphql/schema/visibility/migration.rb +1 -0
- data/lib/graphql/schema/visibility/profile.rb +98 -244
- data/lib/graphql/schema/visibility/visit.rb +190 -0
- data/lib/graphql/schema/visibility.rb +178 -38
- data/lib/graphql/schema/warden.rb +18 -5
- data/lib/graphql/schema.rb +266 -54
- data/lib/graphql/static_validation/all_rules.rb +1 -1
- data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +47 -13
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +79 -17
- data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
- data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
- data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
- data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
- data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
- data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +1 -1
- data/lib/graphql/static_validation/validation_context.rb +1 -0
- data/lib/graphql/static_validation/validator.rb +6 -1
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -1
- data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
- data/lib/graphql/subscriptions/event.rb +12 -1
- data/lib/graphql/subscriptions/serialize.rb +1 -1
- data/lib/graphql/subscriptions.rb +1 -1
- data/lib/graphql/testing/helpers.rb +7 -4
- data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
- data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
- data/lib/graphql/tracing/appoptics_trace.rb +9 -1
- data/lib/graphql/tracing/appoptics_tracing.rb +7 -0
- data/lib/graphql/tracing/appsignal_trace.rb +32 -55
- data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
- data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
- data/lib/graphql/tracing/data_dog_trace.rb +46 -158
- data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
- data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
- data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
- data/lib/graphql/tracing/detailed_trace.rb +93 -0
- data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
- data/lib/graphql/tracing/legacy_trace.rb +4 -61
- data/lib/graphql/tracing/monitor_trace.rb +283 -0
- data/lib/graphql/tracing/new_relic_trace.rb +47 -54
- data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
- data/lib/graphql/tracing/notifications_trace.rb +182 -34
- data/lib/graphql/tracing/notifications_tracing.rb +2 -0
- data/lib/graphql/tracing/null_trace.rb +9 -0
- data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
- data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
- data/lib/graphql/tracing/perfetto_trace.rb +734 -0
- data/lib/graphql/tracing/platform_trace.rb +5 -0
- data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
- data/lib/graphql/tracing/prometheus_trace.rb +72 -68
- data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
- data/lib/graphql/tracing/scout_trace.rb +32 -55
- data/lib/graphql/tracing/scout_tracing.rb +2 -0
- data/lib/graphql/tracing/sentry_trace.rb +62 -94
- data/lib/graphql/tracing/statsd_trace.rb +33 -41
- data/lib/graphql/tracing/statsd_tracing.rb +2 -0
- data/lib/graphql/tracing/trace.rb +111 -1
- data/lib/graphql/tracing.rb +31 -30
- data/lib/graphql/types/relay/connection_behaviors.rb +3 -3
- data/lib/graphql/types/relay/edge_behaviors.rb +2 -2
- data/lib/graphql/types.rb +18 -11
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +55 -47
- metadata +146 -11
- data/lib/graphql/backtrace/inspect_result.rb +0 -38
- data/lib/graphql/backtrace/trace.rb +0 -93
- data/lib/graphql/backtrace/tracer.rb +0 -80
- data/lib/graphql/schema/null_mask.rb +0 -11
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d2e4ec1acc9810683bb2628dc5403c74d729742612655f1be275267a89b107b3
|
4
|
+
data.tar.gz: fb092108f803095aea29b325859fc810feaa8e5fdb6c873c431f9824428fbc15
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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 =
|
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
|
-
|
76
|
-
|
77
|
-
|
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
|
-
|
86
|
-
|
87
|
-
|
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
|
-
|
120
|
-
|
121
|
-
@
|
122
|
-
|
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.
|
270
|
+
!dir.empty? && !GraphQL::Execution::DirectiveChecks.include?(dir, query)
|
268
271
|
end
|
269
272
|
|
270
|
-
def
|
271
|
-
|
272
|
-
|
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
|
data/lib/graphql/analysis.rb
CHANGED
@@ -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.
|
63
|
+
if !analyzers_to_run.empty?
|
59
64
|
|
60
65
|
analyzers_to_run.select!(&:visit?)
|
61
|
-
if analyzers_to_run.
|
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
|
-
|
68
|
-
Timeout::timeout(query.validate_timeout_remaining) do
|
69
|
-
visitor.visit
|
70
|
-
end
|
73
|
+
visitor.visit
|
71
74
|
|
72
|
-
if visitor.rescued_errors.
|
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
|
83
|
-
[
|
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 ||=
|
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
|
-
|
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
|
data/lib/graphql/backtrace.rb
CHANGED
@@ -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.
|
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
|