graphql 1.11.6 → 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 (293) 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 +17 -7
  8. data/lib/generators/graphql/interface_generator.rb +7 -7
  9. data/lib/generators/graphql/loader_generator.rb +1 -0
  10. data/lib/generators/graphql/mutation_create_generator.rb +22 -0
  11. data/lib/generators/graphql/mutation_delete_generator.rb +22 -0
  12. data/lib/generators/graphql/mutation_generator.rb +6 -30
  13. data/lib/generators/graphql/mutation_update_generator.rb +22 -0
  14. data/lib/generators/graphql/object_generator.rb +12 -38
  15. data/lib/generators/graphql/orm_mutations_base.rb +40 -0
  16. data/lib/generators/graphql/relay.rb +63 -0
  17. data/lib/generators/graphql/relay_generator.rb +21 -0
  18. data/lib/generators/graphql/scalar_generator.rb +4 -2
  19. data/lib/generators/graphql/templates/base_connection.erb +8 -0
  20. data/lib/generators/graphql/templates/base_edge.erb +8 -0
  21. data/lib/generators/graphql/templates/enum.erb +5 -1
  22. data/lib/generators/graphql/templates/graphql_controller.erb +2 -2
  23. data/lib/generators/graphql/templates/input.erb +9 -0
  24. data/lib/generators/graphql/templates/interface.erb +4 -2
  25. data/lib/generators/graphql/templates/mutation.erb +1 -1
  26. data/lib/generators/graphql/templates/mutation_create.erb +20 -0
  27. data/lib/generators/graphql/templates/mutation_delete.erb +20 -0
  28. data/lib/generators/graphql/templates/mutation_update.erb +21 -0
  29. data/lib/generators/graphql/templates/node_type.erb +9 -0
  30. data/lib/generators/graphql/templates/object.erb +5 -3
  31. data/lib/generators/graphql/templates/query_type.erb +1 -3
  32. data/lib/generators/graphql/templates/scalar.erb +3 -1
  33. data/lib/generators/graphql/templates/schema.erb +19 -34
  34. data/lib/generators/graphql/templates/union.erb +4 -2
  35. data/lib/generators/graphql/type_generator.rb +47 -10
  36. data/lib/generators/graphql/union_generator.rb +5 -5
  37. data/lib/graphql/analysis/analyze_query.rb +7 -0
  38. data/lib/graphql/analysis/ast/field_usage.rb +28 -1
  39. data/lib/graphql/analysis/ast/query_complexity.rb +10 -14
  40. data/lib/graphql/analysis/ast/visitor.rb +14 -5
  41. data/lib/graphql/analysis/ast.rb +11 -2
  42. data/lib/graphql/argument.rb +1 -1
  43. data/lib/graphql/backtrace/inspect_result.rb +0 -1
  44. data/lib/graphql/backtrace/legacy_tracer.rb +56 -0
  45. data/lib/graphql/backtrace/table.rb +34 -3
  46. data/lib/graphql/backtrace/traced_error.rb +0 -1
  47. data/lib/graphql/backtrace/tracer.rb +40 -10
  48. data/lib/graphql/backtrace.rb +28 -19
  49. data/lib/graphql/backwards_compatibility.rb +2 -1
  50. data/lib/graphql/base_type.rb +6 -4
  51. data/lib/graphql/boolean_type.rb +1 -1
  52. data/lib/graphql/compatibility/execution_specification.rb +1 -0
  53. data/lib/graphql/compatibility/lazy_execution_specification.rb +2 -0
  54. data/lib/graphql/compatibility/query_parser_specification.rb +2 -0
  55. data/lib/graphql/compatibility/schema_parser_specification.rb +2 -0
  56. data/lib/graphql/dataloader/null_dataloader.rb +22 -0
  57. data/lib/graphql/dataloader/request.rb +19 -0
  58. data/lib/graphql/dataloader/request_all.rb +19 -0
  59. data/lib/graphql/dataloader/source.rb +155 -0
  60. data/lib/graphql/dataloader.rb +308 -0
  61. data/lib/graphql/date_encoding_error.rb +16 -0
  62. data/lib/graphql/define/assign_global_id_field.rb +1 -1
  63. data/lib/graphql/define/instance_definable.rb +48 -3
  64. data/lib/graphql/define/type_definer.rb +5 -5
  65. data/lib/graphql/deprecated_dsl.rb +18 -5
  66. data/lib/graphql/deprecation.rb +9 -0
  67. data/lib/graphql/directive/deprecated_directive.rb +1 -1
  68. data/lib/graphql/directive/include_directive.rb +1 -1
  69. data/lib/graphql/directive/skip_directive.rb +1 -1
  70. data/lib/graphql/directive.rb +1 -5
  71. data/lib/graphql/enum_type.rb +9 -3
  72. data/lib/graphql/execution/errors.rb +110 -7
  73. data/lib/graphql/execution/execute.rb +8 -1
  74. data/lib/graphql/execution/interpreter/arguments.rb +57 -5
  75. data/lib/graphql/execution/interpreter/arguments_cache.rb +49 -15
  76. data/lib/graphql/execution/interpreter/handles_raw_value.rb +0 -7
  77. data/lib/graphql/execution/interpreter/resolve.rb +37 -25
  78. data/lib/graphql/execution/interpreter/runtime.rb +670 -294
  79. data/lib/graphql/execution/interpreter.rb +16 -16
  80. data/lib/graphql/execution/lazy.rb +5 -1
  81. data/lib/graphql/execution/lookahead.rb +2 -2
  82. data/lib/graphql/execution/multiplex.rb +39 -23
  83. data/lib/graphql/field.rb +1 -1
  84. data/lib/graphql/float_type.rb +1 -1
  85. data/lib/graphql/function.rb +4 -0
  86. data/lib/graphql/id_type.rb +1 -1
  87. data/lib/graphql/input_object_type.rb +3 -1
  88. data/lib/graphql/int_type.rb +1 -1
  89. data/lib/graphql/integer_decoding_error.rb +17 -0
  90. data/lib/graphql/integer_encoding_error.rb +18 -2
  91. data/lib/graphql/interface_type.rb +4 -2
  92. data/lib/graphql/internal_representation/document.rb +2 -2
  93. data/lib/graphql/internal_representation/rewrite.rb +1 -1
  94. data/lib/graphql/introspection/directive_location_enum.rb +2 -2
  95. data/lib/graphql/introspection/directive_type.rb +11 -5
  96. data/lib/graphql/introspection/entry_points.rb +2 -2
  97. data/lib/graphql/introspection/enum_value_type.rb +2 -2
  98. data/lib/graphql/introspection/field_type.rb +3 -3
  99. data/lib/graphql/introspection/input_value_type.rb +10 -4
  100. data/lib/graphql/introspection/schema_type.rb +10 -5
  101. data/lib/graphql/introspection/type_type.rb +18 -12
  102. data/lib/graphql/introspection.rb +5 -2
  103. data/lib/graphql/invalid_null_error.rb +1 -1
  104. data/lib/graphql/language/block_string.rb +2 -6
  105. data/lib/graphql/language/cache.rb +37 -0
  106. data/lib/graphql/language/document_from_schema_definition.rb +60 -26
  107. data/lib/graphql/language/lexer.rb +50 -28
  108. data/lib/graphql/language/lexer.rl +2 -4
  109. data/lib/graphql/language/nodes.rb +14 -4
  110. data/lib/graphql/language/parser.rb +856 -825
  111. data/lib/graphql/language/parser.y +28 -11
  112. data/lib/graphql/language/printer.rb +10 -1
  113. data/lib/graphql/language/sanitized_printer.rb +5 -5
  114. data/lib/graphql/language/token.rb +0 -4
  115. data/lib/graphql/language.rb +1 -0
  116. data/lib/graphql/name_validator.rb +0 -4
  117. data/lib/graphql/object_type.rb +4 -4
  118. data/lib/graphql/pagination/active_record_relation_connection.rb +47 -3
  119. data/lib/graphql/pagination/connection.rb +19 -1
  120. data/lib/graphql/pagination/connections.rb +45 -30
  121. data/lib/graphql/pagination/relation_connection.rb +69 -28
  122. data/lib/graphql/parse_error.rb +0 -1
  123. data/lib/graphql/query/arguments.rb +2 -2
  124. data/lib/graphql/query/arguments_cache.rb +1 -2
  125. data/lib/graphql/query/context.rb +22 -4
  126. data/lib/graphql/query/executor.rb +0 -1
  127. data/lib/graphql/query/input_validation_result.rb +9 -0
  128. data/lib/graphql/query/literal_input.rb +1 -1
  129. data/lib/graphql/query/null_context.rb +21 -9
  130. data/lib/graphql/query/serial_execution/field_resolution.rb +1 -1
  131. data/lib/graphql/query/serial_execution.rb +1 -0
  132. data/lib/graphql/query/validation_pipeline.rb +3 -4
  133. data/lib/graphql/query/variable_validation_error.rb +3 -3
  134. data/lib/graphql/query/variables.rb +35 -4
  135. data/lib/graphql/query.rb +20 -8
  136. data/lib/graphql/railtie.rb +9 -1
  137. data/lib/graphql/rake_task.rb +3 -0
  138. data/lib/graphql/relay/array_connection.rb +2 -2
  139. data/lib/graphql/relay/base_connection.rb +7 -0
  140. data/lib/graphql/relay/connection_instrumentation.rb +4 -4
  141. data/lib/graphql/relay/connection_type.rb +16 -3
  142. data/lib/graphql/relay/edges_instrumentation.rb +0 -1
  143. data/lib/graphql/relay/global_id_resolve.rb +1 -2
  144. data/lib/graphql/relay/mutation.rb +2 -1
  145. data/lib/graphql/relay/node.rb +3 -0
  146. data/lib/graphql/relay/page_info.rb +1 -1
  147. data/lib/graphql/relay/range_add.rb +14 -5
  148. data/lib/graphql/relay/type_extensions.rb +2 -0
  149. data/lib/graphql/rubocop/graphql/base_cop.rb +36 -0
  150. data/lib/graphql/rubocop/graphql/default_null_true.rb +43 -0
  151. data/lib/graphql/rubocop/graphql/default_required_true.rb +43 -0
  152. data/lib/graphql/rubocop.rb +4 -0
  153. data/lib/graphql/scalar_type.rb +3 -1
  154. data/lib/graphql/schema/addition.rb +247 -0
  155. data/lib/graphql/schema/argument.rb +177 -21
  156. data/lib/graphql/schema/build_from_definition.rb +150 -55
  157. data/lib/graphql/schema/default_type_error.rb +2 -0
  158. data/lib/graphql/schema/directive/feature.rb +1 -1
  159. data/lib/graphql/schema/directive/flagged.rb +57 -0
  160. data/lib/graphql/schema/directive/include.rb +1 -1
  161. data/lib/graphql/schema/directive/skip.rb +1 -1
  162. data/lib/graphql/schema/directive/transform.rb +14 -2
  163. data/lib/graphql/schema/directive.rb +103 -4
  164. data/lib/graphql/schema/enum.rb +72 -11
  165. data/lib/graphql/schema/enum_value.rb +18 -6
  166. data/lib/graphql/schema/field/connection_extension.rb +4 -2
  167. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  168. data/lib/graphql/schema/field.rb +332 -111
  169. data/lib/graphql/schema/field_extension.rb +89 -2
  170. data/lib/graphql/schema/find_inherited_value.rb +4 -1
  171. data/lib/graphql/schema/finder.rb +5 -5
  172. data/lib/graphql/schema/input_object.rb +79 -55
  173. data/lib/graphql/schema/interface.rb +12 -20
  174. data/lib/graphql/schema/introspection_system.rb +1 -1
  175. data/lib/graphql/schema/list.rb +21 -4
  176. data/lib/graphql/schema/loader.rb +11 -0
  177. data/lib/graphql/schema/member/accepts_definition.rb +15 -3
  178. data/lib/graphql/schema/member/base_dsl_methods.rb +5 -16
  179. data/lib/graphql/schema/member/build_type.rb +4 -7
  180. data/lib/graphql/schema/member/cached_graphql_definition.rb +29 -2
  181. data/lib/graphql/schema/member/has_arguments.rb +166 -74
  182. data/lib/graphql/schema/member/has_deprecation_reason.rb +25 -0
  183. data/lib/graphql/schema/member/has_directives.rb +98 -0
  184. data/lib/graphql/schema/member/has_fields.rb +77 -22
  185. data/lib/graphql/schema/member/has_interfaces.rb +100 -0
  186. data/lib/graphql/schema/member/has_validators.rb +31 -0
  187. data/lib/graphql/schema/member/instrumentation.rb +0 -1
  188. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  189. data/lib/graphql/schema/member/validates_input.rb +2 -2
  190. data/lib/graphql/schema/member.rb +5 -0
  191. data/lib/graphql/schema/middleware_chain.rb +1 -1
  192. data/lib/graphql/schema/non_null.rb +9 -3
  193. data/lib/graphql/schema/object.rb +40 -80
  194. data/lib/graphql/schema/printer.rb +16 -20
  195. data/lib/graphql/schema/relay_classic_mutation.rb +38 -4
  196. data/lib/graphql/schema/resolver/has_payload_type.rb +29 -2
  197. data/lib/graphql/schema/resolver.rb +110 -64
  198. data/lib/graphql/schema/scalar.rb +18 -2
  199. data/lib/graphql/schema/subscription.rb +55 -9
  200. data/lib/graphql/schema/timeout_middleware.rb +3 -1
  201. data/lib/graphql/schema/traversal.rb +1 -1
  202. data/lib/graphql/schema/type_expression.rb +1 -1
  203. data/lib/graphql/schema/type_membership.rb +18 -4
  204. data/lib/graphql/schema/union.rb +8 -1
  205. data/lib/graphql/schema/validation.rb +4 -2
  206. data/lib/graphql/schema/validator/allow_blank_validator.rb +29 -0
  207. data/lib/graphql/schema/validator/allow_null_validator.rb +26 -0
  208. data/lib/graphql/schema/validator/exclusion_validator.rb +33 -0
  209. data/lib/graphql/schema/validator/format_validator.rb +48 -0
  210. data/lib/graphql/schema/validator/inclusion_validator.rb +35 -0
  211. data/lib/graphql/schema/validator/length_validator.rb +59 -0
  212. data/lib/graphql/schema/validator/numericality_validator.rb +82 -0
  213. data/lib/graphql/schema/validator/required_validator.rb +82 -0
  214. data/lib/graphql/schema/validator.rb +171 -0
  215. data/lib/graphql/schema/warden.rb +126 -53
  216. data/lib/graphql/schema.rb +262 -281
  217. data/lib/graphql/static_validation/all_rules.rb +2 -0
  218. data/lib/graphql/static_validation/base_visitor.rb +9 -6
  219. data/lib/graphql/static_validation/definition_dependencies.rb +0 -1
  220. data/lib/graphql/static_validation/error.rb +3 -1
  221. data/lib/graphql/static_validation/literal_validator.rb +1 -1
  222. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +4 -2
  223. data/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb +6 -2
  224. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +3 -2
  225. data/lib/graphql/static_validation/rules/arguments_are_defined_error.rb +4 -2
  226. data/lib/graphql/static_validation/rules/directives_are_defined.rb +1 -1
  227. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +1 -1
  228. data/lib/graphql/static_validation/rules/fields_will_merge.rb +90 -47
  229. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +25 -4
  230. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -2
  231. data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +30 -0
  232. data/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb +30 -0
  233. data/lib/graphql/static_validation/rules/query_root_exists.rb +17 -0
  234. data/lib/graphql/static_validation/rules/query_root_exists_error.rb +26 -0
  235. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +4 -2
  236. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +5 -5
  237. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
  238. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +14 -8
  239. data/lib/graphql/static_validation/validation_context.rb +12 -2
  240. data/lib/graphql/static_validation/validation_timeout_error.rb +25 -0
  241. data/lib/graphql/static_validation/validator.rb +41 -10
  242. data/lib/graphql/static_validation.rb +1 -0
  243. data/lib/graphql/string_encoding_error.rb +13 -3
  244. data/lib/graphql/string_type.rb +1 -1
  245. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +39 -8
  246. data/lib/graphql/subscriptions/broadcast_analyzer.rb +0 -3
  247. data/lib/graphql/subscriptions/event.rb +68 -32
  248. data/lib/graphql/subscriptions/instrumentation.rb +0 -1
  249. data/lib/graphql/subscriptions/serialize.rb +34 -5
  250. data/lib/graphql/subscriptions/subscription_root.rb +1 -1
  251. data/lib/graphql/subscriptions.rb +34 -39
  252. data/lib/graphql/tracing/active_support_notifications_tracing.rb +8 -21
  253. data/lib/graphql/tracing/appoptics_tracing.rb +3 -1
  254. data/lib/graphql/tracing/appsignal_tracing.rb +15 -0
  255. data/lib/graphql/tracing/data_dog_tracing.rb +24 -2
  256. data/lib/graphql/tracing/notifications_tracing.rb +59 -0
  257. data/lib/graphql/tracing/platform_tracing.rb +24 -12
  258. data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +4 -1
  259. data/lib/graphql/tracing/skylight_tracing.rb +1 -1
  260. data/lib/graphql/tracing.rb +2 -2
  261. data/lib/graphql/types/big_int.rb +5 -1
  262. data/lib/graphql/types/int.rb +10 -3
  263. data/lib/graphql/types/iso_8601_date.rb +13 -5
  264. data/lib/graphql/types/iso_8601_date_time.rb +8 -1
  265. data/lib/graphql/types/relay/base_connection.rb +6 -91
  266. data/lib/graphql/types/relay/base_edge.rb +2 -34
  267. data/lib/graphql/types/relay/connection_behaviors.rb +174 -0
  268. data/lib/graphql/types/relay/default_relay.rb +31 -0
  269. data/lib/graphql/types/relay/edge_behaviors.rb +64 -0
  270. data/lib/graphql/types/relay/has_node_field.rb +41 -0
  271. data/lib/graphql/types/relay/has_nodes_field.rb +41 -0
  272. data/lib/graphql/types/relay/node.rb +2 -4
  273. data/lib/graphql/types/relay/node_behaviors.rb +15 -0
  274. data/lib/graphql/types/relay/node_field.rb +3 -22
  275. data/lib/graphql/types/relay/nodes_field.rb +16 -18
  276. data/lib/graphql/types/relay/page_info.rb +2 -14
  277. data/lib/graphql/types/relay/page_info_behaviors.rb +25 -0
  278. data/lib/graphql/types/relay.rb +11 -3
  279. data/lib/graphql/types/string.rb +8 -2
  280. data/lib/graphql/unauthorized_error.rb +1 -1
  281. data/lib/graphql/union_type.rb +3 -1
  282. data/lib/graphql/upgrader/member.rb +1 -0
  283. data/lib/graphql/upgrader/schema.rb +1 -0
  284. data/lib/graphql/version.rb +1 -1
  285. data/lib/graphql.rb +68 -37
  286. data/readme.md +3 -6
  287. metadata +83 -113
  288. data/lib/graphql/execution/interpreter/hash_response.rb +0 -46
  289. data/lib/graphql/types/relay/base_field.rb +0 -22
  290. data/lib/graphql/types/relay/base_interface.rb +0 -29
  291. data/lib/graphql/types/relay/base_object.rb +0 -26
  292. /data/lib/generators/graphql/{templates → install/templates}/base_mutation.erb +0 -0
  293. /data/lib/generators/graphql/{templates → install/templates}/mutation_type.erb +0 -0
