graphql 2.0.27 → 2.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/generators/graphql/install/templates/base_mutation.erb +2 -0
- data/lib/generators/graphql/install/templates/mutation_type.erb +2 -0
- data/lib/generators/graphql/install_generator.rb +3 -0
- data/lib/generators/graphql/templates/base_argument.erb +2 -0
- data/lib/generators/graphql/templates/base_connection.erb +2 -0
- data/lib/generators/graphql/templates/base_edge.erb +2 -0
- data/lib/generators/graphql/templates/base_enum.erb +2 -0
- data/lib/generators/graphql/templates/base_field.erb +2 -0
- data/lib/generators/graphql/templates/base_input_object.erb +2 -0
- data/lib/generators/graphql/templates/base_interface.erb +2 -0
- data/lib/generators/graphql/templates/base_object.erb +2 -0
- data/lib/generators/graphql/templates/base_resolver.erb +6 -0
- data/lib/generators/graphql/templates/base_scalar.erb +2 -0
- data/lib/generators/graphql/templates/base_union.erb +2 -0
- data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
- data/lib/generators/graphql/templates/loader.erb +2 -0
- data/lib/generators/graphql/templates/mutation.erb +2 -0
- data/lib/generators/graphql/templates/node_type.erb +2 -0
- data/lib/generators/graphql/templates/query_type.erb +2 -0
- data/lib/generators/graphql/templates/schema.erb +2 -0
- data/lib/graphql/analysis/ast/analyzer.rb +7 -0
- data/lib/graphql/analysis/ast/field_usage.rb +32 -7
- data/lib/graphql/analysis/ast/query_complexity.rb +80 -128
- data/lib/graphql/analysis/ast/query_depth.rb +7 -2
- data/lib/graphql/analysis/ast/visitor.rb +2 -2
- data/lib/graphql/analysis/ast.rb +21 -11
- data/lib/graphql/backtrace/trace.rb +12 -15
- data/lib/graphql/coercion_error.rb +1 -9
- data/lib/graphql/dataloader/async_dataloader.rb +85 -0
- data/lib/graphql/dataloader/source.rb +11 -3
- data/lib/graphql/dataloader.rb +109 -142
- data/lib/graphql/duration_encoding_error.rb +16 -0
- data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +170 -0
- data/lib/graphql/execution/interpreter/runtime.rb +70 -248
- data/lib/graphql/execution/interpreter.rb +91 -157
- data/lib/graphql/execution/lookahead.rb +88 -21
- data/lib/graphql/introspection/dynamic_fields.rb +1 -1
- data/lib/graphql/introspection/entry_points.rb +11 -5
- data/lib/graphql/introspection/schema_type.rb +3 -1
- data/lib/graphql/language/block_string.rb +34 -18
- data/lib/graphql/language/definition_slice.rb +1 -1
- data/lib/graphql/language/document_from_schema_definition.rb +37 -37
- data/lib/graphql/language/lexer.rb +271 -177
- data/lib/graphql/language/nodes.rb +74 -56
- data/lib/graphql/language/parser.rb +697 -1986
- data/lib/graphql/language/printer.rb +299 -146
- data/lib/graphql/language/sanitized_printer.rb +20 -22
- data/lib/graphql/language/static_visitor.rb +167 -0
- data/lib/graphql/language/visitor.rb +20 -81
- data/lib/graphql/language.rb +1 -0
- data/lib/graphql/load_application_object_failed_error.rb +5 -1
- data/lib/graphql/pagination/array_connection.rb +3 -3
- data/lib/graphql/pagination/connection.rb +28 -1
- data/lib/graphql/pagination/mongoid_relation_connection.rb +1 -2
- data/lib/graphql/pagination/relation_connection.rb +3 -3
- data/lib/graphql/query/context/scoped_context.rb +101 -0
- data/lib/graphql/query/context.rb +36 -98
- data/lib/graphql/query/null_context.rb +4 -11
- data/lib/graphql/query/validation_pipeline.rb +2 -2
- data/lib/graphql/query/variables.rb +3 -3
- data/lib/graphql/query.rb +13 -22
- data/lib/graphql/railtie.rb +9 -6
- data/lib/graphql/rake_task.rb +3 -12
- data/lib/graphql/schema/argument.rb +6 -1
- data/lib/graphql/schema/build_from_definition.rb +0 -11
- data/lib/graphql/schema/directive/one_of.rb +12 -0
- data/lib/graphql/schema/directive/specified_by.rb +14 -0
- data/lib/graphql/schema/directive.rb +1 -1
- data/lib/graphql/schema/enum.rb +3 -3
- data/lib/graphql/schema/field/connection_extension.rb +1 -15
- data/lib/graphql/schema/field/scope_extension.rb +8 -1
- data/lib/graphql/schema/field.rb +8 -5
- data/lib/graphql/schema/has_single_input_argument.rb +156 -0
- data/lib/graphql/schema/input_object.rb +2 -2
- data/lib/graphql/schema/interface.rb +10 -10
- data/lib/graphql/schema/introspection_system.rb +2 -0
- data/lib/graphql/schema/loader.rb +0 -2
- data/lib/graphql/schema/member/base_dsl_methods.rb +2 -1
- data/lib/graphql/schema/member/has_arguments.rb +61 -38
- data/lib/graphql/schema/member/has_fields.rb +8 -5
- data/lib/graphql/schema/member/has_interfaces.rb +23 -9
- data/lib/graphql/schema/member/scoped.rb +19 -0
- data/lib/graphql/schema/member/validates_input.rb +3 -3
- data/lib/graphql/schema/object.rb +8 -0
- data/lib/graphql/schema/printer.rb +8 -7
- data/lib/graphql/schema/relay_classic_mutation.rb +6 -128
- data/lib/graphql/schema/resolver.rb +7 -3
- data/lib/graphql/schema/scalar.rb +3 -3
- data/lib/graphql/schema/subscription.rb +11 -4
- data/lib/graphql/schema/union.rb +1 -1
- data/lib/graphql/schema/warden.rb +96 -94
- data/lib/graphql/schema.rb +219 -72
- data/lib/graphql/static_validation/all_rules.rb +1 -1
- data/lib/graphql/static_validation/base_visitor.rb +1 -1
- data/lib/graphql/static_validation/literal_validator.rb +1 -1
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
- data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +1 -1
- data/lib/graphql/static_validation/validation_context.rb +5 -5
- data/lib/graphql/static_validation/validator.rb +3 -0
- data/lib/graphql/static_validation.rb +0 -1
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +3 -2
- data/lib/graphql/subscriptions/event.rb +8 -2
- data/lib/graphql/subscriptions.rb +14 -12
- data/lib/graphql/testing/helpers.rb +125 -0
- data/lib/graphql/testing.rb +2 -0
- data/lib/graphql/tracing/appoptics_trace.rb +2 -2
- data/lib/graphql/tracing/appoptics_tracing.rb +2 -2
- data/lib/graphql/tracing/data_dog_trace.rb +21 -34
- data/lib/graphql/tracing/data_dog_tracing.rb +7 -21
- data/lib/graphql/tracing/legacy_hooks_trace.rb +74 -0
- data/lib/graphql/tracing/platform_tracing.rb +2 -0
- data/lib/graphql/tracing/{prometheus_tracing → prometheus_trace}/graphql_collector.rb +3 -1
- data/lib/graphql/tracing/sentry_trace.rb +94 -0
- data/lib/graphql/tracing/trace.rb +1 -0
- data/lib/graphql/tracing.rb +3 -1
- data/lib/graphql/types/iso_8601_duration.rb +77 -0
- data/lib/graphql/types/relay/connection_behaviors.rb +32 -2
- data/lib/graphql/types/relay/edge_behaviors.rb +7 -0
- data/lib/graphql/types.rb +1 -0
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +3 -3
- data/readme.md +12 -2
- metadata +33 -25
- data/lib/graphql/deprecation.rb +0 -9
- data/lib/graphql/filter.rb +0 -59
- data/lib/graphql/language/parser.y +0 -560
- data/lib/graphql/static_validation/type_stack.rb +0 -216
- data/lib/graphql/subscriptions/instrumentation.rb +0 -28
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ba7d4c9ad987cbae751625aced364d2ace39bfb84ee22e32ab07f33c335f4ebf
|
|
4
|
+
data.tar.gz: 40773fa35507e6cbeefb468c4ad153e1561e813092187abaf6f246c5e06802d6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fb0a00a30aec2c81a6a0e239f8f7c3313747e190c06d821395a75868a46d84b65f699e4dbf40342455549b2b1b7f92ab5630dcdd2bebfbe95aef66a574a5bd5d
|
|
7
|
+
data.tar.gz: 6b1b63f5004336b304adbfb6b946d0b8ba87bd60a5356859f38fae3df92f15f092d07c22f1bd9857fdf04883b8b7d2134f38fb67bf11afe08df009528dd3c0ed
|
|
@@ -105,6 +105,9 @@ module Graphql
|
|
|
105
105
|
template("#{base_type}.erb", "#{options[:directory]}/types/#{base_type}.rb")
|
|
106
106
|
end
|
|
107
107
|
|
|
108
|
+
# All resolvers are defined as living in their own module, including this class.
|
|
109
|
+
template("base_resolver.erb", "#{options[:directory]}/resolvers/base_resolver.rb")
|
|
110
|
+
|
|
108
111
|
# Note: You can't have a schema without the query type, otherwise introspection breaks
|
|
109
112
|
template("query_type.erb", "#{options[:directory]}/types/query_type.rb")
|
|
110
113
|
insert_root_type('query', 'QueryType')
|
|
@@ -29,6 +29,13 @@ module GraphQL
|
|
|
29
29
|
true
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
# Analyzer hook to decide at analysis time whether analysis
|
|
33
|
+
# requires a visitor pass; can be disabled for precomputed results.
|
|
34
|
+
# @return [Boolean] If analysis requires visitation or not
|
|
35
|
+
def visit?
|
|
36
|
+
true
|
|
37
|
+
end
|
|
38
|
+
|
|
32
39
|
# The result for this analyzer. Returning {GraphQL::AnalysisError} results
|
|
33
40
|
# in a query error.
|
|
34
41
|
# @return [Any] The analyzer result
|
|
@@ -8,6 +8,7 @@ module GraphQL
|
|
|
8
8
|
@used_fields = Set.new
|
|
9
9
|
@used_deprecated_fields = Set.new
|
|
10
10
|
@used_deprecated_arguments = Set.new
|
|
11
|
+
@used_deprecated_enum_values = Set.new
|
|
11
12
|
end
|
|
12
13
|
|
|
13
14
|
def on_leave_field(node, parent, visitor)
|
|
@@ -15,7 +16,7 @@ module GraphQL
|
|
|
15
16
|
field = "#{visitor.parent_type_definition.graphql_name}.#{field_defn.graphql_name}"
|
|
16
17
|
@used_fields << field
|
|
17
18
|
@used_deprecated_fields << field if field_defn.deprecation_reason
|
|
18
|
-
arguments = visitor.query.arguments_for(node,
|
|
19
|
+
arguments = visitor.query.arguments_for(node, field_defn)
|
|
19
20
|
# If there was an error when preparing this argument object,
|
|
20
21
|
# then this might be an error or something:
|
|
21
22
|
if arguments.respond_to?(:argument_values)
|
|
@@ -28,6 +29,7 @@ module GraphQL
|
|
|
28
29
|
used_fields: @used_fields.to_a,
|
|
29
30
|
used_deprecated_fields: @used_deprecated_fields.to_a,
|
|
30
31
|
used_deprecated_arguments: @used_deprecated_arguments.to_a,
|
|
32
|
+
used_deprecated_enum_values: @used_deprecated_enum_values.to_a,
|
|
31
33
|
}
|
|
32
34
|
end
|
|
33
35
|
|
|
@@ -41,16 +43,39 @@ module GraphQL
|
|
|
41
43
|
|
|
42
44
|
next if argument.value.nil?
|
|
43
45
|
|
|
44
|
-
|
|
46
|
+
argument_type = argument.definition.type
|
|
47
|
+
if argument_type.non_null?
|
|
48
|
+
argument_type = argument_type.of_type
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
if argument_type.kind.input_object?
|
|
45
52
|
extract_deprecated_arguments(argument.value.arguments.argument_values) # rubocop:disable Development/ContextIsPassedCop -- runtime args instance
|
|
46
|
-
elsif
|
|
47
|
-
argument
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
elsif argument_type.kind.enum?
|
|
54
|
+
extract_deprecated_enum_value(argument_type, argument.value)
|
|
55
|
+
elsif argument_type.list?
|
|
56
|
+
inner_type = argument_type.unwrap
|
|
57
|
+
case inner_type.kind
|
|
58
|
+
when TypeKinds::INPUT_OBJECT
|
|
59
|
+
argument.value.each do |value|
|
|
60
|
+
extract_deprecated_arguments(value.arguments.argument_values) # rubocop:disable Development/ContextIsPassedCop -- runtime args instance
|
|
61
|
+
end
|
|
62
|
+
when TypeKinds::ENUM
|
|
63
|
+
argument.value.each do |value|
|
|
64
|
+
extract_deprecated_enum_value(inner_type, value)
|
|
65
|
+
end
|
|
66
|
+
else
|
|
67
|
+
# Not a kind of input that we track
|
|
68
|
+
end
|
|
51
69
|
end
|
|
52
70
|
end
|
|
53
71
|
end
|
|
72
|
+
|
|
73
|
+
def extract_deprecated_enum_value(enum_type, value)
|
|
74
|
+
enum_value = @query.warden.enum_values(enum_type).find { |ev| ev.value == value }
|
|
75
|
+
if enum_value&.deprecation_reason
|
|
76
|
+
@used_deprecated_enum_values << enum_value.path
|
|
77
|
+
end
|
|
78
|
+
end
|
|
54
79
|
end
|
|
55
80
|
end
|
|
56
81
|
end
|
|
@@ -16,10 +16,11 @@ module GraphQL
|
|
|
16
16
|
max_possible_complexity
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
# ScopedTypeComplexity models a tree of GraphQL types mapped to inner selections, ie:
|
|
20
|
+
# Hash<GraphQL::BaseType, Hash<String, ScopedTypeComplexity>>
|
|
21
|
+
class ScopedTypeComplexity < Hash
|
|
22
|
+
# A proc for defaulting empty namespace requests as a new scope hash.
|
|
23
|
+
DEFAULT_PROC = ->(h, k) { h[k] = {} }
|
|
23
24
|
|
|
24
25
|
attr_reader :field_definition, :response_path, :query
|
|
25
26
|
|
|
@@ -27,32 +28,19 @@ module GraphQL
|
|
|
27
28
|
# @param field_definition [GraphQL::Field, GraphQL::Schema::Field] Used for getting the `.complexity` configuration
|
|
28
29
|
# @param query [GraphQL::Query] Used for `query.possible_types`
|
|
29
30
|
# @param response_path [Array<String>] The path to the response key for the field
|
|
31
|
+
# @return [Hash<GraphQL::BaseType, Hash<String, ScopedTypeComplexity>>]
|
|
30
32
|
def initialize(parent_type, field_definition, query, response_path)
|
|
33
|
+
super(&DEFAULT_PROC)
|
|
31
34
|
@parent_type = parent_type
|
|
32
35
|
@field_definition = field_definition
|
|
33
36
|
@query = query
|
|
34
37
|
@response_path = response_path
|
|
35
|
-
@scoped_children = nil
|
|
36
38
|
@nodes = []
|
|
37
39
|
end
|
|
38
40
|
|
|
39
41
|
# @return [Array<GraphQL::Language::Nodes::Field>]
|
|
40
42
|
attr_reader :nodes
|
|
41
43
|
|
|
42
|
-
# Returns true if this field has no selections, ie, it's a scalar.
|
|
43
|
-
# We need a quick way to check whether we should continue traversing.
|
|
44
|
-
def terminal?
|
|
45
|
-
@scoped_children.nil?
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# This value is only calculated when asked for to avoid needless hash allocations.
|
|
49
|
-
# Also, if it's never asked for, we determine that this scope complexity
|
|
50
|
-
# is a scalar field ({#terminal?}).
|
|
51
|
-
# @return [Hash<Hash<Class => ScopedTypeComplexity>]
|
|
52
|
-
def scoped_children
|
|
53
|
-
@scoped_children ||= Hash.new(&HASH_CHILDREN)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
44
|
def own_complexity(child_complexity)
|
|
57
45
|
@field_definition.calculate_complexity(query: @query, nodes: @nodes, child_complexity: child_complexity)
|
|
58
46
|
end
|
|
@@ -65,19 +53,14 @@ module GraphQL
|
|
|
65
53
|
return if visitor.skipping?
|
|
66
54
|
parent_type = visitor.parent_type_definition
|
|
67
55
|
field_key = node.alias || node.name
|
|
68
|
-
|
|
69
|
-
#
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
#
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
complexity = complexities_on_type.last.scoped_children[parent_type][field_key] ||= ScopedTypeComplexity.new(parent_type, visitor.field_definition, visitor.query, visitor.response_path)
|
|
78
|
-
complexity.nodes.push(node)
|
|
79
|
-
# Push it on the stack.
|
|
80
|
-
complexities_on_type.push(complexity)
|
|
56
|
+
|
|
57
|
+
# Find or create a complexity scope stack for this query.
|
|
58
|
+
scopes_stack = @complexities_on_type_by_query[visitor.query] ||= [ScopedTypeComplexity.new(nil, nil, query, visitor.response_path)]
|
|
59
|
+
|
|
60
|
+
# Find or create the complexity costing node for this field.
|
|
61
|
+
scope = scopes_stack.last[parent_type][field_key] ||= ScopedTypeComplexity.new(parent_type, visitor.field_definition, visitor.query, visitor.response_path)
|
|
62
|
+
scope.nodes.push(node)
|
|
63
|
+
scopes_stack.push(scope)
|
|
81
64
|
end
|
|
82
65
|
|
|
83
66
|
def on_leave_field(node, parent, visitor)
|
|
@@ -85,89 +68,61 @@ module GraphQL
|
|
|
85
68
|
# we'll visit them when we hit the spreads instead
|
|
86
69
|
return if visitor.visiting_fragment_definition?
|
|
87
70
|
return if visitor.skipping?
|
|
88
|
-
|
|
89
|
-
|
|
71
|
+
scopes_stack = @complexities_on_type_by_query[visitor.query]
|
|
72
|
+
scopes_stack.pop
|
|
90
73
|
end
|
|
91
74
|
|
|
92
75
|
private
|
|
93
76
|
|
|
94
77
|
# @return [Integer]
|
|
95
78
|
def max_possible_complexity
|
|
96
|
-
@complexities_on_type_by_query.reduce(0) do |total, (query,
|
|
97
|
-
|
|
98
|
-
# Use this entry point to calculate the total complexity
|
|
99
|
-
total_complexity_for_query = merged_max_complexity_for_scopes(query, [root_complexity.scoped_children])
|
|
100
|
-
total + total_complexity_for_query
|
|
79
|
+
@complexities_on_type_by_query.reduce(0) do |total, (query, scopes_stack)|
|
|
80
|
+
total + merged_max_complexity_for_scopes(query, [scopes_stack.first])
|
|
101
81
|
end
|
|
102
82
|
end
|
|
103
83
|
|
|
104
84
|
# @param query [GraphQL::Query] Used for `query.possible_types`
|
|
105
|
-
# @param
|
|
85
|
+
# @param scopes [Array<ScopedTypeComplexity>] Array of scoped type complexities
|
|
106
86
|
# @return [Integer]
|
|
107
|
-
def merged_max_complexity_for_scopes(query,
|
|
108
|
-
#
|
|
87
|
+
def merged_max_complexity_for_scopes(query, scopes)
|
|
88
|
+
# Aggregate a set of all possible scope types encountered (scope keys).
|
|
109
89
|
# Use a hash, but ignore the values; it's just a fast way to work with the keys.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
all_scopes.merge!(h)
|
|
90
|
+
possible_scope_types = scopes.each_with_object({}) do |scope, memo|
|
|
91
|
+
memo.merge!(scope)
|
|
113
92
|
end
|
|
114
93
|
|
|
115
|
-
#
|
|
116
|
-
#
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
missing_concrete_types = query.possible_types(scope).select { |t| !all_scopes.key?(t) }
|
|
124
|
-
# This concrete type is possible _only_ as a member of the abstract type.
|
|
125
|
-
# So, attribute to it the complexity which belongs to the abstract type.
|
|
126
|
-
missing_concrete_types.each do |concrete_scope|
|
|
127
|
-
all_scopes[concrete_scope] = all_scopes[scope]
|
|
94
|
+
# Expand abstract scope types into their concrete implementations;
|
|
95
|
+
# overlapping abstracts coalesce through their intersecting types.
|
|
96
|
+
possible_scope_types.keys.each do |possible_scope_type|
|
|
97
|
+
next unless possible_scope_type.kind.abstract?
|
|
98
|
+
|
|
99
|
+
query.possible_types(possible_scope_type).each do |impl_type|
|
|
100
|
+
possible_scope_types[impl_type] ||= true
|
|
128
101
|
end
|
|
129
|
-
|
|
102
|
+
possible_scope_types.delete(possible_scope_type)
|
|
130
103
|
end
|
|
131
104
|
|
|
132
|
-
#
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
# as a set and calculate the complexity for them as a unit
|
|
140
|
-
all_scopes.each do |scope, _|
|
|
141
|
-
# These will be the selections on `scope`
|
|
142
|
-
children_for_scope = []
|
|
143
|
-
scoped_children_hashes.each do |sc_h|
|
|
144
|
-
sc_h.each do |inner_scope, children_hash|
|
|
145
|
-
if applies_to?(query, scope, inner_scope)
|
|
146
|
-
children_for_scope << children_hash
|
|
147
|
-
end
|
|
105
|
+
# Aggregate the lexical selections that may apply to each possible type,
|
|
106
|
+
# and then return the maximum cost among possible typed selections.
|
|
107
|
+
possible_scope_types.each_key.reduce(0) do |max, possible_scope_type|
|
|
108
|
+
# Collect inner selections from all scopes that intersect with this possible type.
|
|
109
|
+
all_inner_selections = scopes.each_with_object([]) do |scope, memo|
|
|
110
|
+
scope.each do |scope_type, inner_selections|
|
|
111
|
+
memo << inner_selections if types_intersect?(query, scope_type, possible_scope_type)
|
|
148
112
|
end
|
|
149
113
|
end
|
|
150
114
|
|
|
151
|
-
#
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
complexity_by_scope[scope] = complexity_value
|
|
115
|
+
# Find the maximum complexity for the scope type among possible lexical branches.
|
|
116
|
+
complexity = merged_max_complexity(query, all_inner_selections)
|
|
117
|
+
complexity > max ? complexity : max
|
|
155
118
|
end
|
|
156
|
-
|
|
157
|
-
# Return the max complexity among all scopes
|
|
158
|
-
complexity_by_scope.each_value.max
|
|
159
119
|
end
|
|
160
120
|
|
|
161
|
-
def
|
|
162
|
-
if
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
# Check if these two scopes have _any_ types in common.
|
|
167
|
-
possible_right_types = query.possible_types(right_scope)
|
|
168
|
-
possible_left_types = query.possible_types(left_scope)
|
|
169
|
-
!(possible_right_types & possible_left_types).empty?
|
|
170
|
-
end
|
|
121
|
+
def types_intersect?(query, a, b)
|
|
122
|
+
return true if a == b
|
|
123
|
+
|
|
124
|
+
a_types = query.possible_types(a)
|
|
125
|
+
query.possible_types(b).any? { |t| a_types.include?(t) }
|
|
171
126
|
end
|
|
172
127
|
|
|
173
128
|
# A hook which is called whenever a field's max complexity is calculated.
|
|
@@ -179,50 +134,47 @@ module GraphQL
|
|
|
179
134
|
def field_complexity(scoped_type_complexity, max_complexity:, child_complexity: nil)
|
|
180
135
|
end
|
|
181
136
|
|
|
182
|
-
# @param
|
|
183
|
-
#
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
137
|
+
# @param inner_selections [Array<Hash<String, ScopedTypeComplexity>>] Field selections for a scope
|
|
138
|
+
# @return [Integer] Total complexity value for all these selections in the parent scope
|
|
139
|
+
def merged_max_complexity(query, inner_selections)
|
|
140
|
+
# Aggregate a set of all unique field selection keys across all scopes.
|
|
141
|
+
# Use a hash, but ignore the values; it's just a fast way to work with the keys.
|
|
142
|
+
unique_field_keys = inner_selections.each_with_object({}) do |inner_selection, memo|
|
|
143
|
+
memo.merge!(inner_selection)
|
|
189
144
|
end
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
field_complexity(complexity_for_key, max_complexity: complexity, child_complexity: nil)
|
|
145
|
+
|
|
146
|
+
# Add up the total cost for each unique field name's coalesced selections
|
|
147
|
+
unique_field_keys.each_key.reduce(0) do |total, field_key|
|
|
148
|
+
composite_scopes = nil
|
|
149
|
+
field_cost = 0
|
|
150
|
+
|
|
151
|
+
# Collect composite selection scopes for further aggregation,
|
|
152
|
+
# leaf selections report their costs directly.
|
|
153
|
+
inner_selections.each do |inner_selection|
|
|
154
|
+
child_scope = inner_selection[field_key]
|
|
155
|
+
next unless child_scope
|
|
156
|
+
|
|
157
|
+
# Empty child scopes are leaf nodes with zero child complexity.
|
|
158
|
+
if child_scope.empty?
|
|
159
|
+
field_cost = child_scope.own_complexity(0)
|
|
160
|
+
field_complexity(child_scope, max_complexity: field_cost, child_complexity: nil)
|
|
207
161
|
else
|
|
208
|
-
|
|
209
|
-
|
|
162
|
+
composite_scopes ||= []
|
|
163
|
+
composite_scopes << child_scope
|
|
210
164
|
end
|
|
211
165
|
end
|
|
212
166
|
|
|
213
|
-
|
|
167
|
+
if composite_scopes
|
|
168
|
+
child_complexity = merged_max_complexity_for_scopes(query, composite_scopes)
|
|
214
169
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
170
|
+
# This is the last composite scope visited; assume it's representative (for backwards compatibility).
|
|
171
|
+
# Note: it would be more correct to score each composite scope and use the maximum possibility.
|
|
172
|
+
field_cost = composite_scopes.last.own_complexity(child_complexity)
|
|
173
|
+
field_complexity(composite_scopes.last, max_complexity: field_cost, child_complexity: child_complexity)
|
|
174
|
+
end
|
|
220
175
|
|
|
221
|
-
|
|
176
|
+
total + field_cost
|
|
222
177
|
end
|
|
223
|
-
|
|
224
|
-
# Calculate the child complexity by summing the complexity of all selections
|
|
225
|
-
complexity_for_keys.each_value.inject(0, &:+)
|
|
226
178
|
end
|
|
227
179
|
end
|
|
228
180
|
end
|
|
@@ -28,17 +28,22 @@ module GraphQL
|
|
|
28
28
|
def initialize(query)
|
|
29
29
|
@max_depth = 0
|
|
30
30
|
@current_depth = 0
|
|
31
|
+
@count_introspection_fields = query.schema.count_introspection_fields
|
|
31
32
|
super
|
|
32
33
|
end
|
|
33
34
|
|
|
34
35
|
def on_enter_field(node, parent, visitor)
|
|
35
|
-
return if visitor.skipping? ||
|
|
36
|
+
return if visitor.skipping? ||
|
|
37
|
+
visitor.visiting_fragment_definition? ||
|
|
38
|
+
(@count_introspection_fields == false && visitor.field_definition.introspection?)
|
|
36
39
|
|
|
37
40
|
@current_depth += 1
|
|
38
41
|
end
|
|
39
42
|
|
|
40
43
|
def on_leave_field(node, parent, visitor)
|
|
41
|
-
return if visitor.skipping? ||
|
|
44
|
+
return if visitor.skipping? ||
|
|
45
|
+
visitor.visiting_fragment_definition? ||
|
|
46
|
+
(@count_introspection_fields == false && visitor.field_definition.introspection?)
|
|
42
47
|
|
|
43
48
|
if @max_depth < @current_depth
|
|
44
49
|
@max_depth = @current_depth
|
|
@@ -5,12 +5,12 @@ module GraphQL
|
|
|
5
5
|
# Depth first traversal through a query AST, calling AST analyzers
|
|
6
6
|
# along the way.
|
|
7
7
|
#
|
|
8
|
-
# The visitor is a special case of GraphQL::Language::
|
|
8
|
+
# The visitor is a special case of GraphQL::Language::StaticVisitor, visiting
|
|
9
9
|
# only the selected operation, providing helpers for common use cases such
|
|
10
10
|
# as skipped fields and visiting fragment spreads.
|
|
11
11
|
#
|
|
12
12
|
# @see {GraphQL::Analysis::AST::Analyzer} AST Analyzers for queries
|
|
13
|
-
class Visitor < GraphQL::Language::
|
|
13
|
+
class Visitor < GraphQL::Language::StaticVisitor
|
|
14
14
|
def initialize(query:, analyzers:)
|
|
15
15
|
@analyzers = analyzers
|
|
16
16
|
@path = []
|
data/lib/graphql/analysis/ast.rb
CHANGED
|
@@ -6,6 +6,7 @@ require "graphql/analysis/ast/query_complexity"
|
|
|
6
6
|
require "graphql/analysis/ast/max_query_complexity"
|
|
7
7
|
require "graphql/analysis/ast/query_depth"
|
|
8
8
|
require "graphql/analysis/ast/max_query_depth"
|
|
9
|
+
require "timeout"
|
|
9
10
|
|
|
10
11
|
module GraphQL
|
|
11
12
|
module Analysis
|
|
@@ -51,30 +52,39 @@ module GraphQL
|
|
|
51
52
|
query.current_trace.analyze_query(query: query) do
|
|
52
53
|
query_analyzers = analyzers
|
|
53
54
|
.map { |analyzer| analyzer.new(query) }
|
|
54
|
-
.
|
|
55
|
+
.tap { _1.select!(&:analyze?) }
|
|
55
56
|
|
|
56
57
|
analyzers_to_run = query_analyzers + multiplex_analyzers
|
|
57
58
|
if analyzers_to_run.any?
|
|
58
|
-
visitor = GraphQL::Analysis::AST::Visitor.new(
|
|
59
|
-
query: query,
|
|
60
|
-
analyzers: analyzers_to_run
|
|
61
|
-
)
|
|
62
59
|
|
|
63
|
-
|
|
60
|
+
analyzers_to_run.select!(&:visit?)
|
|
61
|
+
if analyzers_to_run.any?
|
|
62
|
+
visitor = GraphQL::Analysis::AST::Visitor.new(
|
|
63
|
+
query: query,
|
|
64
|
+
analyzers: analyzers_to_run
|
|
65
|
+
)
|
|
64
66
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
|
69
75
|
end
|
|
76
|
+
|
|
77
|
+
query_analyzers.map(&:result)
|
|
70
78
|
else
|
|
71
79
|
[]
|
|
72
80
|
end
|
|
73
81
|
end
|
|
82
|
+
rescue Timeout::Error
|
|
83
|
+
[GraphQL::AnalysisError.new("Timeout on validation of query")]
|
|
74
84
|
end
|
|
75
85
|
|
|
76
86
|
def analysis_errors(results)
|
|
77
|
-
results.flatten.select { |r| r.is_a?(GraphQL::AnalysisError) }
|
|
87
|
+
results.flatten.tap { _1.select! { |r| r.is_a?(GraphQL::AnalysisError) } }
|
|
78
88
|
end
|
|
79
89
|
end
|
|
80
90
|
end
|