graphql 2.3.5 → 2.3.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/analyzer.rb +89 -0
  3. data/lib/graphql/analysis/field_usage.rb +82 -0
  4. data/lib/graphql/analysis/max_query_complexity.rb +20 -0
  5. data/lib/graphql/analysis/max_query_depth.rb +20 -0
  6. data/lib/graphql/analysis/query_complexity.rb +183 -0
  7. data/lib/graphql/analysis/{ast/query_depth.rb → query_depth.rb} +23 -25
  8. data/lib/graphql/analysis/visitor.rb +283 -0
  9. data/lib/graphql/analysis.rb +92 -1
  10. data/lib/graphql/dataloader/async_dataloader.rb +2 -0
  11. data/lib/graphql/execution/interpreter/runtime.rb +6 -6
  12. data/lib/graphql/execution/interpreter.rb +1 -1
  13. data/lib/graphql/execution/lookahead.rb +10 -10
  14. data/lib/graphql/introspection/directive_type.rb +1 -1
  15. data/lib/graphql/introspection/entry_points.rb +2 -2
  16. data/lib/graphql/introspection/field_type.rb +1 -1
  17. data/lib/graphql/introspection/schema_type.rb +13 -3
  18. data/lib/graphql/introspection/type_type.rb +5 -5
  19. data/lib/graphql/language/document_from_schema_definition.rb +19 -26
  20. data/lib/graphql/language/lexer.rb +0 -3
  21. data/lib/graphql/language/sanitized_printer.rb +1 -1
  22. data/lib/graphql/language.rb +0 -1
  23. data/lib/graphql/query/context.rb +4 -0
  24. data/lib/graphql/query/null_context.rb +4 -0
  25. data/lib/graphql/query/validation_pipeline.rb +2 -2
  26. data/lib/graphql/query.rb +26 -3
  27. data/lib/graphql/schema/always_visible.rb +1 -0
  28. data/lib/graphql/schema/argument.rb +19 -5
  29. data/lib/graphql/schema/directive.rb +2 -0
  30. data/lib/graphql/schema/enum.rb +4 -4
  31. data/lib/graphql/schema/field.rb +13 -1
  32. data/lib/graphql/schema/has_single_input_argument.rb +2 -1
  33. data/lib/graphql/schema/input_object.rb +8 -7
  34. data/lib/graphql/schema/introspection_system.rb +2 -14
  35. data/lib/graphql/schema/member/has_arguments.rb +7 -6
  36. data/lib/graphql/schema/member/has_fields.rb +6 -4
  37. data/lib/graphql/schema/resolver.rb +5 -5
  38. data/lib/graphql/schema/subset.rb +397 -0
  39. data/lib/graphql/schema/type_expression.rb +2 -2
  40. data/lib/graphql/schema/validator/all_validator.rb +60 -0
  41. data/lib/graphql/schema/validator.rb +2 -0
  42. data/lib/graphql/schema/warden.rb +88 -1
  43. data/lib/graphql/schema.rb +44 -15
  44. data/lib/graphql/static_validation/base_visitor.rb +6 -5
  45. data/lib/graphql/static_validation/literal_validator.rb +4 -4
  46. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  47. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
  48. data/lib/graphql/static_validation/rules/directives_are_defined.rb +1 -2
  49. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +1 -1
  50. data/lib/graphql/static_validation/rules/fields_will_merge.rb +7 -7
  51. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  52. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +1 -1
  53. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  54. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  55. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  56. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +3 -3
  57. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +3 -3
  58. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +1 -1
  59. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +1 -1
  60. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
  61. data/lib/graphql/static_validation/validation_context.rb +2 -2
  62. data/lib/graphql/subscriptions/broadcast_analyzer.rb +11 -5
  63. data/lib/graphql/subscriptions/event.rb +1 -1
  64. data/lib/graphql/subscriptions.rb +3 -3
  65. data/lib/graphql/testing/helpers.rb +2 -2
  66. data/lib/graphql/types/relay/connection_behaviors.rb +10 -0
  67. data/lib/graphql/types/relay/edge_behaviors.rb +10 -0
  68. data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
  69. data/lib/graphql/version.rb +1 -1
  70. metadata +12 -13
  71. data/lib/graphql/analysis/ast/analyzer.rb +0 -91
  72. data/lib/graphql/analysis/ast/field_usage.rb +0 -84
  73. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -22
  74. data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
  75. data/lib/graphql/analysis/ast/query_complexity.rb +0 -185
  76. data/lib/graphql/analysis/ast/visitor.rb +0 -284
  77. data/lib/graphql/analysis/ast.rb +0 -94
  78. data/lib/graphql/language/token.rb +0 -34
  79. data/lib/graphql/schema/invalid_type_error.rb +0 -7
