class GraphQL::Execution::FieldResolveStep
Constants
- SCOPED_RELAY_FIELD_NAMES
-
Only these built-in fields on
Relayconnection and edge types return already-scoped items. Custom fields on those types must still be authorized normally.
Attributes
Public Class Methods
# File lib/graphql/execution/field_resolve_step.rb, line 10 def initialize(parent_type:, runner:, key:, selections_step:) @selections_step = selections_step @key = key @parent_type = parent_type @ast_node = @ast_nodes = nil @runner = runner @field_definition = nil @arguments = nil @field_results = nil @path = nil @enqueued_authorization = false @all_next_objects = nil @all_next_results = nil @static_type = nil @next_selections = nil @results = nil @finish_extension_idx = nil @was_scoped = nil @pending_steps = nil @field_authorization_objects = @field_authorization_results = @field_authorization_errors = nil @field_authorization_next_idx = @field_authorization_pending = nil @arguments_without_loads = @post_processors = @directive_finalizers = nil end
Public Instance Methods
# File lib/graphql/execution/field_resolve_step.rb, line 121 def add_graphql_error(result, key, err, return_type: @field_definition.type) err.path = path if err.ast_node.nil? err.ast_nodes = ast_nodes end @runner.add_finalizer(@selections_step.query, result, key, err) if !err.is_a?(GraphQL::Execution::Skip) field_type = return_type should_propagate_null = field_type.non_null? #: bool while (should_propagate_null == false && field_type.kind.wraps?) field_type = field_type.of_type should_propagate_null = field_type.non_null? end if should_propagate_null propagate_nulls end end err end
# File lib/graphql/execution/field_resolve_step.rb, line 502 def add_non_null_error(is_from_array, return_type: nil) if is_from_array && return_type list_type = @field_definition.type list_type = list_type.of_type if list_type.non_null? # Preserve existing messages for single-level lists. element_type = return_type.of_type if list_type.of_type.list? end err = @parent_type.const_get(:InvalidNullError).new(@parent_type, @field_definition, ast_nodes, is_from_array: is_from_array, path: path, element_type: element_type) nn_result = @runner.schema.type_error(err, @selections_step.query.context) if nn_result.nil? propagate_nulls end nn_result end
Source
# File lib/graphql/execution/field_resolve_step.rb, line 276 def any_lazy_results? lazies = false #: bool if @runner.resolves_lazies # TODO extract this @field_results.each do |field_result| if @runner.lazy?(field_result) lazies = true break elsif field_result.is_a?(Array) field_result.each do |inner_fr| if @runner.lazy?(inner_fr) break lazies = true end end if lazies break end end end end lazies end
Source
# File lib/graphql/execution/field_resolve_step.rb, line 47 def append_selection(ast_node) if @ast_node.nil? @ast_node = ast_node elsif @ast_nodes.nil? @ast_nodes = [@ast_node, ast_node] else @ast_nodes << ast_node end nil end
Source
# File lib/graphql/execution/field_resolve_step.rb, line 202 def arguments_without_loads if @arguments_without_loads.nil? @arguments_without_loads, _errors = @selections_step.query.input_values.argument_values(@field_definition, ast_node.arguments, nil) end @arguments_without_loads end
Used for compatibility in Schema::Subscription
Source
# File lib/graphql/execution/field_resolve_step.rb, line 43 def ast_nodes @ast_nodes ||= [@ast_node] end
Source
# File lib/graphql/execution/field_resolve_step.rb, line 182 def build_arguments query = @selections_step.query field_name = @ast_node.name @field_definition = query.types.field(@parent_type, field_name) || raise(GraphQL::Error, "No field definition found for #{@parent_type.to_type_signature}.#{ast_node.name} (at #{@ast_node.position})") set_current_field @arguments, errors = query.input_values.argument_values(@field_definition, @ast_node.arguments, self) # rubocop:disable Development/ContextIsPassedCop if errors build_errors_result(errors, nil) return end if (@pending_steps.nil? || @pending_steps.size == 0) && @field_results.nil? # Make sure the arguments flow didn't already call through execute_field end ensure set_current_field(nil) end
# File lib/graphql/execution/field_resolve_step.rb, line 170 def build_errors_result(errors, single_error) first_error = errors.nil? ? single_error : errors.pop @field_results = [first_error] @results = [@selections_step.results.first] if errors errors.each do |e| add_graphql_error(@results.first, key, e) end end build_results end
# File lib/graphql/execution/field_resolve_step.rb, line 404 def build_leaf_result(result_h, result_key, field_result, return_type, ctx, is_from_array) if field_result.nil? if return_type.non_null? add_non_null_error(is_from_array, return_type: return_type) else nil end elsif field_result.is_a?(Finalizer) if field_result.is_a?(GraphQL::RuntimeError) add_graphql_error(result_h, result_key, field_result, return_type: return_type) else field_result.path = path @runner.add_finalizer(ctx.query, result_h, key, field_result) end elsif return_type.list? if return_type.non_null? return_type = return_type.of_type end inner_type = return_type.of_type result_a = field_result.is_a?(Array) ? Array.new(field_result.size) : [] field_result.each_with_index do |item, idx| result_a[idx] = build_leaf_result(result_a, idx, item, inner_type, ctx, true) end result_a else return_type.coerce_result(field_result, ctx) end end
Source
# File lib/graphql/execution/field_resolve_step.rb, line 345 def build_results return_type = @field_definition.type return_result_type = return_type.unwrap @post_processors&.each do |post_processor| @field_results = post_processor.after_resolve(@field_results) end if return_result_type.kind.composite? @static_type = return_result_type if @ast_nodes @next_selections = [] @ast_nodes.each do |ast_node| @next_selections.concat(ast_node.selections) end else @next_selections = @ast_node.selections end @all_next_objects = [] @all_next_results = [] is_list = return_type.list? is_non_null = return_type.non_null? i = 0 s = @results.size while i < s do result_h = @results[i] result = @field_results[i] i += 1 build_graphql_result(result_h, @key, result, return_type, is_non_null, is_list, false) end @enqueued_authorization = true if @pending_steps.nil? || @pending_steps.size == 0 enqueue_next_steps else # Do nothing -- it will enqueue itself later end else ctx = @selections_step.query.context i = 0 s = @results.size while i < s do result_h = @results[i] field_result = @field_results[i] i += 1 finish_leaf_result(result_h, @key, field_result, return_type, ctx) end end end
Source
# File lib/graphql/execution/field_resolve_step.rb, line 89 def call return nil if @selections_step.killed set_current_field if @field_definition if @enqueued_authorization enqueue_next_steps elsif @finish_extension_idx finish_extensions elsif @field_results build_results elsif @field_authorization_results if @field_authorization_next_idx < @field_authorization_objects.size authorize_next_field_object else finish_field_authorizations end elsif @arguments execute_field else build_arguments end rescue StandardError => err if @field_definition && !err.message.start_with?("Resolving ") # TODO remove this check ^^^^^^ when NullDataloader isn't recursive raise err, "Resolving #{@field_definition.path}: #{err.message}", err.backtrace else raise end ensure set_current_field(nil) end
Source
# File lib/graphql/execution/field_resolve_step.rb, line 434 def enqueue_next_steps if !@all_next_results.empty? @all_next_objects.compact! query = @selections_step.query ctx = query.context if @static_type.kind.abstract? next_objects_by_type = Hash.new { |h, obj_t| h[obj_t] = [] }.compare_by_identity next_results_by_type = Hash.new { |h, obj_t| h[obj_t] = [] }.compare_by_identity @all_next_objects.each_with_index do |next_object, i| result = @all_next_results[i] if (object_type = @runner.runtime_type_at[result]) # OK else query.current_trace.begin_resolve_type(@static_type, next_object, query.context) object_type = ResolveTypeStep.resolve_type(@static_type, next_object, query) if object_type.is_a?(Array) object_type, next_object = object_type end if @runner.resolves_lazies && @runner.lazy?(object_type) # TODO batch this object_type, next_object = sync(object_type) end ResolveTypeStep.assert_valid_resolved_type(@static_type, object_type, next_object, self) query.current_trace.end_resolve_type(@static_type, next_object, query.context, object_type) @runner.runtime_type_at[result] = object_type end next_objects_by_type[object_type] << next_object next_results_by_type[object_type] << result end next_objects_by_type.each do |obj_type, next_objects| query.current_trace.objects(obj_type, next_objects, ctx) @runner.add_step(SelectionsStep.new( path: path, field_resolve_step: self, parent_type: obj_type, selections: @next_selections, objects: next_objects, results: next_results_by_type[obj_type], runner: @runner, query: query, )) end else query.current_trace.objects(@static_type, @all_next_objects, ctx) @runner.add_step(SelectionsStep.new( path: path, field_resolve_step: self, parent_type: @static_type, selections: @next_selections, objects: @all_next_objects, results: @all_next_results, runner: @runner, query: query, )) end end end
Source
# File lib/graphql/execution/field_resolve_step.rb, line 209 def execute_field objects = @selections_step.objects if @arguments.is_a?(GraphQL::RuntimeError) build_errors_result(nil, @arguments) return end @results = @selections_step.results query = @selections_step.query ctx = query.context if (v = @field_definition.validators).any? # rubocop:disable Development/NoneWithoutBlockCop begin Schema::Validator.validate!(v, nil, ctx, @arguments) rescue GraphQL::RuntimeError => err build_errors_result(nil, err) return end end @field_definition.extras.each do |extra| case extra when :lookahead if @arguments.frozen? @arguments = @arguments.dup end @arguments[:lookahead] = Execution::Lookahead.new( query: query, ast_nodes: ast_nodes, field: @field_definition, ) when :ast_node if @arguments.frozen? @arguments = @arguments.dup end @arguments[:ast_node] = ast_node else raise ArgumentError, "This `extra` isn't supported yet: #{extra.inspect}. Open an issue on GraphQL-Ruby to add compatibility for it." end end if @field_definition.dynamic_introspection objects = @selections_step.graphql_objects.map { |o| @field_definition.owner.wrap(o, ctx) } end if @runner.authorizes?(@field_definition, ctx) @field_authorization_objects = objects @field_authorization_results = Array.new(objects.size) @field_authorization_next_idx = 0 if objects.size > 1 && !@runner.dataloader.is_a?(GraphQL::Dataloader::NullDataloader) # Enqueue this step once per object, so Dataloader sources loaded by `authorized?` batch across objects. # Each call authorizes the next object; see `authorize_next_field_object`. @field_authorization_pending = objects.size objects.size.times { @runner.dataloader.append_job(self) } return end objects.size.times { |idx| authorize_field_at(idx, ctx) } authorized_objects = collect_field_authorizations return if authorized_objects.nil? else authorized_objects = objects end execute_authorized_field(authorized_objects) rescue StandardError => err handle_execute_field_error(err) end
Source
# File lib/graphql/execution/field_resolve_step.rb, line 298 def finish_extensions ctx = @selections_step.query.context memos = @extended.memos || EmptyObjects::EMPTY_HASH while ext = @field_definition.extensions[@finish_extension_idx] # These two are hardcoded here because of how they need to interact with runtime metadata. # It would probably be better case ext when Schema::Field::ConnectionExtension conns = ctx.schema.connections lookahead = @arguments[:lookahead] # The memo omits the extension's extras (`:lookahead`) conn_arguments = memos[@finish_extension_idx] || @arguments @field_results.map!.each_with_index do |value, idx| object = @extended.object[idx] conn = conns.populate_connection(@field_definition, object, value, conn_arguments, ctx) if conn conn.was_authorized_by_scope_items = @was_scoped conn.lookahead ||= lookahead end conn rescue GraphQL::RuntimeError => err err end when Schema::Field::ScopeExtension if @was_scoped.nil? if (rt = @field_definition.type.unwrap).respond_to?(:scope_items) @was_scoped = true @field_results.map! { |v| v.nil? ? v : rt.scope_items(v, ctx) } else @was_scoped = false end end else memo = memos[@finish_extension_idx] @field_results = ext.after_resolve(objects: @extended.object, arguments: @extended.arguments, context: ctx, values: @field_results, memo: memo) # rubocop:disable Development/ContextIsPassedCop end @finish_extension_idx += 1 if any_lazy_results? @runner.dataloader.lazy_at_depth(path.size, self) return end end @finish_extension_idx = nil build_results end
# File lib/graphql/execution/field_resolve_step.rb, line 397 def finish_leaf_result(result_h, key, field_result, return_type, ctx) final_field_result = build_leaf_result(result_h, key, field_result, return_type, ctx, false) @directive_finalizers&.each { |f| @runner.add_finalizer(ctx.query, result_h, key, f) } result_h[@key] = final_field_result end
Source
# File lib/graphql/execution/field_resolve_step.rb, line 39 def path @path ||= [*@selections_step.path, @key].freeze end
Source
# File lib/graphql/execution/field_resolve_step.rb, line 141 def propagate_nulls propagating_null = true #: bool highest_nulled_depth = path.size highest_list_depth = nil #: Integer? current_field_step = self #: FieldResolveStep? while current_field_step && current_field_step.field_definition return_type = current_field_step.field_definition.type if propagating_null && return_type.non_null? highest_nulled_depth = current_field_step.path.size else propagating_null = false end if return_type.list? highest_list_depth = current_field_step.path.size end current_field_step = current_field_step.selections_step.field_resolve_step end if highest_list_depth.nil? || highest_nulled_depth <= highest_list_depth kill_field_step = self #: FieldResolveStep? while kill_field_step && kill_field_step.field_definition && highest_nulled_depth <= kill_field_step.path.size kill_field_step.selections_step.killed = true kill_field_step = kill_field_step.selections_step.field_resolve_step end end end
# File lib/graphql/execution/field_resolve_step.rb, line 517 def set_current_field(new_value = @field_definition) Fiber[:__graphql_current_field] = new_value end
Source
# File lib/graphql/execution/field_resolve_step.rb, line 71 def sync(lazy) if lazy.is_a?(Array) lazy.map! { |l| sync(l)} else @runner.schema.sync_lazy(lazy) end rescue GraphQL::UnauthorizedError => auth_err @runner.schema.unauthorized_object(auth_err) rescue GraphQL::ExecutionError => err err rescue StandardError => stderr begin @selections_step.query.handle_or_reraise(stderr, field: @field_definition, arguments: @arguments, object: nil) rescue GraphQL::ExecutionError => ex_err ex_err end end
Source
# File lib/graphql/execution/field_resolve_step.rb, line 58 def value return nil if @selections_step.killed query = @selections_step.query set_current_field query.current_trace.begin_execute_field(@field_definition, @field_results, @arguments, query) sync(@field_results) query.current_trace.end_execute_field(@field_definition, @field_results, @arguments, query, @field_results) @runner.add_step(self) true ensure set_current_field(nil) end