@@ -1,22 +1,22 @@
1
1
  # frozen_string_literal: true
2
- # test_via: ../object.rb
3
2
  require "graphql/schema/field/connection_extension"
4
3
  require "graphql/schema/field/scope_extension"
5
4
 
6
5
  module GraphQL
7
6
  class Schema
8
7
  class Field
9
- if !String.method_defined?(:-@)
10
- using GraphQL::StringDedupBackport
11
- end
12
-
13
8
  include GraphQL::Schema::Member::CachedGraphQLDefinition
14
9
  include GraphQL::Schema::Member::AcceptsDefinition
15
10
  include GraphQL::Schema::Member::HasArguments
16
11
  include GraphQL::Schema::Member::HasAstNode
17
12
  include GraphQL::Schema::Member::HasPath
13
+ include GraphQL::Schema::Member::HasValidators
18
14
  extend GraphQL::Schema::FindInheritedValue
19
15
  include GraphQL::Schema::FindInheritedValue::EmptyObjects
16
+ include GraphQL::Schema::Member::HasDirectives
17
+ include GraphQL::Schema::Member::HasDeprecationReason
18
+
19
+ class FieldImplementationFailed < GraphQL::Error; end
20
20
 
21
21
  # @return [String] the GraphQL name for this field, camelized unless `camelize: false` is provided
