graphql 1.13.0 → 1.13.24

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 (141) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/core.rb +3 -8
  3. data/lib/generators/graphql/enum_generator.rb +4 -10
  4. data/lib/generators/graphql/field_extractor.rb +31 -0
  5. data/lib/generators/graphql/input_generator.rb +50 -0
  6. data/lib/generators/graphql/install/mutation_root_generator.rb +34 -0
  7. data/lib/generators/graphql/install_generator.rb +10 -3
  8. data/lib/generators/graphql/interface_generator.rb +7 -7
  9. data/lib/generators/graphql/mutation_create_generator.rb +22 -0
  10. data/lib/generators/graphql/mutation_delete_generator.rb +22 -0
  11. data/lib/generators/graphql/mutation_generator.rb +5 -30
  12. data/lib/generators/graphql/mutation_update_generator.rb +22 -0
  13. data/lib/generators/graphql/object_generator.rb +8 -37
  14. data/lib/generators/graphql/orm_mutations_base.rb +40 -0
  15. data/lib/generators/graphql/scalar_generator.rb +4 -2
  16. data/lib/generators/graphql/templates/enum.erb +5 -1
  17. data/lib/generators/graphql/templates/input.erb +9 -0
  18. data/lib/generators/graphql/templates/interface.erb +4 -2
  19. data/lib/generators/graphql/templates/mutation.erb +1 -1
  20. data/lib/generators/graphql/templates/mutation_create.erb +20 -0
  21. data/lib/generators/graphql/templates/mutation_delete.erb +20 -0
  22. data/lib/generators/graphql/templates/mutation_update.erb +21 -0
  23. data/lib/generators/graphql/templates/object.erb +4 -2
  24. data/lib/generators/graphql/templates/scalar.erb +3 -1
  25. data/lib/generators/graphql/templates/union.erb +4 -2
  26. data/lib/generators/graphql/type_generator.rb +46 -9
  27. data/lib/generators/graphql/union_generator.rb +5 -5
  28. data/lib/graphql/analysis/ast/field_usage.rb +6 -2
  29. data/lib/graphql/analysis/ast/visitor.rb +2 -1
  30. data/lib/graphql/argument.rb +1 -1
  31. data/lib/graphql/base_type.rb +5 -3
  32. data/lib/graphql/boolean_type.rb +1 -1
  33. data/lib/graphql/dataloader/source.rb +2 -2
  34. data/lib/graphql/date_encoding_error.rb +16 -0
  35. data/lib/graphql/define/instance_definable.rb +15 -0
  36. data/lib/graphql/directive/deprecated_directive.rb +1 -1
  37. data/lib/graphql/directive/include_directive.rb +1 -1
  38. data/lib/graphql/directive/skip_directive.rb +1 -1
  39. data/lib/graphql/directive.rb +1 -1
  40. data/lib/graphql/enum_type.rb +2 -2
  41. data/lib/graphql/execution/interpreter/arguments_cache.rb +4 -2
  42. data/lib/graphql/execution/interpreter/runtime.rb +48 -28
  43. data/lib/graphql/execution/multiplex.rb +3 -0
  44. data/lib/graphql/field.rb +1 -1
  45. data/lib/graphql/float_type.rb +1 -1
  46. data/lib/graphql/id_type.rb +1 -1
  47. data/lib/graphql/input_object_type.rb +1 -1
  48. data/lib/graphql/int_type.rb +1 -1
  49. data/lib/graphql/interface_type.rb +1 -1
  50. data/lib/graphql/introspection/directive_location_enum.rb +2 -2
  51. data/lib/graphql/introspection/directive_type.rb +4 -2
  52. data/lib/graphql/introspection/field_type.rb +1 -1
  53. data/lib/graphql/introspection/schema_type.rb +7 -2
  54. data/lib/graphql/introspection/type_type.rb +14 -8
  55. data/lib/graphql/introspection.rb +4 -1
  56. data/lib/graphql/language/block_string.rb +2 -2
  57. data/lib/graphql/language/document_from_schema_definition.rb +8 -3
  58. data/lib/graphql/language/lexer.rb +50 -25
  59. data/lib/graphql/language/lexer.rl +2 -0
  60. data/lib/graphql/language/nodes.rb +15 -3
  61. data/lib/graphql/language/parser.rb +829 -816
  62. data/lib/graphql/language/parser.y +8 -2
  63. data/lib/graphql/language/printer.rb +4 -0
  64. data/lib/graphql/object_type.rb +2 -2
  65. data/lib/graphql/pagination/active_record_relation_connection.rb +43 -6
  66. data/lib/graphql/pagination/relation_connection.rb +59 -29
  67. data/lib/graphql/query/context.rb +10 -0
  68. data/lib/graphql/query/input_validation_result.rb +9 -0
  69. data/lib/graphql/query/validation_pipeline.rb +2 -3
  70. data/lib/graphql/query/variable_validation_error.rb +2 -2
  71. data/lib/graphql/query/variables.rb +30 -3
  72. data/lib/graphql/query.rb +0 -1
  73. data/lib/graphql/relay/connection_type.rb +15 -2
  74. data/lib/graphql/relay/global_id_resolve.rb +1 -2
  75. data/lib/graphql/relay/mutation.rb +1 -1
  76. data/lib/graphql/relay/page_info.rb +1 -1
  77. data/lib/graphql/relay/range_add.rb +4 -0
  78. data/lib/graphql/rubocop/graphql/default_required_true.rb +4 -4
  79. data/lib/graphql/scalar_type.rb +1 -1
  80. data/lib/graphql/schema/argument.rb +29 -16
  81. data/lib/graphql/schema/build_from_definition.rb +9 -7
  82. data/lib/graphql/schema/directive.rb +25 -2
  83. data/lib/graphql/schema/enum.rb +4 -3
  84. data/lib/graphql/schema/enum_value.rb +3 -1
  85. data/lib/graphql/schema/field.rb +196 -92
  86. data/lib/graphql/schema/field_extension.rb +89 -2
  87. data/lib/graphql/schema/input_object.rb +27 -9
  88. data/lib/graphql/schema/interface.rb +8 -2
  89. data/lib/graphql/schema/introspection_system.rb +1 -1
  90. data/lib/graphql/schema/list.rb +21 -4
  91. data/lib/graphql/schema/loader.rb +3 -0
  92. data/lib/graphql/schema/member/accepts_definition.rb +7 -2
  93. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -1
  94. data/lib/graphql/schema/member/cached_graphql_definition.rb +29 -2
  95. data/lib/graphql/schema/member/has_arguments.rb +2 -2
  96. data/lib/graphql/schema/member/has_fields.rb +1 -1
  97. data/lib/graphql/schema/member/has_interfaces.rb +11 -1
  98. data/lib/graphql/schema/member/validates_input.rb +2 -2
  99. data/lib/graphql/schema/non_null.rb +9 -3
  100. data/lib/graphql/schema/object.rb +3 -1
  101. data/lib/graphql/schema/relay_classic_mutation.rb +8 -0
  102. data/lib/graphql/schema/resolver.rb +19 -13
  103. data/lib/graphql/schema/scalar.rb +15 -1
  104. data/lib/graphql/schema/traversal.rb +1 -1
  105. data/lib/graphql/schema/union.rb +2 -0
  106. data/lib/graphql/schema/validator/required_validator.rb +29 -15
  107. data/lib/graphql/schema/validator.rb +4 -7
  108. data/lib/graphql/schema/warden.rb +11 -2
  109. data/lib/graphql/schema.rb +34 -10
  110. data/lib/graphql/static_validation/all_rules.rb +1 -0
  111. data/lib/graphql/static_validation/base_visitor.rb +1 -1
  112. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
  113. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +1 -1
  114. data/lib/graphql/static_validation/rules/fields_will_merge.rb +14 -7
  115. data/lib/graphql/static_validation/rules/query_root_exists.rb +17 -0
  116. data/lib/graphql/static_validation/rules/query_root_exists_error.rb +26 -0
  117. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +3 -1
  118. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
  119. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +6 -0
  120. data/lib/graphql/static_validation/validation_context.rb +4 -0
  121. data/lib/graphql/string_type.rb +1 -1
  122. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +2 -0
  123. data/lib/graphql/subscriptions/serialize.rb +22 -2
  124. data/lib/graphql/tracing/active_support_notifications_tracing.rb +6 -20
  125. data/lib/graphql/tracing/data_dog_tracing.rb +24 -15
  126. data/lib/graphql/tracing/notifications_tracing.rb +59 -0
  127. data/lib/graphql/tracing/platform_tracing.rb +20 -10
  128. data/lib/graphql/types/iso_8601_date.rb +13 -5
  129. data/lib/graphql/types/iso_8601_date_time.rb +8 -1
  130. data/lib/graphql/types/relay/connection_behaviors.rb +28 -10
  131. data/lib/graphql/types/relay/default_relay.rb +5 -1
  132. data/lib/graphql/types/relay/edge_behaviors.rb +13 -2
  133. data/lib/graphql/types/relay/node_field.rb +2 -3
  134. data/lib/graphql/types/relay/nodes_field.rb +19 -3
  135. data/lib/graphql/types/string.rb +1 -1
  136. data/lib/graphql/union_type.rb +1 -1
  137. data/lib/graphql/version.rb +1 -1
  138. data/lib/graphql.rb +14 -1
  139. metadata +56 -30
  140. /data/lib/generators/graphql/{templates → install/templates}/base_mutation.erb +0 -0
  141. /data/lib/generators/graphql/{templates → install/templates}/mutation_type.erb +0 -0
