graphql 2.0.11 → 2.0.14
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/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}"
|