22
22
  attr_reader :name
@@ -24,9 +24,6 @@ module GraphQL
24
24
 
25
25
  attr_writer :description
26
26
 
27
- # @return [String, nil] If present, the field is marked as deprecated with this documentation
28
- attr_accessor :deprecation_reason
29
-
30
27
  # @return [Symbol] Method or hash key on the underlying object to look up
31
28
  attr_reader :method_sym
32
29
 
@@ -41,7 +38,9 @@ module GraphQL
41
38
 
42
39
  # @return [Class] The GraphQL type this field belongs to. (For fields defined on mutations, it's the payload type)
43
40
  def owner_type
44
- @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
45
44
  owner.payload_type
46
45
  else
47
46
  owner
@@ -61,6 +60,10 @@ module GraphQL
61
60
  @introspection
62
61
  end
63
62
 
63
+ def inspect
64
+ "#<#{self.class} #{path}#{all_argument_definitions.any? ? "(...)" : ""}: #{type.to_type_signature}>"
65
+ end
66
+
64
67
  alias :mutation :resolver
65
68
 
66
69
  # @return [Boolean] Apply tracing to this field? (Default: skip scalars, this is the override value)
@@ -82,11 +85,11 @@ module GraphQL
82
85
  # @see {.initialize} for other options
83
86
  def self.from_options(name = nil, type = nil, desc = nil, resolver: nil, mutation: nil, subscription: nil,**kwargs, &block)