@@ -377,6 +377,7 @@ module GraphQL
377
377
  Class.new(GraphQL::Schema::Directive) do
378
378
  graphql_name(directive_definition.name)
379
379
  description(directive_definition.description)
380
+ repeatable(directive_definition.repeatable)
380
381
  locations(*directive_definition.locations.map { |location| location.name.to_sym })
381
382
  ast_node(directive_definition)
382
383
  builder.build_arguments(self, directive_definition.arguments, type_resolver)
@@ -426,17 +427,18 @@ module GraphQL
426
427
 
427
428
  # Don't do this for interfaces
428
429
  if default_resolve
429
- owner.class_eval <<-RUBY, __FILE__, __LINE__
430
- # frozen_string_literal: true
431
- def #{resolve_method_name}(**args)
432
- field_instance = self.class.get_field("#{field_definition.name}")
433
- context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context)
434
- end
435
- RUBY
430
+ define_field_resolve_method(owner, resolve_method_name, field_definition.name)
436
431
  end
437
432
  end
438
433
  end
439
434
 
435
+ def define_field_resolve_method(owner, method_name, field_name)
436
+ owner.define_method(method_name) { |**args|
437
+ field_instance = self.class.get_field(field_name)
438
+ context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context)
439
+ }
440
+ end
441
+
440
442
  def build_resolve_type(lookup_hash, directives, missing_type_handler)
