graphql 1.12.24 → 1.13.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

Files changed (189) 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 -10
  27. data/lib/generators/graphql/union_generator.rb +5 -5
  28. data/lib/graphql/analysis/ast/field_usage.rb +2 -2
  29. data/lib/graphql/analysis/ast/query_complexity.rb +10 -14
  30. data/lib/graphql/analysis/ast/visitor.rb +5 -4
  31. data/lib/graphql/argument.rb +1 -1
  32. data/lib/graphql/backtrace/table.rb +1 -1
  33. data/lib/graphql/base_type.rb +5 -3
  34. data/lib/graphql/boolean_type.rb +1 -1
  35. data/lib/graphql/dataloader/source.rb +2 -2
  36. data/lib/graphql/dataloader.rb +55 -22
  37. data/lib/graphql/date_encoding_error.rb +16 -0
  38. data/lib/graphql/define/instance_definable.rb +15 -0
  39. data/lib/graphql/directive/deprecated_directive.rb +1 -1
  40. data/lib/graphql/directive/include_directive.rb +1 -1
  41. data/lib/graphql/directive/skip_directive.rb +1 -1
  42. data/lib/graphql/directive.rb +1 -5
  43. data/lib/graphql/enum_type.rb +7 -3
  44. data/lib/graphql/execution/errors.rb +1 -0
  45. data/lib/graphql/execution/interpreter/arguments.rb +1 -1
  46. data/lib/graphql/execution/interpreter/arguments_cache.rb +6 -4
  47. data/lib/graphql/execution/interpreter/runtime.rb +66 -38
  48. data/lib/graphql/execution/lookahead.rb +2 -2
  49. data/lib/graphql/execution/multiplex.rb +4 -1
  50. data/lib/graphql/field.rb +1 -1
  51. data/lib/graphql/float_type.rb +1 -1
  52. data/lib/graphql/id_type.rb +1 -1
  53. data/lib/graphql/input_object_type.rb +1 -1
  54. data/lib/graphql/int_type.rb +1 -1
  55. data/lib/graphql/interface_type.rb +1 -1
  56. data/lib/graphql/introspection/directive_location_enum.rb +2 -2
  57. data/lib/graphql/introspection/directive_type.rb +5 -3
  58. data/lib/graphql/introspection/entry_points.rb +2 -2
  59. data/lib/graphql/introspection/enum_value_type.rb +2 -2
  60. data/lib/graphql/introspection/field_type.rb +3 -3
  61. data/lib/graphql/introspection/input_value_type.rb +4 -4
  62. data/lib/graphql/introspection/schema_type.rb +9 -4
  63. data/lib/graphql/introspection/type_type.rb +18 -12
  64. data/lib/graphql/introspection.rb +4 -1
  65. data/lib/graphql/language/block_string.rb +2 -6
  66. data/lib/graphql/language/document_from_schema_definition.rb +11 -4
  67. data/lib/graphql/language/lexer.rb +50 -28
  68. data/lib/graphql/language/lexer.rl +2 -4
  69. data/lib/graphql/language/nodes.rb +4 -3
  70. data/lib/graphql/language/parser.rb +841 -820
  71. data/lib/graphql/language/parser.y +13 -6
  72. data/lib/graphql/language/printer.rb +10 -1
  73. data/lib/graphql/language/sanitized_printer.rb +5 -5
  74. data/lib/graphql/language/token.rb +0 -4
  75. data/lib/graphql/name_validator.rb +0 -4
  76. data/lib/graphql/object_type.rb +2 -2
  77. data/lib/graphql/pagination/active_record_relation_connection.rb +43 -6
  78. data/lib/graphql/pagination/relation_connection.rb +59 -29
  79. data/lib/graphql/query/arguments.rb +1 -1
  80. data/lib/graphql/query/arguments_cache.rb +1 -1
  81. data/lib/graphql/query/context.rb +15 -2
  82. data/lib/graphql/query/input_validation_result.rb +9 -0
  83. data/lib/graphql/query/literal_input.rb +1 -1
  84. data/lib/graphql/query/null_context.rb +12 -7
  85. data/lib/graphql/query/serial_execution/field_resolution.rb +1 -1
  86. data/lib/graphql/query/validation_pipeline.rb +2 -3
  87. data/lib/graphql/query/variable_validation_error.rb +2 -2
  88. data/lib/graphql/query/variables.rb +35 -4
  89. data/lib/graphql/query.rb +0 -1
  90. data/lib/graphql/relay/connection_type.rb +15 -2
  91. data/lib/graphql/relay/edges_instrumentation.rb +0 -1
  92. data/lib/graphql/relay/global_id_resolve.rb +1 -2
  93. data/lib/graphql/relay/mutation.rb +1 -1
  94. data/lib/graphql/relay/page_info.rb +1 -1
  95. data/lib/graphql/relay/range_add.rb +4 -0
  96. data/lib/graphql/rubocop/graphql/base_cop.rb +36 -0
  97. data/lib/graphql/rubocop/graphql/default_null_true.rb +43 -0
  98. data/lib/graphql/rubocop/graphql/default_required_true.rb +43 -0
  99. data/lib/graphql/rubocop.rb +4 -0
  100. data/lib/graphql/scalar_type.rb +1 -1
  101. data/lib/graphql/schema/addition.rb +37 -28
  102. data/lib/graphql/schema/argument.rb +30 -15
  103. data/lib/graphql/schema/build_from_definition.rb +6 -5
  104. data/lib/graphql/schema/directive/feature.rb +1 -1
  105. data/lib/graphql/schema/directive/flagged.rb +2 -2
  106. data/lib/graphql/schema/directive/include.rb +1 -1
  107. data/lib/graphql/schema/directive/skip.rb +1 -1
  108. data/lib/graphql/schema/directive/transform.rb +1 -1
  109. data/lib/graphql/schema/directive.rb +23 -4
  110. data/lib/graphql/schema/enum.rb +61 -12
  111. data/lib/graphql/schema/enum_value.rb +6 -0
  112. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  113. data/lib/graphql/schema/field.rb +261 -83
  114. data/lib/graphql/schema/field_extension.rb +89 -2
  115. data/lib/graphql/schema/find_inherited_value.rb +1 -0
  116. data/lib/graphql/schema/finder.rb +5 -5
  117. data/lib/graphql/schema/input_object.rb +24 -7
  118. data/lib/graphql/schema/interface.rb +11 -20
  119. data/lib/graphql/schema/introspection_system.rb +1 -1
  120. data/lib/graphql/schema/list.rb +21 -4
  121. data/lib/graphql/schema/loader.rb +3 -0
  122. data/lib/graphql/schema/member/accepts_definition.rb +15 -3
  123. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -1
  124. data/lib/graphql/schema/member/build_type.rb +0 -4
  125. data/lib/graphql/schema/member/cached_graphql_definition.rb +29 -2
  126. data/lib/graphql/schema/member/has_arguments.rb +56 -14
  127. data/lib/graphql/schema/member/has_deprecation_reason.rb +1 -1
  128. data/lib/graphql/schema/member/has_fields.rb +76 -18
  129. data/lib/graphql/schema/member/has_interfaces.rb +100 -0
  130. data/lib/graphql/schema/member/validates_input.rb +2 -2
  131. data/lib/graphql/schema/member.rb +1 -0
  132. data/lib/graphql/schema/non_null.rb +9 -3
  133. data/lib/graphql/schema/object.rb +10 -75
  134. data/lib/graphql/schema/printer.rb +1 -1
  135. data/lib/graphql/schema/relay_classic_mutation.rb +37 -3
  136. data/lib/graphql/schema/resolver/has_payload_type.rb +27 -2
  137. data/lib/graphql/schema/resolver.rb +37 -17
  138. data/lib/graphql/schema/scalar.rb +15 -1
  139. data/lib/graphql/schema/subscription.rb +11 -1
  140. data/lib/graphql/schema/traversal.rb +1 -1
  141. data/lib/graphql/schema/type_expression.rb +1 -1
  142. data/lib/graphql/schema/type_membership.rb +18 -4
  143. data/lib/graphql/schema/union.rb +8 -1
  144. data/lib/graphql/schema/validator/format_validator.rb +0 -4
  145. data/lib/graphql/schema/validator/numericality_validator.rb +1 -0
  146. data/lib/graphql/schema/validator/required_validator.rb +29 -15
  147. data/lib/graphql/schema/validator.rb +4 -7
  148. data/lib/graphql/schema/warden.rb +126 -53
  149. data/lib/graphql/schema.rb +120 -24
  150. data/lib/graphql/static_validation/all_rules.rb +1 -0
  151. data/lib/graphql/static_validation/base_visitor.rb +6 -6
  152. data/lib/graphql/static_validation/definition_dependencies.rb +0 -1
  153. data/lib/graphql/static_validation/literal_validator.rb +1 -1
  154. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  155. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
  156. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +1 -1
  157. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
  158. data/lib/graphql/static_validation/rules/query_root_exists.rb +17 -0
  159. data/lib/graphql/static_validation/rules/query_root_exists_error.rb +26 -0
  160. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +3 -3
  161. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +4 -4
  162. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
  163. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +13 -7
  164. data/lib/graphql/static_validation/validation_context.rb +4 -0
  165. data/lib/graphql/string_type.rb +1 -1
  166. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +8 -4
  167. data/lib/graphql/subscriptions/event.rb +20 -12
  168. data/lib/graphql/subscriptions/serialize.rb +22 -2
  169. data/lib/graphql/subscriptions.rb +17 -19
  170. data/lib/graphql/tracing/active_support_notifications_tracing.rb +6 -20
  171. data/lib/graphql/tracing/data_dog_tracing.rb +24 -2
  172. data/lib/graphql/tracing/notifications_tracing.rb +59 -0
  173. data/lib/graphql/tracing/platform_tracing.rb +20 -10
  174. data/lib/graphql/types/iso_8601_date.rb +13 -5
  175. data/lib/graphql/types/iso_8601_date_time.rb +8 -1
  176. data/lib/graphql/types/relay/connection_behaviors.rb +28 -10
  177. data/lib/graphql/types/relay/default_relay.rb +5 -1
  178. data/lib/graphql/types/relay/edge_behaviors.rb +13 -2
  179. data/lib/graphql/types/relay/has_node_field.rb +1 -1
  180. data/lib/graphql/types/relay/has_nodes_field.rb +1 -1
  181. data/lib/graphql/types/relay/node_field.rb +2 -3
  182. data/lib/graphql/types/relay/nodes_field.rb +19 -3
  183. data/lib/graphql/types/string.rb +1 -1
  184. data/lib/graphql/union_type.rb +1 -1
  185. data/lib/graphql/version.rb +1 -1
  186. data/lib/graphql.rb +22 -32
  187. metadata +31 -11
  188. /data/lib/generators/graphql/{templates → install/templates}/base_mutation.erb +0 -0
  189. /data/lib/generators/graphql/{templates → install/templates}/mutation_type.erb +0 -0
