@@ -10,7 +10,7 @@ module Analysis
1010 #
1111 # @see {GraphQL::Analysis::Analyzer} AST Analyzers for queries
1212 class Visitor < GraphQL ::Language ::StaticVisitor
13- def initialize ( query :, analyzers :)
13+ def initialize ( query :, analyzers :, timeout : )
1414 @analyzers = analyzers
1515 @path = [ ]
1616 @object_types = [ ]
@@ -24,6 +24,11 @@ def initialize(query:, analyzers:)
2424 @types = query . types
2525 @response_path = [ ]
2626 @skip_stack = [ false ]
27+ @timeout_time = if timeout
28+ Process . clock_gettime ( Process ::CLOCK_MONOTONIC , :float_second ) + timeout
29+ else
30+ Float ::INFINITY
31+ end
2732 super ( query . selected_operation )
2833 end
2934
@@ -72,28 +77,25 @@ def response_path
7277 module_eval <<-RUBY , __FILE__ , __LINE__
7378 def call_on_enter_#{ node_type } (node, parent)
7479 @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+ a.on_enter_#{ node_type } (node, parent, self)
81+ rescue AnalysisError => err
82+ @rescued_errors << err
8083 end
8184 end
8285
8386 def call_on_leave_#{ node_type } (node, parent)
8487 @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
88+ a.on_leave_#{ node_type } (node, parent, self)
89+ rescue AnalysisError => err
90+ @rescued_errors << err
9091 end
9192 end
9293
9394 RUBY
9495 end
9596
9697 def on_operation_definition ( node , parent )
98+ check_timeout
9799 object_type = @schema . root_type_for_operation ( node . operation_type )
98100 @object_types . push ( object_type )
99101 @path . push ( "#{ node . operation_type } #{ node . name ? " #{ node . name } " : "" } " )
@@ -104,31 +106,27 @@ def on_operation_definition(node, parent)
104106 @path . pop
105107 end
106108
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-
118109 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 )
110+ check_timeout
111+ object_type = if node . type
112+ @types . type ( node . type . name )
113+ else
114+ @object_types . last
128115 end
116+ @object_types . push ( object_type )
117+ @path . push ( "...#{ node . type ? " on #{ node . type . name } " : "" } " )
118+ @skipping = @skip_stack . last || skip? ( node )
119+ @skip_stack << @skipping
120+ call_on_enter_inline_fragment ( node , parent )
121+ super
122+ @skipping = @skip_stack . pop
123+ call_on_leave_inline_fragment ( node , parent )
124+ @object_types . pop
125+ @path . pop
129126 end
130127
131128 def on_field ( node , parent )
129+ check_timeout
132130 @response_path . push ( node . alias || node . name )
133131 parent_type = @object_types . last
134132 # This could be nil if the previous field wasn't found:
@@ -156,6 +154,7 @@ def on_field(node, parent)
156154 end
157155
158156 def on_directive ( node , parent )
157+ check_timeout
159158 directive_defn = @schema . directives [ node . name ]
160159 @directive_definitions . push ( directive_defn )
161160 call_on_enter_directive ( node , parent )
@@ -165,6 +164,7 @@ def on_directive(node, parent)
165164 end
166165
167166 def on_argument ( node , parent )
167+ check_timeout
168168 argument_defn = if ( arg = @argument_definitions . last )
169169 arg_type = arg . type . unwrap
170170 if arg_type . kind . input_object?
@@ -190,6 +190,7 @@ def on_argument(node, parent)
190190 end
191191
192192 def on_fragment_spread ( node , parent )
193+ check_timeout
193194 @path . push ( "... #{ node . name } " )
194195 @skipping = @skip_stack . last || skip? ( node )
195196 @skip_stack << @skipping
@@ -267,16 +268,10 @@ def skip?(ast_node)
267268 !dir . empty? && !GraphQL ::Execution ::DirectiveChecks . include? ( dir , query )
268269 end
269270
270- def on_fragment_with_type ( node )
271- object_type = if node . type
272- @types . type ( node . type . name )
273- else
274- @object_types . last
271+ def check_timeout
272+ if Process . clock_gettime ( Process ::CLOCK_MONOTONIC , :float_second ) > @timeout_time
273+ raise GraphQL ::Analysis ::TimeoutError
275274 end
276- @object_types . push ( object_type )
277- yield ( node )
278- @object_types . pop
279- @path . pop
280275 end
281276 end
282277 end
0 commit comments