441
443
  resolve_type_proc = nil
442
444
  resolve_type_proc = ->(ast_node) {
@@ -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
@@ -20,7 +22,7 @@ module GraphQL
20
22
  # but downcase the first letter.
21
23
  def default_graphql_name
22
24
  @default_graphql_name ||= begin
23
- camelized_name = super
25
+ camelized_name = super.dup
24
26
  camelized_name[0] = camelized_name[0].downcase
25
27
  camelized_name
26
28
  end
@@ -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
@@ -62,7 +66,7 @@ module GraphQL
62
66
  defn.ast_node = ast_node
63
67
  defn.metadata[:type_class] = self
64
68
  all_argument_definitions.each do |arg_defn|
65
- arg_graphql = arg_defn.to_graphql
69
+ arg_graphql = arg_defn.to_graphql(silence_deprecation_warning: true)
66
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
@@ -86,6 +90,11 @@ module GraphQL
86
90
  yield
87
91
  end
88
92
 
93
+ # Continuing is passed as a block, yield to continue.
94
+ def resolve_each(object, arguments, context)
95
+ yield
96
+ end
97
+
89
98
  def on_field?
90
99
  locations.include?(FIELD)
91
100
  end
@@ -97,6 +106,14 @@ module GraphQL
97
106
  def on_operation?
98
107
  locations.include?(QUERY) && locations.include?(MUTATION) && locations.include?(SUBSCRIPTION)
99
108
  end
109
+
110
+ def repeatable?
111
+ !!@repeatable
112
+ end
113
+
114
+ def repeatable(new_value)
115
+ @repeatable = new_value
116
+ end
100
117
  end
101
118
 
102
119
  # @return [GraphQL::Schema::Field, GraphQL::Schema::Argument, Class, Module]
@@ -116,6 +133,10 @@ module GraphQL
116
133
  @arguments = self.class.coerce_arguments(nil, arguments, Query::NullContext)
117
134
  end
118
135
 
136
+ def graphql_name
137
+ self.class.graphql_name
138
+ end
139
+
119
140
  LOCATIONS = [
120
141
  QUERY = :QUERY,
121
142
  MUTATION = :MUTATION,
@@ -135,6 +156,7 @@ module GraphQL
135
156
  ENUM_VALUE = :ENUM_VALUE,
136
157
  INPUT_OBJECT = :INPUT_OBJECT,
137
158
  INPUT_FIELD_DEFINITION = :INPUT_FIELD_DEFINITION,
159
+ VARIABLE_DEFINITION = :VARIABLE_DEFINITION,
138
160
  ]
139
161
 
140
162
  DEFAULT_DEPRECATION_REASON = 'No longer supported'
@@ -157,6 +179,7 @@ module GraphQL
157
179
  ENUM_VALUE: 'Location adjacent to an enum value definition.',
158
180
  INPUT_OBJECT: 'Location adjacent to an input object type definition.',
159
181
  INPUT_FIELD_DEFINITION: 'Location adjacent to an input object field definition.',
182
+ VARIABLE_DEFINITION: 'Location adjacent to a variable definition.',
160
183
  }