84
87
  if kwargs[:field]
85
- if kwargs[:field] == GraphQL::Relay::Node.field
86
- warn("Legacy-style `GraphQL::Relay::Node.field` is being added to a class-based type. See `GraphQL::Types::Relay::NodeField` for a replacement.")
88
+ if kwargs[:field].is_a?(GraphQL::Field) && kwargs[:field] == GraphQL::Types::Relay::NodeField.graphql_definition
89
+ GraphQL::Deprecation.warn("Legacy-style `GraphQL::Relay::Node.field` is being added to a class-based type. See `GraphQL::Types::Relay::NodeField` for a replacement.")
87
90
  return GraphQL::Types::Relay::NodeField
88
- elsif kwargs[:field] == GraphQL::Relay::Node.plural_field
89
- warn("Legacy-style `GraphQL::Relay::Node.plural_field` is being added to a class-based type. See `GraphQL::Types::Relay::NodesField` for a replacement.")
91
+ elsif kwargs[:field].is_a?(GraphQL::Field) && kwargs[:field] == GraphQL::Types::Relay::NodesField.graphql_definition
92
+ GraphQL::Deprecation.warn("Legacy-style `GraphQL::Relay::Node.plural_field` is being added to a class-based type. See `GraphQL::Types::Relay::NodesField` for a replacement.")
90
93
  return GraphQL::Types::Relay::NodesField
