Skip to content

Commit be42b03

Browse files
authored
Merge branch 'master' into exec-next-fixes
2 parents de18ad2 + fa35e79 commit be42b03

47 files changed

Lines changed: 131318 additions & 635 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

benchmark/run.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,19 @@ def self.run(task)
4040
x.report("validate - abstract fragments 2") { CARD_SCHEMA.validate(ABSTRACT_FRAGMENTS_2) }
4141
x.report("validate - big query") { BIG_SCHEMA.validate(BIG_QUERY) }
4242
x.report("validate - fields will merge") { FIELDS_WILL_MERGE_SCHEMA.validate(FIELDS_WILL_MERGE_QUERY) }
43+
when "validate_profile"
44+
profile_schemas = [CARD_SCHEMA, BIG_SCHEMA, FIELDS_WILL_MERGE_SCHEMA].map do |s|
45+
ps = Class.new(s)
46+
ps.use(GraphQL::Schema::Visibility, profiles: { default: {} })
47+
ps.validate(GraphQL.parse("{ __typename }"), context: { visibility_profile: :default })
48+
ps
49+
end
50+
ctx = { visibility_profile: :default }
51+
x.report("validate (visibility profile) - introspection ") { profile_schemas[0].validate(DOCUMENT, context: ctx) }
52+
x.report("validate (visibility profile) - abstract fragments") { profile_schemas[0].validate(ABSTRACT_FRAGMENTS, context: ctx) }
53+
x.report("validate (visibility profile) - abstract fragments 2") { profile_schemas[0].validate(ABSTRACT_FRAGMENTS_2, context: ctx) }
54+
x.report("validate (visibility profile) - big query") { profile_schemas[1].validate(BIG_QUERY, context: ctx) }
55+
x.report("validate (visibility profile) - fields will merge") { profile_schemas[2].validate(FIELDS_WILL_MERGE_QUERY, context: ctx) }
4356
when "scan"
4457
require "graphql/c_parser"
4558
x.report("scan c - introspection") { GraphQL.scan_with_c(QUERY_STRING) }

javascript_client/package-lock.json

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/graphql/execution.rb

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@
1010
module GraphQL
1111
module Execution
1212
# @api private
13-
class Skip < GraphQL::Error; end
13+
class Skip < GraphQL::RuntimeError
14+
attr_accessor :path
15+
def ast_nodes=(_ignored); end
1416

15-
# Just a singleton for implementing {Query::Context#skip}
16-
# @api private
17-
SKIP = Skip.new
17+
def assign_graphql_result(query, result_data, key)
18+
result_data.delete(key)
19+
end
20+
end
1821
end
1922
end

