graphql 1.12.21 → 1.13.2

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.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/core.rb +3 -1
  3. data/lib/generators/graphql/install_generator.rb +9 -2
  4. data/lib/generators/graphql/mutation_generator.rb +1 -1
  5. data/lib/generators/graphql/type_generator.rb +0 -1
  6. data/lib/graphql/analysis/ast/field_usage.rb +2 -2
  7. data/lib/graphql/analysis/ast/query_complexity.rb +10 -14
  8. data/lib/graphql/analysis/ast/visitor.rb +4 -4
  9. data/lib/graphql/backtrace/table.rb +1 -1
  10. data/lib/graphql/base_type.rb +4 -2
  11. data/lib/graphql/boolean_type.rb +1 -1
  12. data/lib/graphql/dataloader.rb +55 -22
  13. data/lib/graphql/directive/deprecated_directive.rb +1 -1
  14. data/lib/graphql/directive/include_directive.rb +1 -1
  15. data/lib/graphql/directive/skip_directive.rb +1 -1
  16. data/lib/graphql/directive.rb +0 -4
  17. data/lib/graphql/enum_type.rb +5 -1
  18. data/lib/graphql/execution/errors.rb +1 -0
  19. data/lib/graphql/execution/interpreter/arguments.rb +1 -1
  20. data/lib/graphql/execution/interpreter/arguments_cache.rb +2 -2
  21. data/lib/graphql/execution/interpreter/runtime.rb +31 -19
  22. data/lib/graphql/execution/lookahead.rb +2 -2
  23. data/lib/graphql/execution/multiplex.rb +4 -1
  24. data/lib/graphql/float_type.rb +1 -1
  25. data/lib/graphql/id_type.rb +1 -1
  26. data/lib/graphql/int_type.rb +1 -1
  27. data/lib/graphql/introspection/directive_type.rb +1 -1
  28. data/lib/graphql/introspection/entry_points.rb +2 -2
  29. data/lib/graphql/introspection/enum_value_type.rb +2 -2
  30. data/lib/graphql/introspection/field_type.rb +2 -2
  31. data/lib/graphql/introspection/input_value_type.rb +4 -4
  32. data/lib/graphql/introspection/schema_type.rb +2 -2
  33. data/lib/graphql/introspection/type_type.rb +10 -10
  34. data/lib/graphql/language/block_string.rb +2 -6
  35. data/lib/graphql/language/document_from_schema_definition.rb +4 -2
  36. data/lib/graphql/language/lexer.rb +0 -3
  37. data/lib/graphql/language/lexer.rl +0 -4
  38. data/lib/graphql/language/nodes.rb +12 -2
  39. data/lib/graphql/language/parser.rb +442 -434
  40. data/lib/graphql/language/parser.y +5 -4
  41. data/lib/graphql/language/printer.rb +6 -1
  42. data/lib/graphql/language/sanitized_printer.rb +5 -5
  43. data/lib/graphql/language/token.rb +0 -4
  44. data/lib/graphql/name_validator.rb +0 -4
  45. data/lib/graphql/query/arguments.rb +1 -1
  46. data/lib/graphql/query/arguments_cache.rb +1 -1
  47. data/lib/graphql/query/context.rb +15 -2
  48. data/lib/graphql/query/literal_input.rb +1 -1
  49. data/lib/graphql/query/null_context.rb +12 -7
  50. data/lib/graphql/query/serial_execution/field_resolution.rb +1 -1
  51. data/lib/graphql/query/variables.rb +5 -1
  52. data/lib/graphql/relay/edges_instrumentation.rb +0 -1
  53. data/lib/graphql/relay/global_id_resolve.rb +1 -1
  54. data/lib/graphql/relay/page_info.rb +1 -1
  55. data/lib/graphql/rubocop/graphql/base_cop.rb +36 -0
  56. data/lib/graphql/rubocop/graphql/default_null_true.rb +43 -0
  57. data/lib/graphql/rubocop/graphql/default_required_true.rb +43 -0
  58. data/lib/graphql/rubocop.rb +4 -0
  59. data/lib/graphql/schema/addition.rb +37 -28
  60. data/lib/graphql/schema/argument.rb +8 -6
  61. data/lib/graphql/schema/build_from_definition.rb +5 -5
  62. data/lib/graphql/schema/directive/feature.rb +1 -1
  63. data/lib/graphql/schema/directive/flagged.rb +2 -2
  64. data/lib/graphql/schema/directive/include.rb +1 -1
  65. data/lib/graphql/schema/directive/skip.rb +1 -1
  66. data/lib/graphql/schema/directive/transform.rb +1 -1
  67. data/lib/graphql/schema/directive.rb +7 -3
  68. data/lib/graphql/schema/enum.rb +60 -10
  69. data/lib/graphql/schema/enum_value.rb +6 -0
  70. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  71. data/lib/graphql/schema/field.rb +126 -38
  72. data/lib/graphql/schema/field_extension.rb +52 -2
  73. data/lib/graphql/schema/find_inherited_value.rb +1 -0
  74. data/lib/graphql/schema/finder.rb +5 -5
  75. data/lib/graphql/schema/input_object.rb +8 -5
  76. data/lib/graphql/schema/interface.rb +11 -20
  77. data/lib/graphql/schema/introspection_system.rb +1 -1
  78. data/lib/graphql/schema/list.rb +3 -1
  79. data/lib/graphql/schema/member/accepts_definition.rb +15 -3
  80. data/lib/graphql/schema/member/build_type.rb +0 -4
  81. data/lib/graphql/schema/member/cached_graphql_definition.rb +29 -2
  82. data/lib/graphql/schema/member/has_arguments.rb +55 -13
  83. data/lib/graphql/schema/member/has_deprecation_reason.rb +1 -1
  84. data/lib/graphql/schema/member/has_fields.rb +76 -18
  85. data/lib/graphql/schema/member/has_interfaces.rb +90 -0
  86. data/lib/graphql/schema/member.rb +1 -0
  87. data/lib/graphql/schema/non_null.rb +3 -1
  88. data/lib/graphql/schema/object.rb +10 -75
  89. data/lib/graphql/schema/printer.rb +1 -1
  90. data/lib/graphql/schema/relay_classic_mutation.rb +37 -3
  91. data/lib/graphql/schema/resolver/has_payload_type.rb +27 -2
  92. data/lib/graphql/schema/resolver.rb +37 -17
  93. data/lib/graphql/schema/scalar.rb +2 -0
  94. data/lib/graphql/schema/subscription.rb +11 -1
  95. data/lib/graphql/schema/traversal.rb +1 -1
  96. data/lib/graphql/schema/type_expression.rb +1 -1
  97. data/lib/graphql/schema/type_membership.rb +18 -4
  98. data/lib/graphql/schema/union.rb +8 -1
  99. data/lib/graphql/schema/validator/format_validator.rb +0 -4
  100. data/lib/graphql/schema/validator/numericality_validator.rb +1 -0
  101. data/lib/graphql/schema/validator.rb +4 -7
  102. data/lib/graphql/schema/warden.rb +116 -52
  103. data/lib/graphql/schema.rb +106 -22
  104. data/lib/graphql/static_validation/base_visitor.rb +5 -5
  105. data/lib/graphql/static_validation/definition_dependencies.rb +0 -1
  106. data/lib/graphql/static_validation/literal_validator.rb +1 -1
  107. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  108. data/lib/graphql/static_validation/rules/fields_will_merge.rb +15 -8
  109. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +3 -1
  110. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +4 -4
  111. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +7 -7
  112. data/lib/graphql/string_type.rb +1 -1
  113. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +8 -4
  114. data/lib/graphql/subscriptions/event.rb +20 -12
  115. data/lib/graphql/subscriptions.rb +17 -19
  116. data/lib/graphql/types/relay/connection_behaviors.rb +26 -9
  117. data/lib/graphql/types/relay/default_relay.rb +5 -1
  118. data/lib/graphql/types/relay/edge_behaviors.rb +13 -2
  119. data/lib/graphql/types/relay/has_node_field.rb +1 -1
  120. data/lib/graphql/types/relay/has_nodes_field.rb +1 -1
  121. data/lib/graphql/version.rb +1 -1
  122. data/lib/graphql.rb +10 -32
  123. metadata +10 -5