91
94
  end
92
95
  end
@@ -119,6 +122,9 @@ module GraphQL
119
122
  else
120
123
  kwargs[:type] = type
121
124
  end
125
+ if type.is_a?(Class) && type < GraphQL::Schema::Mutation
126
+ raise ArgumentError, "Use `field #{name.inspect}, mutation: Mutation, ...` to provide a mutation to this field instead"
127
+ end
122
128
  end
123
129
  new(**kwargs, &block)
124
130
  end
@@ -170,6 +176,8 @@ module GraphQL
170
176
 
171
177
  # @return Boolean
172
178
  attr_reader :relay_node_field
179
+ # @return Boolean
180
+ attr_reader :relay_nodes_field
173
181
 
174
182
  # @return [Boolean] Should we warn if this field's name conflicts with a built-in method?
175
183
  def method_conflict_warning?
@@ -184,6 +192,7 @@ module GraphQL
184
192
  # @param deprecation_reason [String] If present, the field is marked "deprecated" with this message
185
193
  # @param method [Symbol] The method to call on the underlying object to resolve this field (defaults to `name`)
186
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
187
196
  # @param resolver_method [Symbol] The method on the type to call to resolve this field (defaults to `name`)
188
197
  # @param connection [Boolean] `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name
189
198
  # @param connection_extension [Class] The extension to add, to implement connections. If `nil`, no extension is added.
@@ -199,11 +208,14 @@ module GraphQL
199
208
  # @param scope [Boolean] If true, the return type's `.scope_items` method will be called on the return value
200
209
  # @param subscription_scope [Symbol, String] A key in `context` which will be used to scope subscription payloads
201
210
  # @param extensions [Array<Class, Hash<Class => Object>>] Named extensions to apply to this field (see also {#extension})
211
+ # @param directives [Hash{Class => Hash}] Directives to apply to this field
202
212
  # @param trace [Boolean] If true, a {GraphQL::Tracing} tracer will measure this scalar field
