Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,7 @@ gem 'stackprof', platform: :ruby
gem 'pry'
gem 'pry-stack_explorer', platform: :ruby

if RUBY_VERSION >= "3.0"
gem "libev_scheduler"
gem "evt"
end

if RUBY_VERSION >= "3.2.0"
gem "minitest-mock"
gem "async", "~>2.0"
gem "minitest-mock"
end
Expand Down
2 changes: 0 additions & 2 deletions gemfiles/mongoid_8.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ gem "ruby-prof", platform: :ruby
gem "pry"
gem "pry-stack_explorer", platform: :ruby
gem "mongoid", "~> 8.0"
gem "libev_scheduler"
gem "evt"
gem "async"
gem "concurrent-ruby", "1.3.4"
gem "minitest-mock"
Expand Down
2 changes: 0 additions & 2 deletions gemfiles/mongoid_9.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ gem "ruby-prof", platform: :ruby
gem "pry"
gem "pry-stack_explorer", platform: :ruby
gem "mongoid", "~> 9.0"
gem "libev_scheduler"
gem "evt"
gem "async"
gem "minitest-mock"

Expand Down
2 changes: 0 additions & 2 deletions gemfiles/rails_7.2_postgresql.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ gem "pry-stack_explorer", platform: :ruby
gem "rails", "~> 7.2.0", require: "rails/all"
gem "pg", platform: :ruby
gem "sequel"
gem "evt"
gem "async"
gem "libev_scheduler"
gem "google-protobuf"

gemspec path: "../"
1 change: 0 additions & 1 deletion gemfiles/rails_8.0.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ gem "rails", "~> 8.0.0", require: "rails/all"
gem "sqlite3"
gem "pg", platform: :ruby
gem "sequel"
gem "evt"
gem "async"
gem "google-protobuf"
gem "minitest-mock"
Expand Down
1 change: 0 additions & 1 deletion gemfiles/rails_8.1.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ gem "rails", "~> 8.1.0", require: "rails/all"
gem "sqlite3"
gem "pg", platform: :ruby
gem "sequel"
gem "evt"
gem "async"
gem "google-protobuf"
gem "minitest-mock"
Expand Down
4 changes: 0 additions & 4 deletions gemfiles/rails_master.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ gem "rails", github: "rails/rails", require: "rails/all", ref: "main"
gem 'sqlite3'
gem 'pg'
gem "sequel"
gem "evt"
if RUBY_ENGINE == "ruby" # This doesn't work on truffle-ruby because there's no `IO::READABLE`
gem "libev_scheduler"
end
gem "async"
gem "google-protobuf"
gem "redis"
Expand Down
3 changes: 3 additions & 0 deletions lib/graphql.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ def self.eager_load!
class Error < StandardError
end

class RuntimeError < Error
end

# This error is raised when GraphQL-Ruby encounters a situation
# that it *thought* would never happen. Please report this bug!
class InvariantError < Error
Expand Down
48 changes: 26 additions & 22 deletions lib/graphql/execution/batching/field_compatibility.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,18 +68,22 @@ def resolve_batch(frs, objects, context, kwargs)
end

if @owner.method_defined?(@resolver_method)
frs.selections_step.graphql_objects.map do |obj_inst|
if dynamic_introspection
obj_inst = @owner.wrap(obj_inst, context)
end
with_extensions(obj_inst, kwargs, context) do |obj, ruby_kwargs|
if ruby_kwargs.empty?
obj.public_send(@resolver_method)
else
obj.public_send(@resolver_method, **ruby_kwargs)
results = []
frs.selections_step.graphql_objects.each_with_index do |obj_inst, idx|
if frs.object_is_authorized[idx]
if dynamic_introspection
obj_inst = @owner.wrap(obj_inst, context)
end
results << with_extensions(obj_inst, kwargs, context) do |obj, ruby_kwargs|
if ruby_kwargs.empty?
obj.public_send(@resolver_method)
else
obj.public_send(@resolver_method, **ruby_kwargs)
end
end
end
end
results
elsif @resolver_class
objects.map do |o|
resolver_inst_kwargs = kwargs.dup
Expand All @@ -106,17 +110,13 @@ def resolve_batch(frs, objects, context, kwargs)
new_return_value
end
end
rescue GraphQL::Error => err
err.path = frs.path
context.errors << err
nil
rescue RuntimeError => err
err
rescue StandardError => stderr
begin
context.query.handle_or_reraise(stderr)
rescue GraphQL::ExecutionError => ex_err
ex_err.path = frs.path
context.errors << ex_err
nil
ex_err
end
end
elsif objects.first.is_a?(Hash)
Expand All @@ -128,15 +128,19 @@ def resolve_batch(frs, objects, context, kwargs)
if extensions.empty?
objects.map { |o| o.public_send(@method_sym)}
else
frs.selections_step.graphql_objects.map do |obj_inst|
with_extensions(obj_inst, EmptyObjects::EMPTY_HASH, context) do |obj, arguments|
if arguments.empty?
obj.object.public_send(@method_sym)
else
obj.object.public_send(@method_sym, **arguments)
results = []
frs.selections_step.graphql_objects.each_with_index do |obj_inst, idx|
if frs.object_is_authorized[idx]
results << with_extensions(obj_inst, EmptyObjects::EMPTY_HASH, context) do |obj, arguments|
if arguments.empty?
obj.object.public_send(@method_sym)
else
obj.object.public_send(@method_sym, **arguments)
end
end
end
end
results
end
end
end
Expand Down
93 changes: 70 additions & 23 deletions lib/graphql/execution/batching/field_resolve_step.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ def initialize(parent_type:, runner:, key:, selections_step:)
@ast_node = @ast_nodes = nil
@runner = runner
@field_definition = nil
@arguments = nil
@field_results = nil
@path = nil
@enqueued_authorization = false
Expand All @@ -18,9 +19,10 @@ def initialize(parent_type:, runner:, key:, selections_step:)
@all_next_results = nil
@static_type = nil
@next_selections = nil
@object_is_authorized = nil
end