lib/graphql/execution/interpreter.rb

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,6 @@ def run_all(schema, query_options, context: {}, max_complexity: schema.max_compl
5858
# Do as much eager evaluation of the query as possible
5959
results = []
6060
queries.each_with_index do |query, idx|
61-
if query.subscription? && !query.subscription_update?
62-
subs_namespace = query.context.namespace(:subscriptions)
63-
subs_namespace[:events] = []
64-
subs_namespace[:subscriptions] = {}
65-
end
6661
multiplex.dataloader.append_job {
6762
operation = query.selected_operation
6863
result = if operation.nil? || !query.valid? || !query.context.errors.empty?
@@ -74,7 +69,9 @@ def run_all(schema, query_options, context: {}, max_complexity: schema.max_compl
7469
# in particular, assign it here:
7570
runtime = Runtime.new(query: query)
7671
query.context.namespace(:interpreter_runtime)[:runtime] = runtime
77-
72+
if query.subscription? && !query.subscription_update?
73+
schema.subscriptions.initialize_subscriptions(query)
74+
end
7875
query.current_trace.execute_query(query: query) do
7976
runtime.run_eager
8077
end
@@ -91,9 +88,6 @@ def run_all(schema, query_options, context: {}, max_complexity: schema.max_compl
9188
# Then, find all errors and assign the result to the query object
9289
results.each_with_index do |data_result, idx|
9390
query = queries[idx]
94-
if (events = query.context.namespace(:subscriptions)[:events]) && !events.empty?
95-
schema.subscriptions.write_subscription(query, events)
96-
end
9791
# Assign the result so that it can be accessed in instrumentation
9892
query.result_values = if data_result.equal?(NO_OPERATION)
9993
if !query.valid? || !query.context.errors.empty?
@@ -103,6 +97,9 @@ def run_all(schema, query_options, context: {}, max_complexity: schema.max_compl
10397
data_result
10498
end
10599
else
100+
if query.subscription?
101+
schema.subscriptions.finish_subscriptions(query)
102+
end
106103
result = {}
107104

108105
if !query.context.errors.empty?

lib/graphql/execution/interpreter/runtime.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,7 @@ def continue_value(value, field, is_non_null, ast_node, result_name, selection_r
603603
err
604604
end
605605
continue_value(next_value, field, is_non_null, ast_node, result_name, selection_result)
606-
elsif GraphQL::Execution::SKIP == value
606+
elsif value.is_a?(GraphQL::Execution::Skip)
607607
# It's possible a lazy was already written here
608608
case selection_result
609609
when GraphQLResultHash

lib/graphql/execution/lazy.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def value
3838
# (fewer clauses in a hot `case` block), but now it requires special handling here.
3939
# I think it's still worth it for the performance win, but if the number of special
4040
# cases grows, then maybe it's worth rethinking somehow.
41-
if @value.is_a?(StandardError) && @value != GraphQL::Execution::SKIP
41+
if @value.is_a?(StandardError) && !@value.is_a?(GraphQL::Execution::Skip)
4242
raise @value
4343
else
4444
@value

lib/graphql/execution/next.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,16 @@ def self.use(schema, authorization: true)
5252

5353
def self.run_all(schema, query_options, context: {}, max_complexity: schema.max_complexity)
5454
queries = query_options.map do |opts|
55-
case opts
55+
query = case opts
5656
when Hash
5757
schema.query_class.new(schema, nil, **opts)
5858
when GraphQL::Query, GraphQL::Query::Partial
5959
opts
6060
else
6161
raise "Expected Hash or GraphQL::Query, not #{opts.class} (#{opts.inspect})"
6262
end
63+
query.context[:__graphql_execute_next] = true
64+
query
6365
end
6466
multiplex = Execution::Multiplex.new(schema: schema, queries: queries, context: context, max_complexity: max_complexity)
6567
runner = Runner.new(multiplex, **schema.execution_next_options)

lib/graphql/execution/next/field_resolve_step.rb

Lines changed: 89 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -48,59 +48,86 @@ def append_selection(ast_node)
4848
nil
4949
end
5050

51-
def coerce_arguments(argument_owner, ast_arguments_or_hash)
51+
def coerce_arguments(argument_owner, ast_arguments_or_hash, run_loads = true)
5252
arg_defns = @selections_step.query.types.arguments(argument_owner)
5353
if arg_defns.empty?
5454
return EmptyObjects::EMPTY_HASH
5555
end
5656
args_hash = {}
57-
if ast_arguments_or_hash.is_a?(Hash)
58-
ast_arguments_or_hash.each do |key, value|
59-
key_s = nil
60-
arg_defn = arg_defns.find { |a|
61-
a.keyword == key || a.graphql_name == (key_s ||= String(key))
62-
}
63-
coerce_argument_value(args_hash, arg_defn, value)
64-
end
65-
else
66-
ast_arguments_or_hash.each { |arg_node|
67-
arg_defn = arg_defns.find { |ad| ad.graphql_name == arg_node.name }
68-
coerce_argument_value(args_hash, arg_defn, arg_node.value)
69-
}
57+
58+
if ast_arguments_or_hash.nil? # This can happen with `.trigger`
59+
return args_hash
7060
end
71-
# TODO refactor the loop above into this one
72-
arg_defns.each do |arg_defn|
73-
if arg_defn.default_value? && !args_hash.key?(arg_defn.keyword)
74-
coerce_argument_value(args_hash, arg_defn, arg_defn.default_value)
61+
62+
arg_inputs_are_h = ast_arguments_or_hash.is_a?(Hash)
63+
64+
arg_defns.each do |arg_graphql_name, arg_defn|
65+
arg_value = nil
66+
was_found = false
67+
if arg_inputs_are_h
68+
ast_arguments_or_hash.each do |key, value|
69+
if key == arg_defn.keyword || key.to_s == arg_defn.graphql_name
70+
arg_value = value
71+
was_found = true
72+
break
73+
end
74+
end
75+
else
76+
ast_arguments_or_hash.each do |arg_node|
77+
if arg_node.name == arg_defn.graphql_name
78+
arg_value = arg_node.value
79+
was_found = true
80+
break
81+
end
82+
end
83+
end
84+
85+
if arg_value.is_a?(Language::Nodes::VariableIdentifier)
86+
vars = @selections_step.query.variables
87+
arg_value = if vars.key?(arg_value.name)
88+
vars[arg_value.name]
89+
elsif vars.key?(arg_value.name.to_sym)
90+
vars[arg_value.name.to_sym]
91+
else
92+
was_found = false
93+
nil
94+
end
95+
end
96+
97+
if !was_found && arg_defn.default_value?
98+
was_found = true
99+
arg_value = arg_defn.default_value
100+
end
101+
102+
if was_found
103+
coerce_argument_value(args_hash, arg_defn, arg_value, run_loads)
75104
end
76105
end
77106

78107
args_hash
79108
end
80109

81-
def coerce_argument_value(arguments, arg_defn, arg_value, target_keyword: arg_defn.keyword, as_type: nil)
110+
def coerce_argument_value(arguments, arg_defn, arg_value, run_loads, target_keyword: run_loads ? arg_defn.keyword : arg_defn.graphql_name, as_type: nil)
82111
arg_t = as_type || arg_defn.type
83112
if arg_t.non_null?
84113
arg_t = arg_t.of_type
85114
end
86115

87-
arg_value = if arg_value.is_a?(Language::Nodes::VariableIdentifier)
116+
if arg_value.is_a?(Language::Nodes::VariableIdentifier)
88117
vars = @selections_step.query.variables
89-
if vars.key?(arg_value.name)
118+
arg_value = if vars.key?(arg_value.name)
90119
vars[arg_value.name]
91120
elsif vars.key?(arg_value.name.to_sym)
92121
vars[arg_value.name.to_sym]
93122
else
94-
return # not present
123+
nil
95124
end
96-
elsif arg_value.is_a?(Language::Nodes::NullValue)
97-
nil
125+
end
126+
127+
if arg_value.is_a?(Language::Nodes::NullValue)
128+
arg_value = nil
98129
elsif arg_value.is_a?(Language::Nodes::Enum)
99-
arg_value.name
100-
elsif arg_value.is_a?(Language::Nodes::InputObject)
101-
arg_value.arguments # rubocop:disable Development/ContextIsPassedCop
102-
else
103-
arg_value
130+
arg_value = arg_value.name
104131
end
105132

106133
ctx = @selections_step.query.context
@@ -111,7 +138,7 @@ def coerce_argument_value(arguments, arg_defn, arg_value, target_keyword: arg_de
111138
arg_value = Array(arg_value)
112139
inner_t = arg_t.of_type
113140
result = Array.new(arg_value.size)
114-
arg_value.each_with_index { |v, i| coerce_argument_value(result, arg_defn, v, target_keyword: i, as_type: inner_t) }
141+
arg_value.each_with_index { |v, i| coerce_argument_value(result, arg_defn, v, run_loads, target_keyword: i, as_type: inner_t) }
115142
result
116143
end
117144
elsif arg_t.kind.leaf?
@@ -125,7 +152,8 @@ def coerce_argument_value(arguments, arg_defn, arg_value, target_keyword: arg_de
125152
end
126153
end
127154
elsif arg_t.kind.input_object?
128-
input_obj_args = coerce_arguments(arg_t, arg_value)
155+
input_obj_vals = arg_value.is_a?(Language::Nodes::InputObject) ? arg_value.arguments : arg_value # rubocop:disable Development/ContextIsPassedCop
156+
input_obj_args = coerce_arguments(arg_t, input_obj_vals)
129157
arg_t.new(nil, ruby_kwargs: input_obj_args, context: @selections_step.query.context, defaults_used: nil)
130158
else
131159
raise "Unsupported argument value: #{arg_t.to_type_signature} / #{arg_value.class} (#{arg_value.inspect})"
@@ -145,7 +173,7 @@ def coerce_argument_value(arguments, arg_defn, arg_value, target_keyword: arg_de
145173

146174
if arg_value.is_a?(GraphQL::Error)
147175
@arguments = arg_value
148-
elsif arg_defn.loads && as_type.nil? && !arg_value.nil?
176+
elsif run_loads && arg_defn.loads && as_type.nil? && !arg_value.nil?
149177
# This is for legacy compat:
150178
load_receiver = if (r = @field_definition.resolver)
151179
r.new(field: @field_definition, context: @selections_step.query.context, object: nil)
@@ -256,7 +284,7 @@ def build_arguments
256284
@arguments ||= arguments # may have already been set to an error
257285

258286
if (@pending_steps.nil? || @pending_steps.size == 0) &&
259-
@field_results.nil? # Make sure this wasn't continue via inline dataloader execution
287+
@field_results.nil? # Make sure the arguments flow didn't already call through
260288
execute_field
261289
end
262290
end
@@ -316,6 +344,14 @@ def execute_field
316344
is_authed = @field_definition.authorized?(o, @arguments, ctx)
317345
if is_authed
318346
authorized_objects << o
347+
else
348+
begin
349+
err = GraphQL::UnauthorizedFieldError.new(object: o, type: @parent_type, context: ctx, field: @field_definition)
350+
authorized_objects << query.schema.unauthorized_object(err)
351+
is_authed = true
352+
rescue GraphQL::ExecutionError => exec_err
353+
add_graphql_error(exec_err)
354+
end
319355
end
320356
is_authed
321357
}
@@ -609,29 +645,35 @@ def resolve_batch(objects, context, args_hash)
609645
method_receiver = @field_definition.dynamic_introspection ? @field_definition.owner : @parent_type
610646
case @field_definition.execution_next_mode
611647
when :resolve_batch
612-
if args_hash.empty?
613-
method_receiver.public_send(@field_definition.execution_next_mode_key, objects, context)
614-
else
648+
begin
615649
method_receiver.public_send(@field_definition.execution_next_mode_key, objects, context, **args_hash)
650+
rescue GraphQL::ExecutionError => exec_err
651+
Array.new(objects.size, exec_err)
616652
end
617653
when :resolve_static
618-
result = if args_hash.empty?
619-
method_receiver.public_send(@field_definition.execution_next_mode_key, context)
620-
else
621-
method_receiver.public_send(@field_definition.execution_next_mode_key, context, **args_hash)
622-
end
654+
result = method_receiver.public_send(@field_definition.execution_next_mode_key, context, **args_hash)
623655
Array.new(objects.size, result)
624656
when :resolve_each
625-
if args_hash.empty?
626-
objects.map { |o| method_receiver.public_send(@field_definition.execution_next_mode_key, o, context) }
627-
else
628-
objects.map { |o| method_receiver.public_send(@field_definition.execution_next_mode_key, o, context, **args_hash) }
657+
objects.map do |o|
658+
method_receiver.public_send(@field_definition.execution_next_mode_key, o, context, **args_hash)
659+
rescue GraphQL::ExecutionError => err
660+
err
629661
end
630662
when :hash_key
631663
objects.map { |o| o[@field_definition.execution_next_mode_key] }
632664
when :direct_send
633665
if args_hash.empty?
634-
objects.map { |o| o.public_send(@field_definition.execution_next_mode_key) }
666+
objects.map do |o|
667+
o.public_send(@field_definition.execution_next_mode_key)
668+
rescue GraphQL::ExecutionError => err
669+
err
670+
rescue StandardError => stderr
671+
begin
672+
@selections_step.query.handle_or_reraise(stderr)
673+
rescue GraphQL::ExecutionError => ex_err
674+
ex_err
675+
end
676+
end
635677
else
636678
objects.map { |o| o.public_send(@field_definition.execution_next_mode_key, **args_hash) }
637679
end
@@ -677,19 +719,14 @@ def resolve_batch(objects, context, args_hash)
677719
if @field_definition.dynamic_introspection
678720
obj_inst = @owner.wrap(obj_inst, context)
679721
end
680-
if args_hash.empty?
681-
obj_inst.public_send(@field_definition.execution_next_mode_key)
682-
else
683-
obj_inst.public_send(@field_definition.execution_next_mode_key, **args_hash)
684-
end
722+
obj_inst.public_send(@field_definition.execution_next_mode_key, **args_hash)
685723
rescue GraphQL::ExecutionError => exec_err
686724
exec_err
687725
end
688726
else
689727
raise "Batching execution for #{path} not implemented (execution_next_mode: #{@execution_next_mode.inspect}); provide `resolve_static:`, `resolve_batch:`, `hash_key:`, `method:`, or use a compatibility plug-in"
690728
end
691729
end
692-
693730
end
694731

695732
class RawValueFieldResolveStep < FieldResolveStep

0 commit comments

Comments
 (0)