@@ -35,7 +35,7 @@ module GraphQL
35
35
  GraphQL::Schema::Directive::INPUT_FIELD_DEFINITION,
36
36
  )
37
37
 
38
- argument :by, [String], "Flags to check for this schema member", required: true
38
+ argument :by, [String], "Flags to check for this schema member"
39
39
 
40
40
  module VisibleByFlag
41
41
  def self.included(schema_class)
@@ -44,7 +44,7 @@ module GraphQL
44
44
 
45
45
  def visible?(context)
46
46
  if dir = self.directives.find { |d| d.is_a?(Flagged) }
47
- relevant_flags = (f = context[:flags]) && dir.arguments[:by] & f
47
+ relevant_flags = (f = context[:flags]) && dir.arguments[:by] & f # rubocop:disable Development/ContextIsPassedCop -- definition-related
48
48
  relevant_flags && relevant_flags.any? && super
49
49
  else
50
50
  super
@@ -11,7 +11,7 @@ module GraphQL
11
11
  GraphQL::Schema::Directive::INLINE_FRAGMENT
12
12
  )
13
13
 
14
- argument :if, Boolean, required: true,
14
+ argument :if, Boolean,
15
15
  description: "Included when true."
16
16
 
17
17
  default_directive true
@@ -11,7 +11,7 @@ module GraphQL
11
11
  GraphQL::Schema::Directive::INLINE_FRAGMENT