@@ -1,91 +0,0 @@
1
- # frozen_string_literal: true
2
- module GraphQL
3
- module Analysis
4
- module AST
5
- # Query analyzer for query ASTs. Query analyzers respond to visitor style methods
6
- # but are prefixed by `enter` and `leave`.
7
- #
8
- # When an analyzer is initialized with a Multiplex, you can always get the current query from
9
- # `visitor.query` in the visit methods.
10
- #
11
- # @param [GraphQL::Query, GraphQL::Execution::Multiplex] The query or multiplex to analyze
12
- class Analyzer
13
- def initialize(subject)
14
- @subject = subject
15
-
16
- if subject.is_a?(GraphQL::Query)
17
- @query = subject
18
- @multiplex = nil
19
- else
20
- @multiplex = subject
21
- @query = nil
22
- end
23
- end
24
-
25
- # Analyzer hook to decide at analysis time whether a query should
26
- # be analyzed or not.
27
- # @return [Boolean] If the query should be analyzed or not
28
- def analyze?
29
- true
30
- end
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
-
39
- # The result for this analyzer. Returning {GraphQL::AnalysisError} results
40
- # in a query error.
41
- # @return [Any] The analyzer result
42
- def result
43
- raise GraphQL::RequiredImplementationMissingError
44
- end
45
-
46
- class << self
47
- private
48
-
49
- def build_visitor_hooks(member_name)
50
- class_eval(<<-EOS, __FILE__, __LINE__ + 1)
51
- def on_enter_#{member_name}(node, parent, visitor)
52
- end
53
-
54
- def on_leave_#{member_name}(node, parent, visitor)
55
- end
56
- EOS
57
- end
58
- end
59
-
60
- build_visitor_hooks :argument
61
- build_visitor_hooks :directive
62
- build_visitor_hooks :document
63
- build_visitor_hooks :enum
64
- build_visitor_hooks :field
65
- build_visitor_hooks :fragment_spread
66
- build_visitor_hooks :inline_fragment
67
- build_visitor_hooks :input_object
68
- build_visitor_hooks :list_type
69
- build_visitor_hooks :non_null_type
70
- build_visitor_hooks :null_value
71
- build_visitor_hooks :operation_definition
72
- build_visitor_hooks :type_name
73
- build_visitor_hooks :variable_definition
74
- build_visitor_hooks :variable_identifier
75
- build_visitor_hooks :abstract_node
76
-
77
- protected
78
-
79
- # @return [GraphQL::Query, GraphQL::Execution::Multiplex] Whatever this analyzer is analyzing
80
- attr_reader :subject
81
-
82
- # @return [GraphQL::Query, nil] `nil` if this analyzer is visiting a multiplex
83
- # (When this is `nil`, use `visitor.query` inside visit methods to get the current query)
84
- attr_reader :query
85
-
86
- # @return [GraphQL::Execution::Multiplex, nil] `nil` if this analyzer is visiting a query
87
- attr_reader :multiplex
88
- end
89
- end
90
- end
91
- end
@@ -1,84 +0,0 @@
1
- # frozen_string_literal: true
2
- module GraphQL
3
- module Analysis
4
- module AST
5
- class FieldUsage < Analyzer
6
- def initialize(query)
7
- super
8
- @used_fields = Set.new
9
- @used_deprecated_fields = Set.new
10
- @used_deprecated_arguments = Set.new
11
- @used_deprecated_enum_values = Set.new
12
- end
13
-
14
- def on_leave_field(node, parent, visitor)
15
- field_defn = visitor.field_definition
16
- field = "#{visitor.parent_type_definition.graphql_name}.#{field_defn.graphql_name}"
17
- @used_fields << field
18
- @used_deprecated_fields << field if field_defn.deprecation_reason
19
- arguments = visitor.query.arguments_for(node, field_defn)
20
- # If there was an error when preparing this argument object,
21
- # then this might be an error or something:
22
- if arguments.respond_to?(:argument_values)
23
- extract_deprecated_arguments(arguments.argument_values)
24
- end
25
- end
26
-
27
- def result
28
- {
29
- used_fields: @used_fields.to_a,
30
- used_deprecated_fields: @used_deprecated_fields.to_a,
31
- used_deprecated_arguments: @used_deprecated_arguments.to_a,
32
- used_deprecated_enum_values: @used_deprecated_enum_values.to_a,
33
- }
34
- end
35
-
36
- private
37
-
38
- def extract_deprecated_arguments(argument_values)
39
- argument_values.each_pair do |_argument_name, argument|
40
- if argument.definition.deprecation_reason
41
- @used_deprecated_arguments << argument.definition.path
42
- end
43
-
44
- arg_val = argument.value
45
-
46
- next if arg_val.nil?
47
-
48
- argument_type = argument.definition.type
49
- if argument_type.non_null?
50
- argument_type = argument_type.of_type
51
- end
52
-
53
- if argument_type.kind.input_object?
54
- extract_deprecated_arguments(argument.original_value.arguments.argument_values) # rubocop:disable Development/ContextIsPassedCop -- runtime args instance
55
- elsif argument_type.kind.enum?
56
- extract_deprecated_enum_value(argument_type, arg_val)
57
- elsif argument_type.list?
58
- inner_type = argument_type.unwrap
59
- case inner_type.kind
60
- when TypeKinds::INPUT_OBJECT
61
- argument.original_value.each do |value|
62
- extract_deprecated_arguments(value.arguments.argument_values) # rubocop:disable Development/ContextIsPassedCop -- runtime args instance
63
- end
64
- when TypeKinds::ENUM
65
- arg_val.each do |value|
66
- extract_deprecated_enum_value(inner_type, value)
67
- end
68
- else
69
- # Not a kind of input that we track
70
- end
71
- end
72
- end
73
- end
74
-
75
- def extract_deprecated_enum_value(enum_type, value)
76
- enum_value = @query.warden.enum_values(enum_type).find { |ev| ev.value == value }
77
- if enum_value&.deprecation_reason
78
- @used_deprecated_enum_values << enum_value.path
79
- end
80
- end
81
- end
82
- end
83
- end
84
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
- module GraphQL
3
- module Analysis
4
- module AST
5
- # Used under the hood to implement complexity validation,
6
- # see {Schema#max_complexity} and {Query#max_complexity}
7
- class MaxQueryComplexity < QueryComplexity
8
- def result
9
- return if subject.max_complexity.nil?
10
-
11
- total_complexity = max_possible_complexity
12
-
13
- if total_complexity > subject.max_complexity
14
- GraphQL::AnalysisError.new("Query has complexity of #{total_complexity}, which exceeds max complexity of #{subject.max_complexity}")
15
- else
16
- nil
17
- end
18
- end
19
- end
20
- end
21
- end
22
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
- module GraphQL
3
- module Analysis
4
- module AST
5
- class MaxQueryDepth < QueryDepth
6
- def result
7
- configured_max_depth = if query
8
- query.max_depth
9
- else
10
- multiplex.schema.max_depth
11
- end
12
-
13
- if configured_max_depth && @max_depth > configured_max_depth
14
- GraphQL::AnalysisError.new("Query has depth of #{@max_depth}, which exceeds max depth of #{configured_max_depth}")
15
- else
16
- nil
17
- end
18
- end
19
- end
20
- end
21
- end
22
- end
@@ -1,185 +0,0 @@
1
- # frozen_string_literal: true
2
- module GraphQL
3
- module Analysis
4
- # Calculate the complexity of a query, using {Field#complexity} values.
5
- module AST
6
- class QueryComplexity < Analyzer
7
- # State for the query complexity calculation:
8
- # - `complexities_on_type` holds complexity scores for each type
9
- def initialize(query)
10
- super
11
- @skip_introspection_fields = !query.schema.max_complexity_count_introspection_fields
12
- @complexities_on_type_by_query = {}
13
- end
14
-
15
- # Override this method to use the complexity result
16
- def result
17
- max_possible_complexity
18
- end
19
-
20
- # ScopedTypeComplexity models a tree of GraphQL types mapped to inner selections, ie:
21
- # Hash<GraphQL::BaseType, Hash<String, ScopedTypeComplexity>>
22
- class ScopedTypeComplexity < Hash
23
- # A proc for defaulting empty namespace requests as a new scope hash.
24
- DEFAULT_PROC = ->(h, k) { h[k] = {} }
25
-
26
- attr_reader :field_definition, :response_path, :query
27
-
28
- # @param parent_type [Class] The owner of `field_definition`
29
- # @param field_definition [GraphQL::Field, GraphQL::Schema::Field] Used for getting the `.complexity` configuration
30
- # @param query [GraphQL::Query] Used for `query.possible_types`
31
- # @param response_path [Array<String>] The path to the response key for the field
32
- # @return [Hash<GraphQL::BaseType, Hash<String, ScopedTypeComplexity>>]
33
- def initialize(parent_type, field_definition, query, response_path)
34
- super(&DEFAULT_PROC)
35
- @parent_type = parent_type
36
- @field_definition = field_definition
37
- @query = query
38
- @response_path = response_path
39
- @nodes = []
40
- end
41
-
42
- # @return [Array<GraphQL::Language::Nodes::Field>]
43
- attr_reader :nodes
44
-
45
- def own_complexity(child_complexity)
46
- @field_definition.calculate_complexity(query: @query, nodes: @nodes, child_complexity: child_complexity)
47
- end
48
- end
49
-
50
- def on_enter_field(node, parent, visitor)
51
- # We don't want to visit fragment definitions,
52
- # we'll visit them when we hit the spreads instead
53
- return if visitor.visiting_fragment_definition?
54
- return if visitor.skipping?
55
- return if @skip_introspection_fields && visitor.field_definition.introspection?
56
- parent_type = visitor.parent_type_definition
57
- field_key = node.alias || node.name
58
-
59
- # Find or create a complexity scope stack for this query.
60
- scopes_stack = @complexities_on_type_by_query[visitor.query] ||= [ScopedTypeComplexity.new(nil, nil, query, visitor.response_path)]
61
-
62
- # Find or create the complexity costing node for this field.
63
- scope = scopes_stack.last[parent_type][field_key] ||= ScopedTypeComplexity.new(parent_type, visitor.field_definition, visitor.query, visitor.response_path)
64
- scope.nodes.push(node)
65
- scopes_stack.push(scope)
66
- end
67
-
68
- def on_leave_field(node, parent, visitor)
69
- # We don't want to visit fragment definitions,
70
- # we'll visit them when we hit the spreads instead
71
- return if visitor.visiting_fragment_definition?
72
- return if visitor.skipping?
73
- return if @skip_introspection_fields && visitor.field_definition.introspection?
74
- scopes_stack = @complexities_on_type_by_query[visitor.query]
75
- scopes_stack.pop
76
- end
77
-
78
- private
79
-
80
- # @return [Integer]
81
- def max_possible_complexity
82
- @complexities_on_type_by_query.reduce(0) do |total, (query, scopes_stack)|
83
- total + merged_max_complexity_for_scopes(query, [scopes_stack.first])
84
- end
85
- end
86
-
87
- # @param query [GraphQL::Query] Used for `query.possible_types`
88
- # @param scopes [Array<ScopedTypeComplexity>] Array of scoped type complexities
89
- # @return [Integer]
90
- def merged_max_complexity_for_scopes(query, scopes)
91
- # Aggregate a set of all possible scope types encountered (scope keys).
92
- # Use a hash, but ignore the values; it's just a fast way to work with the keys.
93
- possible_scope_types = scopes.each_with_object({}) do |scope, memo|
94
- memo.merge!(scope)
95
- end
96
-
97
- # Expand abstract scope types into their concrete implementations;
98
- # overlapping abstracts coalesce through their intersecting types.
99
- possible_scope_types.keys.each do |possible_scope_type|
100
- next unless possible_scope_type.kind.abstract?
101
-
102
- query.possible_types(possible_scope_type).each do |impl_type|
103
- possible_scope_types[impl_type] ||= true
104
- end
105
- possible_scope_types.delete(possible_scope_type)
106
- end
107
-
108
- # Aggregate the lexical selections that may apply to each possible type,
109
- # and then return the maximum cost among possible typed selections.
110
- possible_scope_types.each_key.reduce(0) do |max, possible_scope_type|
111
- # Collect inner selections from all scopes that intersect with this possible type.
112
- all_inner_selections = scopes.each_with_object([]) do |scope, memo|
113
- scope.each do |scope_type, inner_selections|
114
- memo << inner_selections if types_intersect?(query, scope_type, possible_scope_type)
115
- end
116
- end
117
-
118
- # Find the maximum complexity for the scope type among possible lexical branches.
119
- complexity = merged_max_complexity(query, all_inner_selections)
120
- complexity > max ? complexity : max
121
- end
122
- end
123
-
124
- def types_intersect?(query, a, b)
125
- return true if a == b
126
-
127
- a_types = query.possible_types(a)
128
- query.possible_types(b).any? { |t| a_types.include?(t) }
129
- end
130
-
131
- # A hook which is called whenever a field's max complexity is calculated.
132
- # Override this method to capture individual field complexity details.
133
- #
134
- # @param scoped_type_complexity [ScopedTypeComplexity]
135
- # @param max_complexity [Numeric] Field's maximum complexity including child complexity
136
- # @param child_complexity [Numeric, nil] Field's child complexity
137
- def field_complexity(scoped_type_complexity, max_complexity:, child_complexity: nil)
138
- end
139
-
140
- # @param inner_selections [Array<Hash<String, ScopedTypeComplexity>>] Field selections for a scope
141
- # @return [Integer] Total complexity value for all these selections in the parent scope
142
- def merged_max_complexity(query, inner_selections)
143
- # Aggregate a set of all unique field selection keys across all scopes.
144
- # Use a hash, but ignore the values; it's just a fast way to work with the keys.
145
- unique_field_keys = inner_selections.each_with_object({}) do |inner_selection, memo|
146
- memo.merge!(inner_selection)
147
- end
148
-
149
- # Add up the total cost for each unique field name's coalesced selections
150
- unique_field_keys.each_key.reduce(0) do |total, field_key|
151
- composite_scopes = nil
152
- field_cost = 0
153
-
154
- # Collect composite selection scopes for further aggregation,
155
- # leaf selections report their costs directly.
156
- inner_selections.each do |inner_selection|
157
- child_scope = inner_selection[field_key]
158
- next unless child_scope
159
-
160
- # Empty child scopes are leaf nodes with zero child complexity.
161
- if child_scope.empty?
162
- field_cost = child_scope.own_complexity(0)
163
- field_complexity(child_scope, max_complexity: field_cost, child_complexity: nil)
164
- else
165
- composite_scopes ||= []
166
- composite_scopes << child_scope
167
- end
168
- end
169
-
170
- if composite_scopes
171
- child_complexity = merged_max_complexity_for_scopes(query, composite_scopes)
172
-
173
- # This is the last composite scope visited; assume it's representative (for backwards compatibility).
174
- # Note: it would be more correct to score each composite scope and use the maximum possibility.
175
- field_cost = composite_scopes.last.own_complexity(child_complexity)
176
- field_complexity(composite_scopes.last, max_complexity: field_cost, child_complexity: child_complexity)
177
- end
178
-
179
- total + field_cost
180
- end
181
- end
182
- end
183
- end
184
- end
185
- end
@@ -1,284 +0,0 @@
1
- # frozen_string_literal: true
2
- module GraphQL
3
- module Analysis
4
- module AST
5
- # Depth first traversal through a query AST, calling AST analyzers
6
- # along the way.
7
- #
8
- # The visitor is a special case of GraphQL::Language::StaticVisitor, visiting
9
- # only the selected operation, providing helpers for common use cases such
10
- # as skipped fields and visiting fragment spreads.
11
- #
12
- # @see {GraphQL::Analysis::AST::Analyzer} AST Analyzers for queries
13
- class Visitor < GraphQL::Language::StaticVisitor
14
- def initialize(query:, analyzers:)
15
- @analyzers = analyzers
16
- @path = []
17
- @object_types = []
18
- @directives = []
19
- @field_definitions = []
20
- @argument_definitions = []
21
- @directive_definitions = []
22
- @rescued_errors = []
23
- @query = query
24
- @schema = query.schema
25
- @response_path = []
26
- @skip_stack = [false]
27
- super(query.selected_operation)
28
- end
29
-
30
- # @return [GraphQL::Query] the query being visited
31
- attr_reader :query
32
-
33
- # @return [Array<GraphQL::ObjectType>] Types whose scope we've entered
34
- attr_reader :object_types
35
-
36
- # @return [Array<GraphQL::AnalysisError]
37
- attr_reader :rescued_errors
38
-
39
- def visit
40
- return unless @document
41
- super
42
- end
43
-
44
- # Visit Helpers
45
-
46
- # @return [GraphQL::Execution::Interpreter::Arguments] Arguments for this node, merging default values, literal values and query variables
47
- # @see {GraphQL::Query#arguments_for}
48
- def arguments_for(ast_node, field_definition)
49
- @query.arguments_for(ast_node, field_definition)
50
- end
51
-
52
- # @return [Boolean] If the visitor is currently inside a fragment definition
53
- def visiting_fragment_definition?
54
- @in_fragment_def
55
- end
56
-
57
- # @return [Boolean] If the current node should be skipped because of a skip or include directive
58
- def skipping?
59
- @skipping
60
- end
61
-
62
- # @return [Array<String>] The path to the response key for the current field
63
- def response_path
64
- @response_path.dup
65
- end
66
-
67
- # Visitor Hooks
68
- [
69
- :operation_definition, :fragment_definition,
70
- :inline_fragment, :field, :directive, :argument, :fragment_spread
71
- ].each do |node_type|
72
- module_eval <<-RUBY, __FILE__, __LINE__
73
- def call_on_enter_#{node_type}(node, parent)
74
- @analyzers.each do |a|
75
- begin
76
- a.on_enter_#{node_type}(node, parent, self)
77
- rescue AnalysisError => err
78
- @rescued_errors << err
79
- end
80
- end
81
- end
82
-
83
- def call_on_leave_#{node_type}(node, parent)
84
- @analyzers.each do |a|
85
- begin
86
- a.on_leave_#{node_type}(node, parent, self)
87
- rescue AnalysisError => err
88
- @rescued_errors << err
89
- end
90
- end
91
- end
92
-
93
- RUBY
94
- end
95
-
96
- def on_operation_definition(node, parent)
97
- object_type = @schema.root_type_for_operation(node.operation_type)
98
- @object_types.push(object_type)
99
- @path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}")
100
- call_on_enter_operation_definition(node, parent)
101
- super
102
- call_on_leave_operation_definition(node, parent)
103
- @object_types.pop
104
- @path.pop
105
- end
106
-
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
- def on_inline_fragment(node, parent)
119
- on_fragment_with_type(node) do
120
- @path.push("...#{node.type ? " on #{node.type.name}" : ""}")
121
- @skipping = @skip_stack.last || skip?(node)
122
- @skip_stack << @skipping
123
-
124
- call_on_enter_inline_fragment(node, parent)
125
- super
126
- @skipping = @skip_stack.pop
127
- call_on_leave_inline_fragment(node, parent)
128
- end
129
- end
130
-
131
- def on_field(node, parent)
132
- @response_path.push(node.alias || node.name)
133
- parent_type = @object_types.last
134
- # This could be nil if the previous field wasn't found:
135
- field_definition = parent_type && @schema.get_field(parent_type, node.name, @query.context)
136
- @field_definitions.push(field_definition)
137
- if !field_definition.nil?
138
- next_object_type = field_definition.type.unwrap
139
- @object_types.push(next_object_type)
140
- else
141
- @object_types.push(nil)
142
- end
143
- @path.push(node.alias || node.name)
144
-
145
- @skipping = @skip_stack.last || skip?(node)
146
- @skip_stack << @skipping
147
-
148
- call_on_enter_field(node, parent)
149
- super
150
- @skipping = @skip_stack.pop
151
- call_on_leave_field(node, parent)
152
- @response_path.pop
153
- @field_definitions.pop
154
- @object_types.pop
155
- @path.pop
156
- end
157
-
158
- def on_directive(node, parent)
159
- directive_defn = @schema.directives[node.name]
160
- @directive_definitions.push(directive_defn)
161
- call_on_enter_directive(node, parent)
162
- super
163
- call_on_leave_directive(node, parent)
164
- @directive_definitions.pop
165
- end
166
-
167
- def on_argument(node, parent)
168
- argument_defn = if (arg = @argument_definitions.last)
169
- arg_type = arg.type.unwrap
170
- if arg_type.kind.input_object?
171
- arg_type.get_argument(node.name, @query.context)
172
- else
173
- nil
174
- end
175
- elsif (directive_defn = @directive_definitions.last)
176
- directive_defn.get_argument(node.name, @query.context)
177
- elsif (field_defn = @field_definitions.last)
178
- field_defn.get_argument(node.name, @query.context)
179
- else
180
- nil
181
- end
182
-
183
- @argument_definitions.push(argument_defn)
184
- @path.push(node.name)
185
- call_on_enter_argument(node, parent)
186
- super
187
- call_on_leave_argument(node, parent)
188
- @argument_definitions.pop
189
- @path.pop
190
- end
191
-
192
- def on_fragment_spread(node, parent)
193
- @path.push("... #{node.name}")
194
- @skipping = @skip_stack.last || skip?(node)
195
- @skip_stack << @skipping
196
-
197
- call_on_enter_fragment_spread(node, parent)
198
- enter_fragment_spread_inline(node)
199
- super
200
- @skipping = @skip_stack.pop
201
- leave_fragment_spread_inline(node)
202
- call_on_leave_fragment_spread(node, parent)
203
- @path.pop
204
- end
205
-
206
- # @return [GraphQL::BaseType] The current object type
207
- def type_definition
208
- @object_types.last
209
- end
210
-
211
- # @return [GraphQL::BaseType] The type which the current type came from
212
- def parent_type_definition
213
- @object_types[-2]
214
- end
215
-
216
- # @return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one
217
- def field_definition
218
- @field_definitions.last
219
- end
220
-
221
- # @return [GraphQL::Field, nil] The GraphQL field which returned the object that the current field belongs to
222
- def previous_field_definition
223
- @field_definitions[-2]
224
- end
225
-
226
- # @return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one
227
- def directive_definition
228
- @directive_definitions.last
229
- end
230
-
231
- # @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one
232
- def argument_definition
233
- @argument_definitions.last
234
- end
235
-
236
- # @return [GraphQL::Argument, nil] The previous GraphQL argument
237
- def previous_argument_definition
238
- @argument_definitions[-2]
239
- end
240
-
241
- private
242
-
243
- # Visit a fragment spread inline instead of visiting the definition
244
- # by itself.
245
- def enter_fragment_spread_inline(fragment_spread)
246
- fragment_def = query.fragments[fragment_spread.name]
247
-
248
- object_type = if fragment_def.type
249
- @query.warden.get_type(fragment_def.type.name)
250
- else
251
- object_types.last
252
- end
253
-
254
- object_types << object_type
255
-
256
- on_fragment_definition_children(fragment_def)
257
- end
258
-
259
- # Visit a fragment spread inline instead of visiting the definition
260
- # by itself.
261
- def leave_fragment_spread_inline(_fragment_spread)
262
- object_types.pop
263
- end
264
-
265
- def skip?(ast_node)
266
- dir = ast_node.directives
267
- dir.any? && !GraphQL::Execution::DirectiveChecks.include?(dir, query)
268
- end
269
-
270
- def on_fragment_with_type(node)
271
- object_type = if node.type
272
- @query.warden.get_type(node.type.name)
273
- else
274
- @object_types.last
275
- end
276
- @object_types.push(object_type)
277
- yield(node)
278
- @object_types.pop
279
- @path.pop
280
- end
281
- end
282
- end
283
- end
284
- end