@@ -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}"
60
+ end
61
+ value
62
+ end
63
+
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
85
+ end
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
51
101
  end
52
- own_values[value.graphql_name] = value
53
- nil
102
+
103
+ all_defns
54
104
  end
55
105
 
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)
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 }
61
109
  end
62
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
@@ -89,9 +139,8 @@ module GraphQL
89
139
  GraphQL::TypeKinds::ENUM
90
140
  end
91
141
 
92
- def validate_non_null_input(value_name, ctx)
142
+ def validate_non_null_input(value_name, ctx, max_errors: nil)
93
143
  result = GraphQL::Query::InputValidationResult.new
94
-
95
144
  allowed_values = ctx.warden.enum_values(self)
96
145
  matching_value = allowed_values.find { |v| v.graphql_name == value_name }
97
146
 
@@ -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
@@ -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
@@ -40,7 +38,9 @@ module GraphQL
40
38
 
41
39
  # @return [Class] The GraphQL type this field belongs to. (For fields defined on mutations, it's the payload type)
42
40
  def owner_type
43
- @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
44
44
  owner.payload_type
45
45
  else
46
46
  owner
@@ -61,7 +61,7 @@ module GraphQL
61
61
  end
62
62
 
63
63
  def inspect
64
- "#<#{self.class} #{path}#{arguments.any? ? "(...)" : ""}: #{type.to_type_signature}>"
64
+ "#<#{self.class} #{path}#{all_argument_definitions.any? ? "(...)" : ""}: #{type.to_type_signature}>"
65
65
  end
