graphql 0.11.0 → 0.11.1
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/base_type.rb +6 -2
- data/lib/graphql/definition_helpers.rb +0 -5
- data/lib/graphql/definition_helpers/defined_by_config.rb +106 -102
- data/lib/graphql/definition_helpers/non_null_with_bang.rb +13 -9
- data/lib/graphql/definition_helpers/string_named_hash.rb +19 -15
- data/lib/graphql/definition_helpers/type_definer.rb +25 -21
- data/lib/graphql/enum_type.rb +8 -2
- data/lib/graphql/float_type.rb +1 -1
- data/lib/graphql/input_object_type.rb +27 -6
- data/lib/graphql/interface_type.rb +10 -0
- data/lib/graphql/introspection/fields_field.rb +2 -2
- data/lib/graphql/list_type.rb +12 -2
- data/lib/graphql/non_null_type.rb +11 -1
- data/lib/graphql/object_type.rb +19 -0
- data/lib/graphql/query.rb +2 -14
- data/lib/graphql/query/input_validation_result.rb +23 -0
- data/lib/graphql/query/literal_input.rb +3 -1
- data/lib/graphql/query/serial_execution/field_resolution.rb +3 -1
- data/lib/graphql/query/serial_execution/selection_resolution.rb +21 -15
- data/lib/graphql/query/variable_validation_error.rb +18 -0
- data/lib/graphql/query/variables.rb +4 -8
- data/lib/graphql/scalar_type.rb +10 -4
- data/lib/graphql/schema.rb +4 -2
- data/lib/graphql/schema/printer.rb +12 -3
- data/lib/graphql/schema/type_reducer.rb +4 -3
- data/lib/graphql/static_validation.rb +1 -0
- data/lib/graphql/static_validation/all_rules.rb +1 -0
- data/lib/graphql/static_validation/rules/document_does_not_exceed_max_depth.rb +79 -0
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +1 -1
- data/lib/graphql/static_validation/validation_context.rb +63 -0
- data/lib/graphql/static_validation/validator.rb +1 -52
- data/lib/graphql/version.rb +1 -1
- data/readme.md +21 -22
- data/spec/graphql/enum_type_spec.rb +8 -0
- data/spec/graphql/input_object_type_spec.rb +101 -3
- data/spec/graphql/introspection/schema_type_spec.rb +6 -6
- data/spec/graphql/introspection/type_type_spec.rb +6 -6
- data/spec/graphql/language/transform_spec.rb +9 -5
- data/spec/graphql/list_type_spec.rb +23 -0
- data/spec/graphql/object_type_spec.rb +11 -4
- data/spec/graphql/query/executor_spec.rb +34 -5
- data/spec/graphql/query_spec.rb +22 -3
- data/spec/graphql/scalar_type_spec.rb +28 -0
- data/spec/graphql/schema/type_reducer_spec.rb +2 -2
- data/spec/graphql/static_validation/complexity_validator_spec.rb +15 -0
- data/spec/graphql/static_validation/rules/document_does_not_exceed_max_depth_spec.rb +93 -0
- data/spec/support/dairy_app.rb +2 -2
- data/spec/support/minimum_input_object.rb +8 -5
- metadata +10 -4
- data/spec/graphql/static_validation/complexity_validator.rb +0 -15
@@ -23,4 +23,14 @@ class GraphQL::InterfaceType < GraphQL::BaseType
|
|
23
23
|
def possible_types
|
24
24
|
@possible_types ||= []
|
25
25
|
end
|
26
|
+
|
27
|
+
# @return [GraphQL::Field] The defined field for `field_name`
|
28
|
+
def get_field(field_name)
|
29
|
+
fields[field_name]
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [Array<GraphQL::Field>] All fields on this type
|
33
|
+
def all_fields
|
34
|
+
fields.values
|
35
|
+
end
|
26
36
|
end
|
@@ -4,10 +4,10 @@ GraphQL::Introspection::FieldsField = GraphQL::Field.define do
|
|
4
4
|
argument :includeDeprecated, GraphQL::BOOLEAN_TYPE, default_value: false
|
5
5
|
resolve -> (object, arguments, context) {
|
6
6
|
return nil if !object.kind.fields?
|
7
|
-
fields = object.
|
7
|
+
fields = object.all_fields
|
8
8
|
if !arguments["includeDeprecated"]
|
9
9
|
fields = fields.select {|f| !f.deprecation_reason }
|
10
10
|
end
|
11
|
-
fields
|
11
|
+
fields.sort_by { |f| f.name }
|
12
12
|
}
|
13
13
|
end
|
data/lib/graphql/list_type.rb
CHANGED
@@ -17,10 +17,20 @@ class GraphQL::ListType < GraphQL::BaseType
|
|
17
17
|
"[#{of_type.to_s}]"
|
18
18
|
end
|
19
19
|
|
20
|
-
def
|
21
|
-
|
20
|
+
def validate_non_null_input(value)
|
21
|
+
result = GraphQL::Query::InputValidationResult.new
|
22
|
+
|
23
|
+
ensure_array(value).each_with_index do |item, index|
|
24
|
+
item_result = of_type.validate_input(item)
|
25
|
+
if !item_result.valid?
|
26
|
+
result.merge_result!(index, item_result)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
result
|
22
31
|
end
|
23
32
|
|
33
|
+
|
24
34
|
def coerce_non_null_input(value)
|
25
35
|
ensure_array(value).map{ |item| of_type.coerce_input(item) }
|
26
36
|
end
|
@@ -14,7 +14,17 @@ class GraphQL::NonNullType < GraphQL::BaseType
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def valid_input?(value)
|
17
|
-
|
17
|
+
validate_input(value).valid?
|
18
|
+
end
|
19
|
+
|
20
|
+
def validate_input(value)
|
21
|
+
if value.nil?
|
22
|
+
result = GraphQL::Query::InputValidationResult.new
|
23
|
+
result.add_problem("Expected value to not be null")
|
24
|
+
result
|
25
|
+
else
|
26
|
+
of_type.validate_input(value)
|
27
|
+
end
|
18
28
|
end
|
19
29
|
|
20
30
|
def coerce_input(value)
|
data/lib/graphql/object_type.rb
CHANGED
@@ -42,4 +42,23 @@ class GraphQL::ObjectType < GraphQL::BaseType
|
|
42
42
|
def kind
|
43
43
|
GraphQL::TypeKinds::OBJECT
|
44
44
|
end
|
45
|
+
|
46
|
+
# @return [GraphQL::Field] The field definition for `field_name` (may be inherited from interfaces)
|
47
|
+
def get_field(field_name)
|
48
|
+
fields[field_name] || interface_fields[field_name]
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [Array<GraphQL::Field>] All fields, including ones inherited from interfaces
|
52
|
+
def all_fields
|
53
|
+
interface_fields.merge(self.fields).values
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# Create a {name => defn} hash for fields inherited from interfaces
|
59
|
+
def interface_fields
|
60
|
+
interfaces.reduce({}) do |memo, iface|
|
61
|
+
memo.merge!(iface.fields)
|
62
|
+
end
|
63
|
+
end
|
45
64
|
end
|
data/lib/graphql/query.rb
CHANGED
@@ -6,20 +6,6 @@ class GraphQL::Query
|
|
6
6
|
end
|
7
7
|
end
|
8
8
|
|
9
|
-
class VariableValidationError < GraphQL::ExecutionError
|
10
|
-
def initialize(variable_ast, type, reason)
|
11
|
-
msg = "Variable #{variable_ast.name} of type #{type} #{reason}"
|
12
|
-
super(msg)
|
13
|
-
self.ast_node = variable_ast
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
class VariableMissingError < VariableValidationError
|
18
|
-
def initialize(variable_ast, type)
|
19
|
-
super(variable_ast, type, "can't be null")
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
9
|
# If a resolve function returns `GraphQL::Query::DEFAULT_RESOLVE`,
|
24
10
|
# The executor will send the field's name to the target object
|
25
11
|
# and use the result.
|
@@ -111,3 +97,5 @@ require 'graphql/query/literal_input'
|
|
111
97
|
require 'graphql/query/serial_execution'
|
112
98
|
require 'graphql/query/type_resolver'
|
113
99
|
require 'graphql/query/variables'
|
100
|
+
require 'graphql/query/input_validation_result'
|
101
|
+
require 'graphql/query/variable_validation_error'
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class GraphQL::Query
|
2
|
+
class InputValidationResult
|
3
|
+
attr_accessor :problems
|
4
|
+
|
5
|
+
def valid?
|
6
|
+
@problems.nil?
|
7
|
+
end
|
8
|
+
|
9
|
+
def add_problem(explanation, path = nil)
|
10
|
+
@problems ||= []
|
11
|
+
@problems.push({ 'path' => path || [], 'explanation' => explanation })
|
12
|
+
end
|
13
|
+
|
14
|
+
def merge_result!(path, inner_result)
|
15
|
+
return if inner_result.valid?
|
16
|
+
|
17
|
+
inner_result.problems.each do |p|
|
18
|
+
item_path = [path, *p['path']]
|
19
|
+
add_problem(p['explanation'], item_path)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -55,7 +55,9 @@ module GraphQL
|
|
55
55
|
type.input_fields.each do |arg_name, arg_defn|
|
56
56
|
if hash[arg_name].nil?
|
57
57
|
value = LiteralInput.coerce(arg_defn.type, arg_defn.default_value, variables)
|
58
|
-
|
58
|
+
if !value.nil?
|
59
|
+
hash[arg_name] = value
|
60
|
+
end
|
59
61
|
end
|
60
62
|
end
|
61
63
|
Arguments.new(hash)
|
@@ -10,7 +10,9 @@ module GraphQL
|
|
10
10
|
@target = target
|
11
11
|
@execution_context = execution_context
|
12
12
|
@field = execution_context.get_field(parent_type, ast_node.name)
|
13
|
-
|
13
|
+
if @field.nil?
|
14
|
+
raise("No field found on #{parent_type.name} '#{parent_type}' for '#{ast_node.name}'")
|
15
|
+
end
|
14
16
|
@arguments = GraphQL::Query::LiteralInput.from_arguments(
|
15
17
|
ast_node.arguments,
|
16
18
|
field.arguments,
|
@@ -18,7 +18,7 @@ module GraphQL
|
|
18
18
|
result.merge(resolve_field(ast_node))
|
19
19
|
}
|
20
20
|
rescue GraphQL::InvalidNullError => err
|
21
|
-
execution_context.add_error(err)
|
21
|
+
err.parent_error? || execution_context.add_error(err)
|
22
22
|
nil
|
23
23
|
end
|
24
24
|
|
@@ -56,8 +56,11 @@ module GraphQL
|
|
56
56
|
end
|
57
57
|
|
58
58
|
def flatten_fragment(ast_fragment)
|
59
|
-
|
60
|
-
|
59
|
+
if fragment_type_can_apply?(ast_fragment)
|
60
|
+
flatten_and_merge_selections(ast_fragment.selections)
|
61
|
+
else
|
62
|
+
{}
|
63
|
+
end
|
61
64
|
end
|
62
65
|
|
63
66
|
def fragment_type_can_apply?(ast_fragment)
|
@@ -68,18 +71,21 @@ module GraphQL
|
|
68
71
|
|
69
72
|
def merge_fields(field1, field2)
|
70
73
|
field_type = execution_context.get_field(type, field2.name).type.unwrap
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
74
|
+
|
75
|
+
if field_type.kind.fields?
|
76
|
+
# create a new ast field node merging selections from each field.
|
77
|
+
# Because of static validation, we can assume that name, alias,
|
78
|
+
# arguments, and directives are exactly the same for fields 1 and 2.
|
79
|
+
GraphQL::Language::Nodes::Field.new(
|
80
|
+
name: field2.name,
|
81
|
+
alias: field2.alias,
|
82
|
+
arguments: field2.arguments,
|
83
|
+
directives: field2.directives,
|
84
|
+
selections: field1.selections + field2.selections
|
85
|
+
)
|
86
|
+
else
|
87
|
+
field2
|
88
|
+
end
|
83
89
|
end
|
84
90
|
|
85
91
|
def resolve_field(ast_node)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class GraphQL::Query
|
2
|
+
class VariableValidationError < GraphQL::ExecutionError
|
3
|
+
attr_accessor :value, :validation_result
|
4
|
+
|
5
|
+
def initialize(variable_ast, type, value, validation_result)
|
6
|
+
@value = value
|
7
|
+
@validation_result = validation_result
|
8
|
+
|
9
|
+
msg = "Variable #{variable_ast.name} of type #{type} was provided invalid value"
|
10
|
+
super(msg)
|
11
|
+
self.ast_node = variable_ast
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_h
|
15
|
+
super.merge({ 'value' => value, 'problems' => validation_result.problems })
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -27,14 +27,10 @@ module GraphQL
|
|
27
27
|
default_value = ast_variable.default_value
|
28
28
|
provided_value = @provided_variables[variable_name]
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
raise GraphQL::Query::VariableValidationError.new(ast_variable, variable_type, "was provided invalid value #{JSON.dump(provided_value)}")
|
35
|
-
end
|
36
|
-
end
|
37
|
-
if provided_value.nil?
|
30
|
+
validation_result = variable_type.validate_input(provided_value)
|
31
|
+
if !validation_result.valid?
|
32
|
+
raise GraphQL::Query::VariableValidationError.new(ast_variable, variable_type, provided_value, validation_result)
|
33
|
+
elsif provided_value.nil?
|
38
34
|
GraphQL::Query::LiteralInput.coerce(variable_type, default_value, {})
|
39
35
|
else
|
40
36
|
variable_type.coerce_input(provided_value)
|
data/lib/graphql/scalar_type.rb
CHANGED
@@ -19,8 +19,10 @@ module GraphQL
|
|
19
19
|
self.coerce_result = proc
|
20
20
|
end
|
21
21
|
|
22
|
-
def
|
23
|
-
|
22
|
+
def validate_non_null_input(value)
|
23
|
+
result = Query::InputValidationResult.new
|
24
|
+
result.add_problem("Could not coerce value #{JSON.dump(value)} to #{name}") if coerce_non_null_input(value).nil?
|
25
|
+
result
|
24
26
|
end
|
25
27
|
|
26
28
|
def coerce_non_null_input(value)
|
@@ -28,7 +30,9 @@ module GraphQL
|
|
28
30
|
end
|
29
31
|
|
30
32
|
def coerce_input=(proc)
|
31
|
-
|
33
|
+
if !proc.nil?
|
34
|
+
@coerce_input_proc = proc
|
35
|
+
end
|
32
36
|
end
|
33
37
|
|
34
38
|
def coerce_result(value)
|
@@ -36,7 +40,9 @@ module GraphQL
|
|
36
40
|
end
|
37
41
|
|
38
42
|
def coerce_result=(proc)
|
39
|
-
|
43
|
+
if !proc.nil?
|
44
|
+
@coerce_result_proc = proc
|
45
|
+
end
|
40
46
|
end
|
41
47
|
|
42
48
|
def kind
|
data/lib/graphql/schema.rb
CHANGED
@@ -6,6 +6,7 @@ class GraphQL::Schema
|
|
6
6
|
DYNAMIC_FIELDS = ["__type", "__typename", "__schema"]
|
7
7
|
|
8
8
|
attr_reader :query, :mutation, :subscription, :directives, :static_validator
|
9
|
+
attr_accessor :max_depth
|
9
10
|
# Override these if you don't want the default executor:
|
10
11
|
attr_accessor :query_execution_strategy,
|
11
12
|
:mutation_execution_strategy,
|
@@ -17,10 +18,11 @@ class GraphQL::Schema
|
|
17
18
|
# @param query [GraphQL::ObjectType] the query root for the schema
|
18
19
|
# @param mutation [GraphQL::ObjectType] the mutation root for the schema
|
19
20
|
# @param subscription [GraphQL::ObjectType] the subscription root for the schema
|
20
|
-
def initialize(query:, mutation: nil, subscription: nil)
|
21
|
+
def initialize(query:, mutation: nil, subscription: nil, max_depth: nil)
|
21
22
|
@query = query
|
22
23
|
@mutation = mutation
|
23
24
|
@subscription = subscription
|
25
|
+
@max_depth = max_depth
|
24
26
|
@directives = DIRECTIVES.reduce({}) { |m, d| m[d.name] = d; m }
|
25
27
|
@static_validator = GraphQL::StaticValidation::Validator.new(schema: self)
|
26
28
|
@rescue_middleware = GraphQL::Schema::RescueMiddleware.new
|
@@ -49,7 +51,7 @@ class GraphQL::Schema
|
|
49
51
|
# Resolve field named `field_name` for type `parent_type`.
|
50
52
|
# Handles dynamic fields `__typename`, `__type` and `__schema`, too
|
51
53
|
def get_field(parent_type, field_name)
|
52
|
-
defined_field = parent_type.
|
54
|
+
defined_field = parent_type.get_field(field_name)
|
53
55
|
if defined_field
|
54
56
|
defined_field
|
55
57
|
elsif field_name == "__typename"
|
@@ -48,7 +48,7 @@ module GraphQL
|
|
48
48
|
module TypeKindPrinters
|
49
49
|
module FieldPrinter
|
50
50
|
def print_fields(type)
|
51
|
-
type.
|
51
|
+
type.all_fields.map{ |field| " #{field.name}#{print_args(field)}: #{field.type}" }.join("\n")
|
52
52
|
end
|
53
53
|
|
54
54
|
def print_args(field)
|
@@ -57,7 +57,12 @@ module GraphQL
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def print_input_value(arg)
|
60
|
-
|
60
|
+
if arg.default_value.nil?
|
61
|
+
default_string = nil
|
62
|
+
else
|
63
|
+
default_string = " = #{print_value(arg.default_value, arg.type)}"
|
64
|
+
end
|
65
|
+
|
61
66
|
"#{arg.name}: #{arg.type.to_s}#{default_string}"
|
62
67
|
end
|
63
68
|
|
@@ -98,7 +103,11 @@ module GraphQL
|
|
98
103
|
class ObjectPrinter
|
99
104
|
extend FieldPrinter
|
100
105
|
def self.print(type)
|
101
|
-
|
106
|
+
if type.interfaces.any?
|
107
|
+
implementations = " implements #{type.interfaces.map(&:to_s).join(", ")}"
|
108
|
+
else
|
109
|
+
implementations = nil
|
110
|
+
end
|
102
111
|
"type #{type.name}#{implementations} {\n#{print_fields(type)}\n}"
|
103
112
|
end
|
104
113
|
end
|
@@ -30,7 +30,7 @@ class GraphQL::Schema::TypeReducer
|
|
30
30
|
def find_types(type, type_hash)
|
31
31
|
type_hash[type.name] = type
|
32
32
|
if type.kind.fields?
|
33
|
-
type.
|
33
|
+
type.all_fields.each do |field|
|
34
34
|
reduce_type(field.type, type_hash)
|
35
35
|
field.arguments.each do |name, argument|
|
36
36
|
reduce_type(argument.type, type_hash)
|
@@ -57,10 +57,11 @@ class GraphQL::Schema::TypeReducer
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def reduce_type(type, type_hash)
|
60
|
-
|
60
|
+
if type.is_a?(GraphQL::BaseType)
|
61
|
+
self.class.new(type.unwrap, type_hash).result
|
62
|
+
else
|
61
63
|
raise GraphQL::Schema::InvalidTypeError.new(type, ["Must be a GraphQL::BaseType"])
|
62
64
|
end
|
63
|
-
self.class.new(type.unwrap, type_hash).result
|
64
65
|
end
|
65
66
|
|
66
67
|
def validate_type(type)
|
@@ -5,6 +5,7 @@ require 'graphql/static_validation/message'
|
|
5
5
|
require 'graphql/static_validation/arguments_validator'
|
6
6
|
require 'graphql/static_validation/type_stack'
|
7
7
|
require 'graphql/static_validation/validator'
|
8
|
+
require 'graphql/static_validation/validation_context'
|
8
9
|
require 'graphql/static_validation/literal_validator'
|
9
10
|
|
10
11
|
rules_glob = File.expand_path("../static_validation/rules/*.rb", __FILE__)
|
@@ -20,4 +20,5 @@ GraphQL::StaticValidation::ALL_RULES = [
|
|
20
20
|
GraphQL::StaticValidation::VariableDefaultValuesAreCorrectlyTyped,
|
21
21
|
GraphQL::StaticValidation::VariablesAreUsedAndDefined,
|
22
22
|
GraphQL::StaticValidation::VariableUsagesAreAllowed,
|
23
|
+
GraphQL::StaticValidation::DocumentDoesNotExceedMaxDepth,
|
23
24
|
]
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module GraphQL
|
2
|
+
module StaticValidation
|
3
|
+
class DocumentDoesNotExceedMaxDepth
|
4
|
+
include GraphQL::StaticValidation::Message::MessageHelper
|
5
|
+
|
6
|
+
def validate(context)
|
7
|
+
max_allowed_depth = context.schema.max_depth
|
8
|
+
return if max_allowed_depth.nil?
|
9
|
+
|
10
|
+
visitor = context.visitor
|
11
|
+
|
12
|
+
# operation or fragment name
|
13
|
+
current_field_scope = nil
|
14
|
+
current_depth = 0
|
15
|
+
skip_current_scope = false
|
16
|
+
|
17
|
+
# {name => depth} pairs for operations and fragments
|
18
|
+
depths = Hash.new { |h, k| h[k] = 0 }
|
19
|
+
|
20
|
+
# {name => [fragmentName...]} pairs
|
21
|
+
fragments = Hash.new { |h, k| h[k] = []}
|
22
|
+
|
23
|
+
visitor[GraphQL::Language::Nodes::Document].leave << -> (node, parent) {
|
24
|
+
context.errors.none? && assert_under_max_depth(context, max_allowed_depth, depths, fragments)
|
25
|
+
}
|
26
|
+
|
27
|
+
visitor[GraphQL::Language::Nodes::OperationDefinition] << -> (node, parent) {
|
28
|
+
current_field_scope = node.name
|
29
|
+
}
|
30
|
+
|
31
|
+
visitor[GraphQL::Language::Nodes::FragmentDefinition] << -> (node, parent) {
|
32
|
+
current_field_scope = node.name
|
33
|
+
}
|
34
|
+
|
35
|
+
visitor[GraphQL::Language::Nodes::Field] << -> (node, parent) {
|
36
|
+
# Don't validate queries on __schema, __type
|
37
|
+
skip_current_scope ||= context.skip_field?(node.name)
|
38
|
+
|
39
|
+
if node.selections.any? && !skip_current_scope
|
40
|
+
current_depth += 1
|
41
|
+
if current_depth > depths[current_field_scope]
|
42
|
+
depths[current_field_scope] = current_depth
|
43
|
+
end
|
44
|
+
end
|
45
|
+
}
|
46
|
+
|
47
|
+
visitor[GraphQL::Language::Nodes::Field].leave << -> (node, parent) {
|
48
|
+
if skip_current_scope && context.skip_field?(node.name)
|
49
|
+
skip_current_scope = false
|
50
|
+
elsif node.selections.any?
|
51
|
+
current_depth -= 1
|
52
|
+
end
|
53
|
+
}
|
54
|
+
|
55
|
+
visitor [GraphQL::Language::Nodes::FragmentSpread] << -> (node, parent) {
|
56
|
+
fragments[current_field_scope] << node.name
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def assert_under_max_depth(context, max_allowed_depth, depths, fragments)
|
63
|
+
context.operations.each do |op_name, operation|
|
64
|
+
op_depth = get_total_depth(op_name, depths, fragments)
|
65
|
+
if op_depth > max_allowed_depth
|
66
|
+
op_name ||= "operation"
|
67
|
+
context.errors << message("#{op_name} has depth of #{op_depth}, which exceeds max depth of #{max_allowed_depth}", operation)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Get the total depth of a given fragment or operation
|
73
|
+
def get_total_depth(scope_name, depths, fragments)
|
74
|
+
own_fragments = fragments[scope_name]
|
75
|
+
depths[scope_name] + own_fragments.reduce(0) { |memo, frag_name| memo + get_total_depth(frag_name, depths, fragments) }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|