12
12
  )
13
13
 
14
- argument :if, Boolean, required: true,
14
+ argument :if, Boolean,
15
15
  description: "Skipped when true."
16
16
 
17
17
  default_directive true
@@ -24,7 +24,7 @@ module GraphQL
24
24
  GraphQL::Schema::Directive::FIELD,
25
25
  )
26
26
 
27
- argument :by, String, required: true,
27
+ argument :by, String,
28
28
  description: "The name of the transform to run if applicable"
29
29
 
30
30
  TRANSFORMS = [
@@ -8,6 +8,8 @@ module GraphQL
8
8
  # - {.resolve}: Wraps field resolution (so it should call `yield` to continue)
9
9
  class Directive < GraphQL::Schema::Member
10
10
  extend GraphQL::Schema::Member::HasArguments
11
+ extend GraphQL::Schema::Member::AcceptsDefinition
12
+
11
13
  class << self
12
14
  # Directives aren't types, they don't have kinds.
13
15
  undef_method :kind
@@ -53,6 +55,8 @@ module GraphQL
53
55
  default_directive
54
56
  end
55
57
 
58
+ prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
59
+
56
60
  def to_graphql
57
61
  defn = GraphQL::Directive.new
58
62
  defn.name = self.graphql_name
@@ -61,9 +65,9 @@ module GraphQL
61
65
  defn.default_directive = self.default_directive
62
66
  defn.ast_node = ast_node
63
67
  defn.metadata[:type_class] = self
64
- arguments.each do |name, arg_defn|
65
- arg_graphql = arg_defn.to_graphql
66
- defn.arguments[arg_graphql.name] = arg_graphql
68
+ all_argument_definitions.each do |arg_defn|
69
+ arg_graphql = arg_defn.to_graphql(silence_deprecation_warning: true)
70
+ defn.arguments[arg_graphql.name] = arg_graphql # rubocop:disable Development/ContextIsPassedCop -- legacy-related
67
71
  end
68
72
  # Make a reference to a classic-style Arguments class
69
73
  defn.arguments_class = GraphQL::Query::Arguments.construct_arguments_class(defn)
@@ -46,20 +46,70 @@ module GraphQL
46
46
  def value(*args, **kwargs, &block)
47
47
  kwargs[:owner] = self
48
48
  value = enum_value_class.new(*args, **kwargs, &block)
49
- if own_values.key?(value.graphql_name)
50
- raise ArgumentError, "#{value.graphql_name} is already defined for #{self.graphql_name}, please remove one of the definitions."
49
+ key = value.graphql_name
50
+ prev_value = own_values[key]
51
+ case prev_value
52
+ when nil
53
+ own_values[key] = value
54
+ when GraphQL::Schema::EnumValue
55
+ own_values[key] = [prev_value, value]
56
+ when Array
57
+ prev_value << value
58
+ else
59
+ raise "Invariant: Unexpected enum value for #{key.inspect}: #{prev_value.inspect}"
51
60
  end
52
- own_values[value.graphql_name] = value
53
- nil
61
+ value
54
62
  end
55
63
 
56
- # @return [Hash<String => GraphQL::Schema::Enum::Value>] Possible values of this enum, keyed by name
57
- def values
58
- inherited_values = superclass <= GraphQL::Schema::Enum ? superclass.values : {}
59
- # Local values take precedence over inherited ones
60
- inherited_values.merge(own_values)
64
+ # @return [Array<GraphQL::Schema::EnumValue>] Possible values of this enum
65
+ def enum_values(context = GraphQL::Query::NullContext)
66
+ inherited_values = superclass.respond_to?(:enum_values) ? superclass.enum_values(context) : nil
67
+ visible_values = []
68
+ warden = Warden.from_context(context)
69
+ own_values.each do |key, values_entry|
70
+ if (v = Warden.visible_entry?(:visible_enum_value?, values_entry, context, warden))
71
+ visible_values << v
72
+ end
73
+ end
74
+
75
+ if inherited_values
76
+ # Local values take precedence over inherited ones
77
+ inherited_values.each do |i_val|
78
+ if !visible_values.any? { |v| v.graphql_name == i_val.graphql_name }
79
+ visible_values << i_val
80
+ end
81
+ end
82
+ end
83
+
84
+ visible_values
61
85
  end
62
86
 
87
+ # @return [Array<Schema::EnumValue>] An unfiltered list of all definitions
88
+ def all_enum_value_definitions
89
+ all_defns = if superclass.respond_to?(:all_enum_value_definitions)
90
+ superclass.all_enum_value_definitions
91
+ else
92
+ []
93
+ end
94
+
95
+ @own_values && @own_values.each do |_key, value|
96
+ if value.is_a?(Array)
97
+ all_defns.concat(value)
98
+ else
99
+ all_defns << value
100
+ end
101
+ end
102
+
103
+ all_defns
104
+ end
105
+
106
+ # @return [Hash<String => GraphQL::Schema::EnumValue>] Possible values of this enum, keyed by name.
107
+ def values(context = GraphQL::Query::NullContext)
108
+ enum_values(context).each_with_object({}) { |val, obj| obj[val.graphql_name] = val }
109
+ end
110
+
111
+ prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
112
+
63
113
  # @return [GraphQL::EnumType]
64
114
  def to_graphql
65
115
  enum_type = GraphQL::EnumType.new
@@ -68,7 +118,7 @@ module GraphQL
68
118
  enum_type.introspection = introspection
69
119
  enum_type.ast_node = ast_node
70
120
  values.each do |name, val|
71
- enum_type.add_value(val.to_graphql)
121
+ enum_type.add_value(val.deprecated_to_graphql)
72
122
  end
73
123
  enum_type.metadata[:type_class] = self
74
124
  enum_type
@@ -73,6 +73,8 @@ module GraphQL
73
73
  @value
74
74
  end
75
75
 
76
+ prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
77
+
76
78
  # @return [GraphQL::EnumType::EnumValue] A runtime-ready object derived from this object
77
79
  def to_graphql
78
80
  enum_value = GraphQL::EnumType::EnumValue.new
@@ -85,6 +87,10 @@ module GraphQL
85
87
  enum_value
86
88
  end
87
89
 
90
+ def inspect
91
+ "#<#{self.class} #{path} @value=#{@value.inspect}#{description ? " @description=#{description.inspect}" : ""}>"
92
+ end
93
+
88
94
  def visible?(_ctx); true; end
89
95
  def accessible?(_ctx); true; end
90
96
  def authorized?(_ctx); true; end
@@ -42,7 +42,7 @@ module GraphQL
42
42
  value.after_value ||= original_arguments[:after]
43
43
  value.last_value ||= original_arguments[:last]
44
44
  value.before_value ||= original_arguments[:before]
45
- value.arguments ||= original_arguments
45
+ value.arguments ||= original_arguments # rubocop:disable Development/ContextIsPassedCop -- unrelated .arguments method
46
46
  value.field ||= field
47
47
  if field.has_max_page_size? && !value.has_max_page_size_override?
48
48
  value.max_page_size = field.max_page_size
@@ -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
@@ -61,7 +57,7 @@ module GraphQL
61
57
  end
62
58
 
63
59
  def inspect
64
- "#<#{self.class} #{path}#{arguments.any? ? "(...)" : ""}: #{type.to_type_signature}>"
60
+ "#<#{self.class} #{path}#{all_argument_definitions.any? ? "(...)" : ""}: #{type.to_type_signature}>"
65
61
  end
66
62
 
67
63
  alias :mutation :resolver
@@ -212,7 +208,7 @@ module GraphQL
212
208
  # @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
213
209
  # @param validates [Array<Hash>] Configurations for validating this field
214
210
  # @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: nil, 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)
211
+ 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
212
  if name.nil?
217
213
  raise ArgumentError, "missing first `name` argument or keyword `name:`"
218
214
  end
@@ -220,9 +216,6 @@ module GraphQL
220
216
  if type.nil?
221
217
  raise ArgumentError, "missing second `type` argument or keyword `type:`"
222
218
  end
223
- if null.nil?
224
- raise ArgumentError, "missing keyword argument null:"
225
- end
226
219
  end
227
220
  if (field || function || resolve) && extras.any?
228
221
  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 +274,15 @@ module GraphQL
281
274
  @legacy_edge_class = legacy_edge_class
282
275
 
283
276
  arguments.each do |name, arg|
284
- if arg.is_a?(Hash)
277
+ case arg
278
+ when Hash
285
279
  argument(name: name, **arg)
286
- else
280
+ when GraphQL::Schema::Argument
287
281
  add_argument(arg)
282
+ when Array
283
+ arg.each { |a| add_argument(a) }
284
+ else
285
+ raise ArgumentError, "Unexpected argument config (#{arg.class}): #{arg.inspect}"
288
286
  end
289
287
  end
290
288
 
@@ -292,6 +290,7 @@ module GraphQL
292
290
  @subscription_scope = subscription_scope
293
291
 
294
292
  @extensions = EMPTY_ARRAY
293
+ @call_after_define = false
295
294
  # This should run before connection extension,
296
295
  # but should it run after the definition block?
297
296
  if scoped?
@@ -324,6 +323,9 @@ module GraphQL
324
323
  instance_eval(&definition_block)
325
324
  end
326
325
  end
326
+
327
+ self.extensions.each(&:after_define_apply)
328
+ @call_after_define = true
327
329
  end
328
330
 
329
331
  # If true, subscription updates with this field can be shared between viewers
@@ -356,27 +358,20 @@ module GraphQL
356
358
  # @example adding an extension with options
357
359
  # extensions([MyExtensionClass, { AnotherExtensionClass => { filter: true } }])
358
360
  #
359
- # @param extensions [Array<Class, Hash<Class => Object>>] Add extensions to this field. For hash elements, only the first key/value is used.
361
+ # @param extensions [Array<Class, Hash<Class => Hash>>] Add extensions to this field. For hash elements, only the first key/value is used.
360
362
  # @return [Array<GraphQL::Schema::FieldExtension>] extensions to apply to this field
361
363
  def extensions(new_extensions = nil)
362
- if new_extensions.nil?
363
- # Read the value
364
- @extensions
365
- else
366
- if @extensions.frozen?
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)
364
+ if new_extensions
365
+ new_extensions.each do |extension_config|
366
+ if extension_config.is_a?(Hash)
367
+ extension_class, options = *extension_config.to_a[0]
368
+ self.extension(extension_class, options)
374
369
  else
