55require_relative "./executor/authorization"
66require_relative "./executor/hot_paths"
77require_relative "./executor/response_hash"
8- require_relative "./executor/error_formatting "
8+ require_relative "./executor/error_formatter "
99
1010module GraphQL
1111 module Cardinal
1212 class Executor
1313 include HotPaths
14- include ErrorFormatting
1514
1615 TYPENAME_FIELD = "__typename"
1716 TYPENAME_FIELD_RESOLVER = TypenameResolver . new
@@ -28,7 +27,6 @@ def initialize(schema, resolvers, document, root_object, variables: {}, context:
2827 @context = context
2928 @data = { }
3029 @errors = [ ]
31- @path = [ ]
3230 @exec_queue = [ ]
3331 @exec_count = 0
3432 @context [ :query ] = @query
@@ -69,7 +67,7 @@ def perform
6967 execute_scope ( @exec_queue . shift ) until @exec_queue . empty?
7068 end
7169
72- response = { "data" => @errors . empty? ? @data : format_inline_errors ( @ data, @errors ) }
70+ response = { "data" => @errors . empty? ? @data : ErrorFormatter . new ( @query , @ data, @errors ) . perform }
7371 response [ "errors" ] = @errors . map ( &:to_h ) unless @errors . empty?
7472 response
7573 end
@@ -78,14 +76,13 @@ def perform
7876
7977 def execute_scope ( exec_scope )
8078 unless exec_scope . fields
81- lazy_field_keys = [ ]
8279 exec_scope . fields = execution_fields_by_key ( exec_scope . parent_type , exec_scope . selections )
8380 exec_scope . fields . each_value do |exec_field |
84- @path . push ( exec_field . key )
8581 parent_type = exec_scope . parent_type
8682 parent_sources = exec_scope . sources
8783 field_name = exec_field . name
8884
85+ exec_field . scope = exec_scope
8986 exec_field . type = @query . get_field ( parent_type , field_name ) . type
9087 value_type = exec_field . type . unwrap
9188
@@ -94,71 +91,61 @@ def execute_scope(exec_scope)
9491 if field_name == TYPENAME_FIELD
9592 field_resolver = TYPENAME_FIELD_RESOLVER
9693 else
97- raise NotImplementedError , "No field resolver for ` #{ parent_type . graphql_name } .#{ field_name } ` "
94+ raise NotImplementedError , "No field resolver for ' #{ parent_type . graphql_name } .#{ field_name } ' "
9895 end
9996 end
10097
101- resolved_sources = if !field_resolver . authorized? ( @context )
102- @errors << AuthorizationError . new ( type_name : parent_type . graphql_name , field_name : field_name , path : @path . dup , base : true )
98+ exec_field . result = if !field_resolver . authorized? ( @context )
99+ @errors << AuthorizationError . new ( type_name : parent_type . graphql_name , field_name : field_name , path : exec_field . path , base : true )
103100 Array . new ( parent_sources . length , @errors . last )
104101 elsif !Authorization . can_access_type? ( value_type , @context )
105- @errors << AuthorizationError . new ( type_name : value_type . graphql_name , path : @path . dup , base : true )
102+ @errors << AuthorizationError . new ( type_name : value_type . graphql_name , path : exec_field . path , base : true )
106103 Array . new ( parent_sources . length , @errors . last )
107104 else
108105 begin
109106 @tracers . each { _1 . before_resolve_field ( parent_type , field_name , parent_sources . length , @context ) }
110107 field_resolver . resolve ( parent_sources , exec_field . arguments ( @variables ) , @context , exec_scope )
111108 rescue StandardError => e
112- report_exception ( error : e )
113- @errors << InternalError . new ( path : @path . dup , base : true )
109+ report_exception ( error : e , field : exec_field )
110+ @errors << InternalError . new ( path : exec_field . path , base : true )
114111 Array . new ( parent_sources . length , @errors . last )
115112 ensure
116113 @tracers . each { _1 . after_resolve_field ( parent_type , field_name , parent_sources . length , @context ) }
117114 @exec_count += 1
118115 end
119116 end
120-
121- if resolved_sources . is_a? ( Promise )
122- exec_field . promise = resolved_sources
123- lazy_field_keys << exec_field . key
124- else
125- resolve_execution_field ( exec_scope , exec_field , resolved_sources , lazy_field_keys )
126- lazy_field_keys . clear
127- end
128-
129- @path . pop
130117 end
131118 end
132119
133- if exec_scope . lazy_fields_pending ?
120+ if exec_scope . lazy_fields ?
134121 if exec_scope . lazy_fields_ready?
135- exec_scope . method ( :lazy_exec! ) . call # << noop for loaders that have already run
122+ exec_scope . send ( :lazy_exec! ) # << noop for loaders that have already run
136123 exec_scope . fields . each_value do |exec_field |
137- next unless exec_field . promise
138-
139- @path . push ( exec_field . key )
140- resolve_execution_field ( exec_scope , exec_field , exec_field . promise . value )
141- @path . pop
124+ sources = exec_field . result . is_a? ( Promise ) ? exec_field . result . value : exec_field . result
125+ resolve_execution_field ( exec_field , sources )
142126 end
143127 else
144128 # requeue the scope to wait on others that haven't built fields yet
145129 @exec_queue << exec_scope
146130 end
131+ else
132+ exec_scope . fields . each_value do |exec_field |
133+ resolve_execution_field ( exec_field , exec_field . result )
134+ end
147135 end
148136
149137 nil
150138 end
151139
152- def resolve_execution_field ( exec_scope , exec_field , resolved_sources , lazy_field_keys = nil )
153- parent_sources = exec_scope . sources
154- parent_responses = exec_scope . responses
140+ def resolve_execution_field ( exec_field , resolved_sources )
141+ parent_sources = exec_field . scope . sources
142+ parent_responses = exec_field . scope . responses
155143 field_key = exec_field . key
156- field_name = exec_field . name
157144 field_type = exec_field . type
158145 return_type = field_type . unwrap
159146
160147 if resolved_sources . length != parent_sources . length
161- report_exception ( "Incorrect number of results resolved. Expected #{ parent_sources . length } , got #{ resolved_sources . length } " )
148+ report_exception ( "Incorrect number of results resolved. Expected #{ parent_sources . length } , got #{ resolved_sources . length } " , field : exec_field )
162149 resolved_sources = Array . new ( parent_sources . length , nil )
163150 end
164151
@@ -168,9 +155,7 @@ def resolve_execution_field(exec_scope, exec_field, resolved_sources, lazy_field
168155 next_responses = [ ]
169156 resolved_sources . each_with_index do |source , i |
170157 # DANGER: HOT PATH!
171- response = parent_responses [ i ]
172- lazy_field_keys . each { |k | response [ k ] = nil } if lazy_field_keys && !lazy_field_keys . empty?
173- response [ field_key ] = build_composite_response ( field_type , source , next_sources , next_responses )
158+ parent_responses [ i ] [ field_key ] = build_composite_response ( exec_field , field_type , source , next_sources , next_responses )
174159 end
175160
176161 if return_type . kind . abstract?
@@ -184,7 +169,7 @@ def resolve_execution_field(exec_scope, exec_field, resolved_sources, lazy_field
184169 next_sources . each_with_index do |source , i |
185170 # DANGER: HOT PATH!
186171 impl_type = type_resolver . call ( source , @context )
187- next_sources_by_type [ impl_type ] << ( field_name == TYPENAME_FIELD ? impl_type . graphql_name : source )
172+ next_sources_by_type [ impl_type ] << ( exec_field . name == TYPENAME_FIELD ? impl_type . graphql_name : source )
188173 next_responses_by_type [ impl_type ] << next_responses [ i ] . tap { |r | r . typename = impl_type . graphql_name }
189174 end
190175
@@ -193,7 +178,7 @@ def resolve_execution_field(exec_scope, exec_field, resolved_sources, lazy_field
193178 next_sources_by_type . each do |impl_type , impl_type_sources |
194179 # check concrete type access only once per resolved type...
195180 unless Authorization . can_access_type? ( impl_type , @context )
196- @errors << AuthorizationError . new ( type_name : impl_type . graphql_name , path : @path . dup , base : true )
181+ @errors << AuthorizationError . new ( type_name : impl_type . graphql_name , path : exec_field . path , base : true )
197182 impl_type_sources = Array . new ( impl_type_sources . length , @errors . last )
198183 end
199184
@@ -204,7 +189,8 @@ def resolve_execution_field(exec_scope, exec_field, resolved_sources, lazy_field
204189 responses : next_responses_by_type [ impl_type ] ,
205190 loader_cache : loader_cache ,
206191 loader_group : loader_group ,
207- parent : exec_scope ,
192+ path : exec_field . path ,
193+ parent : exec_field . scope ,
208194 )
209195 end
210196
@@ -215,17 +201,16 @@ def resolve_execution_field(exec_scope, exec_field, resolved_sources, lazy_field
215201 selections : exec_field . selections ,
216202 sources : next_sources ,
217203 responses : next_responses ,
218- parent : exec_scope ,
204+ path : exec_field . path ,
205+ parent : exec_field . scope ,
219206 )
220207 end
221208 else
222209 # build leaf results
223210 resolved_sources . each_with_index do |val , i |
224211 # DANGER: HOT PATH!
225- response = parent_responses [ i ]
226- lazy_field_keys . each { |k | response [ k ] = nil } if lazy_field_keys && !lazy_field_keys . empty?
227- response [ field_key ] = if val . nil? || val . is_a? ( StandardError )
228- build_missing_value ( field_type , val )
212+ parent_responses [ i ] [ field_key ] = if val . nil? || val . is_a? ( StandardError )
213+ build_missing_value ( exec_field , field_type , val )
229214 elsif return_type . kind . scalar?
230215 coerce_scalar_value ( return_type , val )
231216 elsif return_type . kind . enum?
@@ -254,7 +239,7 @@ def execution_fields_by_key(parent_type, selections, map: Hash.new { |h, k| h[k]
254239 fragment = @query . fragments [ node . name ]
255240 fragment_type = @query . get_type ( fragment . type . name )
256241 if @query . possible_types ( fragment_type ) . include? ( parent_type )
257- execution_fields_by_key ( parent_type , node . selections , map : map )
242+ execution_fields_by_key ( parent_type , fragment . selections , map : map )
258243 end
259244
260245 else
@@ -286,9 +271,9 @@ def if_argument?(bool_arg)
286271 end
287272 end
288273
289- def report_exception ( message = nil , error : nil , path : @path . dup )
274+ def report_exception ( message = nil , error : nil , field : nil )
290275 # todo: add real error reporting...
291- puts "Error at #{ path . join ( "." ) } : #{ message || error &.message } "
276+ puts "Error at #{ field . path . join ( "." ) } : #{ message || error &.message } " if field
292277 puts error . backtrace . join ( "\n " ) if error
293278 end
294279 end
0 commit comments