66
66
 
67
67
  alias :mutation :resolver
@@ -176,6 +176,8 @@ module GraphQL
176
176
 
177
177
  # @return Boolean
178
178
  attr_reader :relay_node_field
179
+ # @return Boolean
180
+ attr_reader :relay_nodes_field
179
181
 
180
182
  # @return [Boolean] Should we warn if this field's name conflicts with a built-in method?
181
183
  def method_conflict_warning?
@@ -190,6 +192,7 @@ module GraphQL
190
192
  # @param deprecation_reason [String] If present, the field is marked "deprecated" with this message
191
193
  # @param method [Symbol] The method to call on the underlying object to resolve this field (defaults to `name`)
192
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
193
196
  # @param resolver_method [Symbol] The method on the type to call to resolve this field (defaults to `name`)
194
197
  # @param connection [Boolean] `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name
195
198
  # @param connection_extension [Class] The extension to add, to implement connections. If `nil`, no extension is added.
@@ -212,7 +215,7 @@ module GraphQL
212
215
  # @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
213
216
  # @param validates [Array<Hash>] Configurations for validating this field
214
217
  # @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)
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)
216
219
  if name.nil?
217
220
  raise ArgumentError, "missing first `name` argument or keyword `name:`"
218
221
  end
@@ -220,15 +223,13 @@ module GraphQL
220
223
  if type.nil?