375
- extension_class = extension
376
- @extensions << extension_class.new(field: self, options: nil)
370
+ self.extension(extension_config)
377
371
  end
378
372
  end
379
373
  end
374
+ @extensions
380
375
  end
381
376
 
382
377
  # Add `extension` to this field, initialized with `options` if provided.
@@ -387,10 +382,19 @@ module GraphQL
387
382
  # @example adding an extension with options
388
383
  # extension(MyExtensionClass, filter: true)
389
384
  #
390
- # @param extension [Class] subclass of {Schema::Fieldextension}
391
- # @param options [Object] if provided, given as `options:` when initializing `extension`.
392
- def extension(extension, options = nil)
393
- extensions([{extension => options}])
385
+ # @param extension_class [Class] subclass of {Schema::FieldExtension}
386
+ # @param options [Hash] if provided, given as `options:` when initializing `extension`.
387
+ # @return [void]
388
+ def extension(extension_class, options = nil)
389
+ extension_inst = extension_class.new(field: self, options: options)
390
+ if @extensions.frozen?
391
+ @extensions = @extensions.dup
392
+ end
393
+ if @call_after_define
394
+ extension_inst.after_define_apply
395
+ end
396
+ @extensions << extension_inst
397
+ nil
394
398
  end
395
399
 
