graphql 2.0.11 → 2.0.14
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/generators/graphql/templates/schema.erb +3 -0
- data/lib/graphql/dataloader/source.rb +9 -0
- data/lib/graphql/execution/interpreter/runtime.rb +10 -8
- data/lib/graphql/execution/interpreter.rb +185 -59
- data/lib/graphql/execution/lookahead.rb +26 -26
- data/lib/graphql/execution/multiplex.rb +1 -116
- data/lib/graphql/execution.rb +0 -1
- data/lib/graphql/introspection/type_type.rb +7 -0
- data/lib/graphql/introspection.rb +2 -1
- data/lib/graphql/language/printer.rb +8 -5
- data/lib/graphql/query/validation_pipeline.rb +4 -0
- data/lib/graphql/query/variable_validation_error.rb +2 -2
- data/lib/graphql/query/variables.rb +13 -3
- data/lib/graphql/query.rb +11 -2
- data/lib/graphql/rake_task/validate.rb +1 -1
- data/lib/graphql/schema/argument.rb +25 -13
- data/lib/graphql/schema/build_from_definition.rb +1 -2
- data/lib/graphql/schema/directive/one_of.rb +12 -0
- data/lib/graphql/schema/enum.rb +1 -1
- data/lib/graphql/schema/field.rb +12 -11
- data/lib/graphql/schema/input_object.rb +36 -1
- data/lib/graphql/schema/late_bound_type.rb +4 -0
- data/lib/graphql/schema/list.rb +19 -5
- data/lib/graphql/schema/member/has_arguments.rb +8 -2
- data/lib/graphql/schema/member/validates_input.rb +2 -2
- data/lib/graphql/schema/non_null.rb +2 -2
- data/lib/graphql/schema/scalar.rb +1 -1
- data/lib/graphql/schema.rb +13 -3
- data/lib/graphql/static_validation/all_rules.rb +1 -0
- data/lib/graphql/static_validation/error.rb +2 -2
- data/lib/graphql/static_validation/literal_validator.rb +4 -0
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +11 -5
- data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid.rb +66 -0
- data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid_error.rb +29 -0
- data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +12 -6
- data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +1 -1
- data/lib/graphql/subscriptions.rb +2 -5
- data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
- data/lib/graphql/version.rb +1 -1
- metadata +6 -4
- data/lib/graphql/execution/instrumentation.rb +0 -92
@@ -17,6 +17,10 @@ module GraphQL
|
|
17
17
|
@provided_variables = deep_stringify(provided_variables)
|
18
18
|
@errors = []
|
19
19
|
@storage = ast_variables.each_with_object({}) do |ast_variable, memo|
|
20
|
+
if schema.validate_max_errors && schema.validate_max_errors <= @errors.count
|
21
|
+
add_max_errors_reached_message
|
22
|
+
break
|
23
|
+
end
|
20
24
|
# Find the right value for this variable:
|
21
25
|
# - First, use the value provided at runtime
|
22
26
|
# - Then, fall back to the default value from the query string
|
@@ -29,8 +33,9 @@ module GraphQL
|
|
29
33
|
default_value = ast_variable.default_value
|
30
34
|
provided_value = @provided_variables[variable_name]
|
31
35
|
value_was_provided = @provided_variables.key?(variable_name)
|
36
|
+
max_errors = schema.validate_max_errors - @errors.count if schema.validate_max_errors
|
32
37
|
begin
|
33
|
-
validation_result = variable_type.validate_input(provided_value, ctx)
|
38
|
+
validation_result = variable_type.validate_input(provided_value, ctx, max_errors: max_errors)
|
34
39
|
if validation_result.valid?
|
35
40
|
if value_was_provided
|
36
41
|
# Add the variable if a value was provided
|
@@ -48,8 +53,7 @@ module GraphQL
|
|
48
53
|
# like InputValidationResults generated by validate_non_null_input but unfortunately we don't
|
49
54
|
# have this information available in the coerce_input call chain. Note this path is the path
|
50
55
|
# that appears under errors.extensions.problems.path and NOT the result path under errors.path.
|
51
|
-
validation_result = GraphQL::Query::InputValidationResult.
|
52
|
-
validation_result.add_problem(ex.message)
|
56
|
+
validation_result = GraphQL::Query::InputValidationResult.from_problem(ex.message)
|
53
57
|
end
|
54
58
|
|
55
59
|
if !validation_result.valid?
|
@@ -76,6 +80,12 @@ module GraphQL
|
|
76
80
|
else
|
77
81
|
val
|
78
82
|
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def add_max_errors_reached_message
|
86
|
+
message = "Too many errors processing variables, max validation error limit reached. Execution aborted"
|
87
|
+
validation_result = GraphQL::Query::InputValidationResult.from_problem(message)
|
88
|
+
errors << GraphQL::Query::VariableValidationError.new(nil, nil, nil, validation_result, msg: message)
|
79
89
|
end
|
80
90
|
end
|
81
91
|
end
|
data/lib/graphql/query.rb
CHANGED
@@ -34,7 +34,16 @@ module GraphQL
|
|
34
34
|
attr_accessor :operation_name
|
35
35
|
|
36
36
|
# @return [Boolean] if false, static validation is skipped (execution behavior for invalid queries is undefined)
|
37
|
-
|
37
|
+
attr_reader :validate
|
38
|
+
|
39
|
+
# @param new_validate [Boolean] if false, static validation is skipped. This can't be reasssigned after validation.
|
40
|
+
def validate=(new_validate)
|
41
|
+
if defined?(@validation_pipeline) && @validation_pipeline && @validation_pipeline.has_validated?
|
42
|
+
raise ArgumentError, "Can't reassign Query#validate= after validation has run, remove this assignment."
|
43
|
+
else
|
44
|
+
@validate = new_validate
|
45
|
+
end
|
46
|
+
end
|
38
47
|
|
39
48
|
attr_writer :query_string
|
40
49
|
|
@@ -187,7 +196,7 @@ module GraphQL
|
|
187
196
|
# @return [Hash] A GraphQL response, with `"data"` and/or `"errors"` keys
|
188
197
|
def result
|
189
198
|
if !@executed
|
190
|
-
Execution::
|
199
|
+
Execution::Interpreter.run_all(@schema, [self], context: @context)
|
191
200
|
end
|
192
201
|
@result ||= Query::Result.new(query: self, values: @result_values)
|
193
202
|
end
|
@@ -15,7 +15,7 @@ module GraphQL
|
|
15
15
|
puts "Validating graphql-pro v#{version}"
|
16
16
|
puts " - Checking for graphql-pro credentials..."
|
17
17
|
|
18
|
-
creds = `bundle config gems.graphql.pro`[/[a-z0-9]{11}:[a-z0-9]{11}/]
|
18
|
+
creds = `bundle config gems.graphql.pro --parseable`[/[a-z0-9]{11}:[a-z0-9]{11}/]
|
19
19
|
if creds.nil?
|
20
20
|
puts " #{ex} failed, please set with `bundle config gems.graphql.pro $MY_CREDENTIALS`"
|
21
21
|
exit(1)
|
@@ -18,8 +18,14 @@ module GraphQL
|
|
18
18
|
# @return [GraphQL::Schema::Field, Class] The field or input object this argument belongs to
|
19
19
|
attr_reader :owner
|
20
20
|
|
21
|
-
# @
|
22
|
-
|
21
|
+
# @param new_prepare [Method, Proc]
|
22
|
+
# @return [Symbol] A method or proc to call to transform this value before sending it to field resolution method
|
23
|
+
def prepare(new_prepare = NO_DEFAULT)
|
24
|
+
if new_prepare != NO_DEFAULT
|
25
|
+
@prepare = new_prepare
|
26
|
+
end
|
27
|
+
@prepare
|
28
|
+
end
|
23
29
|
|
24
30
|
# @return [Symbol] This argument's name in Ruby keyword arguments
|
25
31
|
attr_reader :keyword
|
@@ -96,8 +102,14 @@ module GraphQL
|
|
96
102
|
"#<#{self.class} #{path}: #{type.to_type_signature}#{description ? " @description=#{description.inspect}" : ""}>"
|
97
103
|
end
|
98
104
|
|
105
|
+
# @param default_value [Object] The value to use when the client doesn't provide one
|
99
106
|
# @return [Object] the value used when the client doesn't provide a value for this argument
|
100
|
-
|
107
|
+
def default_value(new_default_value = NO_DEFAULT)
|
108
|
+
if new_default_value != NO_DEFAULT
|
109
|
+
@default_value = new_default_value
|
110
|
+
end
|
111
|
+
@default_value
|
112
|
+
end
|
101
113
|
|
102
114
|
# @return [Boolean] True if this argument has a default value
|
103
115
|
def default_value?
|
@@ -259,26 +271,26 @@ module GraphQL
|
|
259
271
|
# If this isn't lazy, then the block returns eagerly and assigns the result here
|
260
272
|
# If it _is_ lazy, then we write the lazy to the hash, then update it later
|
261
273
|
argument_values[arg_key] = context.schema.after_lazy(coerced_value) do |resolved_coerced_value|
|
274
|
+
owner.validate_directive_argument(self, resolved_coerced_value)
|
275
|
+
prepared_value = begin
|
276
|
+
prepare_value(parent_object, resolved_coerced_value, context: context)
|
277
|
+
rescue StandardError => err
|
278
|
+
context.schema.handle_or_reraise(context, err)
|
279
|
+
end
|
280
|
+
|
262
281
|
if loads && !from_resolver?
|
263
282
|
loaded_value = begin
|
264
|
-
load_and_authorize_value(owner,
|
283
|
+
load_and_authorize_value(owner, prepared_value, context)
|
265
284
|
rescue StandardError => err
|
266
285
|
context.schema.handle_or_reraise(context, err)
|
267
286
|
end
|
268
287
|
end
|
269
288
|
|
270
|
-
maybe_loaded_value = loaded_value ||
|
289
|
+
maybe_loaded_value = loaded_value || prepared_value
|
271
290
|
context.schema.after_lazy(maybe_loaded_value) do |resolved_loaded_value|
|
272
|
-
owner.validate_directive_argument(self, resolved_loaded_value)
|
273
|
-
prepared_value = begin
|
274
|
-
prepare_value(parent_object, resolved_loaded_value, context: context)
|
275
|
-
rescue StandardError => err
|
276
|
-
context.schema.handle_or_reraise(context, err)
|
277
|
-
end
|
278
|
-
|
279
291
|
# TODO code smell to access such a deeply-nested constant in a distant module
|
280
292
|
argument_values[arg_key] = GraphQL::Execution::Interpreter::ArgumentValue.new(
|
281
|
-
value:
|
293
|
+
value: resolved_loaded_value,
|
282
294
|
definition: self,
|
283
295
|
default_used: default_used,
|
284
296
|
)
|
@@ -55,14 +55,13 @@ module GraphQL
|
|
55
55
|
end
|
56
56
|
})
|
57
57
|
|
58
|
+
directives.merge!(GraphQL::Schema.default_directives)
|
58
59
|
document.definitions.each do |definition|
|
59
60
|
if definition.is_a?(GraphQL::Language::Nodes::DirectiveDefinition)
|
60
61
|
directives[definition.name] = build_directive(definition, directive_type_resolver)
|
61
62
|
end
|
62
63
|
end
|
63
64
|
|
64
|
-
directives = GraphQL::Schema.default_directives.merge(directives)
|
65
|
-
|
66
65
|
# In case any directives referenced built-in types for their arguments:
|
67
66
|
replace_late_bound_types_with_built_in(types)
|
68
67
|
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
class Schema
|
4
|
+
class Directive < GraphQL::Schema::Member
|
5
|
+
class OneOf < GraphQL::Schema::Directive
|
6
|
+
description "Requires that exactly one field must be supplied and that field must not be `null`."
|
7
|
+
locations(GraphQL::Schema::Directive::INPUT_OBJECT)
|
8
|
+
default_directive true
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/graphql/schema/enum.rb
CHANGED
@@ -122,7 +122,7 @@ module GraphQL
|
|
122
122
|
GraphQL::TypeKinds::ENUM
|
123
123
|
end
|
124
124
|
|
125
|
-
def validate_non_null_input(value_name, ctx)
|
125
|
+
def validate_non_null_input(value_name, ctx, max_errors: nil)
|
126
126
|
allowed_values = ctx.warden.enum_values(self)
|
127
127
|
matching_value = allowed_values.find { |v| v.graphql_name == value_name }
|
128
128
|
|
data/lib/graphql/schema/field.rb
CHANGED
@@ -666,7 +666,18 @@ module GraphQL
|
|
666
666
|
inner_object = obj.object
|
667
667
|
|
668
668
|
if defined?(@hash_key)
|
669
|
-
|
669
|
+
hash_value = if inner_object.is_a?(Hash)
|
670
|
+
inner_object.key?(@hash_key) ? inner_object[@hash_key] : inner_object[@hash_key_str]
|
671
|
+
elsif inner_object.respond_to?(:[])
|
672
|
+
inner_object[@hash_key]
|
673
|
+
else
|
674
|
+
nil
|
675
|
+
end
|
676
|
+
if hash_value == false
|
677
|
+
hash_value
|
678
|
+
else
|
679
|
+
hash_value || (@fallback_value != :not_given ? @fallback_value : nil)
|
680
|
+
end
|
670
681
|
elsif obj.respond_to?(resolver_method)
|
671
682
|
method_to_call = resolver_method
|
672
683
|
method_receiver = obj
|
@@ -679,16 +690,6 @@ module GraphQL
|
|
679
690
|
elsif inner_object.is_a?(Hash)
|
680
691
|
if @dig_keys
|
681
692
|
inner_object.dig(*@dig_keys)
|
682
|
-
elsif defined?(@hash_key)
|
683
|
-
if inner_object.key?(@hash_key)
|
684
|
-
inner_object[@hash_key]
|
685
|
-
elsif inner_object.key?(@hash_key_str)
|
686
|
-
inner_object[@hash_key_str]
|
687
|
-
elsif @fallback_value != :not_given
|
688
|
-
@fallback_value
|
689
|
-
else
|
690
|
-
nil
|
691
|
-
end
|
692
693
|
elsif inner_object.key?(@method_sym)
|
693
694
|
inner_object[@method_sym]
|
694
695
|
elsif inner_object.key?(@method_str)
|
@@ -69,6 +69,19 @@ module GraphQL
|
|
69
69
|
true
|
70
70
|
end
|
71
71
|
|
72
|
+
def self.one_of
|
73
|
+
if !one_of?
|
74
|
+
if all_argument_definitions.any? { |arg| arg.type.non_null? }
|
75
|
+
raise ArgumentError, "`one_of` may not be used with required arguments -- add `required: false` to argument definitions to use `one_of`"
|
76
|
+
end
|
77
|
+
directive(GraphQL::Schema::Directive::OneOf)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.one_of?
|
82
|
+
directives.any? { |d| d.is_a?(GraphQL::Schema::Directive::OneOf) }
|
83
|
+
end
|
84
|
+
|
72
85
|
def unwrap_value(value)
|
73
86
|
case value
|
74
87
|
when Array
|
@@ -109,6 +122,14 @@ module GraphQL
|
|
109
122
|
class << self
|
110
123
|
def argument(*args, **kwargs, &block)
|
111
124
|
argument_defn = super(*args, **kwargs, &block)
|
125
|
+
if one_of?
|
126
|
+
if argument_defn.type.non_null?
|
127
|
+
raise ArgumentError, "Argument '#{argument_defn.path}' must be nullable because it is part of a OneOf type, add `required: false`."
|
128
|
+
end
|
129
|
+
if argument_defn.default_value?
|
130
|
+
raise ArgumentError, "Argument '#{argument_defn.path}' cannot have a default value because it is part of a OneOf type, remove `default_value: ...`."
|
131
|
+
end
|
132
|
+
end
|
112
133
|
# Add a method access
|
113
134
|
method_name = argument_defn.keyword
|
114
135
|
class_eval <<-RUBY, __FILE__, __LINE__
|
@@ -126,7 +147,7 @@ module GraphQL
|
|
126
147
|
# @api private
|
127
148
|
INVALID_OBJECT_MESSAGE = "Expected %{object} to be a key-value object responding to `to_h` or `to_unsafe_h`."
|
128
149
|
|
129
|
-
def validate_non_null_input(input, ctx)
|
150
|
+
def validate_non_null_input(input, ctx, max_errors: nil)
|
130
151
|
warden = ctx.warden
|
131
152
|
|
132
153
|
if input.is_a?(Array)
|
@@ -166,6 +187,20 @@ module GraphQL
|
|
166
187
|
end
|
167
188
|
end
|
168
189
|
|
190
|
+
if one_of?
|
191
|
+
if input.size == 1
|
192
|
+
input.each do |name, value|
|
193
|
+
if value.nil?
|
194
|
+
result ||= Query::InputValidationResult.new
|
195
|
+
result.add_problem("'#{graphql_name}' requires exactly one argument, but '#{name}' was `null`.")
|
196
|
+
end
|
197
|
+
end
|
198
|
+
else
|
199
|
+
result ||= Query::InputValidationResult.new
|
200
|
+
result.add_problem("'#{graphql_name}' requires exactly one argument, but #{input.size} were provided.")
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
169
204
|
result
|
170
205
|
end
|
171
206
|
|
data/lib/graphql/schema/list.rb
CHANGED
@@ -45,16 +45,24 @@ module GraphQL
|
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
|
-
def validate_non_null_input(value, ctx)
|
49
|
-
result =
|
48
|
+
def validate_non_null_input(value, ctx, max_errors: nil)
|
49
|
+
result = GraphQL::Query::InputValidationResult.new
|
50
50
|
ensure_array(value).each_with_index do |item, index|
|
51
51
|
item_result = of_type.validate_input(item, ctx)
|
52
|
-
|
53
|
-
|
52
|
+
unless item_result.valid?
|
53
|
+
if max_errors
|
54
|
+
if max_errors == 0
|
55
|
+
add_max_errros_reached_message(result)
|
56
|
+
break
|
57
|
+
end
|
58
|
+
|
59
|
+
max_errors -= 1
|
60
|
+
end
|
61
|
+
|
54
62
|
result.merge_result!(index, item_result)
|
55
63
|
end
|
56
64
|
end
|
57
|
-
result
|
65
|
+
result.valid? ? nil : result
|
58
66
|
end
|
59
67
|
|
60
68
|
private
|
@@ -67,6 +75,12 @@ module GraphQL
|
|
67
75
|
[value]
|
68
76
|
end
|
69
77
|
end
|
78
|
+
|
79
|
+
def add_max_errros_reached_message(result)
|
80
|
+
message = "Too many errors processing list variable, max validation error limit reached. Execution aborted"
|
81
|
+
item_result = GraphQL::Query::InputValidationResult.from_problem(message)
|
82
|
+
result.merge_result!(nil, item_result)
|
83
|
+
end
|
70
84
|
end
|
71
85
|
end
|
72
86
|
end
|
@@ -323,8 +323,14 @@ module GraphQL
|
|
323
323
|
end
|
324
324
|
# Double-check that the located object is actually of this type
|
325
325
|
# (Don't want to allow arbitrary access to objects this way)
|
326
|
-
|
327
|
-
context.schema.after_lazy(
|
326
|
+
maybe_lazy_resolve_type = context.schema.resolve_type(argument.loads, application_object, context)
|
327
|
+
context.schema.after_lazy(maybe_lazy_resolve_type) do |resolve_type_result|
|
328
|
+
if resolve_type_result.is_a?(Array) && resolve_type_result.size == 2
|
329
|
+
application_object_type, application_object = resolve_type_result
|
330
|
+
else
|
331
|
+
application_object_type = resolve_type_result
|
332
|
+
# application_object is already assigned
|
333
|
+
end
|
328
334
|
possible_object_types = context.warden.possible_types(argument.loads)
|
329
335
|
if !possible_object_types.include?(application_object_type)
|
330
336
|
err = GraphQL::LoadApplicationObjectFailedError.new(argument: argument, id: id, object: application_object)
|
@@ -8,11 +8,11 @@ module GraphQL
|
|
8
8
|
validate_input(val, ctx).valid?
|
9
9
|
end
|
10
10
|
|
11
|
-
def validate_input(val, ctx)
|
11
|
+
def validate_input(val, ctx, max_errors: nil)
|
12
12
|
if val.nil?
|
13
13
|
Query::InputValidationResult::VALID
|
14
14
|
else
|
15
|
-
validate_non_null_input(val, ctx) || Query::InputValidationResult::VALID
|
15
|
+
validate_non_null_input(val, ctx, max_errors: max_errors) || Query::InputValidationResult::VALID
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
@@ -31,13 +31,13 @@ module GraphQL
|
|
31
31
|
"#<#{self.class.name} @of_type=#{@of_type.inspect}>"
|
32
32
|
end
|
33
33
|
|
34
|
-
def validate_input(value, ctx)
|
34
|
+
def validate_input(value, ctx, max_errors: nil)
|
35
35
|
if value.nil?
|
36
36
|
result = GraphQL::Query::InputValidationResult.new
|
37
37
|
result.add_problem("Expected value to not be null")
|
38
38
|
result
|
39
39
|
else
|
40
|
-
of_type.validate_input(value, ctx)
|
40
|
+
of_type.validate_input(value, ctx, max_errors: max_errors)
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
data/lib/graphql/schema.rb
CHANGED
@@ -31,6 +31,7 @@ require "graphql/schema/union"
|
|
31
31
|
require "graphql/schema/directive"
|
32
32
|
require "graphql/schema/directive/deprecated"
|
33
33
|
require "graphql/schema/directive/include"
|
34
|
+
require "graphql/schema/directive/one_of"
|
34
35
|
require "graphql/schema/directive/skip"
|
35
36
|
require "graphql/schema/directive/feature"
|
36
37
|
require "graphql/schema/directive/flagged"
|
@@ -754,13 +755,21 @@ module GraphQL
|
|
754
755
|
# rubocop:disable Lint/DuplicateMethods
|
755
756
|
module ResolveTypeWithType
|
756
757
|
def resolve_type(type, obj, ctx)
|
757
|
-
|
758
|
+
maybe_lazy_resolve_type_result = if type.is_a?(Module) && type.respond_to?(:resolve_type)
|
758
759
|
type.resolve_type(obj, ctx)
|
759
760
|
else
|
760
761
|
super
|
761
762
|
end
|
762
763
|
|
763
|
-
after_lazy(
|
764
|
+
after_lazy(maybe_lazy_resolve_type_result) do |resolve_type_result|
|
765
|
+
if resolve_type_result.is_a?(Array) && resolve_type_result.size == 2
|
766
|
+
resolved_type = resolve_type_result[0]
|
767
|
+
resolved_value = resolve_type_result[1]
|
768
|
+
else
|
769
|
+
resolved_type = resolve_type_result
|
770
|
+
resolved_value = obj
|
771
|
+
end
|
772
|
+
|
764
773
|
if resolved_type.nil? || (resolved_type.is_a?(Module) && resolved_type.respond_to?(:kind))
|
765
774
|
if resolved_value
|
766
775
|
[resolved_type, resolved_value]
|
@@ -905,6 +914,7 @@ module GraphQL
|
|
905
914
|
"include" => GraphQL::Schema::Directive::Include,
|
906
915
|
"skip" => GraphQL::Schema::Directive::Skip,
|
907
916
|
"deprecated" => GraphQL::Schema::Directive::Deprecated,
|
917
|
+
"oneOf" => GraphQL::Schema::Directive::OneOf,
|
908
918
|
}.freeze
|
909
919
|
end
|
910
920
|
|
@@ -982,7 +992,7 @@ module GraphQL
|
|
982
992
|
# @param context [Hash] Multiplex-level context
|
983
993
|
# @return [Array<Hash>] One result for each query in the input
|
984
994
|
def multiplex(queries, **kwargs)
|
985
|
-
GraphQL::Execution::
|
995
|
+
GraphQL::Execution::Interpreter.run_all(self, queries, **kwargs)
|
986
996
|
end
|
987
997
|
|
988
998
|
def instrumenters
|
@@ -108,6 +108,10 @@ module GraphQL
|
|
108
108
|
arg_type = @warden.get_argument(type, name).type
|
109
109
|
recursively_validate(GraphQL::Language::Nodes::NullValue.new(name: name), arg_type)
|
110
110
|
end
|
111
|
+
|
112
|
+
if type.one_of? && ast_node.arguments.size != 1
|
113
|
+
results << Query::InputValidationResult.from_problem("`#{type.graphql_name}` is a OneOf type, so only one argument may be given (instead of #{ast_node.arguments.size})")
|
114
|
+
end
|
111
115
|
merge_results(results)
|
112
116
|
end
|
113
117
|
end
|
@@ -9,11 +9,17 @@ module GraphQL
|
|
9
9
|
|
10
10
|
def on_directive(node, parent)
|
11
11
|
if !@directive_names.include?(node.name)
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
12
|
+
@directives_are_defined_errors_by_name ||= {}
|
13
|
+
error = @directives_are_defined_errors_by_name[node.name] ||= begin
|
14
|
+
err = GraphQL::StaticValidation::DirectivesAreDefinedError.new(
|
15
|
+
"Directive @#{node.name} is not defined",
|
16
|
+
nodes: [],
|
17
|
+
directive: node.name
|
18
|
+
)
|
19
|
+
add_error(err)
|
20
|
+
err
|
21
|
+
end
|
22
|
+
error.nodes << node
|
17
23
|
else
|
18
24
|
super
|
19
25
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
module StaticValidation
|
4
|
+
module OneOfInputObjectsAreValid
|
5
|
+
def on_input_object(node, parent)
|
6
|
+
return super unless parent.is_a?(GraphQL::Language::Nodes::Argument)
|
7
|
+
|
8
|
+
parent_type = get_parent_type(context, parent)
|
9
|
+
return super unless parent_type && parent_type.kind.input_object? && parent_type.one_of?
|
10
|
+
|
11
|
+
validate_one_of_input_object(node, context, parent_type)
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def validate_one_of_input_object(ast_node, context, parent_type)
|
18
|
+
present_fields = ast_node.arguments.map(&:name)
|
19
|
+
input_object_type = parent_type.to_type_signature
|
20
|
+
|
21
|
+
if present_fields.count != 1
|
22
|
+
add_error(
|
23
|
+
OneOfInputObjectsAreValidError.new(
|
24
|
+
"OneOf Input Object '#{input_object_type}' must specify exactly one key.",
|
25
|
+
path: context.path,
|
26
|
+
nodes: ast_node,
|
27
|
+
input_object_type: input_object_type
|
28
|
+
)
|
29
|
+
)
|
30
|
+
return
|
31
|
+
end
|
32
|
+
|
33
|
+
field = present_fields.first
|
34
|
+
value = ast_node.arguments.first.value
|
35
|
+
|
36
|
+
if value.is_a?(GraphQL::Language::Nodes::NullValue)
|
37
|
+
add_error(
|
38
|
+
OneOfInputObjectsAreValidError.new(
|
39
|
+
"Argument '#{input_object_type}.#{field}' must be non-null.",
|
40
|
+
path: [*context.path, field],
|
41
|
+
nodes: ast_node.arguments.first,
|
42
|
+
input_object_type: input_object_type
|
43
|
+
)
|
44
|
+
)
|
45
|
+
return
|
46
|
+
end
|
47
|
+
|
48
|
+
if value.is_a?(GraphQL::Language::Nodes::VariableIdentifier)
|
49
|
+
variable_name = value.name
|
50
|
+
variable_type = @declared_variables[variable_name].type
|
51
|
+
|
52
|
+
unless variable_type.is_a?(GraphQL::Language::Nodes::NonNullType)
|
53
|
+
add_error(
|
54
|
+
OneOfInputObjectsAreValidError.new(
|
55
|
+
"Variable '#{variable_name}' must be non-nullable to be used for OneOf Input Object '#{input_object_type}'.",
|
56
|
+
path: [*context.path, field],
|
57
|
+
nodes: ast_node,
|
58
|
+
input_object_type: input_object_type
|
59
|
+
)
|
60
|
+
)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
module StaticValidation
|
4
|
+
class OneOfInputObjectsAreValidError < StaticValidation::Error
|
5
|
+
attr_reader :input_object_type
|
6
|
+
|
7
|
+
def initialize(message, path:, nodes:, input_object_type:)
|
8
|
+
super(message, path: path, nodes: nodes)
|
9
|
+
@input_object_type = input_object_type
|
10
|
+
end
|
11
|
+
|
12
|
+
# A hash representation of this Message
|
13
|
+
def to_h
|
14
|
+
extensions = {
|
15
|
+
"code" => code,
|
16
|
+
"inputObjectType" => input_object_type
|
17
|
+
}
|
18
|
+
|
19
|
+
super.merge({
|
20
|
+
"extensions" => extensions
|
21
|
+
})
|
22
|
+
end
|
23
|
+
|
24
|
+
def code
|
25
|
+
"invalidOneOfInputObject"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -34,12 +34,18 @@ module GraphQL
|
|
34
34
|
used_directives = {}
|
35
35
|
node.directives.each do |ast_directive|
|
36
36
|
directive_name = ast_directive.name
|
37
|
-
if used_directives[directive_name]
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
37
|
+
if (first_node = used_directives[directive_name])
|
38
|
+
@directives_are_unique_errors_by_first_node ||= {}
|
39
|
+
err = @directives_are_unique_errors_by_first_node[first_node] ||= begin
|
40
|
+
error = GraphQL::StaticValidation::UniqueDirectivesPerLocationError.new(
|
41
|
+
"The directive \"#{directive_name}\" can only be used once at this location.",
|
42
|
+
nodes: [used_directives[directive_name]],
|
43
|
+
directive: directive_name,
|
44
|
+
)
|
45
|
+
add_error(error)
|
46
|
+
error
|
47
|
+
end
|
48
|
+
err.nodes << ast_directive
|
43
49
|
elsif !((dir_defn = context.schema_directives[directive_name]) && dir_defn.repeatable?)
|
44
50
|
used_directives[directive_name] = ast_directive
|
45
51
|
end
|
@@ -23,7 +23,7 @@ module GraphQL
|
|
23
23
|
problems = validation_result.problems
|
24
24
|
first_problem = problems && problems.first
|
25
25
|
if first_problem
|
26
|
-
error_message = first_problem["
|
26
|
+
error_message = first_problem["explanation"]
|
27
27
|
end
|
28
28
|
|
29
29
|
error_message ||= "Default value for $#{node.name} doesn't match type #{type.to_type_signature}"
|