161
184
 
162
185
  private
@@ -108,6 +108,8 @@ module GraphQL
108
108
  enum_values(context).each_with_object({}) { |val, obj| obj[val.graphql_name] = val }
109
109
  end
110
110
 
111
+ prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
112
+
111
113
  # @return [GraphQL::EnumType]
112
114
  def to_graphql
113
115
  enum_type = GraphQL::EnumType.new
@@ -116,7 +118,7 @@ module GraphQL
116
118
  enum_type.introspection = introspection
117
119
  enum_type.ast_node = ast_node
118
120
  values.each do |name, val|
119
- enum_type.add_value(val.to_graphql)
121
+ enum_type.add_value(val.deprecated_to_graphql)
120
122
  end
121
123
  enum_type.metadata[:type_class] = self
122
124
  enum_type
@@ -137,9 +139,8 @@ module GraphQL
137
139
  GraphQL::TypeKinds::ENUM
138
140
  end
139
141
 
140
- def validate_non_null_input(value_name, ctx)
142
+ def validate_non_null_input(value_name, ctx, max_errors: nil)
141
143
  result = GraphQL::Query::InputValidationResult.new
142
-
143
144
  allowed_values = ctx.warden.enum_values(self)
144
145
  matching_value = allowed_values.find { |v| v.graphql_name == value_name }
145
146
 
@@ -55,7 +55,7 @@ module GraphQL
55
55
  end
56
56
 
57
57
  if block_given?
58
- instance_eval(&block)
58
+ instance_exec(self, &block)
59
59
  end
60
60
  end
61
61
 
@@ -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
@@ -16,6 +16,8 @@ module GraphQL
16
16
  include GraphQL::Schema::Member::HasDirectives
17
17
  include GraphQL::Schema::Member::HasDeprecationReason
18
18
 
19
+ class FieldImplementationFailed < GraphQL::Error; end
20
+
19
21
  # @return [String] the GraphQL name for this field, camelized unless `camelize: false` is provided
20
22
  attr_reader :name
21
23
  alias :graphql_name :name
@@ -36,7 +38,9 @@ module GraphQL
36
38
 
37
39
  # @return [Class] The GraphQL type this field belongs to. (For fields defined on mutations, it's the payload type)
38
40
  def owner_type
39
- @owner_type ||= if owner < GraphQL::Schema::Mutation
41
+ @owner_type ||= if owner.nil?
42
+ raise GraphQL::InvariantError, "Field #{original_name.inspect} (graphql name: #{graphql_name.inspect}) has no owner, but all fields should have an owner. How did this happen?!"
43
+ elsif owner < GraphQL::Schema::Mutation
40
44
  owner.payload_type
41
45
  else
42
46
  owner
@@ -172,6 +176,8 @@ module GraphQL
172
176
 
173
177
  # @return Boolean
174
178
  attr_reader :relay_node_field
179
+ # @return Boolean
180
+ attr_reader :relay_nodes_field
175
181
 
176
182
  # @return [Boolean] Should we warn if this field's name conflicts with a built-in method?
177
183
  def method_conflict_warning?
@@ -186,6 +192,7 @@ module GraphQL
186
192
  # @param deprecation_reason [String] If present, the field is marked "deprecated" with this message
187
193
  # @param method [Symbol] The method to call on the underlying object to resolve this field (defaults to `name`)
188
194
  # @param hash_key [String, Symbol] The hash key to lookup on the underlying object (if its a Hash) to resolve this field (defaults to `name` or `name.to_s`)
195
+ # @param dig [Array<String, Symbol>] The nested hash keys to lookup on the underlying hash to resolve this field using dig
189
196
  # @param resolver_method [Symbol] The method on the type to call to resolve this field (defaults to `name`)
190
197
  # @param connection [Boolean] `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name
191
198
  # @param connection_extension [Class] The extension to add, to implement connections. If `nil`, no extension is added.
@@ -208,7 +215,7 @@ module GraphQL
208
215
  # @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
209
216
  # @param validates [Array<Hash>] Configurations for validating this field
210
217
  # @param legacy_edge_class [Class, nil] (DEPRECATED) If present, pass this along to the legacy field definition
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)
218
+ def initialize(type: nil, name: nil, owner: nil, null: true, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, dig: 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)
212
219
  if name.nil?
213
220
  raise ArgumentError, "missing first `name` argument or keyword `name:`"
214
221
  end