203
213
  # @param broadcastable [Boolean] Whether or not this field can be distributed in subscription broadcasts
204
214
  # @param ast_node [Language::Nodes::FieldDefinition, nil] If this schema was parsed from definition, this AST node defined the field
205
215
  # @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
206
- 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, &definition_block)
216
+ # @param validates [Array<Hash>] Configurations for validating this field
217
+ # @param legacy_edge_class [Class, nil] (DEPRECATED) If present, pass this along to the legacy field definition
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)
207
219
  if name.nil?
208
220
  raise ArgumentError, "missing first `name` argument or keyword `name:`"
209
221
  end
@@ -211,15 +223,13 @@ module GraphQL
211
223
  if type.nil?
212
224
  raise ArgumentError, "missing second `type` argument or keyword `type:`"
213
225
  end
214
- if null.nil?
215
- raise ArgumentError, "missing keyword argument null:"
216
- end
217
226
  end
218
227
  if (field || function || resolve) && extras.any?
219
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:`"
220
229
  end
221
230
  @original_name = name
222
231
  name_s = -name.to_s
232
+
223
233
  @underscored_name = -Member::BuildType.underscore(name_s)
224
234
  @name = -(camelize ? Member::BuildType.camelize(name_s) : name_s)
225
235
  @description = description
@@ -230,10 +240,10 @@ module GraphQL
230
240
  end
231
241
  @function = function
232
242
  @resolve = resolve
233
- @deprecation_reason = deprecation_reason
243
+ self.deprecation_reason = deprecation_reason
234
244
 
235
- if method && hash_key
236
- 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}`)"
237
247
  end
238
248
 
239
249
  if resolver_method
@@ -241,13 +251,18 @@ module GraphQL
241
251
  raise ArgumentError, "Provide `method:` _or_ `resolver_method:`, not both. (called with: `method: #{method.inspect}, resolver_method: #{resolver_method.inspect}`)"
242
252
  end
243
253
 
