Skip to content

Commit 0bcd67a

Browse files
authored
Merge pull request #4865 from gmac/gmac/analysis_fragment_skip_include
Fix fragment skip/include tracking during analysis visitation
2 parents 0e8f620 + 4b7b34c commit 0bcd67a

File tree

3 files changed

+158
-6
lines changed

3 files changed

+158
-6
lines changed

lib/graphql/analysis/ast/visitor.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,12 @@ def on_fragment_definition(node, parent)
118118
def on_inline_fragment(node, parent)
119119
on_fragment_with_type(node) do
120120
@path.push("...#{node.type ? " on #{node.type.name}" : ""}")
121+
@skipping = @skip_stack.last || skip?(node)
122+
@skip_stack << @skipping
123+
121124
call_on_enter_inline_fragment(node, parent)
122125
super
126+
@skipping = @skip_stack.pop
123127
call_on_leave_inline_fragment(node, parent)
124128
end
125129
end
@@ -187,9 +191,13 @@ def on_argument(node, parent)
187191

188192
def on_fragment_spread(node, parent)
189193
@path.push("... #{node.name}")
194+
@skipping = @skip_stack.last || skip?(node)
195+
@skip_stack << @skipping
196+
190197
call_on_enter_fragment_spread(node, parent)
191198
enter_fragment_spread_inline(node)
192199
super
200+
@skipping = @skip_stack.pop
193201
leave_fragment_spread_inline(node)
194202
call_on_leave_fragment_spread(node, parent)
195203
@path.pop

spec/graphql/analysis/ast/query_complexity_spec.rb

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,15 @@
2121

2222
describe "simple queries" do
2323
let(:query_string) {%|
24-
query cheeses($isSkipped: Boolean = false){
24+
query cheeses {
2525
# complexity of 3
2626
cheese1: cheese(id: 1) {
2727
id
2828
flavor
2929
}
3030
3131
# complexity of 4
32-
cheese2: cheese(id: 2) @skip(if: $isSkipped) {
32+
cheese2: cheese(id: 2) {
3333
similarCheese(source: SHEEP) {
3434
... on Cheese {
3535
similarCheese(source: SHEEP) {
@@ -45,12 +45,40 @@
4545
complexities = reduce_result.first
4646
assert_equal 7, complexities
4747
end
48+
end
49+
50+
describe "with skip/include" do
51+
let(:query_string) {%|
52+
query cheeses($skip: Boolean = false, $include: Boolean = true) {
53+
fields: cheese(id: 1) {
54+
flavor
55+
origin @skip(if: $skip)
56+
source @include(if: $include)
57+
}
58+
inlineFragments: cheese(id: 1) {
59+
...on Cheese { flavor }
60+
...on Cheese @skip(if: $skip) { origin }
61+
...on Cheese @include(if: $include) { source }
62+
}
63+
fragmentSpreads: cheese(id: 1) {
64+
...Flavorful
65+
...Original @skip(if: $skip)
66+
...Sourced @include(if: $include)
67+
}
68+
}
69+
fragment Flavorful on Cheese { flavor }
70+
fragment Original on Cheese { origin }
71+
fragment Sourced on Cheese { source }
72+
|}
73+
74+
it "sums up all included complexities" do
75+
assert_equal 12, reduce_result.first
76+
end
4877

4978
describe "when skipped by directives" do
50-
let(:variables) { { "isSkipped" => true } }
51-
it "doesn't include skipped fields" do
52-
complexity = reduce_result.first
53-
assert_equal 3, complexity
79+
let(:variables) { { "skip" => true, "include" => false } }
80+
it "doesn't include skipped fields and fragments" do
81+
assert_equal 6, reduce_result.first
5482
end
5583
end
5684
end

spec/graphql/analysis/ast_spec.rb

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,122 @@ def result
105105
end
106106
end
107107

108+
class AstSkipInclude < GraphQL::Analysis::AST::Analyzer
109+
def initialize(query)
110+
super
111+
@included = []
112+
end
113+
114+
def on_enter_field(node, parent, visitor)
115+
@included << "enter #{node.name}" unless visitor.skipping?
116+
end
117+
118+
def on_leave_field(node, parent, visitor)
119+
@included << "leave #{node.name}" unless visitor.skipping?
120+
end
121+
122+
def on_enter_inline_fragment(node, parent, visitor)
123+
@included << "enter ...on #{node.type.name}" unless visitor.skipping?
124+
end
125+
126+
def on_leave_inline_fragment(node, parent, visitor)
127+
@included << "leave ...on #{node.type.name}" unless visitor.skipping?
128+
end
129+
130+
def on_enter_fragment_spread(node, parent, visitor)
131+
@included << "enter ...#{node.name}" unless visitor.skipping?
132+
end
133+
134+
def on_leave_fragment_spread(node, parent, visitor)
135+
@included << "leave ...#{node.name}" unless visitor.skipping?
136+
end
137+
138+
def result
139+
@included
140+
end
141+
end
142+
143+
describe "skip and include behaviors" do
144+
let(:reduce_result) { GraphQL::Analysis::AST.analyze_query(query, [AstSkipInclude]) }
145+
let(:query) { GraphQL::Query.new(Dummy::Schema, query_string) }
146+
let(:query_string) {%|{}|}
147+
148+
describe "for fields" do
149+
let(:query_string) {%|
150+
{
151+
cheese {
152+
flavor
153+
origin @skip(if: true)
154+
source @include(if: false)
155+
}
156+
cheese @skip(if: true) { flavor }
157+
cheese @include(if: false) { flavor }
158+
}
159+
|}
160+
161+
it "tracks inclusions" do
162+
expected = [
163+
"enter cheese",
164+
"enter flavor",
165+
"leave flavor",
166+
"leave cheese",
167+
]
168+
assert_equal expected, reduce_result.first
169+
end
170+
end
171+
172+
describe "for inline fragments" do
173+
let(:query_string) {%|
174+
{
175+
cheese {
176+
...on Cheese @skip(if: true) { origin }
177+
...on Cheese { flavor }
178+
...on Cheese @include(if: false) { source }
179+
}
180+
}
181+
|}
182+
183+
it "tracks inclusions" do
184+
expected = [
185+
"enter cheese",
186+
"enter ...on Cheese",
187+
"enter flavor",
188+
"leave flavor",
189+
"leave ...on Cheese",
190+
"leave cheese",
191+
]
192+
assert_equal expected, reduce_result.first
193+
end
194+
end
195+
196+
describe "for fragment spreads" do
197+
let(:query_string) {%|
198+
{
199+
cheese {
200+
...Original @skip(if: true)
201+
...Flavorful
202+
...Sourced @include(if: false)
203+
}
204+
}
205+
fragment Flavorful on Cheese { flavor }
206+
fragment Original on Cheese { origin }
207+
fragment Sourced on Cheese { source }
208+
|}
209+
210+
it "tracks inclusions" do
211+
expected = [
212+
"enter cheese",
213+
"enter ...Flavorful",
214+
"enter flavor",
215+
"leave flavor",
216+
"leave ...Flavorful",
217+
"leave cheese",
218+
]
219+
assert_equal expected, reduce_result.first
220+
end
221+
end
222+
end
223+
108224
describe "using the AST analysis engine" do
109225
let(:schema) do
110226
query_type = Class.new(GraphQL::Schema::Object) do

0 commit comments

Comments
 (0)