@@ -222,8 +229,10 @@ module GraphQL
222
229
  end
223
230
  @original_name = name
224
231
  name_s = -name.to_s
232
+
225
233
  @underscored_name = -Member::BuildType.underscore(name_s)
226
234
  @name = -(camelize ? Member::BuildType.camelize(name_s) : name_s)
235
+ NameValidator.validate!(@name)
227
236
  @description = description
228
237
  if field.is_a?(GraphQL::Schema::Field)
229
238
  raise ArgumentError, "Instead of passing a field as `field:`, use `add_field(field)` to add an already-defined field."
@@ -234,8 +243,8 @@ module GraphQL
234
243
  @resolve = resolve
235
244
  self.deprecation_reason = deprecation_reason
236
245
 
237
- if method && hash_key
238
- raise ArgumentError, "Provide `method:` _or_ `hash_key:`, not both. (called with: `method: #{method.inspect}, hash_key: #{hash_key.inspect}`)"
246
+ if method && hash_key && dig
247
+ raise ArgumentError, "Provide `method:`, `hash_key:` _or_ `dig:`, not multiple. (called with: `method: #{method.inspect}, hash_key: #{hash_key.inspect}, dig: #{dig.inspect}`)"
239
248
  end
240
249
 
241
250
  if resolver_method
@@ -243,13 +252,18 @@ module GraphQL
243
252
  raise ArgumentError, "Provide `method:` _or_ `resolver_method:`, not both. (called with: `method: #{method.inspect}, resolver_method: #{resolver_method.inspect}`)"
244
253
  end
245
254
 
246
- if hash_key
247
- raise ArgumentError, "Provide `hash_key:` _or_ `resolver_method:`, not both. (called with: `hash_key: #{hash_key.inspect}, resolver_method: #{resolver_method.inspect}`)"
255
+ if hash_key || dig
256
+ raise ArgumentError, "Provide `hash_key:`, `dig:`, _or_ `resolver_method:`, not multiple. (called with: `hash_key: #{hash_key.inspect}, dig: #{dig.inspect}, resolver_method: #{resolver_method.inspect}`)"
248
257
  end
249
258
  end
250
259
 
251
260
  # TODO: I think non-string/symbol hash keys are wrongly normalized (eg `1` will not work)
252
261
  method_name = method || hash_key || name_s
262
+ @dig_keys = dig
263
+ if hash_key
264
+ @hash_key = hash_key
265
+ end
266
+
253
267
  resolver_method ||= name_s.to_sym
254
268
 
255
269
  @method_str = -method_name.to_s
@@ -290,6 +304,7 @@ module GraphQL
290
304
  @subscription_scope = subscription_scope
291
305
 
292
306
  @extensions = EMPTY_ARRAY
307
+ @call_after_define = false
293
308
  # This should run before connection extension,
294
309
  # but should it run after the definition block?
295
310
  if scoped?
@@ -322,6 +337,9 @@ module GraphQL
322
337
  instance_eval(&definition_block)
323
338
  end
324
339
  end
340
+
341
+ self.extensions.each(&:after_define_apply)
342
+ @call_after_define = true
325
343
  end
326
344
 
327
345
  # If true, subscription updates with this field can be shared between viewers
@@ -354,27 +372,20 @@ module GraphQL
354
372
  # @example adding an extension with options
355
373
  # extensions([MyExtensionClass, { AnotherExtensionClass => { filter: true } }])
356
374
  #
357
- # @param extensions [Array<Class, Hash<Class => Object>>] Add extensions to this field. For hash elements, only the first key/value is used.
375
+ # @param extensions [Array<Class, Hash<Class => Hash>>] Add extensions to this field. For hash elements, only the first key/value is used.
358
376
  # @return [Array<GraphQL::Schema::FieldExtension>] extensions to apply to this field
359
377
  def extensions(new_extensions = nil)
360
- if new_extensions.nil?
361
- # Read the value
362
- @extensions
363
- else
364
- if @extensions.frozen?
365
- @extensions = @extensions.dup
366
- end
367
- new_extensions.each do |extension|
368
- if extension.is_a?(Hash)
369
- extension = extension.to_a[0]
370
- extension_class, options = *extension
371
- @extensions << extension_class.new(field: self, options: options)
378
+ if new_extensions
379
+ new_extensions.each do |extension_config|
380
+ if extension_config.is_a?(Hash)
381
+ extension_class, options = *extension_config.to_a[0]
382
+ self.extension(extension_class, options)
372
383
  else
373
- extension_class = extension
374
- @extensions << extension_class.new(field: self, options: nil)
384
+ self.extension(extension_config)
375
385
  end
376
386
  end
377
387
  end