221
224
  raise ArgumentError, "missing second `type` argument or keyword `type:`"
222
225
  end
223
- if null.nil?
224
- raise ArgumentError, "missing keyword argument null:"
225
- end
226
226
  end
227
227
  if (field || function || resolve) && extras.any?
228
228
  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:`"
229
229
  end
230
230
  @original_name = name
231
231
  name_s = -name.to_s
232
+
232
233
  @underscored_name = -Member::BuildType.underscore(name_s)
233
234
  @name = -(camelize ? Member::BuildType.camelize(name_s) : name_s)
234
235
  @description = description
@@ -241,8 +242,8 @@ module GraphQL
241
242
  @resolve = resolve
242
243
  self.deprecation_reason = deprecation_reason
243
244
 
244
- if method && hash_key
245
- raise ArgumentError, "Provide `method:` _or_ `hash_key:`, not both. (called with: `method: #{method.inspect}, hash_key: #{hash_key.inspect}`)"
245
+ if method && hash_key && dig
246
+ raise ArgumentError, "Provide `method:`, `hash_key:` _or_ `dig:`, not multiple. (called with: `method: #{method.inspect}, hash_key: #{hash_key.inspect}, dig: #{dig.inspect}`)"
246
247
  end
247
248
 
248
249
  if resolver_method
@@ -250,13 +251,18 @@ module GraphQL
250
251
  raise ArgumentError, "Provide `method:` _or_ `resolver_method:`, not both. (called with: `method: #{method.inspect}, resolver_method: #{resolver_method.inspect}`)"
251
252
  end
252
253
 