396
400
  # Read extras (as symbols) from this field,
@@ -411,6 +415,62 @@ module GraphQL
411
415
  end
412
416
  end
413
417
 
418
+ def calculate_complexity(query:, nodes:, child_complexity:)
419
+ if respond_to?(:complexity_for)
420
+ lookahead = GraphQL::Execution::Lookahead.new(query: query, field: self, ast_nodes: nodes, owner_type: owner)
421
+ complexity_for(child_complexity: child_complexity, query: query, lookahead: lookahead)
422
+ elsif connection?
423
+ arguments = query.arguments_for(nodes.first, self)
424
+ max_possible_page_size = nil
425
+ if arguments[:first]
426
+ max_possible_page_size = arguments[:first]
427
+ end
428
+ if arguments[:last] && (max_possible_page_size.nil? || arguments[:last] > max_possible_page_size)
429
+ max_possible_page_size = arguments[:last]
430
+ end
431
+
432
+ if max_possible_page_size.nil?
433
+ max_possible_page_size = max_page_size || query.schema.default_max_page_size
434
+ end
435
+
436
+ if max_possible_page_size.nil?
437
+ raise GraphQL::Error, "Can't calculate complexity for #{path}, no `first:`, `last:`, `max_page_size` or `default_max_page_size`"
438
+ else
439
+ metadata_complexity = 0
440
+ lookahead = GraphQL::Execution::Lookahead.new(query: query, field: self, ast_nodes: nodes, owner_type: owner)
441
+
442
+ if (page_info_lookahead = lookahead.selection(:page_info)).selected?
443
+ metadata_complexity += 1 # pageInfo
444
+ metadata_complexity += page_info_lookahead.selections.size # subfields
445
+ end
446
+
447
+ if lookahead.selects?(:total) || lookahead.selects?(:total_count) || lookahead.selects?(:count)
448
+ metadata_complexity += 1
449
+ end
450
+
451
+ nodes_edges_complexity = 0
452
+ nodes_edges_complexity += 1 if lookahead.selects?(:edges)
453
+ nodes_edges_complexity += 1 if lookahead.selects?(:nodes)
454
+
455
+ # Possible bug: selections on `edges` and `nodes` are _both_ multiplied here. Should they be?
456
+ items_complexity = child_complexity - metadata_complexity - nodes_edges_complexity
457
+ # Add 1 for _this_ field
458
+ 1 + (max_possible_page_size * items_complexity) + metadata_complexity + nodes_edges_complexity
459
+ end
460
+ else
461
+ defined_complexity = complexity
462
+ case defined_complexity
463
+ when Proc
464
+ arguments = query.arguments_for(nodes.first, self)
465
+ defined_complexity.call(query.context, arguments.keyword_arguments, child_complexity)
466
+ when Numeric
467
+ defined_complexity + child_complexity
468
+ else
469
+ raise("Invalid complexity: #{defined_complexity.inspect} on #{path} (#{inspect})")
470
+ end
471
+ end
472
+ end
473
+
414
474
  def complexity(new_complexity = nil)
