graphql 1.6.8 → 1.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/graphql.rb +5 -0
- data/lib/graphql/analysis/analyze_query.rb +21 -17
- data/lib/graphql/argument.rb +6 -2
- data/lib/graphql/backtrace.rb +50 -0
- data/lib/graphql/backtrace/inspect_result.rb +51 -0
- data/lib/graphql/backtrace/table.rb +120 -0
- data/lib/graphql/backtrace/traced_error.rb +55 -0
- data/lib/graphql/backtrace/tracer.rb +50 -0
- data/lib/graphql/enum_type.rb +1 -10
- data/lib/graphql/execution.rb +1 -2
- data/lib/graphql/execution/execute.rb +98 -89
- data/lib/graphql/execution/flatten.rb +40 -0
- data/lib/graphql/execution/lazy/resolve.rb +7 -7
- data/lib/graphql/execution/multiplex.rb +29 -29
- data/lib/graphql/field.rb +5 -1
- data/lib/graphql/internal_representation/node.rb +16 -0
- data/lib/graphql/invalid_name_error.rb +11 -0
- data/lib/graphql/language/parser.rb +11 -5
- data/lib/graphql/language/parser.y +11 -5
- data/lib/graphql/name_validator.rb +16 -0
- data/lib/graphql/object_type.rb +5 -0
- data/lib/graphql/query.rb +28 -7
- data/lib/graphql/query/context.rb +155 -52
- data/lib/graphql/query/literal_input.rb +36 -9
- data/lib/graphql/query/null_context.rb +7 -1
- data/lib/graphql/query/result.rb +63 -0
- data/lib/graphql/query/serial_execution/field_resolution.rb +3 -4
- data/lib/graphql/query/serial_execution/value_resolution.rb +3 -4
- data/lib/graphql/query/variables.rb +1 -1
- data/lib/graphql/schema.rb +31 -0
- data/lib/graphql/schema/traversal.rb +16 -1
- data/lib/graphql/schema/warden.rb +40 -4
- data/lib/graphql/static_validation/validator.rb +20 -18
- data/lib/graphql/subscriptions.rb +129 -0
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +122 -0
- data/lib/graphql/subscriptions/event.rb +52 -0
- data/lib/graphql/subscriptions/instrumentation.rb +68 -0
- data/lib/graphql/tracing.rb +80 -0
- data/lib/graphql/tracing/active_support_notifications_tracing.rb +31 -0
- data/lib/graphql/version.rb +1 -1
- data/readme.md +1 -1
- data/spec/graphql/analysis/analyze_query_spec.rb +19 -0
- data/spec/graphql/argument_spec.rb +28 -0
- data/spec/graphql/backtrace_spec.rb +144 -0
- data/spec/graphql/define/assign_argument_spec.rb +12 -0
- data/spec/graphql/enum_type_spec.rb +1 -1
- data/spec/graphql/execution/execute_spec.rb +66 -0
- data/spec/graphql/execution/lazy_spec.rb +4 -3
- data/spec/graphql/language/parser_spec.rb +16 -0
- data/spec/graphql/object_type_spec.rb +14 -0
- data/spec/graphql/query/context_spec.rb +134 -27
- data/spec/graphql/query/result_spec.rb +29 -0
- data/spec/graphql/query/variables_spec.rb +13 -0
- data/spec/graphql/query_spec.rb +22 -0
- data/spec/graphql/schema/build_from_definition_spec.rb +2 -0
- data/spec/graphql/schema/traversal_spec.rb +70 -12
- data/spec/graphql/schema/warden_spec.rb +67 -1
- data/spec/graphql/schema_spec.rb +29 -0
- data/spec/graphql/static_validation/validator_spec.rb +16 -0
- data/spec/graphql/subscriptions_spec.rb +331 -0
- data/spec/graphql/tracing/active_support_notifications_tracing_spec.rb +57 -0
- data/spec/graphql/tracing_spec.rb +47 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/support/star_wars/schema.rb +39 -0
- metadata +27 -4
- data/lib/graphql/execution/field_result.rb +0 -54
- data/lib/graphql/execution/selection_result.rb +0 -90
data/lib/graphql/field.rb
CHANGED
@@ -129,6 +129,7 @@ module GraphQL
|
|
129
129
|
:edge_class,
|
130
130
|
:relay_node_field,
|
131
131
|
:relay_nodes_field,
|
132
|
+
:subscription_scope,
|
132
133
|
argument: GraphQL::Define::AssignArgument
|
133
134
|
|
134
135
|
ensure_defined(
|
@@ -136,7 +137,7 @@ module GraphQL
|
|
136
137
|
:mutation, :arguments, :complexity, :function,
|
137
138
|
:resolve, :resolve=, :lazy_resolve, :lazy_resolve=, :lazy_resolve_proc, :resolve_proc,
|
138
139
|
:type, :type=, :name=, :property=, :hash_key=,
|
139
|
-
:relay_node_field, :relay_nodes_field, :edges?, :edge_class
|
140
|
+
:relay_node_field, :relay_nodes_field, :edges?, :edge_class, :subscription_scope
|
140
141
|
)
|
141
142
|
|
142
143
|
# @return [Boolean] True if this is the Relay find-by-id field
|
@@ -180,6 +181,9 @@ module GraphQL
|
|
180
181
|
|
181
182
|
attr_writer :connection
|
182
183
|
|
184
|
+
# @return [nil, String] Prefix for subscription names from this field
|
185
|
+
attr_accessor :subscription_scope
|
186
|
+
|
183
187
|
# @return [Boolean]
|
184
188
|
def connection?
|
185
189
|
@connection
|
@@ -145,6 +145,22 @@ module GraphQL
|
|
145
145
|
# @return [GraphQL::Query]
|
146
146
|
attr_reader :query
|
147
147
|
|
148
|
+
def subscription_topic
|
149
|
+
@subscription_topic ||= begin
|
150
|
+
scope = if definition.subscription_scope
|
151
|
+
@query.context[definition.subscription_scope]
|
152
|
+
else
|
153
|
+
nil
|
154
|
+
end
|
155
|
+
Subscriptions::Event.serialize(
|
156
|
+
definition_name,
|
157
|
+
@query.arguments_for(self, definition),
|
158
|
+
definition,
|
159
|
+
scope: scope
|
160
|
+
)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
148
164
|
protected
|
149
165
|
|
150
166
|
attr_writer :owner_type, :parent
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
class InvalidNameError < GraphQL::ExecutionError
|
4
|
+
attr_reader :name, :valid_regex
|
5
|
+
def initialize(name, valid_regex)
|
6
|
+
@name = name
|
7
|
+
@valid_regex = valid_regex
|
8
|
+
super("Names must match #{@valid_regex.inspect} but '#{@name}' does not")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -21,11 +21,17 @@ end
|
|
21
21
|
|
22
22
|
def parse_document
|
23
23
|
@document ||= begin
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
# Break the string into tokens
|
25
|
+
GraphQL::Tracing.trace("lex", {query_string: @query_string}) do
|
26
|
+
@tokens ||= GraphQL.scan(@query_string)
|
27
|
+
end
|
28
|
+
# From the tokens, build an AST
|
29
|
+
GraphQL::Tracing.trace("parse", {query_string: @query_string}) do
|
30
|
+
if @tokens.none?
|
31
|
+
make_node(:Document, definitions: [], filename: @filename)
|
32
|
+
else
|
33
|
+
do_parse
|
34
|
+
end
|
29
35
|
end
|
30
36
|
end
|
31
37
|
end
|
@@ -361,11 +361,17 @@ end
|
|
361
361
|
|
362
362
|
def parse_document
|
363
363
|
@document ||= begin
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
364
|
+
# Break the string into tokens
|
365
|
+
GraphQL::Tracing.trace("lex", {query_string: @query_string}) do
|
366
|
+
@tokens ||= GraphQL.scan(@query_string)
|
367
|
+
end
|
368
|
+
# From the tokens, build an AST
|
369
|
+
GraphQL::Tracing.trace("parse", {query_string: @query_string}) do
|
370
|
+
if @tokens.none?
|
371
|
+
make_node(:Document, definitions: [], filename: @filename)
|
372
|
+
else
|
373
|
+
do_parse
|
374
|
+
end
|
369
375
|
end
|
370
376
|
end
|
371
377
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
class NameValidator
|
4
|
+
VALID_NAME_REGEX = /^[_a-zA-Z][_a-zA-Z0-9]*$/
|
5
|
+
|
6
|
+
def self.validate!(name)
|
7
|
+
raise GraphQL::InvalidNameError.new(name, VALID_NAME_REGEX) unless valid?(name)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def self.valid?(name)
|
13
|
+
name =~ VALID_NAME_REGEX
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/graphql/object_type.rb
CHANGED
data/lib/graphql/query.rb
CHANGED
@@ -5,6 +5,7 @@ require "graphql/query/context"
|
|
5
5
|
require "graphql/query/executor"
|
6
6
|
require "graphql/query/literal_input"
|
7
7
|
require "graphql/query/null_context"
|
8
|
+
require "graphql/query/result"
|
8
9
|
require "graphql/query/serial_execution"
|
9
10
|
require "graphql/query/variables"
|
10
11
|
require "graphql/query/input_validation_result"
|
@@ -48,6 +49,12 @@ module GraphQL
|
|
48
49
|
selected_operation.name
|
49
50
|
end
|
50
51
|
|
52
|
+
# @return [String, nil] the triggered event, if this query is a subscription update
|
53
|
+
attr_reader :subscription_topic
|
54
|
+
|
55
|
+
# @return [String, nil]
|
56
|
+
attr_reader :operation_name
|
57
|
+
|
51
58
|
# Prepare query `query_string` on `schema`
|
52
59
|
# @param schema [GraphQL::Schema]
|
53
60
|
# @param query_string [String]
|
@@ -59,10 +66,11 @@ module GraphQL
|
|
59
66
|
# @param max_complexity [Numeric] the maximum field complexity for this query (falls back to schema-level value)
|
60
67
|
# @param except [<#call(schema_member, context)>] If provided, objects will be hidden from the schema when `.call(schema_member, context)` returns truthy
|
61
68
|
# @param only [<#call(schema_member, context)>] If provided, objects will be hidden from the schema when `.call(schema_member, context)` returns false
|
62
|
-
def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: {}, validate: true, operation_name: nil, root_value: nil, max_depth: nil, max_complexity: nil, except: nil, only: nil)
|
69
|
+
def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: {}, validate: true, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: nil, max_complexity: nil, except: nil, only: nil)
|
63
70
|
@schema = schema
|
64
71
|
@filter = schema.default_filter.merge(except: except, only: only)
|
65
|
-
@context = Context.new(query: self, values: context)
|
72
|
+
@context = Context.new(query: self, object: root_value, values: context)
|
73
|
+
@subscription_topic = subscription_topic
|
66
74
|
@root_value = root_value
|
67
75
|
@fragments = nil
|
68
76
|
@operations = nil
|
@@ -78,6 +86,10 @@ module GraphQL
|
|
78
86
|
@query_string = query_string || query
|
79
87
|
@document = document
|
80
88
|
|
89
|
+
if @query_string && @document
|
90
|
+
raise ArgumentError, "Query should only be provided a query string or a document, not both."
|
91
|
+
end
|
92
|
+
|
81
93
|
# A two-layer cache of type resolution:
|
82
94
|
# { abstract_type => { value => resolved_type } }
|
83
95
|
@resolved_types_cache = Hash.new do |h1, k1|
|
@@ -94,22 +106,25 @@ module GraphQL
|
|
94
106
|
@mutation = false
|
95
107
|
@operation_name = operation_name
|
96
108
|
@prepared_ast = false
|
97
|
-
|
98
109
|
@validation_pipeline = nil
|
99
110
|
@max_depth = max_depth || schema.max_depth
|
100
111
|
@max_complexity = max_complexity || schema.max_complexity
|
101
112
|
|
102
|
-
@
|
113
|
+
@result_values = nil
|
103
114
|
@executed = false
|
104
115
|
end
|
105
116
|
|
117
|
+
def subscription_update?
|
118
|
+
@subscription_topic && subscription?
|
119
|
+
end
|
120
|
+
|
106
121
|
# @api private
|
107
|
-
def
|
122
|
+
def result_values=(result_hash)
|
108
123
|
if @executed
|
109
124
|
raise "Invariant: Can't reassign result"
|
110
125
|
else
|
111
126
|
@executed = true
|
112
|
-
@
|
127
|
+
@result_values = result_hash
|
113
128
|
end
|
114
129
|
end
|
115
130
|
|
@@ -129,7 +144,7 @@ module GraphQL
|
|
129
144
|
Execution::Multiplex.run_queries(@schema, [self])
|
130
145
|
}
|
131
146
|
end
|
132
|
-
@result
|
147
|
+
@result ||= Query::Result.new(query: self, values: @result_values)
|
133
148
|
end
|
134
149
|
|
135
150
|
def static_errors
|
@@ -228,6 +243,10 @@ module GraphQL
|
|
228
243
|
nil
|
229
244
|
end
|
230
245
|
|
246
|
+
def subscription?
|
247
|
+
with_prepared_ast { @subscription }
|
248
|
+
end
|
249
|
+
|
231
250
|
private
|
232
251
|
|
233
252
|
def find_operation(operations, operation_name)
|
@@ -277,6 +296,7 @@ module GraphQL
|
|
277
296
|
# with no operations returns an empty hash
|
278
297
|
@ast_variables = []
|
279
298
|
@mutation = false
|
299
|
+
@subscription = false
|
280
300
|
operation_name_error = nil
|
281
301
|
if @operations.any?
|
282
302
|
@selected_operation = find_operation(@operations, @operation_name)
|
@@ -289,6 +309,7 @@ module GraphQL
|
|
289
309
|
@ast_variables = @selected_operation.variables
|
290
310
|
@mutation = @selected_operation.operation_type == "mutation"
|
291
311
|
@query = @selected_operation.operation_type == "query"
|
312
|
+
@subscription = @selected_operation.operation_type == "subscription"
|
292
313
|
end
|
293
314
|
end
|
294
315
|
|
@@ -1,10 +1,80 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
# test_via: ../execution/execute.rb
|
3
|
+
# test_via: ../execution/lazy.rb
|
2
4
|
module GraphQL
|
3
5
|
class Query
|
4
6
|
# Expose some query-specific info to field resolve functions.
|
5
7
|
# It delegates `[]` to the hash that's passed to `GraphQL::Query#initialize`.
|
6
8
|
class Context
|
9
|
+
module SharedMethods
|
10
|
+
# @return [Object] The target for field resultion
|
11
|
+
attr_accessor :object
|
12
|
+
|
13
|
+
# @return [Hash, Array, String, Integer, Float, Boolean, nil] The resolved value for this field
|
14
|
+
attr_reader :value
|
15
|
+
|
16
|
+
# @return [Boolean] were any fields of this selection skipped?
|
17
|
+
attr_reader :skipped
|
18
|
+
alias :skipped? :skipped
|
19
|
+
|
20
|
+
# @api private
|
21
|
+
attr_writer :skipped
|
22
|
+
|
23
|
+
# Return this value to tell the runtime
|
24
|
+
# to exclude this field from the response altogether
|
25
|
+
def skip
|
26
|
+
GraphQL::Execution::Execute::SKIP
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Boolean] True if this selection has been nullified by a null child
|
30
|
+
def invalid_null?
|
31
|
+
@invalid_null
|
32
|
+
end
|
33
|
+
|
34
|
+
# Remove this child from the result value
|
35
|
+
# (used for null propagation and skip)
|
36
|
+
# @api private
|
37
|
+
def delete(child_ctx)
|
38
|
+
@value.delete(child_ctx.key)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Create a child context to use for `key`
|
42
|
+
# @param key [String, Integer] The key in the response (name or index)
|
43
|
+
# @param irep_node [InternalRepresentation::Node] The node being evaluated
|
44
|
+
# @api private
|
45
|
+
def spawn_child(key:, irep_node:, object:)
|
46
|
+
FieldResolutionContext.new(
|
47
|
+
context: @context,
|
48
|
+
parent: self,
|
49
|
+
object: object,
|
50
|
+
key: key,
|
51
|
+
irep_node: irep_node,
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Add error at query-level.
|
56
|
+
# @param error [GraphQL::ExecutionError] an execution error
|
57
|
+
# @return [void]
|
58
|
+
def add_error(error)
|
59
|
+
if !error.is_a?(ExecutionError)
|
60
|
+
raise TypeError, "expected error to be a ExecutionError, but was #{error.class}"
|
61
|
+
end
|
62
|
+
errors << error
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
|
66
|
+
# @example Print the GraphQL backtrace during field resolution
|
67
|
+
# puts ctx.backtrace
|
68
|
+
#
|
69
|
+
# @return [GraphQL::Backtrace] The backtrace for this point in query execution
|
70
|
+
def backtrace
|
71
|
+
GraphQL::Backtrace.new(self)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
include SharedMethods
|
7
76
|
extend GraphQL::Delegate
|
77
|
+
|
8
78
|
attr_reader :execution_strategy
|
9
79
|
# `strategy` is required by GraphQL::Batch
|
10
80
|
alias_method :strategy, :execution_strategy
|
@@ -17,7 +87,9 @@ module GraphQL
|
|
17
87
|
end
|
18
88
|
|
19
89
|
# @return [GraphQL::InternalRepresentation::Node] The internal representation for this query node
|
20
|
-
|
90
|
+
def irep_node
|
91
|
+
@irep_node ||= query.irep_selection
|
92
|
+
end
|
21
93
|
|
22
94
|
# @return [GraphQL::Language::Nodes::Field] The AST node for the currently-executing field
|
23
95
|
def ast_node
|
@@ -39,17 +111,23 @@ module GraphQL
|
|
39
111
|
# Make a new context which delegates key lookup to `values`
|
40
112
|
# @param query [GraphQL::Query] the query who owns this context
|
41
113
|
# @param values [Hash] A hash of arbitrary values which will be accessible at query-time
|
42
|
-
def initialize(query:, values:)
|
114
|
+
def initialize(query:, values: , object:)
|
43
115
|
@query = query
|
44
116
|
@schema = query.schema
|
45
117
|
@provided_values = values || {}
|
118
|
+
@object = object
|
46
119
|
# Namespaced storage, where user-provided values are in `nil` namespace:
|
47
120
|
@storage = Hash.new { |h, k| h[k] = {} }
|
48
121
|
@storage[nil] = @provided_values
|
49
122
|
@errors = []
|
50
123
|
@path = []
|
124
|
+
@value = nil
|
125
|
+
@context = self # for SharedMethods
|
51
126
|
end
|
52
127
|
|
128
|
+
# @api private
|
129
|
+
attr_writer :value
|
130
|
+
|
53
131
|
def_delegators :@provided_values, :[], :[]=, :to_h, :key?, :fetch
|
54
132
|
|
55
133
|
# @!method [](key)
|
@@ -58,6 +136,7 @@ module GraphQL
|
|
58
136
|
# @!method []=(key, value)
|
59
137
|
# Reassign `key` to the hash passed to {Schema#execute} as `context:`
|
60
138
|
|
139
|
+
|
61
140
|
# @return [GraphQL::Schema::Warden]
|
62
141
|
def warden
|
63
142
|
@warden ||= @query.warden
|
@@ -70,46 +149,32 @@ module GraphQL
|
|
70
149
|
@storage[ns]
|
71
150
|
end
|
72
151
|
|
73
|
-
def
|
74
|
-
|
75
|
-
context: self,
|
76
|
-
parent: self,
|
77
|
-
key: key,
|
78
|
-
selection: selection,
|
79
|
-
parent_type: parent_type,
|
80
|
-
field: field,
|
81
|
-
)
|
152
|
+
def inspect
|
153
|
+
"#<Query::Context ...>"
|
82
154
|
end
|
83
155
|
|
84
|
-
#
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
end
|
89
|
-
|
90
|
-
# Add error at query-level.
|
91
|
-
# @param error [GraphQL::ExecutionError] an execution error
|
92
|
-
# @return [void]
|
93
|
-
def add_error(error)
|
94
|
-
if !error.is_a?(ExecutionError)
|
95
|
-
raise TypeError, "expected error to be a ExecutionError, but was #{error.class}"
|
96
|
-
end
|
97
|
-
errors << error
|
98
|
-
nil
|
156
|
+
# @api private
|
157
|
+
def received_null_child
|
158
|
+
@invalid_null = true
|
159
|
+
@value = nil
|
99
160
|
end
|
100
161
|
|
101
162
|
class FieldResolutionContext
|
163
|
+
include SharedMethods
|
102
164
|
extend GraphQL::Delegate
|
103
165
|
|
104
|
-
attr_reader :
|
166
|
+
attr_reader :irep_node, :field, :parent_type, :query, :schema, :parent, :key, :type
|
167
|
+
alias :selection :irep_node
|
105
168
|
|
106
|
-
def initialize(context:, key:,
|
169
|
+
def initialize(context:, key:, irep_node:, parent:, object:)
|
107
170
|
@context = context
|
108
171
|
@key = key
|
109
172
|
@parent = parent
|
110
|
-
@
|
111
|
-
@
|
112
|
-
@
|
173
|
+
@object = object
|
174
|
+
@irep_node = irep_node
|
175
|
+
@field = irep_node.definition
|
176
|
+
@parent_type = irep_node.owner_type
|
177
|
+
@type = field.type
|
113
178
|
# This is needed constantly, so set it ahead of time:
|
114
179
|
@query = context.query
|
115
180
|
@schema = context.schema
|
@@ -122,41 +187,79 @@ module GraphQL
|
|
122
187
|
def_delegators :@context,
|
123
188
|
:[], :[]=, :key?, :fetch, :to_h, :namespace,
|
124
189
|
:spawn, :schema, :warden, :errors,
|
125
|
-
:execution_strategy, :strategy
|
190
|
+
:execution_strategy, :strategy
|
126
191
|
|
127
192
|
# @return [GraphQL::Language::Nodes::Field] The AST node for the currently-executing field
|
128
193
|
def ast_node
|
129
|
-
@
|
130
|
-
end
|
131
|
-
|
132
|
-
# @return [GraphQL::InternalRepresentation::Node]
|
133
|
-
def irep_node
|
134
|
-
@selection
|
194
|
+
@irep_node.ast_node
|
135
195
|
end
|
136
196
|
|
137
197
|
# Add error to current field resolution.
|
138
198
|
# @param error [GraphQL::ExecutionError] an execution error
|
139
199
|
# @return [void]
|
140
200
|
def add_error(error)
|
141
|
-
|
142
|
-
raise TypeError, "expected error to be a ExecutionError, but was #{error.class}"
|
143
|
-
end
|
144
|
-
|
201
|
+
super
|
145
202
|
error.ast_node ||= irep_node.ast_node
|
146
203
|
error.path ||= path
|
147
|
-
errors << error
|
148
204
|
nil
|
149
205
|
end
|
150
206
|
|
151
|
-
def
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
207
|
+
def inspect
|
208
|
+
"#<GraphQL Context @ #{irep_node.owner_type.name}.#{field.name}>"
|
209
|
+
end
|
210
|
+
|
211
|
+
# Set a new value for this field in the response.
|
212
|
+
# It may be updated after resolving a {Lazy}.
|
213
|
+
# If it is {Execute::PROPAGATE_NULL}, tell the owner to propagate null.
|
214
|
+
# If it's {Execute::Execution::SKIP}, remove this field result from its parent
|
215
|
+
# @param new_value [Any] The GraphQL-ready value
|
216
|
+
# @api private
|
217
|
+
def value=(new_value)
|
218
|
+
case new_value
|
219
|
+
when GraphQL::Execution::Execute::PROPAGATE_NULL, nil
|
220
|
+
@invalid_null = true
|
221
|
+
@value = nil
|
222
|
+
if @type.kind.non_null?
|
223
|
+
@parent.received_null_child
|
224
|
+
end
|
225
|
+
when GraphQL::Execution::Execute::SKIP
|
226
|
+
@parent.skipped = true
|
227
|
+
@parent.delete(self)
|
228
|
+
else
|
229
|
+
@value = new_value
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
protected
|
234
|
+
|
235
|
+
def received_null_child
|
236
|
+
case @value
|
237
|
+
when Hash
|
238
|
+
self.value = GraphQL::Execution::Execute::PROPAGATE_NULL
|
239
|
+
when Array
|
240
|
+
if list_of_non_null_items?(@type)
|
241
|
+
self.value = GraphQL::Execution::Execute::PROPAGATE_NULL
|
242
|
+
end
|
243
|
+
when nil
|
244
|
+
# TODO This is a hack
|
245
|
+
# It was already nulled out but it's getting reassigned
|
246
|
+
else
|
247
|
+
raise "Unexpected value for received_null_child (#{self.value.class}): #{value}"
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
private
|
252
|
+
|
253
|
+
def list_of_non_null_items?(type)
|
254
|
+
case type
|
255
|
+
when GraphQL::NonNullType
|
256
|
+
# Unwrap [T]!
|
257
|
+
list_of_non_null_items?(type.of_type)
|
258
|
+
when GraphQL::ListType
|
259
|
+
type.of_type.is_a?(GraphQL::NonNullType)
|
260
|
+
else
|
261
|
+
raise "Unexpected list_of_non_null_items check: #{type}"
|
262
|
+
end
|
160
263
|
end
|
161
264
|
end
|
162
265
|
end
|