graphql 1.12.10 → 1.13.4
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/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/object_generator.rb +2 -1
- data/lib/generators/graphql/relay.rb +19 -11
- data/lib/generators/graphql/templates/schema.erb +14 -2
- data/lib/generators/graphql/type_generator.rb +0 -1
- data/lib/graphql/analysis/ast/field_usage.rb +28 -1
- 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 +15 -3
- data/lib/graphql/backtrace/tracer.rb +7 -4
- data/lib/graphql/base_type.rb +4 -2
- data/lib/graphql/boolean_type.rb +1 -1
- data/lib/graphql/dataloader/null_dataloader.rb +1 -0
- data/lib/graphql/dataloader/source.rb +50 -2
- data/lib/graphql/dataloader.rb +110 -41
- data/lib/graphql/define/instance_definable.rb +1 -1
- data/lib/graphql/deprecated_dsl.rb +11 -3
- data/lib/graphql/deprecation.rb +1 -5
- 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/execute.rb +1 -1
- data/lib/graphql/execution/interpreter/arguments.rb +1 -1
- data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -4
- data/lib/graphql/execution/interpreter/resolve.rb +6 -2
- data/lib/graphql/execution/interpreter/runtime.rb +513 -213
- data/lib/graphql/execution/interpreter.rb +4 -8
- data/lib/graphql/execution/lazy.rb +5 -1
- 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/integer_encoding_error.rb +18 -2
- 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 +10 -4
- data/lib/graphql/introspection/schema_type.rb +3 -3
- 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 +13 -3
- 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/connections.rb +40 -16
- data/lib/graphql/pagination/relation_connection.rb +57 -27
- 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/validation_pipeline.rb +1 -1
- data/lib/graphql/query/variables.rb +5 -1
- data/lib/graphql/query.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 +247 -0
- data/lib/graphql/schema/argument.rb +103 -45
- data/lib/graphql/schema/build_from_definition.rb +13 -7
- 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 +14 -2
- data/lib/graphql/schema/directive.rb +7 -3
- data/lib/graphql/schema/enum.rb +70 -11
- 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 +243 -81
- 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 +39 -29
- 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 +1 -4
- data/lib/graphql/schema/member/cached_graphql_definition.rb +29 -2
- data/lib/graphql/schema/member/has_arguments.rb +145 -57
- 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 +12 -17
- 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 +75 -65
- data/lib/graphql/schema/scalar.rb +2 -0
- data/lib/graphql/schema/subscription.rb +36 -8
- 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/allow_blank_validator.rb +29 -0
- data/lib/graphql/schema/validator/allow_null_validator.rb +26 -0
- data/lib/graphql/schema/validator/exclusion_validator.rb +3 -1
- data/lib/graphql/schema/validator/format_validator.rb +4 -5
- data/lib/graphql/schema/validator/inclusion_validator.rb +3 -1
- data/lib/graphql/schema/validator/length_validator.rb +5 -3
- data/lib/graphql/schema/validator/numericality_validator.rb +13 -2
- data/lib/graphql/schema/validator/required_validator.rb +29 -15
- data/lib/graphql/schema/validator.rb +33 -25
- data/lib/graphql/schema/warden.rb +116 -52
- data/lib/graphql/schema.rb +162 -227
- data/lib/graphql/static_validation/all_rules.rb +1 -0
- data/lib/graphql/static_validation/base_visitor.rb +8 -5
- data/lib/graphql/static_validation/definition_dependencies.rb +0 -1
- data/lib/graphql/static_validation/error.rb +3 -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 +52 -26
- data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +25 -4
- data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -2
- 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 +3 -1
- 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/static_validation/validation_context.rb +8 -2
- data/lib/graphql/static_validation/validator.rb +15 -12
- data/lib/graphql/string_encoding_error.rb +13 -3
- data/lib/graphql/string_type.rb +1 -1
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +36 -6
- data/lib/graphql/subscriptions/event.rb +68 -31
- data/lib/graphql/subscriptions/serialize.rb +23 -3
- data/lib/graphql/subscriptions.rb +17 -19
- data/lib/graphql/tracing/active_support_notifications_tracing.rb +6 -20
- data/lib/graphql/tracing/appsignal_tracing.rb +15 -0
- data/lib/graphql/tracing/notifications_tracing.rb +59 -0
- data/lib/graphql/types/big_int.rb +5 -1
- data/lib/graphql/types/int.rb +1 -1
- 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 +2 -2
- data/lib/graphql/types/relay/has_nodes_field.rb +2 -2
- data/lib/graphql/types/relay/node_field.rb +15 -4
- data/lib/graphql/types/relay/nodes_field.rb +14 -4
- data/lib/graphql/types/string.rb +1 -1
- data/lib/graphql/unauthorized_error.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +10 -28
- data/readme.md +1 -4
- metadata +17 -21
- data/lib/graphql/execution/interpreter/hash_response.rb +0 -46
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
|
|
@@ -122,6 +120,9 @@ module GraphQL
|
|
|
122
120
|
else
|
|
123
121
|
kwargs[:type] = type
|
|
124
122
|
end
|
|
123
|
+
if type.is_a?(Class) && type < GraphQL::Schema::Mutation
|
|
124
|
+
raise ArgumentError, "Use `field #{name.inspect}, mutation: Mutation, ...` to provide a mutation to this field instead"
|
|
125
|
+
end
|
|
125
126
|
end
|
|
126
127
|
new(**kwargs, &block)
|
|
127
128
|
end
|
|
@@ -209,7 +210,7 @@ module GraphQL
|
|
|
209
210
|
# @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
|
|
210
211
|
# @param validates [Array<Hash>] Configurations for validating this field
|
|
211
212
|
# @param legacy_edge_class [Class, nil] (DEPRECATED) If present, pass this along to the legacy field definition
|
|
212
|
-
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)
|
|
213
214
|
if name.nil?
|
|
214
215
|
raise ArgumentError, "missing first `name` argument or keyword `name:`"
|
|
215
216
|
end
|
|
@@ -217,9 +218,6 @@ module GraphQL
|
|
|
217
218
|
if type.nil?
|
|
218
219
|
raise ArgumentError, "missing second `type` argument or keyword `type:`"
|
|
219
220
|
end
|
|
220
|
-
if null.nil?
|
|
221
|
-
raise ArgumentError, "missing keyword argument null:"
|
|
222
|
-
end
|
|
223
221
|
end
|
|
224
222
|
if (field || function || resolve) && extras.any?
|
|
225
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:`"
|
|
@@ -278,10 +276,15 @@ module GraphQL
|
|
|
278
276
|
@legacy_edge_class = legacy_edge_class
|
|
279
277
|
|
|
280
278
|
arguments.each do |name, arg|
|
|
281
|
-
|
|
279
|
+
case arg
|
|
280
|
+
when Hash
|
|
282
281
|
argument(name: name, **arg)
|
|
283
|
-
|
|
282
|
+
when GraphQL::Schema::Argument
|
|
284
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}"
|
|
285
288
|
end
|
|
286
289
|
end
|
|
287
290
|
|
|
@@ -289,6 +292,7 @@ module GraphQL
|
|
|
289
292
|
@subscription_scope = subscription_scope
|
|
290
293
|
|
|
291
294
|
@extensions = EMPTY_ARRAY
|
|
295
|
+
@call_after_define = false
|
|
292
296
|
# This should run before connection extension,
|
|
293
297
|
# but should it run after the definition block?
|
|
294
298
|
if scoped?
|
|
@@ -321,6 +325,9 @@ module GraphQL
|
|
|
321
325
|
instance_eval(&definition_block)
|
|
322
326
|
end
|
|
323
327
|
end
|
|
328
|
+
|
|
329
|
+
self.extensions.each(&:after_define_apply)
|
|
330
|
+
@call_after_define = true
|
|
324
331
|
end
|
|
325
332
|
|
|
326
333
|
# If true, subscription updates with this field can be shared between viewers
|
|
@@ -353,27 +360,20 @@ module GraphQL
|
|
|
353
360
|
# @example adding an extension with options
|
|
354
361
|
# extensions([MyExtensionClass, { AnotherExtensionClass => { filter: true } }])
|
|
355
362
|
#
|
|
356
|
-
# @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.
|
|
357
364
|
# @return [Array<GraphQL::Schema::FieldExtension>] extensions to apply to this field
|
|
358
365
|
def extensions(new_extensions = nil)
|
|
359
|
-
if new_extensions
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
@extensions = @extensions.dup
|
|
365
|
-
end
|
|
366
|
-
new_extensions.each do |extension|
|
|
367
|
-
if extension.is_a?(Hash)
|
|
368
|
-
extension = extension.to_a[0]
|
|
369
|
-
extension_class, options = *extension
|
|
370
|
-
@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)
|
|
371
371
|
else
|
|
372
|
-
|
|
373
|
-
@extensions << extension_class.new(field: self, options: nil)
|
|
372
|
+
self.extension(extension_config)
|
|
374
373
|
end
|
|
375
374
|
end
|
|
376
375
|
end
|
|
376
|
+
@extensions
|
|
377
377
|
end
|
|
378
378
|
|
|
379
379
|
# Add `extension` to this field, initialized with `options` if provided.
|
|
@@ -384,10 +384,19 @@ module GraphQL
|
|
|
384
384
|
# @example adding an extension with options
|
|
385
385
|
# extension(MyExtensionClass, filter: true)
|
|
386
386
|
#
|
|
387
|
-
# @param
|
|
388
|
-
# @param options [
|
|
389
|
-
|
|
390
|
-
|
|
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
|
|
391
400
|
end
|
|
392
401
|
|
|
393
402
|
# Read extras (as symbols) from this field,
|
|
@@ -408,6 +417,62 @@ module GraphQL
|
|
|
408
417
|
end
|
|
409
418
|
end
|
|
410
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
|
+
|
|
411
476
|
def complexity(new_complexity = nil)
|
|
412
477
|
case new_complexity
|
|
413
478
|
when Proc
|
|
@@ -436,6 +501,8 @@ module GraphQL
|
|
|
436
501
|
# @return [Integer, nil] Applied to connections if {#has_max_page_size?}
|
|
437
502
|
attr_reader :max_page_size
|
|
438
503
|
|
|
504
|
+
prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
|
|
505
|
+
|
|
439
506
|
# @return [GraphQL::Field]
|
|
440
507
|
def to_graphql
|
|
441
508
|
field_defn = if @field
|
|
@@ -490,9 +557,9 @@ module GraphQL
|
|
|
490
557
|
field_defn.subscription_scope = @subscription_scope
|
|
491
558
|
field_defn.ast_node = ast_node
|
|
492
559
|
|
|
493
|
-
|
|
494
|
-
arg_graphql = defn.
|
|
495
|
-
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
|
|
496
563
|
end
|
|
497
564
|
|
|
498
565
|
# Support a passed-in proc, one way or another
|
|
@@ -510,6 +577,7 @@ module GraphQL
|
|
|
510
577
|
field_defn
|
|
511
578
|
end
|
|
512
579
|
|
|
580
|
+
class MissingReturnTypeError < GraphQL::Error; end
|
|
513
581
|
attr_writer :type
|
|
514
582
|
|
|
515
583
|
def type
|
|
@@ -517,14 +585,21 @@ module GraphQL
|
|
|
517
585
|
Member::BuildType.parse_type(@function.type, null: false)
|
|
518
586
|
elsif @field
|
|
519
587
|
Member::BuildType.parse_type(@field.type, null: false)
|
|
588
|
+
elsif @return_type_expr.nil?
|
|
589
|
+
# Not enough info to determine type
|
|
590
|
+
message = "Can't determine the return type for #{self.path}"
|
|
591
|
+
if @resolver_class
|
|
592
|
+
message += " (it has `resolver: #{@resolver_class}`, consider configuration a `type ...` for that class)"
|
|
593
|
+
end
|
|
594
|
+
raise MissingReturnTypeError, message
|
|
520
595
|
else
|
|
521
596
|
Member::BuildType.parse_type(@return_type_expr, null: @return_type_null)
|
|
522
597
|
end
|
|
523
|
-
rescue GraphQL::Schema::InvalidDocumentError => err
|
|
598
|
+
rescue GraphQL::Schema::InvalidDocumentError, MissingReturnTypeError => err
|
|
524
599
|
# Let this propagate up
|
|
525
600
|
raise err
|
|
526
601
|
rescue StandardError => err
|
|
527
|
-
raise
|
|
602
|
+
raise MissingReturnTypeError, "Failed to build return type for #{@owner.graphql_name}.#{name} from #{@return_type_expr.inspect}: (#{err.class}) #{err.message}", err.backtrace
|
|
528
603
|
end
|
|
529
604
|
|
|
530
605
|
def visible?(context)
|
|
@@ -545,13 +620,39 @@ module GraphQL
|
|
|
545
620
|
|
|
546
621
|
def authorized?(object, args, context)
|
|
547
622
|
if @resolver_class
|
|
548
|
-
# The resolver will check itself during `resolve()`
|
|
623
|
+
# The resolver _instance_ will check itself during `resolve()`
|
|
549
624
|
@resolver_class.authorized?(object, context)
|
|
550
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
|
|
551
634
|
# Faster than `.any?`
|
|
552
|
-
arguments.each_value do |arg|
|
|
553
|
-
|
|
554
|
-
|
|
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
|
|
555
656
|
end
|
|
556
657
|
end
|
|
557
658
|
true
|
|
@@ -608,8 +709,7 @@ module GraphQL
|
|
|
608
709
|
if is_authorized
|
|
609
710
|
public_send_field(object, args, ctx)
|
|
610
711
|
else
|
|
611
|
-
|
|
612
|
-
ctx.schema.unauthorized_field(err)
|
|
712
|
+
raise GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: ctx, field: self)
|
|
613
713
|
end
|
|
614
714
|
end
|
|
615
715
|
rescue GraphQL::UnauthorizedFieldError => err
|
|
@@ -649,7 +749,7 @@ module GraphQL
|
|
|
649
749
|
ruby_kwargs = graphql_args.to_kwargs
|
|
650
750
|
maybe_lazies = []
|
|
651
751
|
# Apply any `prepare` methods. Not great code organization, can this go somewhere better?
|
|
652
|
-
arguments.each do |name, arg_defn|
|
|
752
|
+
arguments(field_ctx).each do |name, arg_defn|
|
|
653
753
|
ruby_kwargs_key = arg_defn.keyword
|
|
654
754
|
|
|
655
755
|
if ruby_kwargs.key?(ruby_kwargs_key)
|
|
@@ -694,51 +794,103 @@ module GraphQL
|
|
|
694
794
|
|
|
695
795
|
def public_send_field(unextended_obj, unextended_ruby_kwargs, query_ctx)
|
|
696
796
|
with_extensions(unextended_obj, unextended_ruby_kwargs, query_ctx) do |obj, ruby_kwargs|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
#
|
|
706
|
-
# - A method on the type instance;
|
|
707
|
-
# - Hash keys, if the wrapped object is a hash;
|
|
708
|
-
# - A method on the wrapped object;
|
|
709
|
-
# - Or, raise not implemented.
|
|
710
|
-
#
|
|
711
|
-
if obj.respond_to?(@resolver_method)
|
|
712
|
-
# Call the method with kwargs, if there are any
|
|
713
|
-
if ruby_kwargs.any?
|
|
714
|
-
obj.public_send(@resolver_method, **ruby_kwargs)
|
|
715
|
-
else
|
|
716
|
-
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)
|
|
717
805
|
end
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
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
|
|
722
838
|
else
|
|
723
|
-
|
|
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
|
|
724
848
|
end
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
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)
|
|
728
869
|
else
|
|
729
|
-
|
|
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."
|
|
730
871
|
end
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
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
|
|
734
884
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
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:
|
|
738
888
|
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
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
|
|
742
894
|
end
|
|
743
895
|
end
|
|
744
896
|
|
|
@@ -752,8 +904,12 @@ module GraphQL
|
|
|
752
904
|
# This is a hack to get the _last_ value for extended obj and args,
|
|
753
905
|
# in case one of the extensions doesn't `yield`.
|
|
754
906
|
# (There's another implementation that uses multiple-return, but I'm wary of the perf cost of the extra arrays)
|
|
755
|
-
extended = { args: args, obj: obj, memos: nil }
|
|
907
|
+
extended = { args: args, obj: obj, memos: nil, added_extras: nil }
|
|
756
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
|
|
757
913
|
yield(obj, args)
|
|
758
914
|
end
|
|
759
915
|
|
|
@@ -782,6 +938,12 @@ module GraphQL
|
|
|
782
938
|
memos = extended[:memos] ||= {}
|
|
783
939
|
memos[idx] = memo
|
|
784
940
|
end
|
|
941
|
+
|
|
942
|
+
if (extras = extension.added_extras)
|
|
943
|
+
ae = extended[:added_extras] ||= []
|
|
944
|
+
ae.concat(extras)
|
|
945
|
+
end
|
|
946
|
+
|
|
785
947
|
extended[:obj] = extended_obj
|
|
786
948
|
extended[:args] = extended_args
|
|
787
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}`."
|