graphql 1.12.24 → 1.13.19

Sign up to get free protection for your applications and to get access to all the features.
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) }