attr_reader :ast_node, :key, :parent_type, :selections_step, :runner, :field_definition
attr_reader :ast_node, :key, :parent_type, :selections_step, :runner, :field_definition, :object_is_authorized, :arguments

def path
@path ||= [*@selections_step.path, @key].freeze
Expand Down Expand Up @@ -103,7 +105,16 @@ def coerce_argument_value(arg_t, arg_value)
arg_value.map { |v| coerce_argument_value(inner_t, v) }
end
elsif arg_t.kind.leaf?
arg_t.coerce_input(arg_value, @selections_step.query.context)
begin
ctx = @selections_step.query.context
arg_t.coerce_input(arg_value, ctx)
rescue GraphQL::UnauthorizedEnumValueError => enum_err
begin
@runner.schema.unauthorized_object(enum_err)
rescue GraphQL::ExecutionError => ex_err
ex_err
end
end
elsif arg_t.kind.input_object?
coerce_arguments(arg_t, arg_value)
else
Expand All @@ -124,11 +135,9 @@ def sync(lazy)
else
@runner.schema.sync_lazy(lazy)
end
rescue GraphQL::UnauthorizedError => err
@runner.schema.unauthorized_object(err)
rescue GraphQL::UnauthorizedError => auth_err
@runner.schema.unauthorized_object(auth_err)
rescue GraphQL::ExecutionError => err
err.path = path
err.ast_nodes = ast_nodes
err
end

Expand All @@ -142,19 +151,51 @@ def call
end
end

def add_graphql_error(err)
err.path = path
err.ast_nodes = ast_nodes
@selections_step.query.context.add_error(err)
err
end

module AlwaysAuthorized
def self.[](_key)
true
end
end

def execute_field
field_name = @ast_node.name
@field_definition = @selections_step.query.get_field(@parent_type, field_name) || raise("Invariant: no field found for #{@parent_type.to_type_signature}.#{ast_node.name}")
objects = @selections_step.objects
if field_name == "__typename"
# TODO handle custom introspection
@field_results = Array.new(objects.size, @parent_type.graphql_name)
@object_is_authorized = AlwaysAuthorized
build_results
return
end

arguments = coerce_arguments(@field_definition, @ast_node.arguments) # rubocop:disable Development/ContextIsPassedCop
@arguments = coerce_arguments(@field_definition, @ast_node.arguments) # rubocop:disable Development/ContextIsPassedCop

@field_results = @field_definition.resolve_batch(self, objects, @selections_step.query.context, arguments)

ctx = @selections_step.query.context

if (@runner.authorizes.fetch(@field_definition) { @runner.authorizes[@field_definition] = @field_definition.authorizes?(ctx) })
authorized_objects = []
@object_is_authorized = objects.map { |o|
is_authed = @field_definition.authorized?(o, @arguments, ctx)
if is_authed
authorized_objects << o
end
is_authed
}
else
authorized_objects = objects
@object_is_authorized = AlwaysAuthorized
end

@field_results = @field_definition.resolve_batch(self, authorized_objects, ctx, @arguments)

if @runner.resolves_lazies # TODO extract this
lazies = false
Expand Down Expand Up @@ -205,8 +246,14 @@ def build_results
is_list = return_type.list?
is_non_null = return_type.non_null?
results = @selections_step.results
@field_results.each_with_index do |result, i|
result_h = results[i]
field_result_idx = 0
results.each_with_index do |result_h, i|
if @object_is_authorized[i]
result = @field_results[field_result_idx]
field_result_idx += 1
else
result = nil
end
build_graphql_result(result_h, @key, result, return_type, is_non_null, is_list, false)
end
@enqueued_authorization = true
Expand All @@ -219,22 +266,25 @@ def build_results
else
results = @selections_step.results
ctx = @selections_step.query.context
@field_results.each_with_index do |result, i|
result_h = results[i]
result_h[@key] = if result.nil?
field_result_idx = 0
results.each_with_index do |result_h, i|
if @object_is_authorized[i]
field_result = @field_results[field_result_idx]
field_result_idx += 1
else
field_result = nil
end
result_h[@key] = if field_result.nil?
if return_type.non_null?
add_non_null_error(false)
else
nil
end
elsif result.is_a?(GraphQL::Error)
result.path = path
result.ast_nodes = ast_nodes
ctx.add_error(result)
result
elsif field_result.is_a?(GraphQL::Error)
add_graphql_error(field_result)
else
# TODO `nil`s in [T!] types aren't handled
return_type.coerce_result(result, ctx)
return_type.coerce_result(field_result, ctx)
end
end
end
Expand Down Expand Up @@ -308,10 +358,7 @@ def build_graphql_result(graphql_result, key, field_result, return_type, is_nn,
graphql_result[key] = nil
end
elsif field_result.is_a?(GraphQL::Error)
field_result.path = path
field_result.ast_nodes = ast_nodes
@selections_step.query.context.add_error(field_result)
graphql_result[key] = field_result
graphql_result[key] = add_graphql_error(field_result)
elsif is_list
if is_nn
return_type = return_type.of_type
Expand Down
Loading
Loading