253
- if hash_key
254
- raise ArgumentError, "Provide `hash_key:` _or_ `resolver_method:`, not both. (called with: `hash_key: #{hash_key.inspect}, resolver_method: #{resolver_method.inspect}`)"
254
+ if hash_key || dig
255
+ 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}`)"
255
256
  end
256
257
  end
257
258
 
258
259
  # TODO: I think non-string/symbol hash keys are wrongly normalized (eg `1` will not work)
259
260
  method_name = method || hash_key || name_s
261
+ @dig_keys = dig
262
+ if hash_key
263
+ @hash_key = hash_key
264
+ end
265
+
260
266
  resolver_method ||= name_s.to_sym
261
267
 
262
268
  @method_str = -method_name.to_s
@@ -281,10 +287,15 @@ module GraphQL
281
287
  @legacy_edge_class = legacy_edge_class
282
288
 
283
289
  arguments.each do |name, arg|
284
- if arg.is_a?(Hash)
290
+ case arg
291
+ when Hash
285
292
  argument(name: name, **arg)
286
- else
293
+ when GraphQL::Schema::Argument
287
294
  add_argument(arg)
295
+ when Array
296
+ arg.each { |a| add_argument(a) }
297
+ else
298
+ raise ArgumentError, "Unexpected argument config (#{arg.class}): #{arg.inspect}"
288
299
  end
289
300
  end
290
301
 
@@ -292,6 +303,7 @@ module GraphQL
292
303
  @subscription_scope = subscription_scope
293
304
 
294
305
  @extensions = EMPTY_ARRAY
306
+ @call_after_define = false
295
307
  # This should run before connection extension,
296
308
  # but should it run after the definition block?
297
309
  if scoped?
@@ -324,6 +336,9 @@ module GraphQL
324
336
  instance_eval(&definition_block)
325
337
  end
326
338
  end
339
+
340
+ self.extensions.each(&:after_define_apply)
341
+ @call_after_define = true
327
342
  end
328
343
 
329
344
  # If true, subscription updates with this field can be shared between viewers
@@ -356,27 +371,20 @@ module GraphQL
356
371
  # @example adding an extension with options
357
372
  # extensions([MyExtensionClass, { AnotherExtensionClass => { filter: true } }])
358
373
  #
359
- # @param extensions [Array<Class, Hash<Class => Object>>] Add extensions to this field. For hash elements, only the first key/value is used.
374
+ # @param extensions [Array<Class, Hash<Class => Hash>>] Add extensions to this field. For hash elements, only the first key/value is used.
360
375
  # @return [Array<GraphQL::Schema::FieldExtension>] extensions to apply to this field
361
376
  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)
377
+ if new_extensions
378
+ new_extensions.each do |extension_config|
379
+ if extension_config.is_a?(Hash)
380
+ extension_class, options = *extension_config.to_a[0]
381
+ self.extension(extension_class, options)
374
382
  else
375
- extension_class = extension
376
- @extensions << extension_class.new(field: self, options: nil)
383
+ self.extension(extension_config)
377
384
  end
378
385
  end
379
386
  end
387
+ @extensions
380
388
  end
381
389
 
382
390
  # Add `extension` to this field, initialized with `options` if provided.
@@ -387,10 +395,19 @@ module GraphQL
387
395
  # @example adding an extension with options
388
396
  # extension(MyExtensionClass, filter: true)
389
397
  #
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}])
398
+ # @param extension_class [Class] subclass of {Schema::FieldExtension}
399
+ # @param options [Hash] if provided, given as `options:` when initializing `extension`.
400
+ # @return [void]
401
+ def extension(extension_class, options = nil)
402
+ extension_inst = extension_class.new(field: self, options: options)
403
+ if @extensions.frozen?
404
+ @extensions = @extensions.dup
405
+ end
406
+ if @call_after_define
407
+ extension_inst.after_define_apply
408
+ end
409
+ @extensions << extension_inst
410
+ nil
394
411
  end
395
412
 
396
413
  # Read extras (as symbols) from this field,
@@ -411,6 +428,71 @@ module GraphQL
411
428
  end
412
429
  end
413
430
 
431
+ def calculate_complexity(query:, nodes:, child_complexity:)
432
+ if respond_to?(:complexity_for)
433
+ lookahead = GraphQL::Execution::Lookahead.new(query: query, field: self, ast_nodes: nodes, owner_type: owner)
434
+ complexity_for(child_complexity: child_complexity, query: query, lookahead: lookahead)
435
+ elsif connection?
436
+ arguments = query.arguments_for(nodes.first, self)
437
+ max_possible_page_size = nil
438
+ if arguments.respond_to?(:[]) # It might have been an error
439
+ if arguments[:first]
440
+ max_possible_page_size = arguments[:first]
441
+ end
442
+
443
+ if arguments[:last] && (max_possible_page_size.nil? || arguments[:last] > max_possible_page_size)
444
+ max_possible_page_size = arguments[:last]
445
+ end
446
+ end
447
+
448
+ if max_possible_page_size.nil?
449
+ max_possible_page_size = max_page_size || query.schema.default_max_page_size
450
+ end
451
+
452
+ if max_possible_page_size.nil?
453
+ raise GraphQL::Error, "Can't calculate complexity for #{path}, no `first:`, `last:`, `max_page_size` or `default_max_page_size`"
454
+ else
455
+ metadata_complexity = 0
456
+ lookahead = GraphQL::Execution::Lookahead.new(query: query, field: self, ast_nodes: nodes, owner_type: owner)
457
+
458
+ if (page_info_lookahead = lookahead.selection(:page_info)).selected?
459
+ metadata_complexity += 1 # pageInfo
460
+ metadata_complexity += page_info_lookahead.selections.size # subfields
461
+ end
462
+
463
+ if lookahead.selects?(:total) || lookahead.selects?(:total_count) || lookahead.selects?(:count)
464
+ metadata_complexity += 1
465
+ end
466
+
467
+ nodes_edges_complexity = 0
468
+ nodes_edges_complexity += 1 if lookahead.selects?(:edges)
469
+ nodes_edges_complexity += 1 if lookahead.selects?(:nodes)
470
+
471
+ # Possible bug: selections on `edges` and `nodes` are _both_ multiplied here. Should they be?
472
+ items_complexity = child_complexity - metadata_complexity - nodes_edges_complexity
473
+ # Add 1 for _this_ field
474
+ 1 + (max_possible_page_size * items_complexity) + metadata_complexity + nodes_edges_complexity
475
+ end
476
+ else
477
+ defined_complexity = complexity
478
+ case defined_complexity
479
+ when Proc
480
+ arguments = query.arguments_for(nodes.first, self)
481
+ if arguments.is_a?(GraphQL::ExecutionError)
482
+ return child_complexity
483
+ elsif arguments.respond_to?(:keyword_arguments)
484
+ arguments = arguments.keyword_arguments
485
+ end
486
+
487
+ defined_complexity.call(query.context, arguments, child_complexity)
488
+ when Numeric
489
+ defined_complexity + child_complexity
490
+ else
491
+ raise("Invalid complexity: #{defined_complexity.inspect} on #{path} (#{inspect})")
492
+ end
493
+ end
494
+ end
495
+
414
496
  def complexity(new_complexity = nil)
415
497
  case new_complexity
416
498
  when Proc
@@ -439,6 +521,8 @@ module GraphQL
439
521
  # @return [Integer, nil] Applied to connections if {#has_max_page_size?}
440
522
  attr_reader :max_page_size
441
523
 
524
+ prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
525
+
442
526
  # @return [GraphQL::Field]
443
527
  def to_graphql
444
528
  field_defn = if @field
@@ -493,9 +577,9 @@ module GraphQL
493
577
  field_defn.subscription_scope = @subscription_scope
494
578
  field_defn.ast_node = ast_node
495
579
 
496
- arguments.each do |name, defn|
497
- arg_graphql = defn.to_graphql
498
- field_defn.arguments[arg_graphql.name] = arg_graphql
580
+ all_argument_definitions.each do |defn|
581
+ arg_graphql = defn.deprecated_to_graphql
582
+ field_defn.arguments[arg_graphql.name] = arg_graphql # rubocop:disable Development/ContextIsPassedCop -- legacy-related
499
583
  end
500
584
 
501
585
  # Support a passed-in proc, one way or another
@@ -556,13 +640,41 @@ module GraphQL
556
640
 
557
641
  def authorized?(object, args, context)
558
642
  if @resolver_class
559
- # The resolver will check itself during `resolve()`
643
+ # The resolver _instance_ will check itself during `resolve()`
560
644
  @resolver_class.authorized?(object, context)
561
645
  else
562
- # 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
646
+ if (arg_values = context[:current_arguments])
647
+ # ^^ that's provided by the interpreter at runtime, and includes info about whether the default value was used or not.
648
+ using_arg_values = true
649
+ arg_values = arg_values.argument_values
650
+ else
651
+ arg_values = args
652
+ using_arg_values = false
653
+ end
654
+ if args.size > 0
655
+ args = context.warden.arguments(self)
656
+ args.each do |arg|
657
+ arg_key = arg.keyword
658
+ if arg_values.key?(arg_key)
659
+ arg_value = arg_values[arg_key]
660
+ if using_arg_values
661
+ if arg_value.default_used?
662
+ # pass -- no auth required for default used
663
+ next
664
+ else
665
+ application_arg_value = arg_value.value
666
+ if application_arg_value.is_a?(GraphQL::Execution::Interpreter::Arguments)
667
+ application_arg_value.keyword_arguments
668
+ end
669
+ end
670
+ else
671
+ application_arg_value = arg_value
672
+ end
673
+
674
+ if !arg.authorized?(object, application_arg_value, context)
675
+ return false
676
+ end
677
+ end
566
678
  end
567
679
  end
568
680
  true
@@ -659,7 +771,7 @@ module GraphQL
659
771
  ruby_kwargs = graphql_args.to_kwargs
660
772
  maybe_lazies = []
661
773
  # Apply any `prepare` methods. Not great code organization, can this go somewhere better?
662
- arguments.each do |name, arg_defn|
774
+ arguments(field_ctx).each do |name, arg_defn|
663
775
  ruby_kwargs_key = arg_defn.keyword
664
776
 
665
777
  if ruby_kwargs.key?(ruby_kwargs_key)
@@ -704,51 +816,107 @@ module GraphQL
704
816
 
705
817
  def public_send_field(unextended_obj, unextended_ruby_kwargs, query_ctx)
706
818
  with_extensions(unextended_obj, unextended_ruby_kwargs, query_ctx) do |obj, ruby_kwargs|
707
- if @resolver_class
708
- if obj.is_a?(GraphQL::Schema::Object)
709
- obj = obj.object
710
- end
711
- obj = @resolver_class.new(object: obj, context: query_ctx, field: self)
712
- end
713
-
714
- # Find a way to resolve this field, checking:
715
- #
716
- # - A method on the type instance;
717
- # - Hash keys, if the wrapped object is a hash;
718
- # - A method on the wrapped object;
719
- # - Or, raise not implemented.
720
- #
721
- if obj.respond_to?(@resolver_method)
722
- # Call the method with kwargs, if there are any
723
- if ruby_kwargs.any?
724
- obj.public_send(@resolver_method, **ruby_kwargs)
725
- else
726
- obj.public_send(@resolver_method)
819
+ begin
820
+ method_receiver = nil
821
+ method_to_call = nil
822
+ if @resolver_class
823
+ if obj.is_a?(GraphQL::Schema::Object)
824
+ obj = obj.object
825
+ end
826
+ obj = @resolver_class.new(object: obj, context: query_ctx, field: self)
727
827
  end
728
- elsif obj.object.is_a?(Hash)
729
- inner_object = obj.object
730
- if inner_object.key?(@method_sym)
731
- inner_object[@method_sym]
828
+
829
+ # Find a way to resolve this field, checking:
830
+ #
831
+ # - A method on the type instance;
832
+ # - Hash keys, if the wrapped object is a hash or responds to `#[]`
833
+ # - A method on the wrapped object;
834
+ # - Or, raise not implemented.
835
+ #
836
+ if obj.respond_to?(@resolver_method)
837
+ method_to_call = @resolver_method
838
+ method_receiver = obj
839
+ # Call the method with kwargs, if there are any
840
+ if ruby_kwargs.any?
841
+ obj.public_send(@resolver_method, **ruby_kwargs)
842
+ else
843
+ obj.public_send(@resolver_method)
844
+ end
845
+ elsif obj.object.is_a?(Hash)
846
+ inner_object = obj.object
847
+ if @dig_keys
848
+ inner_object.dig(*@dig_keys)
849
+ elsif inner_object.key?(@method_sym)
850
+ inner_object[@method_sym]
851
+ else
852
+ inner_object[@method_str]
853
+ end
854
+ elsif defined?(@hash_key) && obj.object.respond_to?(:[])
855
+ obj.object[@hash_key]
856
+ elsif obj.object.respond_to?(@method_sym)
857
+ method_to_call = @method_sym
858
+ method_receiver = obj.object
859
+ if ruby_kwargs.any?
860
+ obj.object.public_send(@method_sym, **ruby_kwargs)
861
+ else
862
+ obj.object.public_send(@method_sym)
863
+ end
732
864
  else
