graphql 0.15.3 → 0.16.0
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.rb +4 -1
- data/lib/graphql/analysis.rb +5 -0
- data/lib/graphql/analysis/analyze_query.rb +73 -0
- data/lib/graphql/analysis/max_query_complexity.rb +25 -0
- data/lib/graphql/analysis/max_query_depth.rb +25 -0
- data/lib/graphql/analysis/query_complexity.rb +122 -0
- data/lib/graphql/analysis/query_depth.rb +54 -0
- data/lib/graphql/analysis_error.rb +4 -0
- data/lib/graphql/base_type.rb +7 -0
- data/lib/graphql/define/assign_object_field.rb +2 -1
- data/lib/graphql/field.rb +25 -3
- data/lib/graphql/input_object_type.rb +1 -1
- data/lib/graphql/internal_representation.rb +2 -0
- data/lib/graphql/internal_representation/node.rb +81 -0
- data/lib/graphql/internal_representation/rewrite.rb +177 -0
- data/lib/graphql/language/visitor.rb +15 -9
- data/lib/graphql/object_type.rb +1 -1
- data/lib/graphql/query.rb +66 -7
- data/lib/graphql/query/context.rb +10 -3
- data/lib/graphql/query/directive_resolution.rb +5 -5
- data/lib/graphql/query/serial_execution.rb +5 -3
- data/lib/graphql/query/serial_execution/field_resolution.rb +22 -15
- data/lib/graphql/query/serial_execution/operation_resolution.rb +7 -5
- data/lib/graphql/query/serial_execution/selection_resolution.rb +20 -105
- data/lib/graphql/query/serial_execution/value_resolution.rb +15 -12
- data/lib/graphql/schema.rb +7 -2
- data/lib/graphql/schema/timeout_middleware.rb +67 -0
- data/lib/graphql/static_validation/all_rules.rb +0 -1
- data/lib/graphql/static_validation/type_stack.rb +7 -11
- data/lib/graphql/static_validation/validation_context.rb +11 -1
- data/lib/graphql/static_validation/validator.rb +14 -4
- data/lib/graphql/version.rb +1 -1
- data/readme.md +10 -9
- data/spec/graphql/analysis/analyze_query_spec.rb +50 -0
- data/spec/graphql/analysis/max_query_complexity_spec.rb +62 -0
- data/spec/graphql/{static_validation/rules/document_does_not_exceed_max_depth_spec.rb → analysis/max_query_depth_spec.rb} +20 -21
- data/spec/graphql/analysis/query_complexity_spec.rb +235 -0
- data/spec/graphql/analysis/query_depth_spec.rb +80 -0
- data/spec/graphql/directive_spec.rb +1 -0
- data/spec/graphql/internal_representation/rewrite_spec.rb +120 -0
- data/spec/graphql/introspection/schema_type_spec.rb +1 -0
- data/spec/graphql/language/visitor_spec.rb +14 -4
- data/spec/graphql/non_null_type_spec.rb +31 -0
- data/spec/graphql/query/context_spec.rb +24 -1
- data/spec/graphql/query_spec.rb +6 -2
- data/spec/graphql/schema/timeout_middleware_spec.rb +180 -0
- data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/arguments_are_defined_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/directives_are_defined_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/directives_are_in_valid_locations_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/fields_are_defined_on_type_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/fields_have_appropriate_selections_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/fields_will_merge_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/fragment_spreads_are_possible_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/fragment_types_exist_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/fragments_are_finite_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/fragments_are_on_composite_types_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/required_arguments_are_present_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/variable_usages_are_allowed_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/variables_are_input_types_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/variables_are_used_and_defined_spec.rb +1 -1
- data/spec/graphql/static_validation/validator_spec.rb +1 -1
- data/spec/support/dairy_app.rb +22 -1
- metadata +29 -5
- data/lib/graphql/static_validation/rules/document_does_not_exceed_max_depth.rb +0 -79
@@ -0,0 +1,81 @@
|
|
1
|
+
require "set"
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module InternalRepresentation
|
5
|
+
class Node
|
6
|
+
def initialize(ast_node:, return_type: nil, on_types: Set.new, name: nil, definition: nil, children: {}, spreads: [], directives: Set.new)
|
7
|
+
@ast_node = ast_node
|
8
|
+
@return_type = return_type
|
9
|
+
@on_types = on_types
|
10
|
+
@name = name
|
11
|
+
@definition = definition
|
12
|
+
@children = children
|
13
|
+
@spreads = spreads
|
14
|
+
@directives = directives
|
15
|
+
end
|
16
|
+
|
17
|
+
# Note: by the time this gets out of the Rewrite phase, this will be empty -- it's emptied out when fragments are merged back in
|
18
|
+
# @return [Array<GraphQL::Language::Nodes::FragmentSpreads>] Fragment names that were spread in this node
|
19
|
+
attr_reader :spreads
|
20
|
+
|
21
|
+
# These are the compiled directives from fragment spreads, inline fragments, and the field itself
|
22
|
+
# @return [Set<GraphQL::Language::Nodes::Directive>]
|
23
|
+
attr_reader :directives
|
24
|
+
|
25
|
+
# @return [GraphQL::Field, GraphQL::Directive] The definition to use to execute this node
|
26
|
+
attr_reader :definition
|
27
|
+
|
28
|
+
# @return [String] the name to use for the result in the response hash
|
29
|
+
attr_reader :name
|
30
|
+
|
31
|
+
# @return [GraphQL::Language::Nodes::AbstractNode] The AST node (or one of the nodes) where this was derived from
|
32
|
+
attr_reader :ast_node
|
33
|
+
|
34
|
+
# This may come from the previous field's return value or an explicitly-typed fragment
|
35
|
+
# @example On-type from previous return value
|
36
|
+
# {
|
37
|
+
# person(id: 1) {
|
38
|
+
# firstName # => on_type is person
|
39
|
+
# }
|
40
|
+
# }
|
41
|
+
# @example On-type from explicit type condition
|
42
|
+
# {
|
43
|
+
# node(id: $nodeId) {
|
44
|
+
# ... on Nameable {
|
45
|
+
# firstName # => on_type is Nameable
|
46
|
+
# }
|
47
|
+
# }
|
48
|
+
# }
|
49
|
+
# @return [Set<GraphQL::ObjectType, GraphQL::InterfaceType>] the types this field applies to
|
50
|
+
attr_reader :on_types
|
51
|
+
|
52
|
+
# @return [GraphQL::BaseType]
|
53
|
+
attr_reader :return_type
|
54
|
+
|
55
|
+
# @return [Array<GraphQL::Query::Node>]
|
56
|
+
attr_reader :children
|
57
|
+
|
58
|
+
def inspect(indent = 0)
|
59
|
+
own_indent = " " * indent
|
60
|
+
self_inspect = "#{own_indent}<Node #{name} (#{definition ? definition.name + ": " : ""}{#{on_types.to_a.join("|")}} -> #{return_type})>"
|
61
|
+
if children.any?
|
62
|
+
self_inspect << " {\n#{children.values.map { |n| n.inspect(indent + 2 )}.join("\n")}\n#{own_indent}}"
|
63
|
+
end
|
64
|
+
self_inspect
|
65
|
+
end
|
66
|
+
|
67
|
+
def dup
|
68
|
+
self.class.new({
|
69
|
+
ast_node: ast_node,
|
70
|
+
return_type: return_type,
|
71
|
+
on_types: on_types,
|
72
|
+
name: name,
|
73
|
+
definition: definition,
|
74
|
+
children: children,
|
75
|
+
spreads: spreads,
|
76
|
+
directives: directives,
|
77
|
+
})
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
module GraphQL
|
2
|
+
module InternalRepresentation
|
3
|
+
# Convert an AST into a tree of {InternalRepresentation::Node}s
|
4
|
+
#
|
5
|
+
# This rides along with {StaticValidation}, building a tree of nodes.
|
6
|
+
#
|
7
|
+
# However, if any errors occurred during validation, the resulting tree is bogus.
|
8
|
+
# (For example, `nil` could have been pushed instead of a type.)
|
9
|
+
class Rewrite
|
10
|
+
include GraphQL::Language
|
11
|
+
|
12
|
+
# @return [Hash<String => InternalRepresentation::Node>] internal representation of each query root (operation, fragment)
|
13
|
+
attr_reader :operations
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
# { String => Node } Tracks the roots of the query
|
17
|
+
@operations = {}
|
18
|
+
@fragments = {}
|
19
|
+
# [String...] fragments which don't have fragments inside them
|
20
|
+
@independent_fragments = []
|
21
|
+
# Tracks the current node during traversal
|
22
|
+
# Stack<InternalRepresentation::Node>
|
23
|
+
@nodes = []
|
24
|
+
# This tracks dependencies from fragment to Node where it was used
|
25
|
+
# { frag_name => [dependent_node, dependent_node]}
|
26
|
+
@fragment_spreads = Hash.new { |h, k| h[k] = []}
|
27
|
+
# [Nodes::Directive ... ] directive affecting the current scope
|
28
|
+
@parent_directives = []
|
29
|
+
end
|
30
|
+
|
31
|
+
def validate(context)
|
32
|
+
visitor = context.visitor
|
33
|
+
|
34
|
+
visitor[Nodes::OperationDefinition].enter << -> (ast_node, prev_ast_node) {
|
35
|
+
node = Node.new(
|
36
|
+
return_type: context.type_definition.unwrap,
|
37
|
+
ast_node: ast_node,
|
38
|
+
)
|
39
|
+
@nodes.push(node)
|
40
|
+
@operations[ast_node.name] = node
|
41
|
+
}
|
42
|
+
|
43
|
+
visitor[Nodes::Field].enter << -> (ast_node, prev_ast_node) {
|
44
|
+
parent_node = @nodes.last
|
45
|
+
node_name = ast_node.alias || ast_node.name
|
46
|
+
# This node might not be novel, eg inside an inline fragment
|
47
|
+
# but it could contain new type information, which is captured below.
|
48
|
+
# (StaticValidation ensures that merging fields is fair game)
|
49
|
+
node = parent_node.children[node_name] ||= begin
|
50
|
+
Node.new(
|
51
|
+
return_type: context.type_definition && context.type_definition.unwrap,
|
52
|
+
ast_node: ast_node,
|
53
|
+
name: node_name,
|
54
|
+
definition: context.field_definition,
|
55
|
+
)
|
56
|
+
end
|
57
|
+
node.on_types.add(context.parent_type_definition.unwrap)
|
58
|
+
@nodes.push(node)
|
59
|
+
@parent_directives.push([])
|
60
|
+
}
|
61
|
+
|
62
|
+
visitor[Nodes::InlineFragment].enter << -> (ast_node, prev_ast_node) {
|
63
|
+
@parent_directives.push([])
|
64
|
+
}
|
65
|
+
|
66
|
+
visitor[Nodes::Directive].enter << -> (ast_node, prev_ast_node) {
|
67
|
+
# It could be a query error where a directive is somewhere it shouldn't be
|
68
|
+
if @parent_directives.any?
|
69
|
+
@parent_directives.last << Node.new(
|
70
|
+
name: ast_node.name,
|
71
|
+
ast_node: ast_node,
|
72
|
+
definition: context.directive_definition,
|
73
|
+
)
|
74
|
+
end
|
75
|
+
}
|
76
|
+
|
77
|
+
visitor[Nodes::FragmentSpread].enter << -> (ast_node, prev_ast_node) {
|
78
|
+
# Record _both sides_ of the dependency
|
79
|
+
spread_node = Node.new(
|
80
|
+
name: ast_node.name,
|
81
|
+
ast_node: ast_node,
|
82
|
+
)
|
83
|
+
parent_node = @nodes.last
|
84
|
+
# The parent node has a reference to the fragment
|
85
|
+
parent_node.spreads.push(spread_node)
|
86
|
+
# And keep a reference from the fragment to the parent node
|
87
|
+
@fragment_spreads[ast_node.name].push(parent_node)
|
88
|
+
@nodes.push(spread_node)
|
89
|
+
@parent_directives.push([])
|
90
|
+
}
|
91
|
+
|
92
|
+
visitor[Nodes::FragmentDefinition].enter << -> (ast_node, prev_ast_node) {
|
93
|
+
node = Node.new(
|
94
|
+
name: ast_node.name,
|
95
|
+
return_type: context.type_definition,
|
96
|
+
ast_node: ast_node,
|
97
|
+
)
|
98
|
+
@nodes.push(node)
|
99
|
+
@fragments[ast_node.name] = node
|
100
|
+
}
|
101
|
+
|
102
|
+
visitor[Nodes::InlineFragment].leave << -> (ast_node, prev_ast_node) {
|
103
|
+
@parent_directives.pop
|
104
|
+
}
|
105
|
+
|
106
|
+
visitor[Nodes::FragmentSpread].leave << -> (ast_node, prev_ast_node) {
|
107
|
+
# Capture any directives that apply to this spread
|
108
|
+
# so that they can be applied to fields when
|
109
|
+
# the fragment is merged in later
|
110
|
+
spread_node = @nodes.pop
|
111
|
+
spread_node.directives.merge(@parent_directives.flatten)
|
112
|
+
@parent_directives.pop
|
113
|
+
}
|
114
|
+
|
115
|
+
visitor[Nodes::FragmentDefinition].leave << -> (ast_node, prev_ast_node) {
|
116
|
+
# This fragment doesn't depend on any others,
|
117
|
+
# we should save it as the starting point for dependency resolution
|
118
|
+
frag_node = @nodes.pop
|
119
|
+
if frag_node.spreads.none?
|
120
|
+
@independent_fragments << frag_node
|
121
|
+
end
|
122
|
+
}
|
123
|
+
|
124
|
+
visitor[Nodes::OperationDefinition].leave << -> (ast_node, prev_ast_node) {
|
125
|
+
@nodes.pop
|
126
|
+
}
|
127
|
+
|
128
|
+
visitor[Nodes::Field].leave << -> (ast_node, prev_ast_node) {
|
129
|
+
# Pop this field's node
|
130
|
+
# and record any directives that were visited
|
131
|
+
# during this field & before it (eg, inline fragments)
|
132
|
+
field_node = @nodes.pop
|
133
|
+
field_node.directives.merge(@parent_directives.flatten)
|
134
|
+
@parent_directives.pop
|
135
|
+
}
|
136
|
+
|
137
|
+
visitor[Nodes::Document].leave << -> (ast_node, prev_ast_node) {
|
138
|
+
# Resolve fragment dependencies. Start with fragments with no
|
139
|
+
# dependencies and work along the spreads.
|
140
|
+
while fragment_node = @independent_fragments.pop
|
141
|
+
fragment_usages = @fragment_spreads[fragment_node.name]
|
142
|
+
while dependent_node = fragment_usages.pop
|
143
|
+
# remove self from dependent_node.spreads
|
144
|
+
rejected_spread_nodes = dependent_node.spreads.select { |spr| spr.name == fragment_node.name }
|
145
|
+
rejected_spread_nodes.each { |r_node| dependent_node.spreads.delete(r_node) }
|
146
|
+
|
147
|
+
# resolve the dependency (merge into dependent node)
|
148
|
+
deep_merge(dependent_node, fragment_node, rejected_spread_nodes.first.directives)
|
149
|
+
|
150
|
+
if dependent_node.spreads.none? && dependent_node.ast_node.is_a?(Nodes::FragmentDefinition)
|
151
|
+
@independent_fragments.push(dependent_node)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
}
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
|
160
|
+
# Merge the chilren from `fragment_node` into `parent_node`. Merge `directives` into each of those fields.
|
161
|
+
def deep_merge(parent_node, fragment_node, directives)
|
162
|
+
fragment_node.children.each do |name, child_node|
|
163
|
+
deep_merge_child(parent_node, name, child_node, directives)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Merge `node` into `parent_node`'s children, as `name`, applying `extra_directives`
|
168
|
+
def deep_merge_child(parent_node, name, node, extra_directives)
|
169
|
+
child_node = parent_node.children[name] ||= node.dup
|
170
|
+
node.children.each do |merge_child_name, merge_child_node|
|
171
|
+
deep_merge_child(child_node, merge_child_name, merge_child_node, [])
|
172
|
+
end
|
173
|
+
child_node.directives.merge(extra_directives)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -4,12 +4,12 @@ module GraphQL
|
|
4
4
|
#
|
5
5
|
# @example Create a visitor, add hooks, then search a document
|
6
6
|
# total_field_count = 0
|
7
|
-
# visitor = GraphQL::Language::Visitor.new
|
7
|
+
# visitor = GraphQL::Language::Visitor.new(document)
|
8
8
|
# # Whenever you find a field, increment the field count:
|
9
9
|
# visitor[GraphQL::Language::Nodes::Field] << -> (node) { total_field_count += 1 }
|
10
10
|
# # When we finish, print the field count:
|
11
11
|
# visitor[GraphQL::Language::Nodes::Document].leave << -> (node) { p total_field_count }
|
12
|
-
# visitor.visit
|
12
|
+
# visitor.visit
|
13
13
|
# # => 6
|
14
14
|
#
|
15
15
|
class Visitor
|
@@ -22,7 +22,8 @@ module GraphQL
|
|
22
22
|
# @return [Array<Proc>] Hooks to call when leaving _any_ node
|
23
23
|
attr_reader :leave
|
24
24
|
|
25
|
-
def initialize
|
25
|
+
def initialize(document)
|
26
|
+
@document = document
|
26
27
|
@visitors = {}
|
27
28
|
@enter = []
|
28
29
|
@leave = []
|
@@ -38,17 +39,22 @@ module GraphQL
|
|
38
39
|
@visitors[node_class] ||= NodeVisitor.new
|
39
40
|
end
|
40
41
|
|
41
|
-
# Visit `
|
42
|
-
# @param root [GraphQL::Language::Nodes::AbstractNode] some node to start parsing on
|
42
|
+
# Visit `document` and all children, applying hooks as you go
|
43
43
|
# @return [void]
|
44
|
-
def visit
|
45
|
-
|
46
|
-
root.children.reduce(true) { |memo, child| memo && visit(child, root) }
|
47
|
-
end_visit(root, parent)
|
44
|
+
def visit
|
45
|
+
visit_node(@document, nil)
|
48
46
|
end
|
49
47
|
|
50
48
|
private
|
51
49
|
|
50
|
+
def visit_node(node, parent)
|
51
|
+
begin_hooks_ok = begin_visit(node, parent)
|
52
|
+
if begin_hooks_ok
|
53
|
+
node.children.reduce(true) { |memo, child| memo && visit_node(child, node) }
|
54
|
+
end
|
55
|
+
end_visit(node, parent)
|
56
|
+
end
|
57
|
+
|
52
58
|
def begin_visit(node, parent)
|
53
59
|
self.class.apply_hooks(enter, node, parent)
|
54
60
|
node_visitor = self[node.class]
|
data/lib/graphql/object_type.rb
CHANGED
@@ -24,7 +24,7 @@ module GraphQL
|
|
24
24
|
accepts_definitions :interfaces, field: GraphQL::Define::AssignObjectField
|
25
25
|
attr_accessor :name, :description
|
26
26
|
|
27
|
-
# @return [Hash<String
|
27
|
+
# @return [Hash<String => GraphQL::Field>] Map String fieldnames to their {GraphQL::Field} implementations
|
28
28
|
attr_accessor :fields
|
29
29
|
|
30
30
|
def initialize
|
data/lib/graphql/query.rb
CHANGED
@@ -8,7 +8,7 @@ module GraphQL
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
attr_reader :schema, :document, :context, :fragments, :operations, :root_value, :max_depth
|
11
|
+
attr_reader :schema, :document, :context, :fragments, :operations, :root_value, :max_depth, :query_string
|
12
12
|
|
13
13
|
# Prepare query `query_string` on `schema`
|
14
14
|
# @param schema [GraphQL::Schema]
|
@@ -18,11 +18,21 @@ module GraphQL
|
|
18
18
|
# @param validate [Boolean] if true, `query_string` will be validated with {StaticValidation::Validator}
|
19
19
|
# @param operation_name [String] if the query string contains many operations, this is the one which should be executed
|
20
20
|
# @param root_value [Object] the object used to resolve fields on the root type
|
21
|
-
|
21
|
+
# @param max_depth [Numeric] the maximum number of nested selections allowed for this query (falls back to schema-level value)
|
22
|
+
# @param max_complexity [Numeric] the maximum field complexity for this query (falls back to schema-level value)
|
23
|
+
def initialize(schema, query_string = nil, document: nil, context: nil, variables: {}, validate: true, operation_name: nil, root_value: nil, max_depth: nil, max_complexity: nil)
|
22
24
|
fail ArgumentError, "a query string or document is required" unless query_string || document
|
23
25
|
|
24
26
|
@schema = schema
|
25
27
|
@max_depth = max_depth || schema.max_depth
|
28
|
+
@max_complexity = max_complexity || schema.max_complexity
|
29
|
+
@query_analyzers = schema.query_analyzers.dup
|
30
|
+
if @max_depth
|
31
|
+
@query_analyzers << GraphQL::Analysis::MaxQueryDepth.new(@max_depth)
|
32
|
+
end
|
33
|
+
if @max_complexity
|
34
|
+
@query_analyzers << GraphQL::Analysis::MaxQueryComplexity.new(@max_complexity)
|
35
|
+
end
|
26
36
|
@context = Context.new(query: self, values: context)
|
27
37
|
@root_value = root_value
|
28
38
|
@validate = validate
|
@@ -30,6 +40,7 @@ module GraphQL
|
|
30
40
|
@fragments = {}
|
31
41
|
@operations = {}
|
32
42
|
@provided_variables = variables
|
43
|
+
@query_string = query_string
|
33
44
|
|
34
45
|
@document = document || GraphQL.parse(query_string)
|
35
46
|
@document.definitions.each do |part|
|
@@ -39,15 +50,20 @@ module GraphQL
|
|
39
50
|
@operations[part.name] = part
|
40
51
|
end
|
41
52
|
end
|
53
|
+
|
54
|
+
@arguments_cache = {}
|
42
55
|
end
|
43
56
|
|
44
57
|
# Get the result for this query, executing it once
|
45
58
|
def result
|
46
|
-
|
47
|
-
|
59
|
+
@result ||= begin
|
60
|
+
if @validate && (validation_errors.any? || analysis_errors.any?)
|
61
|
+
{ "errors" => validation_errors + analysis_errors}
|
62
|
+
else
|
63
|
+
Executor.new(self).result
|
64
|
+
end
|
48
65
|
end
|
49
66
|
|
50
|
-
@result ||= Executor.new(self).result
|
51
67
|
end
|
52
68
|
|
53
69
|
|
@@ -71,10 +87,39 @@ module GraphQL
|
|
71
87
|
)
|
72
88
|
end
|
73
89
|
|
74
|
-
|
90
|
+
def internal_representation
|
91
|
+
@internal_representation ||= begin
|
92
|
+
perform_validation
|
93
|
+
@internal_representation
|
94
|
+
end
|
95
|
+
end
|
75
96
|
|
76
97
|
def validation_errors
|
77
|
-
@validation_errors ||=
|
98
|
+
@validation_errors ||= begin
|
99
|
+
perform_validation
|
100
|
+
@validation_errors
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Node-level cache for calculating arguments. Used during execution and query analysis.
|
105
|
+
# @return [GraphQL::Query::Arguments] Arguments for this node, merging default values, literal values and query variables
|
106
|
+
def arguments_for(irep_node)
|
107
|
+
@arguments_cache[irep_node] ||= begin
|
108
|
+
GraphQL::Query::LiteralInput.from_arguments(
|
109
|
+
irep_node.ast_node.arguments,
|
110
|
+
irep_node.definition.arguments,
|
111
|
+
self.variables
|
112
|
+
)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def perform_validation
|
119
|
+
validation_result = schema.static_validator.validate(self)
|
120
|
+
@validation_errors = validation_result[:errors]
|
121
|
+
@internal_representation = validation_result[:irep]
|
122
|
+
nil
|
78
123
|
end
|
79
124
|
|
80
125
|
|
@@ -89,6 +134,20 @@ module GraphQL
|
|
89
134
|
operations[operation_name]
|
90
135
|
end
|
91
136
|
end
|
137
|
+
|
138
|
+
def analysis_errors
|
139
|
+
@analysis_errors ||= begin
|
140
|
+
if validation_errors.any?
|
141
|
+
# Can't reduce an invalid query
|
142
|
+
[]
|
143
|
+
elsif @query_analyzers.any?
|
144
|
+
reduce_results = GraphQL::Analysis.analyze_query(self, @query_analyzers)
|
145
|
+
reduce_results.select { |r| r.is_a?(GraphQL::AnalysisError) }.map(&:to_h)
|
146
|
+
else
|
147
|
+
[]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
92
151
|
end
|
93
152
|
end
|
94
153
|
|