|
3 | 3 | require_relative "./executor/execution_scope" |
4 | 4 | require_relative "./executor/execution_field" |
5 | 5 | require_relative "./executor/authorization" |
6 | | -require_relative "./executor/tracer" |
7 | 6 | require_relative "./executor/hot_paths" |
8 | 7 | require_relative "./executor/response_hash" |
9 | | -require_relative "./executor/response_shape" |
| 8 | +require_relative "./executor/error_formatting" |
10 | 9 |
|
11 | 10 | module GraphQL |
12 | 11 | module Cardinal |
13 | 12 | class Executor |
14 | 13 | include HotPaths |
15 | | - include ResponseShape |
| 14 | + include ErrorFormatting |
16 | 15 |
|
17 | 16 | TYPENAME_FIELD = "__typename" |
18 | 17 | TYPENAME_FIELD_RESOLVER = TypenameResolver.new |
19 | 18 |
|
20 | 19 | attr_reader :exec_count |
21 | 20 |
|
22 | | - def initialize(schema, resolvers, document, root_object) |
| 21 | + def initialize(schema, resolvers, document, root_object, variables: {}, context: {}, tracers: []) |
23 | 22 | @query = GraphQL::Query.new(schema, document: document) # << for schema reference |
24 | 23 | @resolvers = resolvers |
25 | 24 | @document = document |
26 | 25 | @root_object = root_object |
27 | | - @tracer = Tracer.new |
28 | | - @variables = {} |
29 | | - @context = { query: @query } |
| 26 | + @tracers = tracers |
| 27 | + @variables = variables |
| 28 | + @context = context |
30 | 29 | @data = {} |
31 | 30 | @errors = [] |
32 | | - @inline_errors = false |
33 | 31 | @path = [] |
34 | 32 | @exec_queue = [] |
35 | 33 | @exec_count = 0 |
| 34 | + @context[:query] = @query |
36 | 35 | end |
37 | 36 |
|
38 | 37 | def perform |
@@ -70,9 +69,7 @@ def perform |
70 | 69 | execute_scope(@exec_queue.shift) until @exec_queue.empty? |
71 | 70 | end |
72 | 71 |
|
73 | | - response = { |
74 | | - "data" => @inline_errors ? shape_response(@data) : @data, |
75 | | - } |
| 72 | + response = { "data" => @errors.empty? ? @data : format_inline_errors(@data, @errors) } |
76 | 73 | response["errors"] = @errors.map(&:to_h) unless @errors.empty? |
77 | 74 | response |
78 | 75 | end |
@@ -102,22 +99,21 @@ def execute_scope(exec_scope) |
102 | 99 | end |
103 | 100 |
|
104 | 101 | resolved_sources = if !field_resolver.authorized?(@context) |
105 | | - @errors << AuthorizationError.new(type_name: parent_type.graphql_name, field_name: field_name, path: @path.dup) |
106 | | - Array.new(parent_sources.length, nil) |
| 102 | + @errors << AuthorizationError.new(type_name: parent_type.graphql_name, field_name: field_name, path: @path.dup, base: true) |
| 103 | + Array.new(parent_sources.length, @errors.last) |
107 | 104 | elsif !Authorization.can_access_type?(value_type, @context) |
108 | | - @errors << AuthorizationError.new(type_name: value_type.graphql_name, path: @path.dup) |
109 | | - Array.new(parent_sources.length, nil) |
| 105 | + @errors << AuthorizationError.new(type_name: value_type.graphql_name, path: @path.dup, base: true) |
| 106 | + Array.new(parent_sources.length, @errors.last) |
110 | 107 | else |
111 | 108 | begin |
112 | | - @tracer&.before_resolve_field(parent_type, field_name, parent_sources.length, @context) |
| 109 | + @tracers.each { _1.before_resolve_field(parent_type, field_name, parent_sources.length, @context) } |
113 | 110 | field_resolver.resolve(parent_sources, exec_field.arguments(@variables), @context, exec_scope) |
114 | 111 | rescue StandardError => e |
115 | | - raise e |
116 | | - report_exception(e.message) |
117 | | - @errors << InternalError.new(e.message, path: @path.dup) |
118 | | - Array.new(parent_sources.length, nil) |
| 112 | + report_exception(error: e) |
| 113 | + @errors << InternalError.new(path: @path.dup, base: true) |
| 114 | + Array.new(parent_sources.length, @errors.last) |
119 | 115 | ensure |
120 | | - @tracer&.after_resolve_field(parent_type, field_name, parent_sources.length, @context) |
| 116 | + @tracers.each { _1.after_resolve_field(parent_type, field_name, parent_sources.length, @context) } |
121 | 117 | @exec_count += 1 |
122 | 118 | end |
123 | 119 | end |
@@ -197,8 +193,8 @@ def resolve_execution_field(exec_scope, exec_field, resolved_sources, lazy_field |
197 | 193 | next_sources_by_type.each do |impl_type, impl_type_sources| |
198 | 194 | # check concrete type access only once per resolved type... |
199 | 195 | unless Authorization.can_access_type?(impl_type, @context) |
200 | | - @errors << AuthorizationError.new(type_name: impl_type.graphql_name, path: @path.dup) |
201 | | - impl_type_sources = Array.new(impl_type_sources.length, nil) |
| 196 | + @errors << AuthorizationError.new(type_name: impl_type.graphql_name, path: @path.dup, base: true) |
| 197 | + impl_type_sources = Array.new(impl_type_sources.length, @errors.last) |
202 | 198 | end |
203 | 199 |
|
204 | 200 | loader_group << ExecutionScope.new( |
@@ -262,7 +258,7 @@ def execution_fields_by_key(parent_type, selections, map: Hash.new { |h, k| h[k] |
262 | 258 | end |
263 | 259 |
|
264 | 260 | else |
265 | | - raise DocumentError.new("selection node type") |
| 261 | + raise DocumentError.new("Invalid selection node type") |
266 | 262 | end |
267 | 263 | end |
268 | 264 | map |
@@ -290,8 +286,10 @@ def if_argument?(bool_arg) |
290 | 286 | end |
291 | 287 | end |
292 | 288 |
|
293 | | - def report_exception(message, path: @path.dup) |
294 | | - # todo: hook up some kind of error reporting... |
| 289 | + def report_exception(message = nil, error: nil, path: @path.dup) |
| 290 | + # todo: add real error reporting... |
| 291 | + puts "Error at #{path.join(".")}: #{message || error&.message}" |
| 292 | + puts error.backtrace.join("\n") if error |
295 | 293 | end |
296 | 294 | end |
297 | 295 | end |
|
0 commit comments