733
- inner_object[@method_str]
865
+ raise <<-ERR
866
+ Failed to implement #{@owner.graphql_name}.#{@name}, tried:
867
+
868
+ - `#{obj.class}##{@resolver_method}`, which did not exist
869
+ - `#{obj.object.class}##{@method_sym}`, which did not exist
870
+ - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
871
+
872
+ To implement this field, define one of the methods above (and check for typos)
873
+ ERR
734
874
  end
735
- elsif obj.object.respond_to?(@method_sym)
736
- if ruby_kwargs.any?
737
- obj.object.public_send(@method_sym, **ruby_kwargs)
875
+ rescue ArgumentError
876
+ assert_satisfactory_implementation(method_receiver, method_to_call, ruby_kwargs)
877
+ # if the line above doesn't raise, re-raise
878
+ raise
879
+ end
880
+ end
881
+ end
882
+
883
+ def assert_satisfactory_implementation(receiver, method_name, ruby_kwargs)
884
+ method_defn = receiver.method(method_name)
885
+ unsatisfied_ruby_kwargs = ruby_kwargs.dup
886
+ unsatisfied_method_params = []
887
+ encountered_keyrest = false
888
+ method_defn.parameters.each do |(param_type, param_name)|
889
+ case param_type
890
+ when :key
891
+ unsatisfied_ruby_kwargs.delete(param_name)
892
+ when :keyreq
893
+ if unsatisfied_ruby_kwargs.key?(param_name)
894
+ unsatisfied_ruby_kwargs.delete(param_name)
738
895
  else