415
475
  case new_complexity
416
476
  when Proc
@@ -439,6 +499,8 @@ module GraphQL
439
499
  # @return [Integer, nil] Applied to connections if {#has_max_page_size?}
440
500
  attr_reader :max_page_size
441
501
 
502
+ prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
503
+
442
504
  # @return [GraphQL::Field]
443
505
  def to_graphql
444
506
  field_defn = if @field
@@ -493,9 +555,9 @@ module GraphQL
493
555
  field_defn.subscription_scope = @subscription_scope
494
556
  field_defn.ast_node = ast_node
495
557
 
496
- arguments.each do |name, defn|
497
- arg_graphql = defn.to_graphql
498
- field_defn.arguments[arg_graphql.name] = arg_graphql
558
+ all_argument_definitions.each do |defn|
559
+ arg_graphql = defn.deprecated_to_graphql
560
+ field_defn.arguments[arg_graphql.name] = arg_graphql # rubocop:disable Development/ContextIsPassedCop -- legacy-related
499
561
  end
500
562
 
501
563
  # Support a passed-in proc, one way or another
@@ -556,13 +618,39 @@ module GraphQL
556
618
 
557
619
  def authorized?(object, args, context)
558
620
  if @resolver_class
559
- # The resolver will check itself during `resolve()`
621
+ # The resolver _instance_ will check itself during `resolve()`
560
622
  @resolver_class.authorized?(object, context)