388
+ @extensions
378
389
  end
379
390
 
380
391
  # Add `extension` to this field, initialized with `options` if provided.
@@ -385,10 +396,19 @@ module GraphQL
385
396
  # @example adding an extension with options
386
397
  # extension(MyExtensionClass, filter: true)
387
398
  #
388
- # @param extension [Class] subclass of {Schema::Fieldextension}
389
- # @param options [Object] if provided, given as `options:` when initializing `extension`.
390
- def extension(extension, options = nil)
391
- extensions([{extension => options}])
399
+ # @param extension_class [Class] subclass of {Schema::FieldExtension}
400
+ # @param options [Hash] if provided, given as `options:` when initializing `extension`.
401
+ # @return [void]
402
+ def extension(extension_class, options = nil)
403
+ extension_inst = extension_class.new(field: self, options: options)
404
+ if @extensions.frozen?
405
+ @extensions = @extensions.dup
406
+ end
407
+ if @call_after_define
408
+ extension_inst.after_define_apply
409
+ end
410
+ @extensions << extension_inst
411
+ nil
392
412
  end
393
413
 
394
414
  # Read extras (as symbols) from this field,
@@ -416,11 +436,14 @@ module GraphQL
416
436
  elsif connection?
417
437
  arguments = query.arguments_for(nodes.first, self)
418
438
  max_possible_page_size = nil
419
- if arguments[:first]
420
- max_possible_page_size = arguments[:first]
421
- end
422
- if arguments[:last] && (max_possible_page_size.nil? || arguments[:last] > max_possible_page_size)
423
- max_possible_page_size = arguments[:last]
439
+ if arguments.respond_to?(:[]) # It might have been an error
440
+ if arguments[:first]
441
+ max_possible_page_size = arguments[:first]
442
+ end
443
+
444
+ if arguments[:last] && (max_possible_page_size.nil? || arguments[:last] > max_possible_page_size)
445
+ max_possible_page_size = arguments[:last]
446
+ end
424
447
  end
425
448
 
426
449
  if max_possible_page_size.nil?
@@ -441,17 +464,28 @@ module GraphQL
441
464
  if lookahead.selects?(:total) || lookahead.selects?(:total_count) || lookahead.selects?(:count)
442
465
  metadata_complexity += 1
443
466
  end
467
+
468
+ nodes_edges_complexity = 0
469
+ nodes_edges_complexity += 1 if lookahead.selects?(:edges)
470
+ nodes_edges_complexity += 1 if lookahead.selects?(:nodes)
471
+
444
472
  # Possible bug: selections on `edges` and `nodes` are _both_ multiplied here. Should they be?
445
- items_complexity = child_complexity - metadata_complexity
473
+ items_complexity = child_complexity - metadata_complexity - nodes_edges_complexity
446
474
  # Add 1 for _this_ field
447
- 1 + (max_possible_page_size * items_complexity) + metadata_complexity
475
+ 1 + (max_possible_page_size * items_complexity) + metadata_complexity + nodes_edges_complexity
448
476
  end
449
477
  else
450
478
  defined_complexity = complexity
451
479
  case defined_complexity
452
480
  when Proc
453
481
  arguments = query.arguments_for(nodes.first, self)
454
- defined_complexity.call(query.context, arguments.keyword_arguments, child_complexity)
482
+ if arguments.is_a?(GraphQL::ExecutionError)
483
+ return child_complexity
484
+ elsif arguments.respond_to?(:keyword_arguments)
485
+ arguments = arguments.keyword_arguments
486
+ end
487
+
488
+ defined_complexity.call(query.context, arguments, child_complexity)
455
489
  when Numeric
456
490
  defined_complexity + child_complexity
457
491
  else
@@ -488,6 +522,8 @@ module GraphQL
488
522
  # @return [Integer, nil] Applied to connections if {#has_max_page_size?}
489
523
  attr_reader :max_page_size
490
524
 
525
+ prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
526
+
491
527
  # @return [GraphQL::Field]
492
528
  def to_graphql
493
529
  field_defn = if @field
@@ -543,7 +579,7 @@ module GraphQL
543
579
  field_defn.ast_node = ast_node
544
580
 
545
581
  all_argument_definitions.each do |defn|
546
- arg_graphql = defn.to_graphql
582
+ arg_graphql = defn.deprecated_to_graphql
547
583
  field_defn.arguments[arg_graphql.name] = arg_graphql # rubocop:disable Development/ContextIsPassedCop -- legacy-related
548
584
  end
549
585
 
@@ -605,7 +641,7 @@ module GraphQL
605
641
 
606
642
  def authorized?(object, args, context)
607
643
  if @resolver_class