739
- obj.object.public_send(@method_sym)
896
+ unsatisfied_method_params << "- `#{param_name}:` is required by Ruby, but not by GraphQL. Consider `#{param_name}: nil` instead, or making this argument required in GraphQL."
740
897
  end
741
- else
742
- raise <<-ERR
743
- Failed to implement #{@owner.graphql_name}.#{@name}, tried:
898
+ when :keyrest
899
+ encountered_keyrest = true
900
+ when :req
901
+ 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."
902
+ when :opt, :rest
903
+ # This is fine, although it will never be present
904
+ end
905
+ end
906
+
907
+ if encountered_keyrest
908
+ unsatisfied_ruby_kwargs.clear
909
+ end
744
910
 
745
- - `#{obj.class}##{@resolver_method}`, which did not exist
746
- - `#{obj.object.class}##{@method_sym}`, which did not exist
747
- - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
911
+ if unsatisfied_ruby_kwargs.any? || unsatisfied_method_params.any?
912
+ raise FieldImplementationFailed.new, <<-ERR
913
+ Failed to call #{method_name} on #{receiver.inspect} because the Ruby method params were incompatible with the GraphQL arguments:
748
914
 
749
- To implement this field, define one of the methods above (and check for typos)
750
- ERR
751
- end
915
+ #{ unsatisfied_ruby_kwargs
916
+ .map { |key, value| "- `#{key}: #{value}` was given by GraphQL but not defined in the Ruby method. Add `#{key}:` to the method parameters." }
917
+ .concat(unsatisfied_method_params)
918
+ .join("\n") }
919
+ ERR
752
920
  end
