graphql 1.11.6 → 1.12.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of graphql might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/lib/generators/graphql/install_generator.rb +5 -5
- data/lib/generators/graphql/object_generator.rb +2 -0
- data/lib/generators/graphql/relay_generator.rb +63 -0
- data/lib/generators/graphql/templates/base_connection.erb +8 -0
- data/lib/generators/graphql/templates/base_edge.erb +8 -0
- data/lib/generators/graphql/templates/node_type.erb +9 -0
- data/lib/generators/graphql/templates/object.erb +1 -1
- data/lib/generators/graphql/templates/query_type.erb +1 -3
- data/lib/generators/graphql/templates/schema.erb +8 -35
- data/lib/graphql.rb +39 -4
- data/lib/graphql/analysis/analyze_query.rb +7 -0
- data/lib/graphql/analysis/ast.rb +11 -2
- data/lib/graphql/analysis/ast/visitor.rb +9 -1
- data/lib/graphql/backtrace.rb +28 -19
- data/lib/graphql/backtrace/legacy_tracer.rb +56 -0
- data/lib/graphql/backtrace/table.rb +22 -2
- data/lib/graphql/backtrace/tracer.rb +40 -9
- data/lib/graphql/backwards_compatibility.rb +2 -1
- data/lib/graphql/base_type.rb +1 -1
- data/lib/graphql/compatibility/execution_specification.rb +1 -0
- data/lib/graphql/compatibility/lazy_execution_specification.rb +2 -0
- data/lib/graphql/compatibility/query_parser_specification.rb +2 -0
- data/lib/graphql/compatibility/schema_parser_specification.rb +2 -0
- data/lib/graphql/dataloader.rb +198 -0
- data/lib/graphql/dataloader/null_dataloader.rb +21 -0
- data/lib/graphql/dataloader/request.rb +24 -0
- data/lib/graphql/dataloader/request_all.rb +22 -0
- data/lib/graphql/dataloader/source.rb +93 -0
- data/lib/graphql/define/assign_global_id_field.rb +1 -1
- data/lib/graphql/define/instance_definable.rb +32 -2
- data/lib/graphql/define/type_definer.rb +5 -5
- data/lib/graphql/deprecated_dsl.rb +7 -2
- data/lib/graphql/deprecation.rb +13 -0
- data/lib/graphql/enum_type.rb +2 -0
- data/lib/graphql/execution/errors.rb +4 -0
- data/lib/graphql/execution/execute.rb +7 -0
- data/lib/graphql/execution/interpreter.rb +10 -6
- data/lib/graphql/execution/interpreter/arguments.rb +57 -5
- data/lib/graphql/execution/interpreter/arguments_cache.rb +8 -0
- data/lib/graphql/execution/interpreter/handles_raw_value.rb +0 -7
- data/lib/graphql/execution/interpreter/runtime.rb +219 -117
- data/lib/graphql/execution/multiplex.rb +20 -6
- data/lib/graphql/function.rb +4 -0
- data/lib/graphql/input_object_type.rb +2 -0
- data/lib/graphql/integer_decoding_error.rb +17 -0
- data/lib/graphql/interface_type.rb +3 -1
- data/lib/graphql/internal_representation/document.rb +2 -2
- data/lib/graphql/internal_representation/rewrite.rb +1 -1
- data/lib/graphql/invalid_null_error.rb +1 -1
- data/lib/graphql/language/document_from_schema_definition.rb +50 -23
- data/lib/graphql/object_type.rb +2 -0
- data/lib/graphql/pagination/connection.rb +5 -1
- data/lib/graphql/pagination/connections.rb +6 -16
- data/lib/graphql/query.rb +6 -1
- data/lib/graphql/query/arguments.rb +1 -1
- data/lib/graphql/query/context.rb +8 -1
- data/lib/graphql/query/serial_execution.rb +1 -0
- data/lib/graphql/query/validation_pipeline.rb +1 -1
- data/lib/graphql/relay/array_connection.rb +2 -2
- data/lib/graphql/relay/base_connection.rb +7 -0
- data/lib/graphql/relay/connection_instrumentation.rb +4 -4
- data/lib/graphql/relay/connection_type.rb +1 -1
- data/lib/graphql/relay/mutation.rb +1 -0
- data/lib/graphql/relay/node.rb +3 -0
- data/lib/graphql/relay/type_extensions.rb +2 -0
- data/lib/graphql/scalar_type.rb +2 -0
- data/lib/graphql/schema.rb +80 -29
- data/lib/graphql/schema/argument.rb +25 -7
- data/lib/graphql/schema/build_from_definition.rb +139 -51
- data/lib/graphql/schema/default_type_error.rb +2 -0
- data/lib/graphql/schema/directive.rb +76 -0
- data/lib/graphql/schema/directive/flagged.rb +57 -0
- data/lib/graphql/schema/enum.rb +3 -0
- data/lib/graphql/schema/enum_value.rb +12 -6
- data/lib/graphql/schema/field.rb +50 -22
- data/lib/graphql/schema/field/connection_extension.rb +3 -2
- data/lib/graphql/schema/field/scope_extension.rb +1 -1
- data/lib/graphql/schema/input_object.rb +33 -22
- data/lib/graphql/schema/interface.rb +1 -0
- data/lib/graphql/schema/member.rb +4 -0
- data/lib/graphql/schema/member/base_dsl_methods.rb +1 -0
- data/lib/graphql/schema/member/build_type.rb +3 -3
- data/lib/graphql/schema/member/has_arguments.rb +67 -50
- data/lib/graphql/schema/member/has_deprecation_reason.rb +25 -0
- data/lib/graphql/schema/member/has_directives.rb +98 -0
- data/lib/graphql/schema/member/has_validators.rb +31 -0
- data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
- data/lib/graphql/schema/middleware_chain.rb +1 -1
- data/lib/graphql/schema/object.rb +11 -0
- data/lib/graphql/schema/printer.rb +5 -4
- data/lib/graphql/schema/relay_classic_mutation.rb +1 -1
- data/lib/graphql/schema/resolver.rb +7 -0
- data/lib/graphql/schema/resolver/has_payload_type.rb +2 -0
- data/lib/graphql/schema/subscription.rb +19 -1
- data/lib/graphql/schema/timeout_middleware.rb +3 -1
- data/lib/graphql/schema/validation.rb +4 -2
- data/lib/graphql/schema/validator.rb +163 -0
- data/lib/graphql/schema/validator/exclusion_validator.rb +31 -0
- data/lib/graphql/schema/validator/format_validator.rb +49 -0
- data/lib/graphql/schema/validator/inclusion_validator.rb +33 -0
- data/lib/graphql/schema/validator/length_validator.rb +57 -0
- data/lib/graphql/schema/validator/numericality_validator.rb +71 -0
- data/lib/graphql/schema/validator/required_validator.rb +68 -0
- data/lib/graphql/static_validation.rb +1 -0
- data/lib/graphql/static_validation/all_rules.rb +1 -0
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +25 -17
- data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +30 -0
- data/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb +30 -0
- data/lib/graphql/static_validation/validation_timeout_error.rb +25 -0
- data/lib/graphql/static_validation/validator.rb +32 -9
- data/lib/graphql/subscriptions.rb +17 -20
- data/lib/graphql/subscriptions/subscription_root.rb +1 -1
- data/lib/graphql/tracing.rb +2 -2
- data/lib/graphql/tracing/appoptics_tracing.rb +3 -1
- data/lib/graphql/tracing/platform_tracing.rb +4 -2
- data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +4 -1
- data/lib/graphql/tracing/skylight_tracing.rb +1 -1
- data/lib/graphql/types/int.rb +9 -2
- data/lib/graphql/types/relay.rb +11 -3
- data/lib/graphql/types/relay/base_connection.rb +2 -91
- data/lib/graphql/types/relay/base_edge.rb +2 -34
- data/lib/graphql/types/relay/connection_behaviors.rb +123 -0
- data/lib/graphql/types/relay/default_relay.rb +27 -0
- data/lib/graphql/types/relay/edge_behaviors.rb +42 -0
- data/lib/graphql/types/relay/has_node_field.rb +41 -0
- data/lib/graphql/types/relay/has_nodes_field.rb +41 -0
- data/lib/graphql/types/relay/node.rb +2 -4
- data/lib/graphql/types/relay/node_behaviors.rb +15 -0
- data/lib/graphql/types/relay/node_field.rb +1 -19
- data/lib/graphql/types/relay/nodes_field.rb +1 -19
- data/lib/graphql/types/relay/page_info.rb +2 -14
- data/lib/graphql/types/relay/page_info_behaviors.rb +25 -0
- data/lib/graphql/types/string.rb +7 -1
- data/lib/graphql/union_type.rb +2 -0
- data/lib/graphql/upgrader/member.rb +1 -0
- data/lib/graphql/upgrader/schema.rb +1 -0
- data/lib/graphql/version.rb +1 -1
- data/readme.md +1 -1
- metadata +53 -9
- data/lib/graphql/types/relay/base_field.rb +0 -22
- data/lib/graphql/types/relay/base_interface.rb +0 -29
- data/lib/graphql/types/relay/base_object.rb +0 -26
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
class Dataloader
|
5
|
+
class Source
|
6
|
+
# @api private
|
7
|
+
attr_reader :results
|
8
|
+
|
9
|
+
# Called by {Dataloader} to prepare the {Source}'s internal state
|
10
|
+
# @api private
|
11
|
+
def setup(dataloader)
|
12
|
+
@pending_keys = []
|
13
|
+
@results = {}
|
14
|
+
@dataloader = dataloader
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :dataloader
|
18
|
+
|
19
|
+
# @return [Dataloader::Request] a pending request for a value from `key`. Call `.load` on that object to wait for the result.
|
20
|
+
def request(key)
|
21
|
+
if !@results.key?(key)
|
22
|
+
@pending_keys << key
|
23
|
+
end
|
24
|
+
Dataloader::Request.new(self, key)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [Dataloader::Request] a pending request for a values from `keys`. Call `.load` on that object to wait for the results.
|
28
|
+
def request_all(keys)
|
29
|
+
pending_keys = keys.select { |k| !@results.key?(k) }
|
30
|
+
@pending_keys.concat(pending_keys)
|
31
|
+
Dataloader::RequestAll.new(self, keys)
|
32
|
+
end
|
33
|
+
|
34
|
+
# @param key [Object] A loading key which will be passed to {#fetch} if it isn't already in the internal cache.
|
35
|
+
# @return [Object] The result from {#fetch} for `key`. If `key` hasn't been loaded yet, the Fiber will yield until it's loaded.
|
36
|
+
def load(key)
|
37
|
+
if @results.key?(key)
|
38
|
+
@results[key]
|
39
|
+
else
|
40
|
+
@pending_keys << key
|
41
|
+
sync
|
42
|
+
@results[key]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# @param keys [Array<Object>] Loading keys which will be passed to `#fetch` (or read from the internal cache).
|
47
|
+
# @return [Object] The result from {#fetch} for `keys`. If `keys` haven't been loaded yet, the Fiber will yield until they're loaded.
|
48
|
+
def load_all(keys)
|
49
|
+
if keys.any? { |k| !@results.key?(k) }
|
50
|
+
pending_keys = keys.select { |k| !@results.key?(k) }
|
51
|
+
@pending_keys.concat(pending_keys)
|
52
|
+
sync
|
53
|
+
end
|
54
|
+
|
55
|
+
keys.map { |k| @results[k] }
|
56
|
+
end
|
57
|
+
|
58
|
+
# Subclasses must implement this method to return a value for each of `keys`
|
59
|
+
# @param keys [Array<Object>] keys passed to {#load}, {#load_all}, {#request}, or {#request_all}
|
60
|
+
# @return [Array<Object>] A loaded value for each of `keys`. The array must match one-for-one to the list of `keys`.
|
61
|
+
def fetch(keys)
|
62
|
+
# somehow retrieve these from the backend
|
63
|
+
raise "Implement `#{self.class}#fetch(#{keys.inspect}) to return a record for each of the keys"
|
64
|
+
end
|
65
|
+
|
66
|
+
# Wait for a batch, if there's anything to batch.
|
67
|
+
# Then run the batch and update the cache.
|
68
|
+
# @return [void]
|
69
|
+
def sync
|
70
|
+
@dataloader.yield
|
71
|
+
end
|
72
|
+
|
73
|
+
# @return [Boolean] True if this source has any pending requests for data.
|
74
|
+
def pending?
|
75
|
+
@pending_keys.any?
|
76
|
+
end
|
77
|
+
|
78
|
+
# Called by {GraphQL::Dataloader} to resolve and pending requests to this source.
|
79
|
+
# @api private
|
80
|
+
# @return [void]
|
81
|
+
def run_pending_keys
|
82
|
+
return if @pending_keys.empty?
|
83
|
+
fetch_keys = @pending_keys.uniq
|
84
|
+
@pending_keys = []
|
85
|
+
results = fetch(fetch_keys)
|
86
|
+
fetch_keys.each_with_index do |key, idx|
|
87
|
+
@results[key] = results[idx]
|
88
|
+
end
|
89
|
+
nil
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -4,7 +4,7 @@ module GraphQL
|
|
4
4
|
module AssignGlobalIdField
|
5
5
|
def self.call(type_defn, field_name, **field_kwargs)
|
6
6
|
resolve = GraphQL::Relay::GlobalIdResolve.new(type: type_defn)
|
7
|
-
GraphQL::Define::AssignObjectField.call(type_defn, field_name, **field_kwargs, type: GraphQL::
|
7
|
+
GraphQL::Define::AssignObjectField.call(type_defn, field_name, **field_kwargs, type: GraphQL::DEPRECATED_ID_TYPE.to_non_null_type, resolve: resolve)
|
8
8
|
end
|
9
9
|
end
|
10
10
|
end
|
@@ -3,6 +3,23 @@ module GraphQL
|
|
3
3
|
module Define
|
4
4
|
# @api deprecated
|
5
5
|
module InstanceDefinable
|
6
|
+
module DeprecatedDefine
|
7
|
+
def define(**kwargs, &block)
|
8
|
+
deprecated_caller = caller(1, 1).first
|
9
|
+
if deprecated_caller.include?("lib/graphql")
|
10
|
+
deprecated_caller = caller(2, 10).find { |c| !c.include?("lib/graphql") }
|
11
|
+
end
|
12
|
+
|
13
|
+
if deprecated_caller
|
14
|
+
GraphQL::Deprecation.warn <<-ERR
|
15
|
+
#{self}.define will be removed in GraphQL-Ruby 2.0; use a class-based definition instead. See https://graphql-ruby.org/schema/class_based_api.html.
|
16
|
+
-> called from #{deprecated_caller}
|
17
|
+
ERR
|
18
|
+
end
|
19
|
+
deprecated_define(**kwargs, &block)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
6
23
|
def self.included(base)
|
7
24
|
base.extend(ClassMethods)
|
8
25
|
base.ensure_defined(:metadata)
|
@@ -14,7 +31,7 @@ module GraphQL
|
|
14
31
|
end
|
15
32
|
|
16
33
|
# @api deprecated
|
17
|
-
def
|
34
|
+
def deprecated_define(**kwargs, &block)
|
18
35
|
# make sure the previous definition_proc was executed:
|
19
36
|
ensure_defined
|
20
37
|
stash_dependent_methods
|
@@ -22,11 +39,16 @@ module GraphQL
|
|
22
39
|
nil
|
23
40
|
end
|
24
41
|
|
42
|
+
# @api deprecated
|
43
|
+
def define(**kwargs, &block)
|
44
|
+
deprecated_define(**kwargs, &block)
|
45
|
+
end
|
46
|
+
|
25
47
|
# @api deprecated
|
26
48
|
def redefine(**kwargs, &block)
|
27
49
|
ensure_defined
|
28
50
|
new_inst = self.dup
|
29
|
-
new_inst.
|
51
|
+
new_inst.deprecated_define(**kwargs, &block)
|
30
52
|
new_inst
|
31
53
|
end
|
32
54
|
|
@@ -125,8 +147,16 @@ module GraphQL
|
|
125
147
|
module ClassMethods
|
126
148
|
# Create a new instance
|
127
149
|
# and prepare a definition using its {.definitions}.
|
150
|
+
# @api deprecated
|
128
151
|
# @param kwargs [Hash] Key-value pairs corresponding to defininitions from `accepts_definitions`
|
129
152
|
# @param block [Proc] Block which calls helper methods from `accepts_definitions`
|
153
|
+
def deprecated_define(**kwargs, &block)
|
154
|
+
instance = self.new
|
155
|
+
instance.deprecated_define(**kwargs, &block)
|
156
|
+
instance
|
157
|
+
end
|
158
|
+
|
159
|
+
# @api deprecated
|
130
160
|
def define(**kwargs, &block)
|
131
161
|
instance = self.new
|
132
162
|
instance.define(**kwargs, &block)
|
@@ -7,11 +7,11 @@ module GraphQL
|
|
7
7
|
class TypeDefiner
|
8
8
|
include Singleton
|
9
9
|
# rubocop:disable Naming/MethodName
|
10
|
-
def Int; GraphQL::
|
11
|
-
def String; GraphQL::
|
12
|
-
def Float; GraphQL::
|
13
|
-
def Boolean; GraphQL::
|
14
|
-
def ID; GraphQL::
|
10
|
+
def Int; GraphQL::DEPRECATED_INT_TYPE; end
|
11
|
+
def String; GraphQL::DEPRECATED_STRING_TYPE; end
|
12
|
+
def Float; GraphQL::DEPRECATED_FLOAT_TYPE; end
|
13
|
+
def Boolean; GraphQL::DEPRECATED_BOOLEAN_TYPE; end
|
14
|
+
def ID; GraphQL::DEPRECATED_ID_TYPE; end
|
15
15
|
# rubocop:enable Naming/MethodName
|
16
16
|
|
17
17
|
# Make a {ListType} which wraps the input type
|
@@ -4,13 +4,13 @@ module GraphQL
|
|
4
4
|
#
|
5
5
|
# 1. Scoped by file (CRuby only), add to the top of the file:
|
6
6
|
#
|
7
|
-
# using GraphQL::
|
7
|
+
# using GraphQL::DeprecationDSL
|
8
8
|
#
|
9
9
|
# (This is a "refinement", there are also other ways to scope it.)
|
10
10
|
#
|
11
11
|
# 2. Global application, add before schema definition:
|
12
12
|
#
|
13
|
-
# GraphQL::
|
13
|
+
# GraphQL::DeprecationDSL.activate
|
14
14
|
#
|
15
15
|
module DeprecatedDSL
|
16
16
|
TYPE_CLASSES = [
|
@@ -23,12 +23,17 @@ module GraphQL
|
|
23
23
|
]
|
24
24
|
|
25
25
|
def self.activate
|
26
|
+
deprecated_caller = caller(1, 1).first
|
27
|
+
GraphQL::Deprecation.warn "DeprecatedDSL will be removed from GraphQL-Ruby 2.0, use `.to_non_null_type` instead of `!` and remove `.activate` from #{deprecated_caller}"
|
26
28
|
TYPE_CLASSES.each { |c| c.extend(Methods) }
|
27
29
|
GraphQL::Schema::List.include(Methods)
|
28
30
|
GraphQL::Schema::NonNull.include(Methods)
|
29
31
|
end
|
32
|
+
|
30
33
|
module Methods
|
31
34
|
def !
|
35
|
+
deprecated_caller = caller(1, 1).first
|
36
|
+
GraphQL::Deprecation.warn "DeprecatedDSL will be removed from GraphQL-Ruby 2.0, use `.to_non_null_type` instead of `!` at #{deprecated_caller}"
|
32
37
|
to_non_null_type
|
33
38
|
end
|
34
39
|
end
|
data/lib/graphql/enum_type.rb
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
module GraphQL
|
3
3
|
# @api deprecated
|
4
4
|
class EnumType < GraphQL::BaseType
|
5
|
+
extend Define::InstanceDefinable::DeprecatedDefine
|
6
|
+
|
5
7
|
accepts_definitions :values, value: GraphQL::Define::AssignEnumValue
|
6
8
|
ensure_defined(:values, :validate_non_null_input, :coerce_non_null_input, :coerce_result)
|
7
9
|
attr_accessor :ast_node
|
@@ -18,6 +18,10 @@ module GraphQL
|
|
18
18
|
#
|
19
19
|
class Errors
|
20
20
|
def self.use(schema)
|
21
|
+
if schema.plugins.any? { |(plugin, kwargs)| plugin == self }
|
22
|
+
definition_line = caller(2, 1).first
|
23
|
+
GraphQL::Deprecation.warn("GraphQL::Execution::Errors is now installed by default, remove `use GraphQL::Execution::Errors` from #{definition_line}")
|
24
|
+
end
|
21
25
|
schema.error_handler = self.new(schema)
|
22
26
|
end
|
23
27
|
|
@@ -18,7 +18,14 @@ module GraphQL
|
|
18
18
|
# @api private
|
19
19
|
PROPAGATE_NULL = PropagateNull.new
|
20
20
|
|
21
|
+
def self.use(schema_class)
|
22
|
+
schema_class.query_execution_strategy(self)
|
23
|
+
schema_class.mutation_execution_strategy(self)
|
24
|
+
schema_class.subscription_execution_strategy(self)
|
25
|
+
end
|
26
|
+
|
21
27
|
def execute(ast_operation, root_type, query)
|
28
|
+
GraphQL::Deprecation.warn "#{self.class} will be removed in GraphQL-Ruby 2.0, please upgrade to the Interpreter: https://graphql-ruby.org/queries/interpreter.html"
|
22
29
|
result = resolve_root_selection(query)
|
23
30
|
lazy_resolve_root_selection(result, **{query: query})
|
24
31
|
GraphQL::Execution::Flatten.call(query.context)
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
require "fiber"
|
2
3
|
require "graphql/execution/interpreter/argument_value"
|
3
4
|
require "graphql/execution/interpreter/arguments"
|
4
5
|
require "graphql/execution/interpreter/arguments_cache"
|
@@ -22,12 +23,15 @@ module GraphQL
|
|
22
23
|
end
|
23
24
|
|
24
25
|
def self.use(schema_class)
|
25
|
-
schema_class.interpreter
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
26
|
+
if schema_class.interpreter?
|
27
|
+
definition_line = caller(2, 1).first
|
28
|
+
GraphQL::Deprecation.warn("GraphQL::Execution::Interpreter is now the default; remove `use GraphQL::Execution::Interpreter` from the schema definition (#{definition_line})")
|
29
|
+
else
|
30
|
+
schema_class.query_execution_strategy(self)
|
31
|
+
schema_class.mutation_execution_strategy(self)
|
32
|
+
schema_class.subscription_execution_strategy(self)
|
33
|
+
schema_class.add_subscription_extension_if_necessary
|
34
|
+
end
|
31
35
|
end
|
32
36
|
|
33
37
|
def self.begin_multiplex(multiplex)
|
@@ -5,6 +5,9 @@ module GraphQL
|
|
5
5
|
class Interpreter
|
6
6
|
# A wrapper for argument hashes in GraphQL queries.
|
7
7
|
#
|
8
|
+
# This object is immutable so that the runtime code can be sure that
|
9
|
+
# modifications don't leak from one use to another
|
10
|
+
#
|
8
11
|
# @see GraphQL::Query#arguments_for to get access to these objects.
|
9
12
|
class Arguments
|
10
13
|
extend Forwardable
|
@@ -16,20 +19,69 @@ module GraphQL
|
|
16
19
|
# @return [Hash<Symbol, Object>]
|
17
20
|
attr_reader :keyword_arguments
|
18
21
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
+
# @param argument_values [nil, Hash{Symbol => ArgumentValue}]
|
23
|
+
# @param keyword_arguments [nil, Hash{Symbol => Object}]
|
24
|
+
def initialize(keyword_arguments: nil, argument_values:)
|
25
|
+
@empty = argument_values.nil? || argument_values.empty?
|
26
|
+
# This is only present when `extras` have been merged in:
|
27
|
+
if keyword_arguments
|
28
|
+
# This is a little crazy. We expect the `:argument_details` extra to _include extras_,
|
29
|
+
# but the object isn't created until _after_ extras are put together.
|
30
|
+
# So, we have to use a special flag here to say, "at the last minute, add yourself to the keyword args."
|
31
|
+
#
|
32
|
+
# Otherwise:
|
33
|
+
# - We can't access the final Arguments instance _while_ we're preparing extras
|
34
|
+
# - After we _can_ access it, it's frozen, so we can't add anything.
|
35
|
+
#
|
36
|
+
# So, this flag gives us a chance to sneak it in before freezing, _and_ while we have access
|
37
|
+
# to the new Arguments instance itself.
|
38
|
+
if keyword_arguments[:argument_details] == :__arguments_add_self
|
39
|
+
keyword_arguments[:argument_details] = self
|
40
|
+
end
|
41
|
+
@keyword_arguments = keyword_arguments.freeze
|
42
|
+
elsif !@empty
|
43
|
+
@keyword_arguments = {}
|
44
|
+
argument_values.each do |name, arg_val|
|
45
|
+
@keyword_arguments[name] = arg_val.value
|
46
|
+
end
|
47
|
+
@keyword_arguments.freeze
|
48
|
+
else
|
49
|
+
@keyword_arguments = NO_ARGS
|
50
|
+
end
|
51
|
+
@argument_values = argument_values ? argument_values.freeze : NO_ARGS
|
52
|
+
freeze
|
22
53
|
end
|
23
54
|
|
24
55
|
# @return [Hash{Symbol => ArgumentValue}]
|
25
56
|
attr_reader :argument_values
|
26
57
|
|
27
|
-
|
28
|
-
|
58
|
+
def empty?
|
59
|
+
@empty
|
60
|
+
end
|
61
|
+
|
62
|
+
def_delegators :keyword_arguments, :key?, :[], :fetch, :keys, :each, :values
|
63
|
+
def_delegators :argument_values, :each_value
|
29
64
|
|
30
65
|
def inspect
|
31
66
|
"#<#{self.class} @keyword_arguments=#{keyword_arguments.inspect}>"
|
32
67
|
end
|
68
|
+
|
69
|
+
# Create a new arguments instance which includes these extras.
|
70
|
+
#
|
71
|
+
# This is called by the runtime to implement field `extras: [...]`
|
72
|
+
#
|
73
|
+
# @param extra_args [Hash<Symbol => Object>]
|
74
|
+
# @return [Interpreter::Arguments]
|
75
|
+
# @api private
|
76
|
+
def merge_extras(extra_args)
|
77
|
+
self.class.new(
|
78
|
+
argument_values: argument_values,
|
79
|
+
keyword_arguments: keyword_arguments.merge(extra_args)
|
80
|
+
)
|
81
|
+
end
|
82
|
+
|
83
|
+
NO_ARGS = {}.freeze
|
84
|
+
EMPTY = self.new(argument_values: nil, keyword_arguments: NO_ARGS).freeze
|
33
85
|
end
|
34
86
|
end
|
35
87
|
end
|
@@ -29,11 +29,16 @@ module GraphQL
|
|
29
29
|
|
30
30
|
private
|
31
31
|
|
32
|
+
NO_ARGUMENTS = {}.freeze
|
33
|
+
|
32
34
|
NO_VALUE_GIVEN = Object.new
|
33
35
|
|
34
36
|
def prepare_args_hash(ast_arg_or_hash_or_value)
|
35
37
|
case ast_arg_or_hash_or_value
|
36
38
|
when Hash
|
39
|
+
if ast_arg_or_hash_or_value.empty?
|
40
|
+
return NO_ARGUMENTS
|
41
|
+
end
|
37
42
|
args_hash = {}
|
38
43
|
ast_arg_or_hash_or_value.each do |k, v|
|
39
44
|
args_hash[k] = prepare_args_hash(v)
|
@@ -42,6 +47,9 @@ module GraphQL
|
|
42
47
|
when Array
|
43
48
|
ast_arg_or_hash_or_value.map { |v| prepare_args_hash(v) }
|
44
49
|
when GraphQL::Language::Nodes::Field, GraphQL::Language::Nodes::InputObject, GraphQL::Language::Nodes::Directive
|
50
|
+
if ast_arg_or_hash_or_value.arguments.empty?
|
51
|
+
return NO_ARGUMENTS
|
52
|
+
end
|
45
53
|
args_hash = {}
|
46
54
|
ast_arg_or_hash_or_value.arguments.each do |arg|
|
47
55
|
v = prepare_args_hash(arg.value)
|
@@ -19,8 +19,10 @@ module GraphQL
|
|
19
19
|
|
20
20
|
def initialize(query:, response:)
|
21
21
|
@query = query
|
22
|
+
@dataloader = query.multiplex.dataloader
|
22
23
|
@schema = query.schema
|
23
24
|
@context = query.context
|
25
|
+
@multiplex_context = query.multiplex.context
|
24
26
|
@interpreter_context = @context.namespace(:interpreter)
|
25
27
|
@response = response
|
26
28
|
@dead_paths = {}
|
@@ -47,15 +49,24 @@ module GraphQL
|
|
47
49
|
root_op_type = root_operation.operation_type || "query"
|
48
50
|
root_type = schema.root_type_for_operation(root_op_type)
|
49
51
|
path = []
|
50
|
-
|
51
|
-
set_interpreter_context(:current_path, path)
|
52
|
+
set_all_interpreter_context(query.root_value, nil, nil, path)
|
52
53
|
object_proxy = authorized_new(root_type, query.root_value, context, path)
|
53
54
|
object_proxy = schema.sync_lazy(object_proxy)
|
54
55
|
if object_proxy.nil?
|
55
56
|
# Root .authorized? returned false.
|
56
57
|
write_in_response(path, nil)
|
57
58
|
else
|
58
|
-
|
59
|
+
# Prepare this runtime state to be encapsulated in a Fiber
|
60
|
+
@progress_path = path
|
61
|
+
@progress_scoped_context = context.scoped_context
|
62
|
+
@progress_object = object_proxy
|
63
|
+
@progress_object_type = root_type
|
64
|
+
@progress_index = nil
|
65
|
+
@progress_is_eager_selection = root_op_type == "mutation"
|
66
|
+
@progress_selections = gather_selections(object_proxy, root_type, root_operation.selections)
|
67
|
+
|
68
|
+
# Make the first fiber which will begin execution
|
69
|
+
enqueue_selections_fiber
|
59
70
|
end
|
60
71
|
delete_interpreter_context(:current_path)
|
61
72
|
delete_interpreter_context(:current_field)
|
@@ -64,7 +75,33 @@ module GraphQL
|
|
64
75
|
nil
|
65
76
|
end
|
66
77
|
|
67
|
-
|
78
|
+
# Use `@dataloader` to enqueue a fiber that will pick up from the current point.
|
79
|
+
# @return [void]
|
80
|
+
def enqueue_selections_fiber
|
81
|
+
# Read these into local variables so that later assignments don't affect the block below.
|
82
|
+
path = @progress_path
|
83
|
+
scoped_context = @progress_scoped_context
|
84
|
+
owner_object = @progress_object
|
85
|
+
owner_type = @progress_object_type
|
86
|
+
idx = @progress_index
|
87
|
+
is_eager_selection = @progress_is_eager_selection
|
88
|
+
gathered_selections = @progress_selections
|
89
|
+
|
90
|
+
@dataloader.enqueue {
|
91
|
+
evaluate_selections(
|
92
|
+
path,
|
93
|
+
scoped_context,
|
94
|
+
owner_object,
|
95
|
+
owner_type,
|
96
|
+
is_eager_selection: is_eager_selection,
|
97
|
+
after: idx,
|
98
|
+
gathered_selections: gathered_selections,
|
99
|
+
)
|
100
|
+
}
|
101
|
+
nil
|
102
|
+
end
|
103
|
+
|
104
|
+
def gather_selections(owner_object, owner_type, selections, selections_by_name = {})
|
68
105
|
selections.each do |node|
|
69
106
|
# Skip gathering this if the directive says so
|
70
107
|
if !directives_include?(node, owner_object, owner_type)
|
@@ -116,139 +153,186 @@ module GraphQL
|
|
116
153
|
raise "Invariant: unexpected selection class: #{node.class}"
|
117
154
|
end
|
118
155
|
end
|
156
|
+
selections_by_name
|
119
157
|
end
|
120
158
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
159
|
+
NO_ARGS = {}.freeze
|
160
|
+
|
161
|
+
# @return [void]
|
162
|
+
def evaluate_selections(path, scoped_context, owner_object, owner_type, is_eager_selection:, gathered_selections:, after:)
|
163
|
+
set_all_interpreter_context(owner_object, nil, nil, path)
|
164
|
+
|
165
|
+
@progress_path = path
|
166
|
+
@progress_scoped_context = scoped_context
|
167
|
+
@progress_object = owner_object
|
168
|
+
@progress_object_type = owner_type
|
169
|
+
@progress_index = nil
|
170
|
+
@progress_is_eager_selection = is_eager_selection
|
171
|
+
@progress_selections = gathered_selections
|
172
|
+
|
173
|
+
# Track `idx` manually to avoid an allocation on this hot path
|
174
|
+
idx = 0
|
175
|
+
gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
|
176
|
+
prev_idx = idx
|
177
|
+
idx += 1
|
178
|
+
# TODO: this is how a `progress` resumes where this left off.
|
179
|
+
# Is there a better way to seek in the hash?
|
180
|
+
# I think we could also use the array of keys; it supports seeking just fine.
|
181
|
+
if after && prev_idx <= after
|
182
|
+
next
|
136
183
|
end
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
dynamic_field
|
147
|
-
else
|
148
|
-
raise "Invariant: no field for #{owner_type}.#{field_name}"
|
149
|
-
end
|
184
|
+
@progress_index = prev_idx
|
185
|
+
# This is how the current runtime gives itself to `dataloader`
|
186
|
+
# so that the dataloader can enqueue another fiber to resume if needed.
|
187
|
+
@dataloader.current_runtime = self
|
188
|
+
evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_selection)
|
189
|
+
# The dataloader knows if ^^ that selection halted and later selections were executed in another fiber.
|
190
|
+
# If that's the case, then don't continue execution here.
|
191
|
+
if @dataloader.yielded?(path)
|
192
|
+
break
|
150
193
|
end
|
151
|
-
|
194
|
+
end
|
195
|
+
nil
|
196
|
+
end
|
152
197
|
|
153
|
-
|
154
|
-
next_path << result_name
|
155
|
-
next_path.freeze
|
198
|
+
attr_reader :progress_path
|
156
199
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
200
|
+
# @return [void]
|
201
|
+
def evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_field)
|
202
|
+
# As a performance optimization, the hash key will be a `Node` if
|
203
|
+
# there's only one selection of the field. But if there are multiple
|
204
|
+
# selections of the field, it will be an Array of nodes
|
205
|
+
if field_ast_nodes_or_ast_node.is_a?(Array)
|
206
|
+
field_ast_nodes = field_ast_nodes_or_ast_node
|
207
|
+
ast_node = field_ast_nodes.first
|
208
|
+
else
|
209
|
+
field_ast_nodes = nil
|
210
|
+
ast_node = field_ast_nodes_or_ast_node
|
211
|
+
end
|
212
|
+
field_name = ast_node.name
|
213
|
+
field_defn = @fields_cache[owner_type][field_name] ||= owner_type.get_field(field_name)
|
214
|
+
is_introspection = false
|
215
|
+
if field_defn.nil?
|
216
|
+
field_defn = if owner_type == schema.query && (entry_point_field = schema.introspection_system.entry_point(name: field_name))
|
217
|
+
is_introspection = true
|
218
|
+
entry_point_field
|
219
|
+
elsif (dynamic_field = schema.introspection_system.dynamic_field(name: field_name))
|
220
|
+
is_introspection = true
|
221
|
+
dynamic_field
|
222
|
+
else
|
223
|
+
raise "Invariant: no field for #{owner_type}.#{field_name}"
|
224
|
+
end
|
225
|
+
end
|
226
|
+
return_type = field_defn.type
|
164
227
|
|
165
|
-
|
166
|
-
|
228
|
+
next_path = path.dup
|
229
|
+
next_path << result_name
|
230
|
+
next_path.freeze
|
167
231
|
|
168
|
-
|
169
|
-
|
170
|
-
|
232
|
+
# This seems janky, but we need to know
|
233
|
+
# the field's return type at this path in order
|
234
|
+
# to propagate `null`
|
235
|
+
set_type_at_path(next_path, return_type)
|
236
|
+
# Set this before calling `run_with_directives`, so that the directive can have the latest path
|
237
|
+
set_all_interpreter_context(nil, field_defn, nil, next_path)
|
171
238
|
|
172
|
-
|
173
|
-
|
174
|
-
rescue GraphQL::ExecutionError => e
|
175
|
-
continue_value(next_path, e, owner_type, field_defn, return_type.non_null?, ast_node)
|
176
|
-
next
|
177
|
-
end
|
239
|
+
context.scoped_context = scoped_context
|
240
|
+
object = owner_object
|
178
241
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
next
|
183
|
-
end
|
242
|
+
if is_introspection
|
243
|
+
object = authorized_new(field_defn.owner, object, context, next_path)
|
244
|
+
end
|
184
245
|
|
185
|
-
|
246
|
+
begin
|
247
|
+
kwarg_arguments = arguments(object, field_defn, ast_node)
|
248
|
+
rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => e
|
249
|
+
continue_value(next_path, e, owner_type, field_defn, return_type.non_null?, ast_node)
|
250
|
+
return
|
251
|
+
end
|
186
252
|
|
253
|
+
after_lazy(kwarg_arguments, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |resolved_arguments|
|
254
|
+
if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
|
255
|
+
continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node)
|
256
|
+
next
|
257
|
+
end
|
258
|
+
|
259
|
+
kwarg_arguments = if resolved_arguments.empty? && field_defn.extras.empty?
|
260
|
+
# We can avoid allocating the `{ Symbol => Object }` hash in this case
|
261
|
+
NO_ARGS
|
262
|
+
else
|
263
|
+
# Bundle up the extras, then make a new arguments instance
|
264
|
+
# that includes the extras, too.
|
265
|
+
extra_args = {}
|
187
266
|
field_defn.extras.each do |extra|
|
188
267
|
case extra
|
189
268
|
when :ast_node
|
190
|
-
|
269
|
+
extra_args[:ast_node] = ast_node
|
191
270
|
when :execution_errors
|
192
|
-
|
271
|
+
extra_args[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
|
193
272
|
when :path
|
194
|
-
|
273
|
+
extra_args[:path] = next_path
|
195
274
|
when :lookahead
|
196
275
|
if !field_ast_nodes
|
197
276
|
field_ast_nodes = [ast_node]
|
198
277
|
end
|
199
|
-
|
278
|
+
|
279
|
+
extra_args[:lookahead] = Execution::Lookahead.new(
|
200
280
|
query: query,
|
201
281
|
ast_nodes: field_ast_nodes,
|
202
282
|
field: field_defn,
|
203
283
|
)
|
204
284
|
when :argument_details
|
205
|
-
|
285
|
+
# Use this flag to tell Interpreter::Arguments to add itself
|
286
|
+
# to the keyword args hash _before_ freezing everything.
|
287
|
+
extra_args[:argument_details] = :__arguments_add_self
|
206
288
|
else
|
207
|
-
|
289
|
+
extra_args[extra] = field_defn.fetch_extra(extra, context)
|
208
290
|
end
|
209
291
|
end
|
292
|
+
resolved_arguments = resolved_arguments.merge_extras(extra_args)
|
293
|
+
resolved_arguments.keyword_arguments
|
294
|
+
end
|
210
295
|
|
211
|
-
|
296
|
+
set_all_interpreter_context(nil, nil, kwarg_arguments, nil)
|
212
297
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
298
|
+
# Optimize for the case that field is selected only once
|
299
|
+
if field_ast_nodes.nil? || field_ast_nodes.size == 1
|
300
|
+
next_selections = ast_node.selections
|
301
|
+
else
|
302
|
+
next_selections = []
|
303
|
+
field_ast_nodes.each { |f| next_selections.concat(f.selections) }
|
304
|
+
end
|
220
305
|
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
end
|
306
|
+
field_result = resolve_with_directives(object, ast_node) do
|
307
|
+
# Actually call the field resolver and capture the result
|
308
|
+
app_result = begin
|
309
|
+
query.with_error_handling do
|
310
|
+
query.trace("execute_field", {owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments}) do
|
311
|
+
field_defn.resolve(object, kwarg_arguments, context)
|
228
312
|
end
|
229
|
-
rescue GraphQL::ExecutionError => err
|
230
|
-
err
|
231
313
|
end
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
314
|
+
rescue GraphQL::ExecutionError => err
|
315
|
+
err
|
316
|
+
end
|
317
|
+
after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |inner_result|
|
318
|
+
continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node)
|
319
|
+
if RawValue === continue_value
|
320
|
+
# Write raw value directly to the response without resolving nested objects
|
321
|
+
write_in_response(next_path, continue_value.resolve)
|
322
|
+
elsif HALT != continue_value
|
323
|
+
continue_field(next_path, continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments)
|
240
324
|
end
|
241
325
|
end
|
326
|
+
end
|
242
327
|
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
else
|
249
|
-
field_result
|
250
|
-
end
|
328
|
+
# If this field is a root mutation field, immediately resolve
|
329
|
+
# all of its child fields before moving on to the next root mutation field.
|
330
|
+
# (Subselections of this mutation will still be resolved level-by-level.)
|
331
|
+
if is_eager_field
|
332
|
+
Interpreter::Resolve.resolve_all([field_result])
|
251
333
|
end
|
334
|
+
|
335
|
+
nil
|
252
336
|
end
|
253
337
|
end
|
254
338
|
|
@@ -309,7 +393,7 @@ module GraphQL
|
|
309
393
|
resolved_type_or_lazy, resolved_value = resolve_type(current_type, value, path)
|
310
394
|
resolved_value ||= value
|
311
395
|
|
312
|
-
after_lazy(resolved_type_or_lazy, owner: current_type, path: path, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |resolved_type|
|
396
|
+
after_lazy(resolved_type_or_lazy, owner: current_type, path: path, ast_node: ast_node, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |resolved_type|
|
313
397
|
possible_types = query.possible_types(current_type)
|
314
398
|
|
315
399
|
if !possible_types.include?(resolved_type)
|
@@ -329,12 +413,13 @@ module GraphQL
|
|
329
413
|
rescue GraphQL::ExecutionError => err
|
330
414
|
err
|
331
415
|
end
|
332
|
-
after_lazy(object_proxy, owner: current_type, path: path, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |inner_object|
|
416
|
+
after_lazy(object_proxy, owner: current_type, path: path, ast_node: ast_node, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |inner_object|
|
333
417
|
continue_value = continue_value(path, inner_object, owner_type, field, is_non_null, ast_node)
|
334
418
|
if HALT != continue_value
|
335
419
|
response_hash = {}
|
336
420
|
write_in_response(path, response_hash)
|
337
|
-
|
421
|
+
gathered_selections = gather_selections(continue_value, current_type, next_selections)
|
422
|
+
evaluate_selections(path, context.scoped_context, continue_value, current_type, is_eager_selection: false, gathered_selections: gathered_selections, after: nil)
|
338
423
|
response_hash
|
339
424
|
end
|
340
425
|
end
|
@@ -352,7 +437,7 @@ module GraphQL
|
|
352
437
|
idx += 1
|
353
438
|
set_type_at_path(next_path, inner_type)
|
354
439
|
# This will update `response_list` with the lazy
|
355
|
-
after_lazy(inner_value, owner: inner_type, path: next_path, scoped_context: scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |inner_inner_value|
|
440
|
+
after_lazy(inner_value, owner: inner_type, path: next_path, ast_node: ast_node, scoped_context: scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |inner_inner_value|
|
356
441
|
continue_value = continue_value(next_path, inner_inner_value, owner_type, field, inner_type.non_null?, ast_node)
|
357
442
|
if HALT != continue_value
|
358
443
|
continue_field(next_path, continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments)
|
@@ -414,30 +499,39 @@ module GraphQL
|
|
414
499
|
true
|
415
500
|
end
|
416
501
|
|
502
|
+
def set_all_interpreter_context(object, field, arguments, path)
|
503
|
+
if object
|
504
|
+
@context[:current_object] = @interpreter_context[:current_object] = object
|
505
|
+
end
|
506
|
+
if field
|
507
|
+
@context[:current_field] = @interpreter_context[:current_field] = field
|
508
|
+
end
|
509
|
+
if arguments
|
510
|
+
@context[:current_arguments] = @interpreter_context[:current_arguments] = arguments
|
511
|
+
end
|
512
|
+
if path
|
513
|
+
@context[:current_path] = @interpreter_context[:current_path] = path
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
417
517
|
# @param obj [Object] Some user-returned value that may want to be batched
|
418
518
|
# @param path [Array<String>]
|
419
519
|
# @param field [GraphQL::Schema::Field]
|
420
520
|
# @param eager [Boolean] Set to `true` for mutation root fields only
|
421
521
|
# @param trace [Boolean] If `false`, don't wrap this with field tracing
|
422
522
|
# @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
|
423
|
-
def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, eager: false, trace: true, &block)
|
424
|
-
|
425
|
-
set_interpreter_context(:current_arguments, arguments)
|
426
|
-
set_interpreter_context(:current_path, path)
|
427
|
-
set_interpreter_context(:current_field, field)
|
523
|
+
def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, ast_node:, eager: false, trace: true, &block)
|
524
|
+
set_all_interpreter_context(owner_object, field, arguments, path)
|
428
525
|
if schema.lazy?(lazy_obj)
|
429
526
|
lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do
|
430
|
-
|
431
|
-
set_interpreter_context(:current_field, field)
|
432
|
-
set_interpreter_context(:current_object, owner_object)
|
433
|
-
set_interpreter_context(:current_arguments, arguments)
|
527
|
+
set_all_interpreter_context(owner_object, field, arguments, path)
|
434
528
|
context.scoped_context = scoped_context
|
435
529
|
# Wrap the execution of _this_ method with tracing,
|
436
530
|
# but don't wrap the continuation below
|
437
531
|
inner_obj = begin
|
438
532
|
query.with_error_handling do
|
439
533
|
if trace
|
440
|
-
query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments}) do
|
534
|
+
query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments, ast_node: ast_node}) do
|
441
535
|
schema.sync_lazy(lazy_obj)
|
442
536
|
end
|
443
537
|
else
|
@@ -447,7 +541,7 @@ module GraphQL
|
|
447
541
|
rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
|
448
542
|
err
|
449
543
|
end
|
450
|
-
after_lazy(inner_obj, owner: owner, field: field, path: path, scoped_context: context.scoped_context, owner_object: owner_object, arguments: arguments, eager: eager, trace: trace, &block)
|
544
|
+
after_lazy(inner_obj, owner: owner, field: field, path: path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: owner_object, arguments: arguments, eager: eager, trace: trace, &block)
|
451
545
|
end
|
452
546
|
|
453
547
|
if eager
|
@@ -462,9 +556,7 @@ module GraphQL
|
|
462
556
|
end
|
463
557
|
|
464
558
|
def arguments(graphql_object, arg_owner, ast_node)
|
465
|
-
|
466
|
-
if arg_owner.arguments_statically_coercible? &&
|
467
|
-
(!arg_owner.is_a?(GraphQL::Schema::Field) || (arg_owner.extras.empty? && arg_owner.extensions.empty?))
|
559
|
+
if arg_owner.arguments_statically_coercible?
|
468
560
|
query.arguments_for(ast_node, arg_owner)
|
469
561
|
else
|
470
562
|
# The arguments must be prepared in the context of the given object
|
@@ -505,6 +597,16 @@ module GraphQL
|
|
505
597
|
end
|
506
598
|
end
|
507
599
|
|
600
|
+
def value_at(path)
|
601
|
+
i = 0
|
602
|
+
value = @response.final_value
|
603
|
+
while value && (part = path[i])
|
604
|
+
value = value[part]
|
605
|
+
i += 1
|
606
|
+
end
|
607
|
+
value
|
608
|
+
end
|
609
|
+
|
508
610
|
# To propagate nulls, we have to know what the field type was
|
509
611
|
# at previous parts of the response.
|
510
612
|
# This hash matches the response
|