561
623
  else
624
+ if (arg_values = context[:current_arguments])
625
+ # ^^ that's provided by the interpreter at runtime, and includes info about whether the default value was used or not.
626
+ using_arg_values = true
627
+ arg_values = arg_values.argument_values
628
+ else
629
+ arg_values = args
630
+ using_arg_values = false
631
+ end
562
632
  # Faster than `.any?`
563
- arguments.each_value do |arg|
564
- if args.key?(arg.keyword) && !arg.authorized?(object, args[arg.keyword], context)
565
- return false
633
+ arguments(context).each_value do |arg|
634
+ arg_key = arg.keyword
635
+ if arg_values.key?(arg_key)
636
+ arg_value = arg_values[arg_key]
637
+ if using_arg_values
638
+ if arg_value.default_used?
639
+ # pass -- no auth required for default used
640
+ next
641
+ else
642
+ application_arg_value = arg_value.value
643
+ if application_arg_value.is_a?(GraphQL::Execution::Interpreter::Arguments)
644
+ application_arg_value.keyword_arguments
645
+ end
646
+ end
647
+ else
648
+ application_arg_value = arg_value
649
+ end
650
+
651
+ if !arg.authorized?(object, application_arg_value, context)
652
+ return false
653
+ end
566
654
  end
567
655
  end
