graphql 1.12.23 → 1.13.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/generators/graphql/core.rb +3 -1
- data/lib/generators/graphql/install_generator.rb +9 -2
- data/lib/generators/graphql/mutation_generator.rb +1 -1
- data/lib/generators/graphql/type_generator.rb +0 -1
- data/lib/graphql/analysis/ast/field_usage.rb +2 -2
- data/lib/graphql/analysis/ast/query_complexity.rb +10 -14
- data/lib/graphql/analysis/ast/visitor.rb +4 -4
- data/lib/graphql/backtrace/table.rb +1 -1
- data/lib/graphql/base_type.rb +4 -2
- data/lib/graphql/boolean_type.rb +1 -1
- data/lib/graphql/dataloader.rb +55 -22
- data/lib/graphql/directive/deprecated_directive.rb +1 -1
- data/lib/graphql/directive/include_directive.rb +1 -1
- data/lib/graphql/directive/skip_directive.rb +1 -1
- data/lib/graphql/directive.rb +0 -4
- data/lib/graphql/enum_type.rb +5 -1
- data/lib/graphql/execution/errors.rb +1 -0
- data/lib/graphql/execution/interpreter/arguments.rb +1 -1
- data/lib/graphql/execution/interpreter/arguments_cache.rb +2 -2
- data/lib/graphql/execution/interpreter/runtime.rb +31 -19
- data/lib/graphql/execution/lookahead.rb +2 -2
- data/lib/graphql/execution/multiplex.rb +4 -1
- data/lib/graphql/float_type.rb +1 -1
- data/lib/graphql/id_type.rb +1 -1
- data/lib/graphql/int_type.rb +1 -1
- data/lib/graphql/introspection/directive_type.rb +1 -1
- data/lib/graphql/introspection/entry_points.rb +2 -2
- data/lib/graphql/introspection/enum_value_type.rb +2 -2
- data/lib/graphql/introspection/field_type.rb +2 -2
- data/lib/graphql/introspection/input_value_type.rb +4 -4
- data/lib/graphql/introspection/schema_type.rb +2 -2
- data/lib/graphql/introspection/type_type.rb +10 -10
- data/lib/graphql/language/block_string.rb +2 -6
- data/lib/graphql/language/document_from_schema_definition.rb +10 -4
- data/lib/graphql/language/lexer.rb +0 -3
- data/lib/graphql/language/lexer.rl +0 -4
- data/lib/graphql/language/nodes.rb +3 -2
- data/lib/graphql/language/parser.rb +442 -434
- data/lib/graphql/language/parser.y +5 -4
- data/lib/graphql/language/printer.rb +6 -1
- data/lib/graphql/language/sanitized_printer.rb +5 -5
- data/lib/graphql/language/token.rb +0 -4
- data/lib/graphql/name_validator.rb +0 -4
- data/lib/graphql/pagination/active_record_relation_connection.rb +43 -6
- data/lib/graphql/pagination/relation_connection.rb +55 -28
- data/lib/graphql/query/arguments.rb +1 -1
- data/lib/graphql/query/arguments_cache.rb +1 -1
- data/lib/graphql/query/context.rb +15 -2
- data/lib/graphql/query/literal_input.rb +1 -1
- data/lib/graphql/query/null_context.rb +12 -7
- data/lib/graphql/query/serial_execution/field_resolution.rb +1 -1
- data/lib/graphql/query/variables.rb +5 -1
- data/lib/graphql/relay/edges_instrumentation.rb +0 -1
- data/lib/graphql/relay/global_id_resolve.rb +1 -1
- data/lib/graphql/relay/page_info.rb +1 -1
- data/lib/graphql/rubocop/graphql/base_cop.rb +36 -0
- data/lib/graphql/rubocop/graphql/default_null_true.rb +43 -0
- data/lib/graphql/rubocop/graphql/default_required_true.rb +43 -0
- data/lib/graphql/rubocop.rb +4 -0
- data/lib/graphql/schema/addition.rb +37 -28
- data/lib/graphql/schema/argument.rb +13 -15
- data/lib/graphql/schema/build_from_definition.rb +5 -5
- data/lib/graphql/schema/directive/feature.rb +1 -1
- data/lib/graphql/schema/directive/flagged.rb +2 -2
- data/lib/graphql/schema/directive/include.rb +1 -1
- data/lib/graphql/schema/directive/skip.rb +1 -1
- data/lib/graphql/schema/directive/transform.rb +1 -1
- data/lib/graphql/schema/directive.rb +7 -3
- data/lib/graphql/schema/enum.rb +60 -10
- data/lib/graphql/schema/enum_value.rb +6 -0
- data/lib/graphql/schema/field/connection_extension.rb +1 -1
- data/lib/graphql/schema/field.rb +229 -77
- data/lib/graphql/schema/field_extension.rb +89 -2
- data/lib/graphql/schema/find_inherited_value.rb +1 -0
- data/lib/graphql/schema/finder.rb +5 -5
- data/lib/graphql/schema/input_object.rb +23 -5
- data/lib/graphql/schema/interface.rb +11 -20
- data/lib/graphql/schema/introspection_system.rb +1 -1
- data/lib/graphql/schema/list.rb +3 -1
- data/lib/graphql/schema/member/accepts_definition.rb +15 -3
- data/lib/graphql/schema/member/build_type.rb +0 -4
- data/lib/graphql/schema/member/cached_graphql_definition.rb +29 -2
- data/lib/graphql/schema/member/has_arguments.rb +55 -13
- data/lib/graphql/schema/member/has_deprecation_reason.rb +1 -1
- data/lib/graphql/schema/member/has_fields.rb +76 -18
- data/lib/graphql/schema/member/has_interfaces.rb +90 -0
- data/lib/graphql/schema/member.rb +1 -0
- data/lib/graphql/schema/non_null.rb +7 -1
- data/lib/graphql/schema/object.rb +10 -75
- data/lib/graphql/schema/printer.rb +1 -1
- data/lib/graphql/schema/relay_classic_mutation.rb +37 -3
- data/lib/graphql/schema/resolver/has_payload_type.rb +27 -2
- data/lib/graphql/schema/resolver.rb +37 -17
- data/lib/graphql/schema/scalar.rb +2 -0
- data/lib/graphql/schema/subscription.rb +11 -1
- data/lib/graphql/schema/traversal.rb +1 -1
- data/lib/graphql/schema/type_expression.rb +1 -1
- data/lib/graphql/schema/type_membership.rb +18 -4
- data/lib/graphql/schema/union.rb +8 -1
- data/lib/graphql/schema/validator/format_validator.rb +0 -4
- data/lib/graphql/schema/validator/numericality_validator.rb +1 -0
- data/lib/graphql/schema/validator/required_validator.rb +29 -15
- data/lib/graphql/schema/validator.rb +4 -7
- data/lib/graphql/schema/warden.rb +116 -52
- data/lib/graphql/schema.rb +111 -23
- data/lib/graphql/static_validation/all_rules.rb +1 -0
- data/lib/graphql/static_validation/base_visitor.rb +5 -5
- data/lib/graphql/static_validation/definition_dependencies.rb +0 -1
- data/lib/graphql/static_validation/literal_validator.rb +1 -1
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
- data/lib/graphql/static_validation/rules/query_root_exists.rb +17 -0
- data/lib/graphql/static_validation/rules/query_root_exists_error.rb +26 -0
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +2 -2
- data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +4 -4
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +13 -7
- data/lib/graphql/string_type.rb +1 -1
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +8 -4
- data/lib/graphql/subscriptions/event.rb +20 -12
- data/lib/graphql/subscriptions/serialize.rb +22 -2
- data/lib/graphql/subscriptions.rb +17 -19
- data/lib/graphql/tracing/active_support_notifications_tracing.rb +6 -20
- data/lib/graphql/tracing/notifications_tracing.rb +59 -0
- data/lib/graphql/types/relay/connection_behaviors.rb +26 -9
- data/lib/graphql/types/relay/default_relay.rb +5 -1
- data/lib/graphql/types/relay/edge_behaviors.rb +13 -2
- data/lib/graphql/types/relay/has_node_field.rb +1 -1
- data/lib/graphql/types/relay/has_nodes_field.rb +1 -1
- data/lib/graphql/types/relay/node_field.rb +14 -3
- data/lib/graphql/types/relay/nodes_field.rb +13 -3
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +10 -32
- metadata +13 -5
data/lib/graphql/schema/field.rb
CHANGED
@@ -5,10 +5,6 @@ require "graphql/schema/field/scope_extension"
|
|
5
5
|
module GraphQL
|
6
6
|
class Schema
|
7
7
|
class Field
|
8
|
-
if !String.method_defined?(:-@)
|
9
|
-
using GraphQL::StringDedupBackport
|
10
|
-
end
|
11
|
-
|
12
8
|
include GraphQL::Schema::Member::CachedGraphQLDefinition
|
13
9
|
include GraphQL::Schema::Member::AcceptsDefinition
|
14
10
|
include GraphQL::Schema::Member::HasArguments
|
@@ -20,6 +16,8 @@ module GraphQL
|
|
20
16
|
include GraphQL::Schema::Member::HasDirectives
|
21
17
|
include GraphQL::Schema::Member::HasDeprecationReason
|
22
18
|
|
19
|
+
class FieldImplementationFailed < GraphQL::Error; end
|
20
|
+
|
23
21
|
# @return [String] the GraphQL name for this field, camelized unless `camelize: false` is provided
|
24
22
|
attr_reader :name
|
25
23
|
alias :graphql_name :name
|
@@ -61,7 +59,7 @@ module GraphQL
|
|
61
59
|
end
|
62
60
|
|
63
61
|
def inspect
|
64
|
-
"#<#{self.class} #{path}#{
|
62
|
+
"#<#{self.class} #{path}#{all_argument_definitions.any? ? "(...)" : ""}: #{type.to_type_signature}>"
|
65
63
|
end
|
66
64
|
|
67
65
|
alias :mutation :resolver
|
@@ -212,7 +210,7 @@ module GraphQL
|
|
212
210
|
# @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
|
213
211
|
# @param validates [Array<Hash>] Configurations for validating this field
|
214
212
|
# @param legacy_edge_class [Class, nil] (DEPRECATED) If present, pass this along to the legacy field definition
|
215
|
-
def initialize(type: nil, name: nil, owner: nil, null:
|
213
|
+
def initialize(type: nil, name: nil, owner: nil, null: true, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, resolver_method: nil, resolve: nil, connection: nil, max_page_size: :not_given, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, 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, legacy_edge_class: nil, &definition_block)
|
216
214
|
if name.nil?
|
217
215
|
raise ArgumentError, "missing first `name` argument or keyword `name:`"
|
218
216
|
end
|
@@ -220,9 +218,6 @@ module GraphQL
|
|
220
218
|
if type.nil?
|
221
219
|
raise ArgumentError, "missing second `type` argument or keyword `type:`"
|
222
220
|
end
|
223
|
-
if null.nil?
|
224
|
-
raise ArgumentError, "missing keyword argument null:"
|
225
|
-
end
|
226
221
|
end
|
227
222
|
if (field || function || resolve) && extras.any?
|
228
223
|
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:`"
|
@@ -281,10 +276,15 @@ module GraphQL
|
|
281
276
|
@legacy_edge_class = legacy_edge_class
|
282
277
|
|
283
278
|
arguments.each do |name, arg|
|
284
|
-
|
279
|
+
case arg
|
280
|
+
when Hash
|
285
281
|
argument(name: name, **arg)
|
286
|
-
|
282
|
+
when GraphQL::Schema::Argument
|
287
283
|
add_argument(arg)
|
284
|
+
when Array
|
285
|
+
arg.each { |a| add_argument(a) }
|
286
|
+
else
|
287
|
+
raise ArgumentError, "Unexpected argument config (#{arg.class}): #{arg.inspect}"
|
288
288
|
end
|
289
289
|
end
|
290
290
|
|
@@ -292,6 +292,7 @@ module GraphQL
|
|
292
292
|
@subscription_scope = subscription_scope
|
293
293
|
|
294
294
|
@extensions = EMPTY_ARRAY
|
295
|
+
@call_after_define = false
|
295
296
|
# This should run before connection extension,
|
296
297
|
# but should it run after the definition block?
|
297
298
|
if scoped?
|
@@ -324,6 +325,9 @@ module GraphQL
|
|
324
325
|
instance_eval(&definition_block)
|
325
326
|
end
|
326
327
|
end
|
328
|
+
|
329
|
+
self.extensions.each(&:after_define_apply)
|
330
|
+
@call_after_define = true
|
327
331
|
end
|
328
332
|
|
329
333
|
# If true, subscription updates with this field can be shared between viewers
|
@@ -356,27 +360,20 @@ module GraphQL
|
|
356
360
|
# @example adding an extension with options
|
357
361
|
# extensions([MyExtensionClass, { AnotherExtensionClass => { filter: true } }])
|
358
362
|
#
|
359
|
-
# @param extensions [Array<Class, Hash<Class =>
|
363
|
+
# @param extensions [Array<Class, Hash<Class => Hash>>] Add extensions to this field. For hash elements, only the first key/value is used.
|
360
364
|
# @return [Array<GraphQL::Schema::FieldExtension>] extensions to apply to this field
|
361
365
|
def extensions(new_extensions = nil)
|
362
|
-
if new_extensions
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
@extensions = @extensions.dup
|
368
|
-
end
|
369
|
-
new_extensions.each do |extension|
|
370
|
-
if extension.is_a?(Hash)
|
371
|
-
extension = extension.to_a[0]
|
372
|
-
extension_class, options = *extension
|
373
|
-
@extensions << extension_class.new(field: self, options: options)
|
366
|
+
if new_extensions
|
367
|
+
new_extensions.each do |extension_config|
|
368
|
+
if extension_config.is_a?(Hash)
|
369
|
+
extension_class, options = *extension_config.to_a[0]
|
370
|
+
self.extension(extension_class, options)
|
374
371
|
else
|
375
|
-
|
376
|
-
@extensions << extension_class.new(field: self, options: nil)
|
372
|
+
self.extension(extension_config)
|
377
373
|
end
|
378
374
|
end
|
379
375
|
end
|
376
|
+
@extensions
|
380
377
|
end
|
381
378
|
|
382
379
|
# Add `extension` to this field, initialized with `options` if provided.
|
@@ -387,10 +384,19 @@ module GraphQL
|
|
387
384
|
# @example adding an extension with options
|
388
385
|
# extension(MyExtensionClass, filter: true)
|
389
386
|
#
|
390
|
-
# @param
|
391
|
-
# @param options [
|
392
|
-
|
393
|
-
|
387
|
+
# @param extension_class [Class] subclass of {Schema::FieldExtension}
|
388
|
+
# @param options [Hash] if provided, given as `options:` when initializing `extension`.
|
389
|
+
# @return [void]
|
390
|
+
def extension(extension_class, options = nil)
|
391
|
+
extension_inst = extension_class.new(field: self, options: options)
|
392
|
+
if @extensions.frozen?
|
393
|
+
@extensions = @extensions.dup
|
394
|
+
end
|
395
|
+
if @call_after_define
|
396
|
+
extension_inst.after_define_apply
|
397
|
+
end
|
398
|
+
@extensions << extension_inst
|
399
|
+
nil
|
394
400
|
end
|
395
401
|
|
396
402
|
# Read extras (as symbols) from this field,
|
@@ -411,6 +417,62 @@ module GraphQL
|
|
411
417
|
end
|
412
418
|
end
|
413
419
|
|
420
|
+
def calculate_complexity(query:, nodes:, child_complexity:)
|
421
|
+
if respond_to?(:complexity_for)
|
422
|
+
lookahead = GraphQL::Execution::Lookahead.new(query: query, field: self, ast_nodes: nodes, owner_type: owner)
|
423
|
+
complexity_for(child_complexity: child_complexity, query: query, lookahead: lookahead)
|
424
|
+
elsif connection?
|
425
|
+
arguments = query.arguments_for(nodes.first, self)
|
426
|
+
max_possible_page_size = nil
|
427
|
+
if arguments[:first]
|
428
|
+
max_possible_page_size = arguments[:first]
|
429
|
+
end
|
430
|
+
if arguments[:last] && (max_possible_page_size.nil? || arguments[:last] > max_possible_page_size)
|
431
|
+
max_possible_page_size = arguments[:last]
|
432
|
+
end
|
433
|
+
|
434
|
+
if max_possible_page_size.nil?
|
435
|
+
max_possible_page_size = max_page_size || query.schema.default_max_page_size
|
436
|
+
end
|
437
|
+
|
438
|
+
if max_possible_page_size.nil?
|
439
|
+
raise GraphQL::Error, "Can't calculate complexity for #{path}, no `first:`, `last:`, `max_page_size` or `default_max_page_size`"
|
440
|
+
else
|
441
|
+
metadata_complexity = 0
|
442
|
+
lookahead = GraphQL::Execution::Lookahead.new(query: query, field: self, ast_nodes: nodes, owner_type: owner)
|
443
|
+
|
444
|
+
if (page_info_lookahead = lookahead.selection(:page_info)).selected?
|
445
|
+
metadata_complexity += 1 # pageInfo
|
446
|
+
metadata_complexity += page_info_lookahead.selections.size # subfields
|
447
|
+
end
|
448
|
+
|
449
|
+
if lookahead.selects?(:total) || lookahead.selects?(:total_count) || lookahead.selects?(:count)
|
450
|
+
metadata_complexity += 1
|
451
|
+
end
|
452
|
+
|
453
|
+
nodes_edges_complexity = 0
|
454
|
+
nodes_edges_complexity += 1 if lookahead.selects?(:edges)
|
455
|
+
nodes_edges_complexity += 1 if lookahead.selects?(:nodes)
|
456
|
+
|
457
|
+
# Possible bug: selections on `edges` and `nodes` are _both_ multiplied here. Should they be?
|
458
|
+
items_complexity = child_complexity - metadata_complexity - nodes_edges_complexity
|
459
|
+
# Add 1 for _this_ field
|
460
|
+
1 + (max_possible_page_size * items_complexity) + metadata_complexity + nodes_edges_complexity
|
461
|
+
end
|
462
|
+
else
|
463
|
+
defined_complexity = complexity
|
464
|
+
case defined_complexity
|
465
|
+
when Proc
|
466
|
+
arguments = query.arguments_for(nodes.first, self)
|
467
|
+
defined_complexity.call(query.context, arguments.keyword_arguments, child_complexity)
|
468
|
+
when Numeric
|
469
|
+
defined_complexity + child_complexity
|
470
|
+
else
|
471
|
+
raise("Invalid complexity: #{defined_complexity.inspect} on #{path} (#{inspect})")
|
472
|
+
end
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
414
476
|
def complexity(new_complexity = nil)
|
415
477
|
case new_complexity
|
416
478
|
when Proc
|
@@ -439,6 +501,8 @@ module GraphQL
|
|
439
501
|
# @return [Integer, nil] Applied to connections if {#has_max_page_size?}
|
440
502
|
attr_reader :max_page_size
|
441
503
|
|
504
|
+
prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
|
505
|
+
|
442
506
|
# @return [GraphQL::Field]
|
443
507
|
def to_graphql
|
444
508
|
field_defn = if @field
|
@@ -493,9 +557,9 @@ module GraphQL
|
|
493
557
|
field_defn.subscription_scope = @subscription_scope
|
494
558
|
field_defn.ast_node = ast_node
|
495
559
|
|
496
|
-
|
497
|
-
arg_graphql = defn.
|
498
|
-
field_defn.arguments[arg_graphql.name] = arg_graphql
|
560
|
+
all_argument_definitions.each do |defn|
|
561
|
+
arg_graphql = defn.deprecated_to_graphql
|
562
|
+
field_defn.arguments[arg_graphql.name] = arg_graphql # rubocop:disable Development/ContextIsPassedCop -- legacy-related
|
499
563
|
end
|
500
564
|
|
501
565
|
# Support a passed-in proc, one way or another
|
@@ -556,13 +620,39 @@ module GraphQL
|
|
556
620
|
|
557
621
|
def authorized?(object, args, context)
|
558
622
|
if @resolver_class
|
559
|
-
# The resolver will check itself during `resolve()`
|
623
|
+
# The resolver _instance_ will check itself during `resolve()`
|
560
624
|
@resolver_class.authorized?(object, context)
|
561
625
|
else
|
626
|
+
if (arg_values = context[:current_arguments])
|
627
|
+
# ^^ that's provided by the interpreter at runtime, and includes info about whether the default value was used or not.
|
628
|
+
using_arg_values = true
|
629
|
+
arg_values = arg_values.argument_values
|
630
|
+
else
|
631
|
+
arg_values = args
|
632
|
+
using_arg_values = false
|
633
|
+
end
|
562
634
|
# Faster than `.any?`
|
563
|
-
arguments.each_value do |arg|
|
564
|
-
|
565
|
-
|
635
|
+
arguments(context).each_value do |arg|
|
636
|
+
arg_key = arg.keyword
|
637
|
+
if arg_values.key?(arg_key)
|
638
|
+
arg_value = arg_values[arg_key]
|
639
|
+
if using_arg_values
|
640
|
+
if arg_value.default_used?
|
641
|
+
# pass -- no auth required for default used
|
642
|
+
next
|
643
|
+
else
|
644
|
+
application_arg_value = arg_value.value
|
645
|
+
if application_arg_value.is_a?(GraphQL::Execution::Interpreter::Arguments)
|
646
|
+
application_arg_value.keyword_arguments
|
647
|
+
end
|
648
|
+
end
|
649
|
+
else
|
650
|
+
application_arg_value = arg_value
|
651
|
+
end
|
652
|
+
|
653
|
+
if !arg.authorized?(object, application_arg_value, context)
|
654
|
+
return false
|
655
|
+
end
|
566
656
|
end
|
567
657
|
end
|
568
658
|
true
|
@@ -659,7 +749,7 @@ module GraphQL
|
|
659
749
|
ruby_kwargs = graphql_args.to_kwargs
|
660
750
|
maybe_lazies = []
|
661
751
|
# Apply any `prepare` methods. Not great code organization, can this go somewhere better?
|
662
|
-
arguments.each do |name, arg_defn|
|
752
|
+
arguments(field_ctx).each do |name, arg_defn|
|
663
753
|
ruby_kwargs_key = arg_defn.keyword
|
664
754
|
|
665
755
|
if ruby_kwargs.key?(ruby_kwargs_key)
|
@@ -704,51 +794,103 @@ module GraphQL
|
|
704
794
|
|
705
795
|
def public_send_field(unextended_obj, unextended_ruby_kwargs, query_ctx)
|
706
796
|
with_extensions(unextended_obj, unextended_ruby_kwargs, query_ctx) do |obj, ruby_kwargs|
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
#
|
716
|
-
# - A method on the type instance;
|
717
|
-
# - Hash keys, if the wrapped object is a hash;
|
718
|
-
# - A method on the wrapped object;
|
719
|
-
# - Or, raise not implemented.
|
720
|
-
#
|
721
|
-
if obj.respond_to?(@resolver_method)
|
722
|
-
# Call the method with kwargs, if there are any
|
723
|
-
if ruby_kwargs.any?
|
724
|
-
obj.public_send(@resolver_method, **ruby_kwargs)
|
725
|
-
else
|
726
|
-
obj.public_send(@resolver_method)
|
797
|
+
begin
|
798
|
+
method_receiver = nil
|
799
|
+
method_to_call = nil
|
800
|
+
if @resolver_class
|
801
|
+
if obj.is_a?(GraphQL::Schema::Object)
|
802
|
+
obj = obj.object
|
803
|
+
end
|
804
|
+
obj = @resolver_class.new(object: obj, context: query_ctx, field: self)
|
727
805
|
end
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
806
|
+
|
807
|
+
# Find a way to resolve this field, checking:
|
808
|
+
#
|
809
|
+
# - A method on the type instance;
|
810
|
+
# - Hash keys, if the wrapped object is a hash;
|
811
|
+
# - A method on the wrapped object;
|
812
|
+
# - Or, raise not implemented.
|
813
|
+
#
|
814
|
+
if obj.respond_to?(@resolver_method)
|
815
|
+
method_to_call = @resolver_method
|
816
|
+
method_receiver = obj
|
817
|
+
# Call the method with kwargs, if there are any
|
818
|
+
if ruby_kwargs.any?
|
819
|
+
obj.public_send(@resolver_method, **ruby_kwargs)
|
820
|
+
else
|
821
|
+
obj.public_send(@resolver_method)
|
822
|
+
end
|
823
|
+
elsif obj.object.is_a?(Hash)
|
824
|
+
inner_object = obj.object
|
825
|
+
if inner_object.key?(@method_sym)
|
826
|
+
inner_object[@method_sym]
|
827
|
+
else
|
828
|
+
inner_object[@method_str]
|
829
|
+
end
|
830
|
+
elsif obj.object.respond_to?(@method_sym)
|
831
|
+
method_to_call = @method_sym
|
832
|
+
method_receiver = obj.object
|
833
|
+
if ruby_kwargs.any?
|
834
|
+
obj.object.public_send(@method_sym, **ruby_kwargs)
|
835
|
+
else
|
836
|
+
obj.object.public_send(@method_sym)
|
837
|
+
end
|
732
838
|
else
|
733
|
-
|
839
|
+
raise <<-ERR
|
840
|
+
Failed to implement #{@owner.graphql_name}.#{@name}, tried:
|
841
|
+
|
842
|
+
- `#{obj.class}##{@resolver_method}`, which did not exist
|
843
|
+
- `#{obj.object.class}##{@method_sym}`, which did not exist
|
844
|
+
- Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
|
845
|
+
|
846
|
+
To implement this field, define one of the methods above (and check for typos)
|
847
|
+
ERR
|
734
848
|
end
|
735
|
-
|
736
|
-
|
737
|
-
|
849
|
+
rescue ArgumentError
|
850
|
+
assert_satisfactory_implementation(method_receiver, method_to_call, ruby_kwargs)
|
851
|
+
# if the line above doesn't raise, re-raise
|
852
|
+
raise
|
853
|
+
end
|
854
|
+
end
|
855
|
+
end
|
856
|
+
|
857
|
+
def assert_satisfactory_implementation(receiver, method_name, ruby_kwargs)
|
858
|
+
method_defn = receiver.method(method_name)
|
859
|
+
unsatisfied_ruby_kwargs = ruby_kwargs.dup
|
860
|
+
unsatisfied_method_params = []
|
861
|
+
encountered_keyrest = false
|
862
|
+
method_defn.parameters.each do |(param_type, param_name)|
|
863
|
+
case param_type
|
864
|
+
when :key
|
865
|
+
unsatisfied_ruby_kwargs.delete(param_name)
|
866
|
+
when :keyreq
|
867
|
+
if unsatisfied_ruby_kwargs.key?(param_name)
|
868
|
+
unsatisfied_ruby_kwargs.delete(param_name)
|
738
869
|
else
|
739
|
-
|
870
|
+
unsatisfied_method_params << "- `#{param_name}:` is required by Ruby, but not by GraphQL. Consider `#{param_name}: nil` instead, or making this argument required in GraphQL."
|
740
871
|
end
|
741
|
-
|
742
|
-
|
743
|
-
|
872
|
+
when :keyrest
|
873
|
+
encountered_keyrest = true
|
874
|
+
when :req
|
875
|
+
unsatisfied_method_params << "- `#{param_name}` is required by Ruby, but GraphQL doesn't pass positional arguments. If it's meant to be a GraphQL argument, use `#{param_name}:` instead. Otherwise, remove it."
|
876
|
+
when :opt, :rest
|
877
|
+
# This is fine, although it will never be present
|
878
|
+
end
|
879
|
+
end
|
880
|
+
|
881
|
+
if encountered_keyrest
|
882
|
+
unsatisfied_ruby_kwargs.clear
|
883
|
+
end
|
744
884
|
|
745
|
-
|
746
|
-
|
747
|
-
|
885
|
+
if unsatisfied_ruby_kwargs.any? || unsatisfied_method_params.any?
|
886
|
+
raise FieldImplementationFailed.new, <<-ERR
|
887
|
+
Failed to call #{method_name} on #{receiver.inspect} because the Ruby method params were incompatible with the GraphQL arguments:
|
748
888
|
|
749
|
-
|
750
|
-
|
751
|
-
|
889
|
+
#{ unsatisfied_ruby_kwargs
|
890
|
+
.map { |key, value| "- `#{key}: #{value}` was given by GraphQL but not defined in the Ruby method. Add `#{key}:` to the method parameters." }
|
891
|
+
.concat(unsatisfied_method_params)
|
892
|
+
.join("\n") }
|
893
|
+
ERR
|
752
894
|
end
|
753
895
|
end
|
754
896
|
|
@@ -762,8 +904,12 @@ module GraphQL
|
|
762
904
|
# This is a hack to get the _last_ value for extended obj and args,
|
763
905
|
# in case one of the extensions doesn't `yield`.
|
764
906
|
# (There's another implementation that uses multiple-return, but I'm wary of the perf cost of the extra arrays)
|
765
|
-
extended = { args: args, obj: obj, memos: nil }
|
907
|
+
extended = { args: args, obj: obj, memos: nil, added_extras: nil }
|
766
908
|
value = run_extensions_before_resolve(obj, args, ctx, extended) do |obj, args|
|
909
|
+
if (added_extras = extended[:added_extras])
|
910
|
+
args = args.dup
|
911
|
+
added_extras.each { |e| args.delete(e) }
|
912
|
+
end
|
767
913
|
yield(obj, args)
|
768
914
|
end
|
769
915
|
|
@@ -792,6 +938,12 @@ module GraphQL
|
|
792
938
|
memos = extended[:memos] ||= {}
|
793
939
|
memos[idx] = memo
|
794
940
|
end
|
941
|
+
|
942
|
+
if (extras = extension.added_extras)
|
943
|
+
ae = extended[:added_extras] ||= []
|
944
|
+
ae.concat(extras)
|
945
|
+
end
|
946
|
+
|
795
947
|
extended[:obj] = extended_obj
|
796
948
|
extended[:args] = extended_args
|
797
949
|
run_extensions_before_resolve(extended_obj, extended_args, ctx, extended, idx: idx + 1) { |o, a| yield(o, a) }
|
@@ -15,23 +15,110 @@ module GraphQL
|
|
15
15
|
# @return [Object]
|
16
16
|
attr_reader :options
|
17
17
|
|
18
|
+
# @return [Array<Symbol>, nil] `default_argument`s added, if any were added (otherwise, `nil`)
|
19
|
+
attr_reader :added_default_arguments
|
20
|
+
|
18
21
|
# Called when the extension is mounted with `extension(name, options)`.
|
19
|
-
# The instance
|
22
|
+
# The instance will be frozen to avoid improper use of state during execution.
|
20
23
|
# @param field [GraphQL::Schema::Field] The field where this extension was mounted
|
21
24
|
# @param options [Object] The second argument to `extension`, or `{}` if nothing was passed.
|
22
25
|
def initialize(field:, options:)
|
23
26
|
@field = field
|
24
27
|
@options = options || {}
|
28
|
+
@added_default_arguments = nil
|
25
29
|
apply
|
26
|
-
freeze
|
27
30
|
end
|
28
31
|
|
32
|
+
class << self
|
33
|
+
# @return [Array(Array, Hash), nil] A list of default argument configs, or `nil` if there aren't any
|
34
|
+
def default_argument_configurations
|
35
|
+
args = superclass.respond_to?(:default_argument_configurations) ? superclass.default_argument_configurations : nil
|
36
|
+
if @own_default_argument_configurations
|
37
|
+
if args
|
38
|
+
args.concat(@own_default_argument_configurations)
|
39
|
+
else
|
40
|
+
args = @own_default_argument_configurations.dup
|
41
|
+
end
|
42
|
+
end
|
43
|
+
args
|
44
|
+
end
|
45
|
+
|
46
|
+
# @see Argument#initialize
|
47
|
+
# @see HasArguments#argument
|
48
|
+
def default_argument(*argument_args, **argument_kwargs)
|
49
|
+
configs = @own_default_argument_configurations ||= []
|
50
|
+
configs << [argument_args, argument_kwargs]
|
51
|
+
end
|
52
|
+
|
53
|
+
# If configured, these `extras` will be added to the field if they aren't already present,
|
54
|
+
# but removed by from `arguments` before the field's `resolve` is called.
|
55
|
+
# (The extras _will_ be present for other extensions, though.)
|
56
|
+
#
|
57
|
+
# @param new_extras [Array<Symbol>] If provided, assign extras used by this extension
|
58
|
+
# @return [Array<Symbol>] any extras assigned to this extension
|
59
|
+
def extras(new_extras = nil)
|
60
|
+
if new_extras
|
61
|
+
@own_extras = new_extras
|
62
|
+
end
|
63
|
+
|
64
|
+
inherited_extras = self.superclass.respond_to?(:extras) ? superclass.extras : nil
|
65
|
+
if @own_extras
|
66
|
+
if inherited_extras
|
67
|
+
inherited_extras + @own_extras
|
68
|
+
else
|
69
|
+
@own_extras
|
70
|
+
end
|
71
|
+
elsif inherited_extras
|
72
|
+
inherited_extras
|
73
|
+
else
|
74
|
+
NO_EXTRAS
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
NO_EXTRAS = [].freeze
|
80
|
+
private_constant :NO_EXTRAS
|
81
|
+
|
29
82
|
# Called when this extension is attached to a field.
|
30
83
|
# The field definition may be extended during this method.
|
31
84
|
# @return [void]
|
32
85
|
def apply
|
33
86
|
end
|
34
87
|
|
88
|
+
# Called after the field's definition block has been executed.
|
89
|
+
# (Any arguments from the block are present on `field`)
|
90
|
+
# @return [void]
|
91
|
+
def after_define
|
92
|
+
end
|
93
|
+
|
94
|
+
# @api private
|
95
|
+
def after_define_apply
|
96
|
+
after_define
|
97
|
+
if (configs = self.class.default_argument_configurations)
|
98
|
+
existing_keywords = field.all_argument_definitions.map(&:keyword)
|
99
|
+
existing_keywords.uniq!
|
100
|
+
@added_default_arguments = []
|
101
|
+
configs.each do |config|
|
102
|
+
argument_args, argument_kwargs = config
|
103
|
+
arg_name = argument_args[0]
|
104
|
+
if !existing_keywords.include?(arg_name)
|
105
|
+
@added_default_arguments << arg_name
|
106
|
+
field.argument(*argument_args, **argument_kwargs)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
if (extras = self.class.extras).any?
|
111
|
+
@added_extras = extras - field.extras
|
112
|
+
field.extras(@added_extras)
|
113
|
+
else
|
114
|
+
@added_extras = nil
|
115
|
+
end
|
116
|
+
freeze
|
117
|
+
end
|
118
|
+
|
119
|
+
# @api private
|
120
|
+
attr_reader :added_extras
|
121
|
+
|
35
122
|
# Called before resolving {#field}. It should either:
|
36
123
|
#
|
37
124
|
# - `yield` values to continue execution; OR
|
@@ -38,7 +38,7 @@ module GraphQL
|
|
38
38
|
|
39
39
|
find_in_directive(directive, path: path)
|
40
40
|
else
|
41
|
-
type = schema.get_type(type_or_directive)
|
41
|
+
type = schema.get_type(type_or_directive) # rubocop:disable Development/ContextIsPassedCop -- build-time
|
42
42
|
|
43
43
|
if type.nil?
|
44
44
|
raise MemberNotFoundError, "Could not find type `#{type_or_directive}` in schema."
|
@@ -56,7 +56,7 @@ module GraphQL
|
|
56
56
|
|
57
57
|
def find_in_directive(directive, path:)
|
58
58
|
argument_name = path.shift
|
59
|
-
argument = directive.
|
59
|
+
argument = directive.get_argument(argument_name) # rubocop:disable Development/ContextIsPassedCop -- build-time
|
60
60
|
|
61
61
|
if argument.nil?
|
62
62
|
raise MemberNotFoundError, "Could not find argument `#{argument_name}` on directive #{directive}."
|
@@ -102,7 +102,7 @@ module GraphQL
|
|
102
102
|
|
103
103
|
def find_in_field(field, path:)
|
104
104
|
argument_name = path.shift
|
105
|
-
argument = field.
|
105
|
+
argument = field.get_argument(argument_name) # rubocop:disable Development/ContextIsPassedCop -- build-time
|
106
106
|
|
107
107
|
if argument.nil?
|
108
108
|
raise MemberNotFoundError, "Could not find argument `#{argument_name}` on field `#{field.name}`."
|
@@ -119,7 +119,7 @@ module GraphQL
|
|
119
119
|
|
120
120
|
def find_in_input_object(input_object, path:)
|
121
121
|
field_name = path.shift
|
122
|
-
input_field = input_object.
|
122
|
+
input_field = input_object.get_argument(field_name) # rubocop:disable Development/ContextIsPassedCop -- build-time
|
123
123
|
|
124
124
|
if input_field.nil?
|
125
125
|
raise MemberNotFoundError, "Could not find input field `#{field_name}` on input object type `#{input_object.graphql_name}`."
|
@@ -136,7 +136,7 @@ module GraphQL
|
|
136
136
|
|
137
137
|
def find_in_enum_type(enum_type, path:)
|
138
138
|
value_name = path.shift
|
139
|
-
enum_value = enum_type.
|
139
|
+
enum_value = enum_type.enum_values.find { |v| v.graphql_name == value_name } # rubocop:disable Development/ContextIsPassedCop -- build-time, not runtime
|
140
140
|
|
141
141
|
if enum_value.nil?
|
142
142
|
raise MemberNotFoundError, "Could not find enum value `#{value_name}` on enum type `#{enum_type.graphql_name}`."
|
@@ -31,7 +31,7 @@ module GraphQL
|
|
31
31
|
end
|
32
32
|
# Apply prepares, not great to have it duplicated here.
|
33
33
|
maybe_lazies = []
|
34
|
-
self.class.arguments.each_value do |arg_defn|
|
34
|
+
self.class.arguments(context).each_value do |arg_defn|
|
35
35
|
ruby_kwargs_key = arg_defn.keyword
|
36
36
|
|
37
37
|
if @ruby_style_hash.key?(ruby_kwargs_key)
|
@@ -79,6 +79,21 @@ module GraphQL
|
|
79
79
|
end
|
80
80
|
end
|
81
81
|
|
82
|
+
def self.authorized?(obj, value, ctx)
|
83
|
+
# Authorize each argument (but this doesn't apply if `prepare` is implemented):
|
84
|
+
if value.is_a?(InputObject)
|
85
|
+
arguments(ctx).each do |_name, input_obj_arg|
|
86
|
+
input_obj_arg = input_obj_arg.type_class
|
87
|
+
if value.key?(input_obj_arg.keyword) &&
|
88
|
+
!input_obj_arg.authorized?(obj, value[input_obj_arg.keyword], ctx)
|
89
|
+
return false
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
# It didn't early-return false:
|
94
|
+
true
|
95
|
+
end
|
96
|
+
|
82
97
|
def unwrap_value(value)
|
83
98
|
case value
|
84
99
|
when Array
|
@@ -129,8 +144,11 @@ module GraphQL
|
|
129
144
|
self[#{method_name.inspect}]
|
130
145
|
end
|
131
146
|
RUBY
|
147
|
+
argument_defn
|
132
148
|
end
|
133
149
|
|
150
|
+
prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
|
151
|
+
|
134
152
|
def to_graphql
|
135
153
|
type_defn = GraphQL::InputObjectType.new
|
136
154
|
type_defn.name = graphql_name
|
@@ -138,8 +156,8 @@ module GraphQL
|
|
138
156
|
type_defn.metadata[:type_class] = self
|
139
157
|
type_defn.mutation = mutation
|
140
158
|
type_defn.ast_node = ast_node
|
141
|
-
|
142
|
-
type_defn.arguments[arg.graphql_definition.name] = arg.graphql_definition
|
159
|
+
all_argument_definitions.each do |arg|
|
160
|
+
type_defn.arguments[arg.graphql_definition(silence_deprecation_warning: true).name] = arg.graphql_definition(silence_deprecation_warning: true) # rubocop:disable Development/ContextIsPassedCop -- legacy-related
|
143
161
|
end
|
144
162
|
# Make a reference to a classic-style Arguments class
|
145
163
|
self.arguments_class = GraphQL::Query::Arguments.construct_arguments_class(type_defn)
|
@@ -172,7 +190,7 @@ module GraphQL
|
|
172
190
|
end
|
173
191
|
|
174
192
|
# Inject missing required arguments
|
175
|
-
missing_required_inputs = self.arguments.reduce({}) do |m, (argument_name, argument)|
|
193
|
+
missing_required_inputs = self.arguments(ctx).reduce({}) do |m, (argument_name, argument)|
|
176
194
|
if !input.key?(argument_name) && argument.type.non_null? && warden.get_argument(self, argument_name)
|
177
195
|
m[argument_name] = nil
|
178
196
|
end
|
@@ -223,7 +241,7 @@ module GraphQL
|
|
223
241
|
|
224
242
|
result = {}
|
225
243
|
|
226
|
-
arguments.each do |input_key, input_field_defn|
|
244
|
+
arguments(ctx).each do |input_key, input_field_defn|
|
227
245
|
input_value = value[input_key]
|
228
246
|
if value.key?(input_key)
|
229
247
|
result[input_key] = if input_value.nil?
|