graphql 2.0.0 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of graphql might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -1
- data/lib/graphql/dataloader/null_dataloader.rb +3 -1
- data/lib/graphql/execution/errors.rb +12 -74
- data/lib/graphql/execution/interpreter/runtime.rb +35 -38
- data/lib/graphql/query/context.rb +96 -9
- data/lib/graphql/query/input_validation_result.rb +10 -1
- data/lib/graphql/query/null_context.rb +0 -3
- data/lib/graphql/query.rb +2 -5
- data/lib/graphql/relay/range_add.rb +9 -16
- data/lib/graphql/schema/addition.rb +5 -0
- data/lib/graphql/schema/argument.rb +13 -4
- data/lib/graphql/schema/enum.rb +3 -5
- data/lib/graphql/schema/field.rb +164 -130
- data/lib/graphql/schema/input_object.rb +12 -12
- data/lib/graphql/schema/list.rb +2 -1
- data/lib/graphql/schema/member/has_arguments.rb +36 -6
- data/lib/graphql/schema/member/has_directives.rb +1 -1
- data/lib/graphql/schema/member/has_interfaces.rb +1 -1
- data/lib/graphql/schema/member/relay_shortcuts.rb +28 -2
- data/lib/graphql/schema/member/validates_input.rb +2 -2
- data/lib/graphql/schema/object.rb +14 -9
- data/lib/graphql/schema/relay_classic_mutation.rb +32 -14
- data/lib/graphql/schema/resolver/has_payload_type.rb +11 -1
- data/lib/graphql/schema/resolver.rb +23 -45
- data/lib/graphql/schema/scalar.rb +7 -7
- data/lib/graphql/schema/subscription.rb +0 -7
- data/lib/graphql/schema/warden.rb +1 -1
- data/lib/graphql/schema.rb +35 -6
- data/lib/graphql/subscriptions.rb +10 -3
- data/lib/graphql/tracing/data_dog_tracing.rb +3 -1
- data/lib/graphql/types/relay/connection_behaviors.rb +0 -16
- data/lib/graphql/version.rb +1 -1
- metadata +6 -7
- data/lib/graphql/query/literal_input.rb +0 -131
@@ -75,7 +75,10 @@ module GraphQL
|
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
78
|
-
|
78
|
+
if validates && !validates.empty?
|
79
|
+
self.validates(validates)
|
80
|
+
end
|
81
|
+
|
79
82
|
if required == :nullable
|
80
83
|
self.owner.validates(required: { argument: arg_name })
|
81
84
|
end
|
@@ -247,24 +250,30 @@ module GraphQL
|
|
247
250
|
end
|
248
251
|
|
249
252
|
loaded_value = nil
|
250
|
-
coerced_value =
|
253
|
+
coerced_value = begin
|
251
254
|
type.coerce_input(value, context)
|
255
|
+
rescue StandardError => err
|
256
|
+
context.schema.handle_or_reraise(context, err)
|
252
257
|
end
|
253
258
|
|
254
259
|
# If this isn't lazy, then the block returns eagerly and assigns the result here
|
255
260
|
# If it _is_ lazy, then we write the lazy to the hash, then update it later
|
256
261
|
argument_values[arg_key] = context.schema.after_lazy(coerced_value) do |resolved_coerced_value|
|
257
262
|
if loads && !from_resolver?
|
258
|
-
loaded_value =
|
263
|
+
loaded_value = begin
|
259
264
|
load_and_authorize_value(owner, coerced_value, context)
|
265
|
+
rescue StandardError => err
|
266
|
+
context.schema.handle_or_reraise(context, err)
|
260
267
|
end
|
261
268
|
end
|
262
269
|
|
263
270
|
maybe_loaded_value = loaded_value || resolved_coerced_value
|
264
271
|
context.schema.after_lazy(maybe_loaded_value) do |resolved_loaded_value|
|
265
272
|
owner.validate_directive_argument(self, resolved_loaded_value)
|
266
|
-
prepared_value =
|
273
|
+
prepared_value = begin
|
267
274
|
prepare_value(parent_object, resolved_loaded_value, context: context)
|
275
|
+
rescue StandardError => err
|
276
|
+
context.schema.handle_or_reraise(context, err)
|
268
277
|
end
|
269
278
|
|
270
279
|
# TODO code smell to access such a deeply-nested constant in a distant module
|
data/lib/graphql/schema/enum.rb
CHANGED
@@ -123,16 +123,14 @@ module GraphQL
|
|
123
123
|
end
|
124
124
|
|
125
125
|
def validate_non_null_input(value_name, ctx)
|
126
|
-
result = GraphQL::Query::InputValidationResult.new
|
127
|
-
|
128
126
|
allowed_values = ctx.warden.enum_values(self)
|
129
127
|
matching_value = allowed_values.find { |v| v.graphql_name == value_name }
|
130
128
|
|
131
129
|
if matching_value.nil?
|
132
|
-
|
130
|
+
GraphQL::Query::InputValidationResult.from_problem("Expected #{GraphQL::Language.serialize(value_name)} to be one of: #{allowed_values.map(&:graphql_name).join(', ')}")
|
131
|
+
else
|
132
|
+
nil
|
133
133
|
end
|
134
|
-
|
135
|
-
result
|
136
134
|
end
|
137
135
|
|
138
136
|
def coerce_result(value, ctx)
|
data/lib/graphql/schema/field.rb
CHANGED
@@ -29,7 +29,13 @@ module GraphQL
|
|
29
29
|
attr_reader :method_str
|
30
30
|
|
31
31
|
# @return [Symbol] The method on the type to look up
|
32
|
-
|
32
|
+
def resolver_method
|
33
|
+
if @resolver_class
|
34
|
+
@resolver_class.resolver_method
|
35
|
+
else
|
36
|
+
@resolver_method
|
37
|
+
end
|
38
|
+
end
|
33
39
|
|
34
40
|
# @return [Class] The thing this field was defined on (type, mutation, resolver)
|
35
41
|
attr_accessor :owner
|
@@ -68,7 +74,10 @@ module GraphQL
|
|
68
74
|
attr_reader :trace
|
69
75
|
|
70
76
|
# @return [String, nil]
|
71
|
-
|
77
|
+
def subscription_scope
|
78
|
+
@subscription_scope || (@resolver_class.respond_to?(:subscription_scope) ? @resolver_class.subscription_scope : nil)
|
79
|
+
end
|
80
|
+
attr_writer :subscription_scope
|
72
81
|
|
73
82
|
# Create a field instance from a list of arguments, keyword arguments, and a block.
|
74
83
|
#
|
@@ -82,11 +91,9 @@ module GraphQL
|
|
82
91
|
# @return [GraphQL::Schema:Field] an instance of `self
|
83
92
|
# @see {.initialize} for other options
|
84
93
|
def self.from_options(name = nil, type = nil, desc = nil, resolver: nil, mutation: nil, subscription: nil,**kwargs, &block)
|
85
|
-
if (
|
86
|
-
# Get the parent config, merge in local overrides
|
87
|
-
kwargs = parent_config.field_options.merge(kwargs)
|
94
|
+
if (resolver_class = resolver || mutation || subscription)
|
88
95
|
# Add a reference to that parent class
|
89
|
-
kwargs[:resolver_class] =
|
96
|
+
kwargs[:resolver_class] = resolver_class
|
90
97
|
end
|
91
98
|
|
92
99
|
if name
|
@@ -101,8 +108,8 @@ module GraphQL
|
|
101
108
|
|
102
109
|
kwargs[:description] = desc
|
103
110
|
kwargs[:type] = type
|
104
|
-
elsif (
|
105
|
-
# The return type should be copied from
|
111
|
+
elsif (resolver || mutation) && type.is_a?(String)
|
112
|
+
# The return type should be copied from the resolver, and the second positional argument is the description
|
106
113
|
kwargs[:description] = type
|
107
114
|
else
|
108
115
|
kwargs[:type] = type
|
@@ -119,8 +126,8 @@ module GraphQL
|
|
119
126
|
def connection?
|
120
127
|
if @connection.nil?
|
121
128
|
# Provide default based on type name
|
122
|
-
return_type_name = if
|
123
|
-
Member::BuildType.to_type_name(
|
129
|
+
return_type_name = if @resolver_class && @resolver_class.type
|
130
|
+
Member::BuildType.to_type_name(@resolver_class.type)
|
124
131
|
elsif @return_type_expr
|
125
132
|
Member::BuildType.to_type_name(@return_type_expr)
|
126
133
|
else
|
@@ -138,8 +145,18 @@ module GraphQL
|
|
138
145
|
if !@scope.nil?
|
139
146
|
# The default was overridden
|
140
147
|
@scope
|
148
|
+
elsif @return_type_expr
|
149
|
+
# Detect a list return type, but don't call `type` since that may eager-load an otherwise lazy-loaded type
|
150
|
+
@return_type_expr.is_a?(Array) ||
|
151
|
+
(@return_type_expr.is_a?(String) && @return_type_expr.include?("[")) ||
|
152
|
+
connection?
|
153
|
+
elsif @resolver_class
|
154
|
+
resolver_type = @resolver_class.type_expr
|
155
|
+
resolver_type.is_a?(Array) ||
|
156
|
+
(resolver_type.is_a?(String) && resolver_type.include?("[")) ||
|
157
|
+
connection?
|
141
158
|
else
|
142
|
-
|
159
|
+
false
|
143
160
|
end
|
144
161
|
end
|
145
162
|
|
@@ -181,9 +198,6 @@ module GraphQL
|
|
181
198
|
# @param connection_extension [Class] The extension to add, to implement connections. If `nil`, no extension is added.
|
182
199
|
# @param max_page_size [Integer, nil] For connections, the maximum number of items to return from this field, or `nil` to allow unlimited results.
|
183
200
|
# @param introspection [Boolean] If true, this field will be marked as `#introspection?` and the name may begin with `__`
|
184
|
-
# @param resolve [<#call(obj, args, ctx)>] **deprecated** for compatibility with <1.8.0
|
185
|
-
# @param field [GraphQL::Field, GraphQL::Schema::Field] **deprecated** for compatibility with <1.8.0
|
186
|
-
# @param function [GraphQL::Function] **deprecated** for compatibility with <1.8.0
|
187
201
|
# @param resolver_class [Class] (Private) A {Schema::Resolver} which this field was derived from. Use `resolver:` to create a field with a resolver.
|
188
202
|
# @param arguments [{String=>GraphQL::Schema::Argument, Hash}] Arguments for this field (may be added in the block, also)
|
189
203
|
# @param camelize [Boolean] If true, the field name will be camelized when building the schema
|
@@ -197,30 +211,22 @@ module GraphQL
|
|
197
211
|
# @param ast_node [Language::Nodes::FieldDefinition, nil] If this schema was parsed from definition, this AST node defined the field
|
198
212
|
# @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
|
199
213
|
# @param validates [Array<Hash>] Configurations for validating this field
|
200
|
-
def initialize(type: nil, name: nil, owner: nil, null: true,
|
214
|
+
def initialize(type: nil, name: nil, owner: nil, null: true, description: :not_given, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: :not_given, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: nil, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, &definition_block)
|
201
215
|
if name.nil?
|
202
216
|
raise ArgumentError, "missing first `name` argument or keyword `name:`"
|
203
217
|
end
|
204
|
-
if !(
|
218
|
+
if !(resolver_class)
|
205
219
|
if type.nil?
|
206
220
|
raise ArgumentError, "missing second `type` argument or keyword `type:`"
|
207
221
|
end
|
208
222
|
end
|
209
|
-
if (field || function || resolve) && extras.any?
|
210
|
-
raise ArgumentError, "keyword `extras:` may only be used with method-based resolve and class-based field such as mutation class, please remove `field:`, `function:` or `resolve:`"
|
211
|
-
end
|
212
223
|
@original_name = name
|
213
224
|
name_s = -name.to_s
|
214
225
|
@underscored_name = -Member::BuildType.underscore(name_s)
|
215
226
|
@name = -(camelize ? Member::BuildType.camelize(name_s) : name_s)
|
216
|
-
|
217
|
-
|
218
|
-
raise ArgumentError, "Instead of passing a field as `field:`, use `add_field(field)` to add an already-defined field."
|
219
|
-
else
|
220
|
-
@field = field
|
227
|
+
if description != :not_given
|
228
|
+
@description = description
|
221
229
|
end
|
222
|
-
@function = function
|
223
|
-
@resolve = resolve
|
224
230
|
self.deprecation_reason = deprecation_reason
|
225
231
|
|
226
232
|
if method && hash_key && dig
|
@@ -253,7 +259,9 @@ module GraphQL
|
|
253
259
|
@max_page_size = max_page_size == :not_given ? nil : max_page_size
|
254
260
|
@introspection = introspection
|
255
261
|
@extras = extras
|
256
|
-
|
262
|
+
if !broadcastable.nil?
|
263
|
+
@broadcastable = broadcastable
|
264
|
+
end
|
257
265
|
@resolver_class = resolver_class
|
258
266
|
@scope = scope
|
259
267
|
@trace = trace
|
@@ -297,13 +305,19 @@ module GraphQL
|
|
297
305
|
self.extensions(extensions)
|
298
306
|
end
|
299
307
|
|
308
|
+
if resolver_class && resolver_class.extensions.any?
|
309
|
+
self.extensions(resolver_class.extensions)
|
310
|
+
end
|
311
|
+
|
300
312
|
if directives.any?
|
301
313
|
directives.each do |(dir_class, options)|
|
302
314
|
self.directive(dir_class, **options)
|
303
315
|
end
|
304
316
|
end
|
305
317
|
|
306
|
-
|
318
|
+
if !validates.empty?
|
319
|
+
self.validates(validates)
|
320
|
+
end
|
307
321
|
|
308
322
|
if definition_block
|
309
323
|
if definition_block.arity == 1
|
@@ -321,7 +335,13 @@ module GraphQL
|
|
321
335
|
# @return [Boolean, nil]
|
322
336
|
# @see GraphQL::Subscriptions::BroadcastAnalyzer
|
323
337
|
def broadcastable?
|
324
|
-
@broadcastable
|
338
|
+
if defined?(@broadcastable)
|
339
|
+
@broadcastable
|
340
|
+
elsif @resolver_class
|
341
|
+
@resolver_class.broadcastable?
|
342
|
+
else
|
343
|
+
nil
|
344
|
+
end
|
325
345
|
end
|
326
346
|
|
327
347
|
# @param text [String]
|
@@ -329,8 +349,12 @@ module GraphQL
|
|
329
349
|
def description(text = nil)
|
330
350
|
if text
|
331
351
|
@description = text
|
332
|
-
|
352
|
+
elsif defined?(@description)
|
333
353
|
@description
|
354
|
+
elsif @resolver_class
|
355
|
+
@description || @resolver_class.description
|
356
|
+
else
|
357
|
+
nil
|
334
358
|
end
|
335
359
|
end
|
336
360
|
|
@@ -394,7 +418,12 @@ module GraphQL
|
|
394
418
|
def extras(new_extras = nil)
|
395
419
|
if new_extras.nil?
|
396
420
|
# Read the value
|
397
|
-
@extras
|
421
|
+
field_extras = @extras
|
422
|
+
if @resolver_class && @resolver_class.extras.any?
|
423
|
+
field_extras + @resolver_class.extras
|
424
|
+
else
|
425
|
+
field_extras
|
426
|
+
end
|
398
427
|
else
|
399
428
|
if @extras.frozen?
|
400
429
|
@extras = @extras.dup
|
@@ -477,7 +506,11 @@ module GraphQL
|
|
477
506
|
when Numeric
|
478
507
|
@complexity = new_complexity
|
479
508
|
when nil
|
480
|
-
@
|
509
|
+
if @resolver_class
|
510
|
+
@complexity || @resolver_class.complexity || 1
|
511
|
+
else
|
512
|
+
@complexity || 1
|
513
|
+
end
|
481
514
|
else
|
482
515
|
raise("Invalid complexity: #{new_complexity.inspect} on #{@name}")
|
483
516
|
end
|
@@ -485,29 +518,31 @@ module GraphQL
|
|
485
518
|
|
486
519
|
# @return [Boolean] True if this field's {#max_page_size} should override the schema default.
|
487
520
|
def has_max_page_size?
|
488
|
-
@has_max_page_size
|
521
|
+
@has_max_page_size || (@resolver_class && @resolver_class.has_max_page_size?)
|
489
522
|
end
|
490
523
|
|
491
524
|
# @return [Integer, nil] Applied to connections if {#has_max_page_size?}
|
492
|
-
|
525
|
+
def max_page_size
|
526
|
+
@max_page_size || (@resolver_class && @resolver_class.max_page_size)
|
527
|
+
end
|
493
528
|
|
494
529
|
class MissingReturnTypeError < GraphQL::Error; end
|
495
530
|
attr_writer :type
|
496
531
|
|
497
532
|
def type
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
533
|
+
if @return_type_expr.nil?
|
534
|
+
if @resolver_class && (t = @resolver_class.type)
|
535
|
+
t
|
536
|
+
else
|
537
|
+
# Not enough info to determine type
|
538
|
+
message = "Can't determine the return type for #{self.path}"
|
539
|
+
if @resolver_class
|
540
|
+
message += " (it has `resolver: #{@resolver_class}`, perhaps that class is missing a `type ...` declaration, or perhaps its type causes a cyclical loading issue)"
|
541
|
+
end
|
542
|
+
raise MissingReturnTypeError, message
|
507
543
|
end
|
508
|
-
raise MissingReturnTypeError, message
|
509
544
|
else
|
510
|
-
Member::BuildType.parse_type(@return_type_expr, null: @return_type_null)
|
545
|
+
@type ||= Member::BuildType.parse_type(@return_type_expr, null: @return_type_null)
|
511
546
|
end
|
512
547
|
rescue GraphQL::Schema::InvalidDocumentError, MissingReturnTypeError => err
|
513
548
|
# Let this propagate up
|
@@ -578,31 +613,94 @@ module GraphQL
|
|
578
613
|
# @param object [GraphQL::Schema::Object] An instance of some type class, wrapping an application object
|
579
614
|
# @param args [Hash] A symbol-keyed hash of Ruby keyword arguments. (Empty if no args)
|
580
615
|
# @param ctx [GraphQL::Query::Context]
|
581
|
-
def resolve(object, args,
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
616
|
+
def resolve(object, args, query_ctx)
|
617
|
+
# Unwrap the GraphQL object to get the application object.
|
618
|
+
application_object = object.object
|
619
|
+
method_receiver = nil
|
620
|
+
method_to_call = nil
|
621
|
+
method_args = nil
|
622
|
+
|
623
|
+
Schema::Validator.validate!(validators, application_object, query_ctx, args)
|
624
|
+
|
625
|
+
query_ctx.schema.after_lazy(self.authorized?(application_object, args, query_ctx)) do |is_authorized|
|
626
|
+
if is_authorized
|
627
|
+
with_extensions(object, args, query_ctx) do |obj, ruby_kwargs|
|
628
|
+
method_args = ruby_kwargs
|
629
|
+
if @resolver_class
|
630
|
+
if obj.is_a?(GraphQL::Schema::Object)
|
631
|
+
obj = obj.object
|
632
|
+
end
|
633
|
+
obj = @resolver_class.new(object: obj, context: query_ctx, field: self)
|
634
|
+
end
|
635
|
+
|
636
|
+
# Find a way to resolve this field, checking:
|
637
|
+
#
|
638
|
+
# - A method on the type instance;
|
639
|
+
# - Hash keys, if the wrapped object is a hash;
|
640
|
+
# - A method on the wrapped object;
|
641
|
+
# - Or, raise not implemented.
|
642
|
+
#
|
643
|
+
if obj.respond_to?(resolver_method)
|
644
|
+
method_to_call = resolver_method
|
645
|
+
method_receiver = obj
|
646
|
+
# Call the method with kwargs, if there are any
|
647
|
+
if ruby_kwargs.any?
|
648
|
+
obj.public_send(resolver_method, **ruby_kwargs)
|
649
|
+
else
|
650
|
+
obj.public_send(resolver_method)
|
651
|
+
end
|
652
|
+
elsif obj.object.is_a?(Hash)
|
653
|
+
inner_object = obj.object
|
654
|
+
if @dig_keys
|
655
|
+
inner_object.dig(*@dig_keys)
|
656
|
+
elsif inner_object.key?(@method_sym)
|
657
|
+
inner_object[@method_sym]
|
658
|
+
else
|
659
|
+
inner_object[@method_str]
|
660
|
+
end
|
661
|
+
elsif obj.object.respond_to?(@method_sym)
|
662
|
+
method_to_call = @method_sym
|
663
|
+
method_receiver = obj.object
|
664
|
+
if ruby_kwargs.any?
|
665
|
+
obj.object.public_send(@method_sym, **ruby_kwargs)
|
666
|
+
else
|
667
|
+
obj.object.public_send(@method_sym)
|
668
|
+
end
|
669
|
+
else
|
670
|
+
raise <<-ERR
|
671
|
+
Failed to implement #{@owner.graphql_name}.#{@name}, tried:
|
588
672
|
|
589
|
-
|
673
|
+
- `#{obj.class}##{resolver_method}`, which did not exist
|
674
|
+
- `#{obj.object.class}##{@method_sym}`, which did not exist
|
675
|
+
- Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
|
590
676
|
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
else
|
595
|
-
raise GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: ctx, field: self)
|
677
|
+
To implement this field, define one of the methods above (and check for typos)
|
678
|
+
ERR
|
679
|
+
end
|
596
680
|
end
|
681
|
+
else
|
682
|
+
raise GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: query_ctx, field: self)
|
597
683
|
end
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
684
|
+
end
|
685
|
+
rescue GraphQL::UnauthorizedFieldError => err
|
686
|
+
err.field ||= self
|
687
|
+
begin
|
688
|
+
query_ctx.schema.unauthorized_field(err)
|
689
|
+
rescue GraphQL::ExecutionError => err
|
690
|
+
err
|
691
|
+
end
|
692
|
+
rescue GraphQL::UnauthorizedError => err
|
693
|
+
begin
|
694
|
+
query_ctx.schema.unauthorized_object(err)
|
695
|
+
rescue GraphQL::ExecutionError => err
|
696
|
+
err
|
697
|
+
end
|
698
|
+
rescue ArgumentError
|
699
|
+
if method_receiver && method_to_call
|
700
|
+
assert_satisfactory_implementation(method_receiver, method_to_call, method_args)
|
701
|
+
end
|
702
|
+
# if the line above doesn't raise, re-raise
|
703
|
+
raise
|
606
704
|
end
|
607
705
|
|
608
706
|
# @param ctx [GraphQL::Query::Context]
|
@@ -618,70 +716,6 @@ module GraphQL
|
|
618
716
|
|
619
717
|
private
|
620
718
|
|
621
|
-
def public_send_field(unextended_obj, unextended_ruby_kwargs, query_ctx)
|
622
|
-
with_extensions(unextended_obj, unextended_ruby_kwargs, query_ctx) do |obj, ruby_kwargs|
|
623
|
-
begin
|
624
|
-
method_receiver = nil
|
625
|
-
method_to_call = nil
|
626
|
-
if @resolver_class
|
627
|
-
if obj.is_a?(GraphQL::Schema::Object)
|
628
|
-
obj = obj.object
|
629
|
-
end
|
630
|
-
obj = @resolver_class.new(object: obj, context: query_ctx, field: self)
|
631
|
-
end
|
632
|
-
|
633
|
-
# Find a way to resolve this field, checking:
|
634
|
-
#
|
635
|
-
# - A method on the type instance;
|
636
|
-
# - Hash keys, if the wrapped object is a hash;
|
637
|
-
# - A method on the wrapped object;
|
638
|
-
# - Or, raise not implemented.
|
639
|
-
#
|
640
|
-
if obj.respond_to?(@resolver_method)
|
641
|
-
method_to_call = @resolver_method
|
642
|
-
method_receiver = obj
|
643
|
-
# Call the method with kwargs, if there are any
|
644
|
-
if ruby_kwargs.any?
|
645
|
-
obj.public_send(@resolver_method, **ruby_kwargs)
|
646
|
-
else
|
647
|
-
obj.public_send(@resolver_method)
|
648
|
-
end
|
649
|
-
elsif obj.object.is_a?(Hash)
|
650
|
-
inner_object = obj.object
|
651
|
-
if @dig_keys
|
652
|
-
inner_object.dig(*@dig_keys)
|
653
|
-
elsif inner_object.key?(@method_sym)
|
654
|
-
inner_object[@method_sym]
|
655
|
-
else
|
656
|
-
inner_object[@method_str]
|
657
|
-
end
|
658
|
-
elsif obj.object.respond_to?(@method_sym)
|
659
|
-
method_to_call = @method_sym
|
660
|
-
method_receiver = obj.object
|
661
|
-
if ruby_kwargs.any?
|
662
|
-
obj.object.public_send(@method_sym, **ruby_kwargs)
|
663
|
-
else
|
664
|
-
obj.object.public_send(@method_sym)
|
665
|
-
end
|
666
|
-
else
|
667
|
-
raise <<-ERR
|
668
|
-
Failed to implement #{@owner.graphql_name}.#{@name}, tried:
|
669
|
-
|
670
|
-
- `#{obj.class}##{@resolver_method}`, which did not exist
|
671
|
-
- `#{obj.object.class}##{@method_sym}`, which did not exist
|
672
|
-
- Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
|
673
|
-
|
674
|
-
To implement this field, define one of the methods above (and check for typos)
|
675
|
-
ERR
|
676
|
-
end
|
677
|
-
rescue ArgumentError
|
678
|
-
assert_satisfactory_implementation(method_receiver, method_to_call, ruby_kwargs)
|
679
|
-
# if the line above doesn't raise, re-raise
|
680
|
-
raise
|
681
|
-
end
|
682
|
-
end
|
683
|
-
end
|
684
|
-
|
685
719
|
def assert_satisfactory_implementation(receiver, method_name, ruby_kwargs)
|
686
720
|
method_defn = receiver.method(method_name)
|
687
721
|
unsatisfied_ruby_kwargs = ruby_kwargs.dup
|
@@ -127,19 +127,15 @@ module GraphQL
|
|
127
127
|
INVALID_OBJECT_MESSAGE = "Expected %{object} to be a key-value object responding to `to_h` or `to_unsafe_h`."
|
128
128
|
|
129
129
|
def validate_non_null_input(input, ctx)
|
130
|
-
result = GraphQL::Query::InputValidationResult.new
|
131
|
-
|
132
130
|
warden = ctx.warden
|
133
131
|
|
134
132
|
if input.is_a?(Array)
|
135
|
-
|
136
|
-
return result
|
133
|
+
return GraphQL::Query::InputValidationResult.from_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) })
|
137
134
|
end
|
138
135
|
|
139
136
|
if !(input.respond_to?(:to_h) || input.respond_to?(:to_unsafe_h))
|
140
137
|
# We're not sure it'll act like a hash, so reject it:
|
141
|
-
|
142
|
-
return result
|
138
|
+
return GraphQL::Query::InputValidationResult.from_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) })
|
143
139
|
end
|
144
140
|
|
145
141
|
# Inject missing required arguments
|
@@ -151,18 +147,22 @@ module GraphQL
|
|
151
147
|
m
|
152
148
|
end
|
153
149
|
|
154
|
-
|
150
|
+
result = nil
|
155
151
|
[input, missing_required_inputs].each do |args_to_validate|
|
156
152
|
args_to_validate.each do |argument_name, value|
|
157
153
|
argument = warden.get_argument(self, argument_name)
|
158
154
|
# Items in the input that are unexpected
|
159
|
-
|
155
|
+
if argument.nil?
|
156
|
+
result ||= Query::InputValidationResult.new
|
160
157
|
result.add_problem("Field is not defined on #{self.graphql_name}", [argument_name])
|
161
|
-
|
158
|
+
else
|
159
|
+
# Items in the input that are expected, but have invalid values
|
160
|
+
argument_result = argument.type.validate_input(value, ctx)
|
161
|
+
result ||= Query::InputValidationResult.new
|
162
|
+
if !argument_result.valid?
|
163
|
+
result.merge_result!(argument_name, argument_result)
|
164
|
+
end
|
162
165
|
end
|
163
|
-
# Items in the input that are expected, but have invalid values
|
164
|
-
argument_result = argument.type.validate_input(value, ctx)
|
165
|
-
result.merge_result!(argument_name, argument_result) unless argument_result.valid?
|
166
166
|
end
|
167
167
|
end
|
168
168
|
|
data/lib/graphql/schema/list.rb
CHANGED
@@ -46,10 +46,11 @@ module GraphQL
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def validate_non_null_input(value, ctx)
|
49
|
-
result =
|
49
|
+
result = nil
|
50
50
|
ensure_array(value).each_with_index do |item, index|
|
51
51
|
item_result = of_type.validate_input(item, ctx)
|
52
52
|
if !item_result.valid?
|
53
|
+
result ||= GraphQL::Query::InputValidationResult.new
|
53
54
|
result.merge_result!(index, item_result)
|
54
55
|
end
|
55
56
|
end
|
@@ -78,23 +78,44 @@ module GraphQL
|
|
78
78
|
# @return [GraphQL::Schema::Argument]
|
79
79
|
def add_argument(arg_defn)
|
80
80
|
@own_arguments ||= {}
|
81
|
-
prev_defn = own_arguments[arg_defn.name]
|
81
|
+
prev_defn = @own_arguments[arg_defn.name]
|
82
82
|
case prev_defn
|
83
83
|
when nil
|
84
|
-
own_arguments[arg_defn.name] = arg_defn
|
84
|
+
@own_arguments[arg_defn.name] = arg_defn
|
85
85
|
when Array
|
86
86
|
prev_defn << arg_defn
|
87
87
|
when GraphQL::Schema::Argument
|
88
|
-
own_arguments[arg_defn.name] = [prev_defn, arg_defn]
|
88
|
+
@own_arguments[arg_defn.name] = [prev_defn, arg_defn]
|
89
89
|
else
|
90
90
|
raise "Invariant: unexpected `@own_arguments[#{arg_defn.name.inspect}]`: #{prev_defn.inspect}"
|
91
91
|
end
|
92
92
|
arg_defn
|
93
93
|
end
|
94
94
|
|
95
|
+
def remove_argument(arg_defn)
|
96
|
+
prev_defn = @own_arguments[arg_defn.name]
|
97
|
+
case prev_defn
|
98
|
+
when nil
|
99
|
+
# done
|
100
|
+
when Array
|
101
|
+
prev_defn.delete(arg_defn)
|
102
|
+
when GraphQL::Schema::Argument
|
103
|
+
@own_arguments.delete(arg_defn.name)
|
104
|
+
else
|
105
|
+
raise "Invariant: unexpected `@own_arguments[#{arg_defn.name.inspect}]`: #{prev_defn.inspect}"
|
106
|
+
end
|
107
|
+
nil
|
108
|
+
end
|
109
|
+
|
95
110
|
# @return [Hash<String => GraphQL::Schema::Argument] Arguments defined on this thing, keyed by name. Includes inherited definitions
|
96
111
|
def arguments(context = GraphQL::Query::NullContext)
|
97
|
-
inherited_arguments =
|
112
|
+
inherited_arguments = if self.is_a?(Class) && superclass.respond_to?(:arguments)
|
113
|
+
superclass.arguments(context)
|
114
|
+
elsif defined?(@resolver_class) && @resolver_class
|
115
|
+
@resolver_class.field_arguments(context)
|
116
|
+
else
|
117
|
+
nil
|
118
|
+
end
|
98
119
|
# Local definitions override inherited ones
|
99
120
|
if own_arguments.any?
|
100
121
|
own_arguments_that_apply = {}
|
@@ -125,6 +146,10 @@ module GraphQL
|
|
125
146
|
all_defns.merge!(ancestor.own_arguments)
|
126
147
|
end
|
127
148
|
end
|
149
|
+
elsif defined?(@resolver_class) && @resolver_class
|
150
|
+
all_defns = {}
|
151
|
+
all_defns.merge!(@resolver_class.own_field_arguments)
|
152
|
+
all_defns.merge!(own_arguments)
|
128
153
|
else
|
129
154
|
all_defns = own_arguments
|
130
155
|
end
|
@@ -137,8 +162,13 @@ module GraphQL
|
|
137
162
|
def get_argument(argument_name, context = GraphQL::Query::NullContext)
|
138
163
|
warden = Warden.from_context(context)
|
139
164
|
if !self.is_a?(Class)
|
140
|
-
|
141
|
-
|
165
|
+
if (arg_config = own_arguments[argument_name]) && (visible_arg = Warden.visible_entry?(:visible_argument?, arg_config, context, warden))
|
166
|
+
visible_arg
|
167
|
+
elsif defined?(@resolver_class) && @resolver_class
|
168
|
+
@resolver_class.get_field_argument(argument_name, context)
|
169
|
+
else
|
170
|
+
nil
|
171
|
+
end
|
142
172
|
else
|
143
173
|
for ancestor in ancestors
|
144
174
|
if ancestor.respond_to?(:own_arguments) &&
|