568
656
  true
@@ -659,7 +747,7 @@ module GraphQL
659
747
  ruby_kwargs = graphql_args.to_kwargs
660
748
  maybe_lazies = []
661
749
  # Apply any `prepare` methods. Not great code organization, can this go somewhere better?
662
- arguments.each do |name, arg_defn|
750
+ arguments(field_ctx).each do |name, arg_defn|
663
751
  ruby_kwargs_key = arg_defn.keyword
664
752
 
665
753
  if ruby_kwargs.key?(ruby_kwargs_key)
@@ -15,15 +15,40 @@ 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 is frozen to avoid improper use of state during execution.
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
30
+ end
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
27
52
  end
28
53
 
29
54
  # Called when this extension is attached to a field.
@@ -32,6 +57,31 @@ module GraphQL
32
57
  def apply
33
58
  end
34
59
 
60
+ # Called after the field's definition block has been executed.
61
+ # (Any arguments from the block are present on `field`)
62
+ # @return [void]
63
+ def after_define
64
+ end
65
+
66
+ # @api private
67
+ def after_define_apply
68
+ after_define
69
+ if (configs = self.class.default_argument_configurations)
70
+ existing_keywords = field.all_argument_definitions.map(&:keyword)
71
+ existing_keywords.uniq!
72
+ @added_default_arguments = []
73
+ configs.each do |config|
74
+ argument_args, argument_kwargs = config
75
+ arg_name = argument_args[0]
76
+ if !existing_keywords.include?(arg_name)
77
+ @added_default_arguments << arg_name
78
+ field.argument(*argument_args, **argument_kwargs)
79
+ end
80
+ end
81
+ end
82
+ freeze
83
+ end
84
+
35
85
  # Called before resolving {#field}. It should either:
36
86
  #
37
87
  # - `yield` values to continue execution; OR
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module GraphQL
2
3
  class Schema
3
4
  module FindInheritedValue
@@ -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.arguments[argument_name]
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.arguments[argument_name]
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.arguments[field_name]
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.values[value_name]
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)
@@ -129,8 +129,11 @@ module GraphQL
129
129
  self[#{method_name.inspect}]
130
130
  end
131
131
  RUBY
132
+ argument_defn
132
133
  end
133
134
 
135
+ prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
136
+
134
137
  def to_graphql
135
138
  type_defn = GraphQL::InputObjectType.new
136
139
  type_defn.name = graphql_name
@@ -138,8 +141,8 @@ module GraphQL
138
141
  type_defn.metadata[:type_class] = self
139
142
  type_defn.mutation = mutation
140
143
  type_defn.ast_node = ast_node
141
- arguments.each do |name, arg|
142
- type_defn.arguments[arg.graphql_definition.name] = arg.graphql_definition
144
+ all_argument_definitions.each do |arg|
145
+ 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
146
  end
144
147
  # Make a reference to a classic-style Arguments class
145
148
  self.arguments_class = GraphQL::Query::Arguments.construct_arguments_class(type_defn)
@@ -172,7 +175,7 @@ module GraphQL
172
175
  end
173
176
 
174
177
  # Inject missing required arguments
175
- missing_required_inputs = self.arguments.reduce({}) do |m, (argument_name, argument)|
178
+ missing_required_inputs = self.arguments(ctx).reduce({}) do |m, (argument_name, argument)|
176
179
  if !input.key?(argument_name) && argument.type.non_null? && warden.get_argument(self, argument_name)
177
180
  m[argument_name] = nil
178
181
  end
@@ -223,7 +226,7 @@ module GraphQL
223
226
 
224
227
  result = {}
225
228
 
226
- arguments.each do |input_key, input_field_defn|
229
+ arguments(ctx).each do |input_key, input_field_defn|
227
230
  input_value = value[input_key]
228
231
  if value.key?(input_key)
229
232
  result[input_key] = if input_value.nil?