608
- # The resolver will check itself during `resolve()`
644
+ # The resolver _instance_ will check itself during `resolve()`
609
645
  @resolver_class.authorized?(object, context)
610
646
  else
611
647
  if (arg_values = context[:current_arguments])
@@ -616,27 +652,29 @@ module GraphQL
616
652
  arg_values = args
617
653
  using_arg_values = false
618
654
  end
619
- # Faster than `.any?`
620
- arguments(context).each_value do |arg|
621
- arg_key = arg.keyword
622
- if arg_values.key?(arg_key)
623
- arg_value = arg_values[arg_key]
624
- if using_arg_values
625
- if arg_value.default_used?
626
- # pass -- no auth required for default used
627
- next
628
- else
629
- application_arg_value = arg_value.value
630
- if application_arg_value.is_a?(GraphQL::Execution::Interpreter::Arguments)
631
- application_arg_value.keyword_arguments
655
+ if args.size > 0
656
+ args = context.warden.arguments(self)
657
+ args.each do |arg|
658
+ arg_key = arg.keyword
659
+ if arg_values.key?(arg_key)
660
+ arg_value = arg_values[arg_key]
661
+ if using_arg_values
662
+ if arg_value.default_used?
663
+ # pass -- no auth required for default used
664
+ next
665
+ else
666
+ application_arg_value = arg_value.value
667
+ if application_arg_value.is_a?(GraphQL::Execution::Interpreter::Arguments)
668
+ application_arg_value.keyword_arguments
669
+ end
632
670
  end
671
+ else
672
+ application_arg_value = arg_value
633
673
  end
634
- else
635
- application_arg_value = arg_value
636
- end
637
674
 
638
- if !arg.authorized?(object, application_arg_value, context)
639
- return false
675
+ if !arg.authorized?(object, application_arg_value, context)
676
+ return false
677
+ end
640
678
  end
641
679
  end
642
680
  end
@@ -779,51 +817,107 @@ module GraphQL
779
817
 
780
818
  def public_send_field(unextended_obj, unextended_ruby_kwargs, query_ctx)
781
819
  with_extensions(unextended_obj, unextended_ruby_kwargs, query_ctx) do |obj, ruby_kwargs|
782
- if @resolver_class
783
- if obj.is_a?(GraphQL::Schema::Object)
784
- obj = obj.object
785
- end
786
- obj = @resolver_class.new(object: obj, context: query_ctx, field: self)
787
- end
788
-
789
- # Find a way to resolve this field, checking:
790
- #
791
- # - A method on the type instance;
792
- # - Hash keys, if the wrapped object is a hash;
793
- # - A method on the wrapped object;
794
- # - Or, raise not implemented.
795
- #
796
- if obj.respond_to?(@resolver_method)
797
- # Call the method with kwargs, if there are any
798
- if ruby_kwargs.any?
799
- obj.public_send(@resolver_method, **ruby_kwargs)
800
- else
801
- obj.public_send(@resolver_method)
820
+ begin
821
+ method_receiver = nil
822
+ method_to_call = nil
823
+ if @resolver_class
824
+ if obj.is_a?(GraphQL::Schema::Object)
825
+ obj = obj.object
826
+ end
827
+ obj = @resolver_class.new(object: obj, context: query_ctx, field: self)
802
828
  end
803
- elsif obj.object.is_a?(Hash)
804
- inner_object = obj.object
805
- if inner_object.key?(@method_sym)
806
- inner_object[@method_sym]
829
+
830
+ # Find a way to resolve this field, checking:
831
+ #
832
+ # - A method on the type instance;
833
+ # - Hash keys, if the wrapped object is a hash or responds to `#[]`
834
+ # - A method on the wrapped object;
835
+ # - Or, raise not implemented.
836
+ #
837
+ if obj.respond_to?(@resolver_method)
838
+ method_to_call = @resolver_method
839
+ method_receiver = obj
840
+ # Call the method with kwargs, if there are any
841
+ if ruby_kwargs.any?
842
+ obj.public_send(@resolver_method, **ruby_kwargs)
843
+ else
844
+ obj.public_send(@resolver_method)
845
+ end
846
+ elsif obj.object.is_a?(Hash)
847
+ inner_object = obj.object
848
+ if @dig_keys
849
+ inner_object.dig(*@dig_keys)
850
+ elsif inner_object.key?(@method_sym)
851
+ inner_object[@method_sym]
852
+ else
853
+ inner_object[@method_str]
854
+ end
855
+ elsif defined?(@hash_key) && obj.object.respond_to?(:[])
856
+ obj.object[@hash_key]
857
+ elsif obj.object.respond_to?(@method_sym)
858
+ method_to_call = @method_sym
859
+ method_receiver = obj.object
860
+ if ruby_kwargs.any?
861
+ obj.object.public_send(@method_sym, **ruby_kwargs)
862
+ else
863
+ obj.object.public_send(@method_sym)
864
+ end
807
865
  else