753
921
  end
754
922
 
@@ -762,8 +930,12 @@ module GraphQL
762
930
  # This is a hack to get the _last_ value for extended obj and args,
763
931
  # in case one of the extensions doesn't `yield`.
764
932
  # (There's another implementation that uses multiple-return, but I'm wary of the perf cost of the extra arrays)
765
- extended = { args: args, obj: obj, memos: nil }
933
+ extended = { args: args, obj: obj, memos: nil, added_extras: nil }
766
934
  value = run_extensions_before_resolve(obj, args, ctx, extended) do |obj, args|
935
+ if (added_extras = extended[:added_extras])
936
+ args = args.dup
937
+ added_extras.each { |e| args.delete(e) }
938
+ end
767
939
  yield(obj, args)
768
940
  end
769
941
 
@@ -792,6 +964,12 @@ module GraphQL
792
964
  memos = extended[:memos] ||= {}
793
965
  memos[idx] = memo
794
966
  end
967
+
968
+ if (extras = extension.added_extras)
969
+ ae = extended[:added_extras] ||= []
970
+ ae.concat(extras)
971
+ end
972
+
795
973
  extended[:obj] = extended_obj
796
974
  extended[:args] = extended_args
797
975
  run_extensions_before_resolve(extended_obj, extended_args, ctx, extended, idx: idx + 1) { |o, a| yield(o, a) }