244
- if hash_key
245
- 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}`)"
246
256
  end
247
257
  end
248
258
 
249
259
  # TODO: I think non-string/symbol hash keys are wrongly normalized (eg `1` will not work)
250
260
  method_name = method || hash_key || name_s
261
+ @dig_keys = dig
262
+ if hash_key
263
+ @hash_key = hash_key
264
+ end
265
+
251
266
  resolver_method ||= name_s.to_sym
252
267
 
253
268
  @method_str = -method_name.to_s
@@ -269,34 +284,51 @@ module GraphQL
269
284
  @relay_nodes_field = relay_nodes_field
270
285
  @ast_node = ast_node
271
286
  @method_conflict_warning = method_conflict_warning
287
+ @legacy_edge_class = legacy_edge_class
272
288
 
273
289
  arguments.each do |name, arg|
274
- if arg.is_a?(Hash)
290
+ case arg
291
+ when Hash
275
292
  argument(name: name, **arg)
276
- else
293
+ when GraphQL::Schema::Argument
277
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}"
278
299
  end
279
300
  end
280
301
 
281
302
  @owner = owner
282
303
  @subscription_scope = subscription_scope
283
304
 
284
- # Do this last so we have as much context as possible when initializing them:
285
305
  @extensions = EMPTY_ARRAY
286
- if extensions.any?
287
- self.extensions(extensions)
288
- end
306
+ @call_after_define = false
289
307
  # This should run before connection extension,
290
308
  # but should it run after the definition block?
291
309
  if scoped?
292
310
  self.extension(ScopeExtension)
293
311
  end
312
+
294
313
  # The problem with putting this after the definition_block
295
314
  # is that it would override arguments
296
315
  if connection? && connection_extension
297
316
  self.extension(connection_extension)
298
317
  end
299
318
 
319
+ # Do this last so we have as much context as possible when initializing them:
320
+ if extensions.any?
321
+ self.extensions(extensions)
322
+ end
323
+
324
+ if directives.any?
325
+ directives.each do |(dir_class, options)|
326
+ self.directive(dir_class, **options)
327
+ end
328
+ end
329
+
330
+ self.validates(validates)
331
+
300
332
  if definition_block
301
333
  if definition_block.arity == 1
302
334
  yield self
@@ -304,6 +336,9 @@ module GraphQL
304
336
  instance_eval(&definition_block)
305
337
  end
306
338
  end
339
+
340
+ self.extensions.each(&:after_define_apply)
341
+ @call_after_define = true
307
342
  end
308
343
 
309
344
  # If true, subscription updates with this field can be shared between viewers
@@ -336,27 +371,20 @@ module GraphQL
336
371
  # @example adding an extension with options
337
372
  # extensions([MyExtensionClass, { AnotherExtensionClass => { filter: true } }])
338
373
  #
339
- # @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.
340
375
  # @return [Array<GraphQL::Schema::FieldExtension>] extensions to apply to this field
341
376
  def extensions(new_extensions = nil)
342
- if new_extensions.nil?
343
- # Read the value
344
- @extensions
345
- else
346
- if @extensions.frozen?
347
- @extensions = @extensions.dup
348
- end
349
- new_extensions.each do |extension|
350
- if extension.is_a?(Hash)
351
- extension = extension.to_a[0]
352
- extension_class, options = *extension
353
- @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)
354
382
  else
355
- extension_class = extension
356
- @extensions << extension_class.new(field: self, options: nil)
383
+ self.extension(extension_config)
357
384
  end
358
385
  end
359
386
  end
387
+ @extensions
360
388
  end
361
389
 
362
390
  # Add `extension` to this field, initialized with `options` if provided.
@@ -367,10 +395,19 @@ module GraphQL
367
395
  # @example adding an extension with options
368
396
  # extension(MyExtensionClass, filter: true)
369
397
  #
370
- # @param extension [Class] subclass of {Schema::Fieldextension}
371
- # @param options [Object] if provided, given as `options:` when initializing `extension`.
372
- def extension(extension, options = nil)
373
- 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
374
411
  end
375
412
 
376
413
  # Read extras (as symbols) from this field,
@@ -391,6 +428,71 @@ module GraphQL
391
428
  end
392
429
  end
393
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
+
394
496
  def complexity(new_complexity = nil)
395
497
  case new_complexity
396
498
  when Proc
@@ -419,6 +521,8 @@ module GraphQL
419
521
  # @return [Integer, nil] Applied to connections if {#has_max_page_size?}
420
522
  attr_reader :max_page_size
421
523
 
524
+ prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
525
+
422
526
  # @return [GraphQL::Field]
423
527
  def to_graphql
424
528
  field_defn = if @field
@@ -438,8 +542,8 @@ module GraphQL
438
542
  field_defn.description = @description
439
543
  end
440
544
 
441
- if @deprecation_reason
442
- field_defn.deprecation_reason = @deprecation_reason
545
+ if self.deprecation_reason
546
+ field_defn.deprecation_reason = self.deprecation_reason
443
547
  end
444
548
 
445
549
  if @resolver_class
@@ -461,6 +565,10 @@ module GraphQL
461
565
  field_defn.relay_nodes_field = @relay_nodes_field
462
566
  end
463
567
 
568
+ if @legacy_edge_class
569
+ field_defn.edge_class = @legacy_edge_class
570
+ end
571
+
464
572
  field_defn.resolve = self.method(:resolve_field)
465
573
  field_defn.connection = connection?
466
574
  field_defn.connection_max_page_size = max_page_size
@@ -469,9 +577,9 @@ module GraphQL
469
577
  field_defn.subscription_scope = @subscription_scope
470
578
  field_defn.ast_node = ast_node
471
579
 
472
- arguments.each do |name, defn|
473
- arg_graphql = defn.to_graphql
474
- 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
475
583
  end
476
584
 
477
585
  # Support a passed-in proc, one way or another
@@ -489,6 +597,7 @@ module GraphQL
489
597
  field_defn
490
598
  end
491
599
 
600
+ class MissingReturnTypeError < GraphQL::Error; end
492
601
  attr_writer :type
493
602
 
494
603
  def type
@@ -496,14 +605,21 @@ module GraphQL
496
605
  Member::BuildType.parse_type(@function.type, null: false)
497
606
  elsif @field
498
607
  Member::BuildType.parse_type(@field.type, null: false)
608
+ elsif @return_type_expr.nil?
609
+ # Not enough info to determine type
610
+ message = "Can't determine the return type for #{self.path}"
611
+ if @resolver_class
612
+ message += " (it has `resolver: #{@resolver_class}`, consider configuration a `type ...` for that class)"
613
+ end
614
+ raise MissingReturnTypeError, message
499
615
  else
500
616
  Member::BuildType.parse_type(@return_type_expr, null: @return_type_null)
501
617
  end
502
- rescue GraphQL::Schema::InvalidDocumentError => err
618
+ rescue GraphQL::Schema::InvalidDocumentError, MissingReturnTypeError => err
503
619
  # Let this propagate up
504
620
  raise err
505
621
  rescue StandardError => err
506
- raise ArgumentError, "Failed to build return type for #{@owner.graphql_name}.#{name} from #{@return_type_expr.inspect}: (#{err.class}) #{err.message}", err.backtrace
622
+ raise MissingReturnTypeError, "Failed to build return type for #{@owner.graphql_name}.#{name} from #{@return_type_expr.inspect}: (#{err.class}) #{err.message}", err.backtrace
507
623
  end
508
624
 
509
625
  def visible?(context)
@@ -524,13 +640,41 @@ module GraphQL
524
640
 
525
641
  def authorized?(object, args, context)
526
642
  if @resolver_class
527
- # The resolver will check itself during `resolve()`
643
+ # The resolver _instance_ will check itself during `resolve()`
528
644
  @resolver_class.authorized?(object, context)
529
645
  else
530
- # Faster than `.any?`
531
- arguments.each_value do |arg|
532
- if args.key?(arg.keyword) && !arg.authorized?(object, args[arg.keyword], context)
533
- 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
534
678
  end
535
679
  end
536
680
  true
@@ -580,12 +724,14 @@ module GraphQL
580
724
  begin
581
725
  # Unwrap the GraphQL object to get the application object.
582
726
  application_object = object.object
727
+
728
+ Schema::Validator.validate!(validators, application_object, ctx, args)
729
+
583
730
  ctx.schema.after_lazy(self.authorized?(application_object, args, ctx)) do |is_authorized|
584
731
  if is_authorized