808
- inner_object[@method_str]
866
+ raise <<-ERR
867
+ Failed to implement #{@owner.graphql_name}.#{@name}, tried:
868
+
869
+ - `#{obj.class}##{@resolver_method}`, which did not exist
870
+ - `#{obj.object.class}##{@method_sym}`, which did not exist
871
+ - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
872
+
873
+ To implement this field, define one of the methods above (and check for typos)
874
+ ERR
809
875
  end
810
- elsif obj.object.respond_to?(@method_sym)
811
- if ruby_kwargs.any?
812
- obj.object.public_send(@method_sym, **ruby_kwargs)
876
+ rescue ArgumentError
877
+ assert_satisfactory_implementation(method_receiver, method_to_call, ruby_kwargs)
878
+ # if the line above doesn't raise, re-raise
879
+ raise
880
+ end
881
+ end
882
+ end
883
+
884
+ def assert_satisfactory_implementation(receiver, method_name, ruby_kwargs)
885
+ method_defn = receiver.method(method_name)
886
+ unsatisfied_ruby_kwargs = ruby_kwargs.dup
887
+ unsatisfied_method_params = []
888
+ encountered_keyrest = false
889
+ method_defn.parameters.each do |(param_type, param_name)|
890
+ case param_type
891
+ when :key
892
+ unsatisfied_ruby_kwargs.delete(param_name)
893
+ when :keyreq
894
+ if unsatisfied_ruby_kwargs.key?(param_name)
895
+ unsatisfied_ruby_kwargs.delete(param_name)
813
896
  else
814
- obj.object.public_send(@method_sym)
897
+ 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."
815
898
  end
816
- else
817
- raise <<-ERR
818
- Failed to implement #{@owner.graphql_name}.#{@name}, tried:
899
+ when :keyrest
900
+ encountered_keyrest = true
901
+ when :req
902
+ 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."
903
+ when :opt, :rest
904
+ # This is fine, although it will never be present
905
+ end
906
+ end
907
+
908
+ if encountered_keyrest
909
+ unsatisfied_ruby_kwargs.clear
910
+ end
819
911
 
820
- - `#{obj.class}##{@resolver_method}`, which did not exist
821
- - `#{obj.object.class}##{@method_sym}`, which did not exist
822
- - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
912
+ if unsatisfied_ruby_kwargs.any? || unsatisfied_method_params.any?
913
+ raise FieldImplementationFailed.new, <<-ERR
914
+ Failed to call #{method_name} on #{receiver.inspect} because the Ruby method params were incompatible with the GraphQL arguments:
823
915
 
824
- To implement this field, define one of the methods above (and check for typos)
825
- ERR
826
- end
916
+ #{ unsatisfied_ruby_kwargs
917
+ .map { |key, value| "- `#{key}: #{value}` was given by GraphQL but not defined in the Ruby method. Add `#{key}:` to the method parameters." }
918
+ .concat(unsatisfied_method_params)
919
+ .join("\n") }
920
+ ERR
827
921
  end
828
922
  end
829
923
 
@@ -837,8 +931,12 @@ module GraphQL
837
931
  # This is a hack to get the _last_ value for extended obj and args,
838
932
  # in case one of the extensions doesn't `yield`.
839
933
  # (There's another implementation that uses multiple-return, but I'm wary of the perf cost of the extra arrays)
840
- extended = { args: args, obj: obj, memos: nil }
934
+ extended = { args: args, obj: obj, memos: nil, added_extras: nil }
841
935
  value = run_extensions_before_resolve(obj, args, ctx, extended) do |obj, args|
936
+ if (added_extras = extended[:added_extras])
937
+ args = args.dup
938
+ added_extras.each { |e| args.delete(e) }
939
+ end
842
940
  yield(obj, args)
843
941
  end
844
942
 
@@ -867,6 +965,12 @@ module GraphQL
867
965
  memos = extended[:memos] ||= {}
868
966
  memos[idx] = memo
869
967
  end
968
+
969
+ if (extras = extension.added_extras)
970
+ ae = extended[:added_extras] ||= []
971
+ ae.concat(extras)
972
+ end
973
+
870
974
  extended[:obj] = extended_obj
871
975
  extended[:args] = extended_args
872
976
  run_extensions_before_resolve(extended_obj, extended_args, ctx, extended, idx: idx + 1) { |o, a| yield(o, a) }