graphql 2.6.14 → 2.6.15
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.
- checksums.yaml +4 -4
- data/lib/generators/graphql/templates/graphql_controller.erb +7 -1
- data/lib/graphql/analysis/max_query_complexity.rb +2 -1
- data/lib/graphql/analysis/max_query_depth.rb +2 -1
- data/lib/graphql/analysis/query_complexity.rb +2 -1
- data/lib/graphql/analysis/query_depth.rb +1 -1
- data/lib/graphql/analysis/visitor.rb +14 -2
- data/lib/graphql/analysis.rb +4 -5
- data/lib/graphql/analysis_error.rb +1 -1
- data/lib/graphql/coercion_error.rb +1 -1
- data/lib/graphql/current.rb +5 -1
- data/lib/graphql/dashboard.rb +1 -1
- data/lib/graphql/date_encoding_error.rb +3 -1
- data/lib/graphql/duration_encoding_error.rb +3 -1
- data/lib/graphql/execution/directive_checks.rb +2 -1
- data/lib/graphql/execution/errors.rb +18 -7
- data/lib/graphql/execution/field_resolve_step.rb +3 -3
- data/lib/graphql/execution/input_values.rb +160 -120
- data/lib/graphql/execution/interpreter/runtime.rb +9 -5
- data/lib/graphql/execution/lookahead.rb +19 -8
- data/lib/graphql/execution/next.rb +1 -0
- data/lib/graphql/execution/runner.rb +19 -12
- data/lib/graphql/execution/selections_step.rb +1 -1
- data/lib/graphql/execution/variable_values.rb +248 -0
- data/lib/graphql/float_decoding_error.rb +3 -1
- data/lib/graphql/integer_decoding_error.rb +3 -1
- data/lib/graphql/introspection/base_object.rb +1 -1
- data/lib/graphql/introspection/directive_location_enum.rb +1 -1
- data/lib/graphql/introspection/directive_type.rb +3 -1
- data/lib/graphql/introspection/dynamic_fields.rb +3 -1
- data/lib/graphql/introspection/enum_value_type.rb +3 -1
- data/lib/graphql/introspection/field_type.rb +5 -1
- data/lib/graphql/introspection/type_kind_enum.rb +1 -1
- data/lib/graphql/introspection.rb +2 -1
- data/lib/graphql/language/comment.rb +2 -1
- data/lib/graphql/language/generation.rb +2 -1
- data/lib/graphql/language/lexer.rb +29 -21
- data/lib/graphql/language/parser.rb +10 -7
- data/lib/graphql/language.rb +1 -1
- data/lib/graphql/name_validator.rb +2 -1
- data/lib/graphql/pagination.rb +1 -1
- data/lib/graphql/query/fingerprint.rb +2 -1
- data/lib/graphql/query/partial.rb +4 -0
- data/lib/graphql/query.rb +8 -12
- data/lib/graphql/railtie.rb +1 -1
- data/lib/graphql/relay.rb +1 -1
- data/lib/graphql/rubocop.rb +1 -1
- data/lib/graphql/runtime_error.rb +1 -1
- data/lib/graphql/runtime_type_error.rb +1 -1
- data/lib/graphql/schema/always_visible.rb +3 -1
- data/lib/graphql/schema/base_64_encoder.rb +3 -1
- data/lib/graphql/schema/built_in_types.rb +2 -2
- data/lib/graphql/schema/directive/deprecated.rb +1 -1
- data/lib/graphql/schema/directive/include.rb +2 -1
- data/lib/graphql/schema/directive/skip.rb +2 -1
- data/lib/graphql/schema/directive/specified_by.rb +1 -1
- data/lib/graphql/schema/field/scope_extension.rb +2 -1
- data/lib/graphql/schema/field.rb +3 -0
- data/lib/graphql/schema/find_inherited_value.rb +5 -1
- data/lib/graphql/schema/member/graphql_type_names.rb +1 -1
- data/lib/graphql/schema/member/has_directives.rb +20 -22
- data/lib/graphql/schema/member/has_path.rb +2 -1
- data/lib/graphql/schema/member/has_unresolved_type_error.rb +2 -1
- data/lib/graphql/schema/member.rb +3 -2
- data/lib/graphql/schema/mutation.rb +6 -1
- data/lib/graphql/schema/object.rb +1 -1
- data/lib/graphql/schema/resolver.rb +2 -2
- data/lib/graphql/schema/type_expression.rb +3 -1
- data/lib/graphql/schema/validator/all_validator.rb +6 -3
- data/lib/graphql/static_validation/all_rules.rb +1 -1
- data/lib/graphql/static_validation/base_visitor.rb +125 -53
- data/lib/graphql/static_validation/definition_dependencies.rb +14 -2
- data/lib/graphql/static_validation/interpreter_visitor.rb +1 -1
- data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +4 -1
- data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +8 -4
- data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -1
- data/lib/graphql/static_validation/rules/fragments_are_named.rb +2 -1
- data/lib/graphql/static_validation/rules/fragments_are_named_error.rb +4 -1
- data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +6 -3
- data/lib/graphql/static_validation/rules/fragments_are_used.rb +2 -1
- data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +3 -1
- data/lib/graphql/static_validation/rules/mutation_root_exists.rb +3 -2
- data/lib/graphql/static_validation/rules/mutation_root_exists_error.rb +4 -1
- data/lib/graphql/static_validation/rules/no_definitions_are_present_error.rb +4 -1
- data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +4 -1
- data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid.rb +9 -2
- data/lib/graphql/static_validation/rules/query_root_exists.rb +3 -2
- data/lib/graphql/static_validation/rules/query_root_exists_error.rb +4 -1
- data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present_error.rb +10 -4
- data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +8 -6
- data/lib/graphql/static_validation/rules/subscription_root_exists_error.rb +4 -1
- data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +5 -4
- data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +2 -1
- data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +2 -1
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +1 -1
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
- data/lib/graphql/static_validation/validation_context.rb +49 -14
- data/lib/graphql/static_validation/validation_timeout_error.rb +4 -1
- data/lib/graphql/static_validation.rb +1 -2
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +14 -13
- data/lib/graphql/testing.rb +1 -1
- data/lib/graphql/tracing/legacy_trace.rb +1 -1
- data/lib/graphql/tracing/null_trace.rb +1 -1
- data/lib/graphql/types/big_int.rb +4 -1
- data/lib/graphql/types/boolean.rb +3 -1
- data/lib/graphql/types/id.rb +3 -1
- data/lib/graphql/types/iso_8601_date.rb +1 -0
- data/lib/graphql/types/iso_8601_date_time.rb +6 -2
- data/lib/graphql/types/iso_8601_duration.rb +3 -0
- data/lib/graphql/types/relay/base_connection.rb +1 -1
- data/lib/graphql/types/relay/base_edge.rb +1 -1
- data/lib/graphql/types/relay/node.rb +1 -1
- data/lib/graphql/types/relay/page_info.rb +1 -1
- data/lib/graphql/types/relay/page_info_behaviors.rb +4 -1
- data/lib/graphql/types/relay.rb +1 -1
- data/lib/graphql/types/string.rb +1 -0
- data/lib/graphql/types.rb +1 -1
- data/lib/graphql/version.rb +2 -2
- metadata +3 -4
- data/lib/graphql/query/variables.rb +0 -99
- data/lib/graphql/static_validation/literal_validator.rb +0 -157
|
@@ -2,44 +2,33 @@
|
|
|
2
2
|
# typed: true
|
|
3
3
|
module GraphQL
|
|
4
4
|
module Execution
|
|
5
|
+
# Coerces input values (variables and arguments) for a query.
|
|
6
|
+
#
|
|
7
|
+
# Static validation uses [value_from_ast](rdoc-ref:#value_from_ast) to check literal values,
|
|
8
|
+
# and the runtime uses it to coerce them.
|
|
5
9
|
class InputValues
|
|
6
|
-
def initialize(query
|
|
10
|
+
def initialize(query)
|
|
7
11
|
@query = query
|
|
8
|
-
@
|
|
12
|
+
@schema = query.schema
|
|
9
13
|
@variable_values = nil
|
|
14
|
+
@coerced_literals = nil
|
|
10
15
|
end
|
|
11
16
|
|
|
12
17
|
def variable_values
|
|
13
|
-
@variable_values ||=
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
raw_values = @query.provided_variables
|
|
19
|
-
values = {}
|
|
20
|
-
variable_nodes.each do |var_node|
|
|
21
|
-
var_ast_value = if raw_values.key?(var_node.name)
|
|
22
|
-
raw_values[var_node.name]
|
|
23
|
-
elsif raw_values.key?(sym_name = var_node.name.to_sym)
|
|
24
|
-
raw_values[sym_name]
|
|
25
|
-
elsif !var_node.default_value.nil?
|
|
26
|
-
var_node.default_value
|
|
27
|
-
else
|
|
28
|
-
next
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
var_type = @runner.schema.type_from_ast(var_node.type, context: @query.context)
|
|
32
|
-
values[var_node.name] = variable_value(var_ast_value, var_type)
|
|
33
|
-
end
|
|
34
|
-
values
|
|
35
|
-
end
|
|
36
|
-
end
|
|
18
|
+
@variable_values ||= GraphQL::Execution::VariableValues.new(
|
|
19
|
+
@query,
|
|
20
|
+
@query.selected_operation&.variables || EmptyObjects::EMPTY_ARRAY,
|
|
21
|
+
@query.provided_variables,
|
|
22
|
+
)
|
|
37
23
|
end
|
|
38
24
|
|
|
39
25
|
def argument_values(owner_defn, argument_nodes, field_resolve_step)
|
|
40
26
|
arg_defns = @query.types.arguments(owner_defn)
|
|
41
|
-
|
|
27
|
+
if arg_defns.empty?
|
|
28
|
+
return [EmptyObjects::EMPTY_HASH, nil]
|
|
29
|
+
end
|
|
42
30
|
errors = nil #: untyped
|
|
31
|
+
argument_values = {}
|
|
43
32
|
|
|
44
33
|
arg_defns.each do |argument_definition|
|
|
45
34
|
arg_ruby_key = argument_definition.keyword
|
|
@@ -49,6 +38,8 @@ module GraphQL
|
|
|
49
38
|
if argument_definition.default_value?
|
|
50
39
|
arg_value = value_from_ast(argument_definition.default_value, argument_definition.type)
|
|
51
40
|
argument_value(argument_values, arg_ruby_key, argument_definition, arg_value, nil, field_resolve_step)
|
|
41
|
+
elsif argument_definition.type.non_null?
|
|
42
|
+
# Add an error
|
|
52
43
|
end
|
|
53
44
|
else
|
|
54
45
|
arg_value = value_from_ast(arg_node.value, argument_definition.type)
|
|
@@ -64,74 +55,6 @@ module GraphQL
|
|
|
64
55
|
|
|
65
56
|
private
|
|
66
57
|
|
|
67
|
-
def variable_value(value, type)
|
|
68
|
-
if type.non_null?
|
|
69
|
-
type = type.of_type
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
if value.is_a?(Language::Nodes::Enum)
|
|
73
|
-
value = value.name
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
if value.nil?
|
|
77
|
-
nil
|
|
78
|
-
elsif type.list?
|
|
79
|
-
inner_type = type.of_type
|
|
80
|
-
if value.is_a?(Array)
|
|
81
|
-
value.map { |v| variable_value(v, inner_type) }.freeze
|
|
82
|
-
else
|
|
83
|
-
[variable_value(value, inner_type)].freeze
|
|
84
|
-
end
|
|
85
|
-
elsif type.kind.input_object?
|
|
86
|
-
coerced_obj = {}
|
|
87
|
-
|
|
88
|
-
if value.is_a?(Hash)
|
|
89
|
-
@query.types.arguments(type).each do |arg|
|
|
90
|
-
arg_key = arg.keyword
|
|
91
|
-
if value.key?(arg.graphql_name)
|
|
92
|
-
arg_value = value[arg.graphql_name]
|
|
93
|
-
elsif value.key?(sym_name = arg.graphql_name.to_sym)
|
|
94
|
-
arg_value = value[sym_name]
|
|
95
|
-
elsif arg.default_value?
|
|
96
|
-
coerced_obj[arg_key] = arg.default_value
|
|
97
|
-
next
|
|
98
|
-
else
|
|
99
|
-
next
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
if arg_value.nil? && arg.replace_null_with_default?
|
|
103
|
-
arg_value = arg.default_value
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
coerced_obj[arg_key] = variable_value(arg_value, arg.type)
|
|
107
|
-
end
|
|
108
|
-
else
|
|
109
|
-
@query.types.arguments(type).each do |arg|
|
|
110
|
-
arg_key = arg.keyword
|
|
111
|
-
arg_name = arg.graphql_name
|
|
112
|
-
if (v_node = value.arguments.find { |a| a.name == arg_name }) # rubocop:disable Development/ContextIsPassedCop
|
|
113
|
-
arg_value = v_node.value
|
|
114
|
-
coerced_obj[arg_key] = if arg_value.nil? && arg.replace_null_with_default?
|
|
115
|
-
arg.default_value
|
|
116
|
-
else
|
|
117
|
-
variable_value(arg_value, arg.type)
|
|
118
|
-
end
|
|
119
|
-
elsif arg.default_value?
|
|
120
|
-
coerced_obj[arg_key] = arg.default_value
|
|
121
|
-
else
|
|
122
|
-
# Nothing
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
coerced_obj
|
|
128
|
-
elsif type.kind.leaf?
|
|
129
|
-
type.coerce_input(value, @query.context)
|
|
130
|
-
else
|
|
131
|
-
raise GraphQL::Error, "Unexpected input type: #{type.graphql_name}."
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
|
|
135
58
|
def argument_value(argument_values, argument_key, argument_definition, arg_value, override_type, field_resolve_step)
|
|
136
59
|
treat_as_type = override_type || argument_definition.type
|
|
137
60
|
if treat_as_type.non_null?
|
|
@@ -183,7 +106,7 @@ module GraphQL
|
|
|
183
106
|
arg_value = begin
|
|
184
107
|
argument_definition.prepare_value(nil, arg_value, context: @query.context)
|
|
185
108
|
rescue StandardError => err
|
|
186
|
-
handled_err = @
|
|
109
|
+
handled_err = @schema.handle_or_reraise(@query.context, err, object: nil, arguments: argument_values, field: field_resolve_step&.field_definition)
|
|
187
110
|
raise handled_err if handled_err.is_a?(GraphQL::RuntimeError)
|
|
188
111
|
handled_err
|
|
189
112
|
end
|
|
@@ -211,7 +134,7 @@ module GraphQL
|
|
|
211
134
|
argument_key: idx,
|
|
212
135
|
)
|
|
213
136
|
ps.push(loads_step)
|
|
214
|
-
|
|
137
|
+
field_resolve_step.runner.add_step(loads_step)
|
|
215
138
|
end
|
|
216
139
|
else
|
|
217
140
|
loads_step = LoadArgumentStep.new(
|
|
@@ -223,7 +146,7 @@ module GraphQL
|
|
|
223
146
|
argument_key: argument_key,
|
|
224
147
|
)
|
|
225
148
|
ps.push(loads_step)
|
|
226
|
-
|
|
149
|
+
field_resolve_step.runner.add_step(loads_step)
|
|
227
150
|
end
|
|
228
151
|
else
|
|
229
152
|
argument_values[argument_key] = arg_value
|
|
@@ -231,31 +154,57 @@ module GraphQL
|
|
|
231
154
|
nil
|
|
232
155
|
end
|
|
233
156
|
|
|
234
|
-
|
|
157
|
+
public
|
|
158
|
+
|
|
159
|
+
# Raised by [value_from_ast](rdoc-ref:#value_from_ast) with `validate: true` when a literal value isn't valid for its type.
|
|
160
|
+
class InvalidLiteralError < GraphQL::Error # :nodoc:
|
|
161
|
+
# **Returns**
|
|
162
|
+
#
|
|
163
|
+
# - `String, nil` — A description of the problem, if there is one
|
|
164
|
+
attr_reader :explanation
|
|
165
|
+
|
|
166
|
+
def initialize(explanation = nil)
|
|
167
|
+
@explanation = explanation
|
|
168
|
+
super(explanation || "Invalid literal value")
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Coerce `value_node` (a literal from a query string or a default value) for `type`.
|
|
173
|
+
#
|
|
174
|
+
# **Parameters**
|
|
175
|
+
#
|
|
176
|
+
# - `value_node` (`GraphQL::Language::Nodes::AbstractNode, Object`)
|
|
177
|
+
# - `type` (`Class<GraphQL::Schema::Member>`)
|
|
178
|
+
# - `validate` (`Boolean`) — When true, raise `InvalidLiteralError` for invalid values and treat variable references as valid.
|
|
179
|
+
#
|
|
180
|
+
# :call-seq:
|
|
181
|
+
# value_from_ast(GraphQL::Language::Nodes::AbstractNode | Object value_node, Class[GraphQL::Schema::Member] type, bool validate:)
|
|
182
|
+
def value_from_ast(value_node, type, validate: false)
|
|
235
183
|
if type.non_null?
|
|
236
184
|
type = type.of_type
|
|
185
|
+
if validate && (value_node.nil? || value_node.is_a?(Language::Nodes::NullValue))
|
|
186
|
+
raise InvalidLiteralError
|
|
187
|
+
end
|
|
237
188
|
end
|
|
238
189
|
|
|
239
|
-
if value_node.nil?
|
|
190
|
+
if value_node.nil? || (value_node.is_a?(Language::Nodes::NullValue) && (validate || !type.kind.leaf?))
|
|
240
191
|
nil
|
|
241
192
|
elsif value_node.is_a?(GraphQL::Language::Nodes::VariableIdentifier)
|
|
242
|
-
|
|
193
|
+
# Variables are checked by `VariableUsagesAreAllowed` during validation
|
|
194
|
+
validate ? nil : variable_values.coerced_value(value_node.name)
|
|
243
195
|
elsif type.list?
|
|
244
196
|
inner_type = type.of_type
|
|
245
197
|
if value_node.is_a?(Array)
|
|
246
198
|
coerced_items = value_node.map do |inner_value_node|
|
|
247
|
-
value_from_ast(inner_value_node, inner_type)
|
|
199
|
+
value_from_ast(inner_value_node, inner_type, validate: validate)
|
|
248
200
|
end
|
|
249
201
|
coerced_items.freeze
|
|
250
|
-
elsif value_node.is_a?(Language::Nodes::NullValue)
|
|
251
|
-
nil
|
|
252
202
|
else
|
|
253
|
-
item_value = value_from_ast(value_node, inner_type)
|
|
203
|
+
item_value = value_from_ast(value_node, inner_type, validate: validate)
|
|
254
204
|
[item_value].freeze
|
|
255
205
|
end
|
|
256
206
|
elsif type.kind.input_object?
|
|
257
207
|
coerced_obj = {}
|
|
258
|
-
# TODO manually handle NullValue here?
|
|
259
208
|
if value_node.is_a?(Hash)
|
|
260
209
|
@query.types.arguments(type).each do |arg|
|
|
261
210
|
arg_value = value_node[arg.keyword]
|
|
@@ -267,9 +216,12 @@ module GraphQL
|
|
|
267
216
|
next
|
|
268
217
|
end
|
|
269
218
|
|
|
270
|
-
coerced_obj[arg_key] = value_from_ast(arg_value, arg.type)
|
|
219
|
+
coerced_obj[arg_key] = value_from_ast(arg_value, arg.type, validate: validate)
|
|
220
|
+
end
|
|
221
|
+
elsif value_node.is_a?(Language::Nodes::InputObject)
|
|
222
|
+
if validate && type.one_of? && value_node.arguments.size != 1 # rubocop:disable Development/ContextIsPassedCop
|
|
223
|
+
raise InvalidLiteralError, "`#{type.graphql_name}` is a OneOf type, so only one argument may be given (instead of #{value_node.arguments.size})" # rubocop:disable Development/ContextIsPassedCop
|
|
271
224
|
end
|
|
272
|
-
else
|
|
273
225
|
arg_nodes_by_name = value_node.arguments.each_with_object({}) do |arg_node, acc| # rubocop:disable Development/ContextIsPassedCop
|
|
274
226
|
acc[arg_node.name] = arg_node
|
|
275
227
|
end
|
|
@@ -277,30 +229,53 @@ module GraphQL
|
|
|
277
229
|
@query.types.arguments(type).each do |arg|
|
|
278
230
|
arg_node = arg_nodes_by_name[arg.graphql_name]
|
|
279
231
|
arg_key = arg.keyword
|
|
280
|
-
if arg_node.nil? || (arg_node.value.is_a?(Language::Nodes::VariableIdentifier) && !variable_values.key?(arg_node.value.name))
|
|
232
|
+
if arg_node.nil? || (!validate && arg_node.value.is_a?(Language::Nodes::VariableIdentifier) && !variable_values.key?(arg_node.value.name))
|
|
281
233
|
if arg.default_value?
|
|
282
234
|
coerced_obj[arg_key] = arg.default_value
|
|
235
|
+
elsif validate && arg.type.non_null?
|
|
236
|
+
raise InvalidLiteralError
|
|
283
237
|
end
|
|
284
238
|
next
|
|
285
239
|
end
|
|
286
240
|
|
|
287
|
-
|
|
288
|
-
coerced_obj[arg_key] = arg_value
|
|
241
|
+
coerced_obj[arg_key] = value_from_ast(arg_node.value, arg.type, validate: validate)
|
|
289
242
|
end
|
|
290
|
-
end
|
|
291
243
|
|
|
244
|
+
if validate
|
|
245
|
+
value_node.arguments.each do |arg_node| # rubocop:disable Development/ContextIsPassedCop
|
|
246
|
+
if @query.types.argument(type, arg_node.name).nil?
|
|
247
|
+
raise InvalidLiteralError
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
elsif validate
|
|
252
|
+
raise InvalidLiteralError
|
|
253
|
+
end
|
|
292
254
|
|
|
293
255
|
coerced_obj
|
|
294
|
-
elsif type.kind.
|
|
295
|
-
if
|
|
296
|
-
value_node
|
|
256
|
+
elsif type.kind.enum?
|
|
257
|
+
if validate
|
|
258
|
+
enum_value_from_ast(value_node, type)
|
|
259
|
+
else
|
|
260
|
+
if value_node.is_a?(Language::Nodes::Enum)
|
|
261
|
+
value_node = value_node.name
|
|
262
|
+
end
|
|
263
|
+
coerce_leaf_value(value_node, type)
|
|
297
264
|
end
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
265
|
+
elsif type.kind.scalar?
|
|
266
|
+
if validate
|
|
267
|
+
scalar_value_from_ast(value_node, type)
|
|
268
|
+
elsif (literals = @coerced_literals&.[](type)) && literals.key?(value_node)
|
|
269
|
+
# This literal was already coerced during validation
|
|
270
|
+
literals[value_node]
|
|
271
|
+
else
|
|
272
|
+
if value_node.is_a?(Language::Nodes::AbstractNode) || value_node.is_a?(Array)
|
|
273
|
+
value_node = coerce_untyped_input(value_node)
|
|
274
|
+
end
|
|
275
|
+
coerce_leaf_value(value_node, type)
|
|
303
276
|
end
|
|
277
|
+
elsif validate
|
|
278
|
+
raise InvalidLiteralError
|
|
304
279
|
else
|
|
305
280
|
raise "Unexpected input type: #{type.to_type_signature}."
|
|
306
281
|
end
|
|
@@ -308,6 +283,69 @@ module GraphQL
|
|
|
308
283
|
|
|
309
284
|
private
|
|
310
285
|
|
|
286
|
+
def coerce_leaf_value(value, type)
|
|
287
|
+
type.coerce_input(value, @query.context)
|
|
288
|
+
rescue GraphQL::UnauthorizedEnumValueError => enum_err
|
|
289
|
+
@schema.unauthorized_object(enum_err)
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def enum_value_from_ast(value_node, type)
|
|
293
|
+
if !value_node.is_a?(Language::Nodes::Enum)
|
|
294
|
+
raise InvalidLiteralError
|
|
295
|
+
end
|
|
296
|
+
allowed_values = @query.types.enum_values(type)
|
|
297
|
+
if allowed_values.none? { |v| v.graphql_name == value_node.name }
|
|
298
|
+
raise InvalidLiteralError, "Expected #{GraphQL::Language.serialize(value_node.name)} to be one of: #{allowed_values.map(&:graphql_name).join(', ')}"
|
|
299
|
+
end
|
|
300
|
+
# Authorization is checked at runtime
|
|
301
|
+
nil
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def scalar_value_from_ast(value_node, type)
|
|
305
|
+
literals = (@coerced_literals ||= {}.compare_by_identity)[type] ||= {}.compare_by_identity
|
|
306
|
+
if literals.key?(value_node)
|
|
307
|
+
return literals[value_node]
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
if contains_variable?(value_node)
|
|
311
|
+
# Variables aren't supported inside scalar literals
|
|
312
|
+
raise InvalidLiteralError
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
ruby_value = case value_node
|
|
316
|
+
when Language::Nodes::InputObject, Array
|
|
317
|
+
# Top-level enums are passed as AST nodes,
|
|
318
|
+
# but nested ones are converted to strings, to match previous behavior.
|
|
319
|
+
coerce_untyped_input(value_node)
|
|
320
|
+
else
|
|
321
|
+
value_node
|
|
322
|
+
end
|
|
323
|
+
coerced_value = begin
|
|
324
|
+
type.coerce_input(ruby_value, @query.context)
|
|
325
|
+
rescue GraphQL::CoercionError
|
|
326
|
+
raise
|
|
327
|
+
rescue StandardError => err
|
|
328
|
+
@query.handle_or_reraise(err)
|
|
329
|
+
end
|
|
330
|
+
if coerced_value.nil?
|
|
331
|
+
raise InvalidLiteralError, "Could not coerce value #{GraphQL::Language.serialize(ruby_value)} to #{type.graphql_name}"
|
|
332
|
+
end
|
|
333
|
+
literals[value_node] = coerced_value
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def contains_variable?(value_node)
|
|
337
|
+
case value_node
|
|
338
|
+
when Language::Nodes::VariableIdentifier
|
|
339
|
+
true
|
|
340
|
+
when Array
|
|
341
|
+
value_node.any? { |v| contains_variable?(v) }
|
|
342
|
+
when Language::Nodes::InputObject
|
|
343
|
+
value_node.arguments.any? { |arg| contains_variable?(arg.value) } # rubocop:disable Development/ContextIsPassedCop
|
|
344
|
+
else
|
|
345
|
+
false
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
|
|
311
349
|
def coerce_untyped_input(input_value)
|
|
312
350
|
case input_value
|
|
313
351
|
when Language::Nodes::AbstractNode
|
|
@@ -322,6 +360,8 @@ module GraphQL
|
|
|
322
360
|
value_h[arg.name] = coerce_untyped_input(arg.value)
|
|
323
361
|
end
|
|
324
362
|
value_h
|
|
363
|
+
when Language::Nodes::VariableIdentifier
|
|
364
|
+
coerce_untyped_input(variable_values[input_value.name])
|
|
325
365
|
else
|
|
326
366
|
raise "Unhandled untyped input AST node: #{input_value.class}"
|
|
327
367
|
end
|
|
@@ -221,7 +221,7 @@ module GraphQL
|
|
|
221
221
|
end
|
|
222
222
|
|
|
223
223
|
#: (untyped, untyped, untyped, untyped, Array[untyped]?, untyped, untyped) -> untyped
|
|
224
|
-
def gather_selections(graphql_response, owner_object, owner_type, selections, selections_to_run, selections_by_name, ordered_result_keys)
|
|
224
|
+
def gather_selections(graphql_response, owner_object, owner_type, selections, selections_to_run, selections_by_name, ordered_result_keys, inlined_fragments: nil)
|
|
225
225
|
selections.each do |node|
|
|
226
226
|
if node.is_a?(GraphQL::Language::Nodes::Field)
|
|
227
227
|
response_key = node.alias || node.name
|
|
@@ -271,14 +271,14 @@ module GraphQL
|
|
|
271
271
|
)
|
|
272
272
|
|
|
273
273
|
if query.types.possible_types(type_defn).include?(owner_type)
|
|
274
|
-
result = gather_selections(graphql_response, owner_object, owner_type, node.selections, selections_to_run, next_selections, ordered_result_keys)
|
|
274
|
+
result = gather_selections(graphql_response, owner_object, owner_type, node.selections, selections_to_run, next_selections, ordered_result_keys, inlined_fragments: inlined_fragments)
|
|
275
275
|
if !result.equal?(next_selections)
|
|
276
276
|
selections_to_run = result
|
|
277
277
|
end
|
|
278
278
|
end
|
|
279
279
|
else
|
|
280
280
|
# it's an untyped fragment, definitely continue
|
|
281
|
-
result = gather_selections(graphql_response, owner_object, owner_type, node.selections, selections_to_run, next_selections, ordered_result_keys)
|
|
281
|
+
result = gather_selections(graphql_response, owner_object, owner_type, node.selections, selections_to_run, next_selections, ordered_result_keys, inlined_fragments: inlined_fragments)
|
|
282
282
|
if !result.equal?(next_selections)
|
|
283
283
|
selections_to_run = result
|
|
284
284
|
end
|
|
@@ -286,8 +286,12 @@ module GraphQL
|
|
|
286
286
|
when GraphQL::Language::Nodes::FragmentSpread
|
|
287
287
|
fragment_def = query.fragments[node.name]
|
|
288
288
|
type_defn = query.types.type(fragment_def.type.name)
|
|
289
|
-
|
|
290
|
-
|
|
289
|
+
# Spreading the same fragment again into these selections adds nothing, so only gather it once.
|
|
290
|
+
inlined_fragments ||= {}.compare_by_identity
|
|
291
|
+
inlined_names = (inlined_fragments[next_selections] ||= {})
|
|
292
|
+
if !inlined_names.key?(node.name) && query.types.possible_types(type_defn).include?(owner_type)
|
|
293
|
+
inlined_names[node.name] = true
|
|
294
|
+
result = gather_selections(graphql_response, owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections, ordered_result_keys, inlined_fragments: inlined_fragments)
|
|
291
295
|
if !result.equal?(next_selections)
|
|
292
296
|
selections_to_run = result
|
|
293
297
|
end
|
|
@@ -344,7 +344,7 @@ module GraphQL
|
|
|
344
344
|
false
|
|
345
345
|
end
|
|
346
346
|
|
|
347
|
-
def find_selections(subselections_by_type, selections_on_type, selected_type, ast_selections, arguments)
|
|
347
|
+
def find_selections(subselections_by_type, selections_on_type, selected_type, ast_selections, arguments, inlined_fragments: nil)
|
|
348
348
|
ast_selections.each do |ast_selection|
|
|
349
349
|
next if skipped_by_directive?(ast_selection)
|
|
350
350
|
|
|
@@ -369,13 +369,19 @@ module GraphQL
|
|
|
369
369
|
on_type = @query.types.type(t.name)
|
|
370
370
|
subselections_on_type = subselections_by_type[on_type] ||= {}
|
|
371
371
|
end
|
|
372
|
-
find_selections(subselections_by_type, subselections_on_type, on_type, ast_selection.selections, arguments)
|
|
372
|
+
find_selections(subselections_by_type, subselections_on_type, on_type, ast_selection.selections, arguments, inlined_fragments: inlined_fragments)
|
|
373
373
|
when GraphQL::Language::Nodes::FragmentSpread
|
|
374
374
|
frag_defn = lookup_fragment(ast_selection)
|
|
375
375
|
# Again, assuming a valid AST
|
|
376
376
|
on_type = @query.types.type(frag_defn.type.name)
|
|
377
377
|
subselections_on_type = subselections_by_type[on_type] ||= {}
|
|
378
|
-
|
|
378
|
+
# Spreading the same fragment again into these selections adds nothing, so only find its selections once.
|
|
379
|
+
inlined_fragments ||= {}.compare_by_identity
|
|
380
|
+
inlined_names = (inlined_fragments[subselections_on_type] ||= {})
|
|
381
|
+
if !inlined_names.key?(ast_selection.name)
|
|
382
|
+
inlined_names[ast_selection.name] = true
|
|
383
|
+
find_selections(subselections_by_type, subselections_on_type, on_type, frag_defn.selections, arguments, inlined_fragments: inlined_fragments)
|
|
384
|
+
end
|
|
379
385
|
else
|
|
380
386
|
raise "Invariant: Unexpected selection type: #{ast_selection.class}"
|
|
381
387
|
end
|
|
@@ -384,7 +390,7 @@ module GraphQL
|
|
|
384
390
|
|
|
385
391
|
# If a selection on `node` matches `field_name` (which is backed by `field_defn`)
|
|
386
392
|
# and matches the `arguments:` constraints, then add that node to `matches`
|
|
387
|
-
def find_selected_nodes(node, field_name, field_defn, arguments:, matches:, alias_name: NOT_CONFIGURED)
|
|
393
|
+
def find_selected_nodes(node, field_name, field_defn, arguments:, matches:, alias_name: NOT_CONFIGURED, inlined_fragments: nil)
|
|
388
394
|
return if skipped_by_directive?(node)
|
|
389
395
|
case node
|
|
390
396
|
when GraphQL::Language::Nodes::Field
|
|
@@ -397,10 +403,14 @@ module GraphQL
|
|
|
397
403
|
end
|
|
398
404
|
end
|
|
399
405
|
when GraphQL::Language::Nodes::InlineFragment
|
|
400
|
-
node.selections.each { |s| find_selected_nodes(s, field_name, field_defn, arguments: arguments, matches: matches, alias_name: alias_name) }
|
|
406
|
+
node.selections.each { |s| find_selected_nodes(s, field_name, field_defn, arguments: arguments, matches: matches, alias_name: alias_name, inlined_fragments: inlined_fragments) }
|
|
401
407
|
when GraphQL::Language::Nodes::FragmentSpread
|
|
402
|
-
|
|
403
|
-
|
|
408
|
+
# A fragment spread again would only add the same nodes, so only search it once.
|
|
409
|
+
if inlined_fragments.nil? || !inlined_fragments.key?(node.name)
|
|
410
|
+
(inlined_fragments ||= {})[node.name] = true
|
|
411
|
+
frag_defn = lookup_fragment(node)
|
|
412
|
+
frag_defn.selections.each { |s| find_selected_nodes(s, field_name, field_defn, arguments: arguments, matches: matches, alias_name: alias_name, inlined_fragments: inlined_fragments) }
|
|
413
|
+
end
|
|
404
414
|
else
|
|
405
415
|
raise "Unexpected selection comparison on #{node.class.name} (#{node})"
|
|
406
416
|
end
|
|
@@ -425,9 +435,10 @@ module GraphQL
|
|
|
425
435
|
|
|
426
436
|
next_nodes = []
|
|
427
437
|
field_name = field_defn.name
|
|
438
|
+
inlined_fragments = {}
|
|
428
439
|
@ast_nodes.each do |ast_node|
|
|
429
440
|
ast_node.selections.each do |selection|
|
|
430
|
-
find_selected_nodes(selection, field_name, field_defn, arguments: arguments, matches: next_nodes, alias_name: alias_name)
|
|
441
|
+
find_selected_nodes(selection, field_name, field_defn, arguments: arguments, matches: next_nodes, alias_name: alias_name, inlined_fragments: inlined_fragments)
|
|
431
442
|
end
|
|
432
443
|
end
|
|
433
444
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# typed: true
|
|
3
3
|
require "graphql/execution/prepare_object_step"
|
|
4
4
|
require "graphql/execution/input_values"
|
|
5
|
+
require "graphql/execution/variable_values"
|
|
5
6
|
require "graphql/execution/field_resolve_step"
|
|
6
7
|
require "graphql/execution/finalize"
|
|
7
8
|
require "graphql/execution/load_argument_step"
|
|
@@ -12,9 +12,6 @@ module GraphQL
|
|
|
12
12
|
@selected_operation = nil
|
|
13
13
|
@dataloader = multiplex.context[:dataloader] ||= @schema.dataloader_class.new
|
|
14
14
|
@resolves_lazies = @schema.resolves_lazies?
|
|
15
|
-
@input_values = Hash.new do |h, query|
|
|
16
|
-
h[query] = InputValues.new(query, self)
|
|
17
|
-
end.compare_by_identity
|
|
18
15
|
|
|
19
16
|
@runtime_directives = nil #: Hash[String, singleton(GraphQL::Schema::Directive)]?
|
|
20
17
|
@schema.directives.each do |name, dir_class|
|
|
@@ -53,7 +50,7 @@ module GraphQL
|
|
|
53
50
|
@dataloader.append_job(step)
|
|
54
51
|
end
|
|
55
52
|
|
|
56
|
-
attr_reader :schema, :variables, :dataloader, :resolves_lazies, :authorizes, :static_type_at, :runtime_type_at, :finalizers
|
|
53
|
+
attr_reader :schema, :variables, :dataloader, :resolves_lazies, :authorizes, :static_type_at, :runtime_type_at, :finalizers
|
|
57
54
|
|
|
58
55
|
# **Returns**
|
|
59
56
|
#
|
|
@@ -95,11 +92,15 @@ module GraphQL
|
|
|
95
92
|
|
|
96
93
|
results = {}.compare_by_identity
|
|
97
94
|
queries.each do |query|
|
|
98
|
-
|
|
95
|
+
# `valid?` also covers variable, operation name and analysis errors, which apply even with `validate: false`.
|
|
96
|
+
if !query.valid? || !query.context.errors.empty?
|
|
99
97
|
results[query] = {
|
|
100
98
|
"errors" => query.static_errors.map(&:to_h)
|
|
101
99
|
}
|
|
102
100
|
next
|
|
101
|
+
elsif query.selected_operation.nil?
|
|
102
|
+
results[query] = {}
|
|
103
|
+
next
|
|
103
104
|
end
|
|
104
105
|
|
|
105
106
|
root_type = query.root_type
|
|
@@ -176,7 +177,7 @@ module GraphQL
|
|
|
176
177
|
Fiber[:__graphql_current_multiplex] = previous_multiplex
|
|
177
178
|
end
|
|
178
179
|
|
|
179
|
-
def gather_selections(type_defn, ast_selections, selections_step, query, all_selections, prototype_result, into:)
|
|
180
|
+
def gather_selections(type_defn, ast_selections, selections_step, query, all_selections, prototype_result, into:, inlined_fragments: nil)
|
|
180
181
|
ast_selections.each do |ast_selection|
|
|
181
182
|
next if !directives_include?(query, ast_selection)
|
|
182
183
|
|
|
@@ -201,7 +202,7 @@ module GraphQL
|
|
|
201
202
|
all_selections << (into = { __node: ast_selection })
|
|
202
203
|
all_selections << (prototype_result = {})
|
|
203
204
|
end
|
|
204
|
-
gather_selections(type_defn, ast_selection.selections, selections_step, query, all_selections, prototype_result, into: into)
|
|
205
|
+
gather_selections(type_defn, ast_selection.selections, selections_step, query, all_selections, prototype_result, into: into, inlined_fragments: inlined_fragments)
|
|
205
206
|
end
|
|
206
207
|
when GraphQL::Language::Nodes::FragmentSpread
|
|
207
208
|
fragment_definition = query.fragments[ast_selection.name]
|
|
@@ -211,7 +212,13 @@ module GraphQL
|
|
|
211
212
|
all_selections << (into = { __node: ast_selection })
|
|
212
213
|
all_selections << (prototype_result = {})
|
|
213
214
|
end
|
|
214
|
-
|
|
215
|
+
# Spreading the same fragment again into these selections adds nothing, so only gather it once.
|
|
216
|
+
inlined_fragments ||= {}.compare_by_identity
|
|
217
|
+
inlined_names = (inlined_fragments[into] ||= {})
|
|
218
|
+
if !inlined_names.key?(ast_selection.name)
|
|
219
|
+
inlined_names[ast_selection.name] = true
|
|
220
|
+
gather_selections(type_defn, fragment_definition.selections, selections_step, query, all_selections, prototype_result, into: into, inlined_fragments: inlined_fragments)
|
|
221
|
+
end
|
|
215
222
|
end
|
|
216
223
|
else
|
|
217
224
|
raise ArgumentError, "Unsupported graphql selection node: #{ast_selection.class} (#{ast_selection.inspect})"
|
|
@@ -285,7 +292,7 @@ module GraphQL
|
|
|
285
292
|
continue_execution = true #: bool
|
|
286
293
|
query_dirs.each do |dir_node|
|
|
287
294
|
dir_defn = runtime_directives[dir_node.name] || raise(GraphQL::Error, "No directive definition found for: #{dir_node.name.inspect}")
|
|
288
|
-
dir_args, errors =
|
|
295
|
+
dir_args, errors = query.input_values.argument_values(dir_defn, dir_node.arguments, nil) # rubocop:disable Development/ContextIsPassedCop
|
|
289
296
|
if errors
|
|
290
297
|
errors.each { |e|
|
|
291
298
|
e.ast_node = dir_node
|
|
@@ -412,14 +419,14 @@ module GraphQL
|
|
|
412
419
|
if ast_selection.directives.any? { |dir_node|
|
|
413
420
|
case dir_node.name
|
|
414
421
|
when "skip"
|
|
415
|
-
skip_args, _errors =
|
|
422
|
+
skip_args, _errors = query.input_values.argument_values(GraphQL::Schema::Directive::Skip, dir_node.arguments, nil) # rubocop:disable Development/ContextIsPassedCop
|
|
416
423
|
skip_args[:if] == true
|
|
417
424
|
when "include"
|
|
418
|
-
include_args, _errors =
|
|
425
|
+
include_args, _errors = query.input_values.argument_values(GraphQL::Schema::Directive::Include, dir_node.arguments, nil) # rubocop:disable Development/ContextIsPassedCop
|
|
419
426
|
include_args[:if] == false
|
|
420
427
|
else
|
|
421
428
|
dir_defn = runtime_directives[dir_node.name]
|
|
422
|
-
dir_args, _errors =
|
|
429
|
+
dir_args, _errors = query.input_values.argument_values(dir_defn, dir_node.arguments, nil) # rubocop:disable Development/ContextIsPassedCop
|
|
423
430
|
!dir_defn.include?(nil, dir_args, query.context)
|
|
424
431
|
end
|
|
425
432
|
}
|
|
@@ -43,7 +43,7 @@ module GraphQL
|
|
|
43
43
|
directives.each do |dir_node|
|
|
44
44
|
dir_defn = @runner.runtime_directives[dir_node.name]
|
|
45
45
|
if dir_defn # not present for `skip` or `include`
|
|
46
|
-
dir_args, _errors =
|
|
46
|
+
dir_args, _errors = query.input_values.argument_values(dir_defn, dir_node.arguments, nil) # rubocop:disable Development/ContextIsPassedCop
|
|
47
47
|
result = case directives_owner
|
|
48
48
|
when Language::Nodes::FragmentSpread
|
|
49
49
|
dir_defn.resolve_fragment_spread(directives_owner, @parent_type, @objects, dir_args, self.query.context)
|