585
732
  public_send_field(object, args, ctx)
586
733
  else
587
- err = GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: ctx, field: self)
588
- ctx.schema.unauthorized_field(err)
734
+ raise GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: ctx, field: self)
589
735
  end
590
736
  end
591
737
  rescue GraphQL::UnauthorizedFieldError => err
@@ -625,7 +771,7 @@ module GraphQL
625
771
  ruby_kwargs = graphql_args.to_kwargs
626
772
  maybe_lazies = []
627
773
  # Apply any `prepare` methods. Not great code organization, can this go somewhere better?
628
- arguments.each do |name, arg_defn|
774
+ arguments(field_ctx).each do |name, arg_defn|
629
775
  ruby_kwargs_key = arg_defn.keyword
630
776
 
631
777
  if ruby_kwargs.key?(ruby_kwargs_key)
@@ -670,51 +816,107 @@ module GraphQL
670
816
 
671
817
  def public_send_field(unextended_obj, unextended_ruby_kwargs, query_ctx)
672
818
  with_extensions(unextended_obj, unextended_ruby_kwargs, query_ctx) do |obj, ruby_kwargs|
673
- if @resolver_class
674
- if obj.is_a?(GraphQL::Schema::Object)
675
- obj = obj.object
676
- end
677
- obj = @resolver_class.new(object: obj, context: query_ctx, field: self)
678
- end
679
-
680
- # Find a way to resolve this field, checking:
681
- #
682
- # - A method on the type instance;
683
- # - Hash keys, if the wrapped object is a hash;
684
- # - A method on the wrapped object;
685
- # - Or, raise not implemented.
686
- #
687
- if obj.respond_to?(@resolver_method)
688
- # Call the method with kwargs, if there are any
689
- if ruby_kwargs.any?
690
- obj.public_send(@resolver_method, **ruby_kwargs)
691
- else
692
- 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)
693
827
  end
694
- elsif obj.object.is_a?(Hash)
695
- inner_object = obj.object
696
- if inner_object.key?(@method_sym)
697
- 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
698
864
  else
699
- 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
700
874
  end
701
- elsif obj.object.respond_to?(@method_sym)
702
- if ruby_kwargs.any?
703
- 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)
704
895
  else
705
- 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."
706
897
  end
707
- else
708
- raise <<-ERR
709
- 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
710
910
 
711
- - `#{obj.class}##{@resolver_method}`, which did not exist
712
- - `#{obj.object.class}##{@method_sym}`, which did not exist
713
- - 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:
714
914
 
715
- To implement this field, define one of the methods above (and check for typos)
716
- ERR
717
- 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
718
920
  end
719
921
  end
720
922
 
@@ -725,33 +927,52 @@ module GraphQL
725
927
  if @extensions.empty?
726
928
  yield(obj, args)
727
929
  else
728
- extended_obj = obj
729
- extended_args = args
730
-
731
- memos = []
732
- value = run_extensions_before_resolve(memos, obj, args, ctx) do |obj, args|
733
- extended_obj = obj
734
- extended_args = args
930
+ # This is a hack to get the _last_ value for extended obj and args,
931
+ # in case one of the extensions doesn't `yield`.
932
+ # (There's another implementation that uses multiple-return, but I'm wary of the perf cost of the extra arrays)
933
+ extended = { args: args, obj: obj, memos: nil, added_extras: nil }
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
735
939
  yield(obj, args)
736
940
  end
737
941
 
942
+ extended_obj = extended[:obj]
943
+ extended_args = extended[:args]
944
+ memos = extended[:memos] || EMPTY_HASH
945
+
738
946
  ctx.schema.after_lazy(value) do |resolved_value|
739
- @extensions.each_with_index do |ext, idx|
947
+ idx = 0
948
+ @extensions.each do |ext|
740
949
  memo = memos[idx]
741
950
  # TODO after_lazy?
742
951
  resolved_value = ext.after_resolve(object: extended_obj, arguments: extended_args, context: ctx, value: resolved_value, memo: memo)
952
+ idx += 1
743
953
  end
744
954
  resolved_value
745
955
  end
746
956
  end
747
957
  end
748
958
 
749
- def run_extensions_before_resolve(memos, obj, args, ctx, idx: 0)
959
+ def run_extensions_before_resolve(obj, args, ctx, extended, idx: 0)
750
960
  extension = @extensions[idx]
751
961
  if extension
752
962
  extension.resolve(object: obj, arguments: args, context: ctx) do |extended_obj, extended_args, memo|
753
- memos << memo
754
- run_extensions_before_resolve(memos, extended_obj, extended_args, ctx, idx: idx + 1) { |o, a| yield(o, a) }
963
+ if memo
964
+ memos = extended[:memos] ||= {}
965
+ memos[idx] = memo
966
+ end
967
+
968
+ if (extras = extension.added_extras)
969
+ ae = extended[:added_extras] ||= []
970
+ ae.concat(extras)
971
+ end
972
+
973
+ extended[:obj] = extended_obj
974
+ extended[:args] = extended_args
975
+ run_extensions_before_resolve(extended_obj, extended_args, ctx, extended, idx: idx + 1) { |o, a| yield(o, a) }
755
976
  end
756
977
  else
757
978
  yield(obj, args)