graphql 1.10.1 → 1.13.0

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 (292) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/core.rb +18 -2
  3. data/lib/generators/graphql/install_generator.rb +36 -6
  4. data/lib/generators/graphql/loader_generator.rb +1 -0
  5. data/lib/generators/graphql/mutation_generator.rb +2 -1
  6. data/lib/generators/graphql/object_generator.rb +54 -9
  7. data/lib/generators/graphql/relay.rb +63 -0
  8. data/lib/generators/graphql/relay_generator.rb +21 -0
  9. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  10. data/lib/generators/graphql/templates/base_connection.erb +8 -0
  11. data/lib/generators/graphql/templates/base_edge.erb +8 -0
  12. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  13. data/lib/generators/graphql/templates/base_field.erb +2 -0
  14. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  15. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  16. data/lib/generators/graphql/templates/base_mutation.erb +2 -0
  17. data/lib/generators/graphql/templates/base_object.erb +2 -0
  18. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  19. data/lib/generators/graphql/templates/base_union.erb +2 -0
  20. data/lib/generators/graphql/templates/enum.erb +2 -0
  21. data/lib/generators/graphql/templates/graphql_controller.erb +16 -12
  22. data/lib/generators/graphql/templates/interface.erb +2 -0
  23. data/lib/generators/graphql/templates/loader.erb +2 -0
  24. data/lib/generators/graphql/templates/mutation.erb +2 -0
  25. data/lib/generators/graphql/templates/mutation_type.erb +2 -0
  26. data/lib/generators/graphql/templates/node_type.erb +9 -0
  27. data/lib/generators/graphql/templates/object.erb +3 -1
  28. data/lib/generators/graphql/templates/query_type.erb +3 -3
  29. data/lib/generators/graphql/templates/scalar.erb +2 -0
  30. data/lib/generators/graphql/templates/schema.erb +21 -33
  31. data/lib/generators/graphql/templates/union.erb +3 -1
  32. data/lib/generators/graphql/type_generator.rb +1 -1
  33. data/lib/graphql/analysis/analyze_query.rb +7 -0
  34. data/lib/graphql/analysis/ast/field_usage.rb +24 -1
  35. data/lib/graphql/analysis/ast/query_complexity.rb +126 -109
  36. data/lib/graphql/analysis/ast/visitor.rb +13 -5
  37. data/lib/graphql/analysis/ast.rb +11 -2
  38. data/lib/graphql/argument.rb +3 -3
  39. data/lib/graphql/backtrace/inspect_result.rb +0 -1
  40. data/lib/graphql/backtrace/legacy_tracer.rb +56 -0
  41. data/lib/graphql/backtrace/table.rb +34 -3
  42. data/lib/graphql/backtrace/traced_error.rb +0 -1
  43. data/lib/graphql/backtrace/tracer.rb +40 -9
  44. data/lib/graphql/backtrace.rb +28 -19
  45. data/lib/graphql/backwards_compatibility.rb +2 -1
  46. data/lib/graphql/base_type.rb +1 -1
  47. data/lib/graphql/compatibility/execution_specification/specification_schema.rb +2 -2
  48. data/lib/graphql/compatibility/execution_specification.rb +1 -0
  49. data/lib/graphql/compatibility/lazy_execution_specification.rb +2 -0
  50. data/lib/graphql/compatibility/query_parser_specification.rb +2 -0
  51. data/lib/graphql/compatibility/schema_parser_specification.rb +2 -0
  52. data/lib/graphql/dataloader/null_dataloader.rb +22 -0
  53. data/lib/graphql/dataloader/request.rb +19 -0
  54. data/lib/graphql/dataloader/request_all.rb +19 -0
  55. data/lib/graphql/dataloader/source.rb +155 -0
  56. data/lib/graphql/dataloader.rb +308 -0
  57. data/lib/graphql/define/assign_global_id_field.rb +2 -2
  58. data/lib/graphql/define/defined_object_proxy.rb +1 -1
  59. data/lib/graphql/define/instance_definable.rb +34 -4
  60. data/lib/graphql/define/type_definer.rb +5 -5
  61. data/lib/graphql/deprecated_dsl.rb +18 -5
  62. data/lib/graphql/deprecation.rb +9 -0
  63. data/lib/graphql/directive.rb +4 -4
  64. data/lib/graphql/enum_type.rb +7 -1
  65. data/lib/graphql/execution/errors.rb +110 -7
  66. data/lib/graphql/execution/execute.rb +8 -1
  67. data/lib/graphql/execution/instrumentation.rb +1 -1
  68. data/lib/graphql/execution/interpreter/argument_value.rb +28 -0
  69. data/lib/graphql/execution/interpreter/arguments.rb +88 -0
  70. data/lib/graphql/execution/interpreter/arguments_cache.rb +103 -0
  71. data/lib/graphql/execution/interpreter/handles_raw_value.rb +18 -0
  72. data/lib/graphql/execution/interpreter/resolve.rb +37 -25
  73. data/lib/graphql/execution/interpreter/runtime.rb +685 -421
  74. data/lib/graphql/execution/interpreter.rb +42 -13
  75. data/lib/graphql/execution/lazy.rb +5 -1
  76. data/lib/graphql/execution/lookahead.rb +25 -110
  77. data/lib/graphql/execution/multiplex.rb +37 -25
  78. data/lib/graphql/field.rb +5 -1
  79. data/lib/graphql/function.rb +4 -0
  80. data/lib/graphql/input_object_type.rb +6 -0
  81. data/lib/graphql/integer_decoding_error.rb +17 -0
  82. data/lib/graphql/integer_encoding_error.rb +18 -2
  83. data/lib/graphql/interface_type.rb +7 -0
  84. data/lib/graphql/internal_representation/document.rb +2 -2
  85. data/lib/graphql/internal_representation/rewrite.rb +1 -1
  86. data/lib/graphql/internal_representation/scope.rb +2 -2
  87. data/lib/graphql/internal_representation/visit.rb +2 -2
  88. data/lib/graphql/introspection/directive_type.rb +8 -4
  89. data/lib/graphql/introspection/entry_points.rb +2 -2
  90. data/lib/graphql/introspection/enum_value_type.rb +2 -2
  91. data/lib/graphql/introspection/field_type.rb +9 -5
  92. data/lib/graphql/introspection/input_value_type.rb +15 -3
  93. data/lib/graphql/introspection/introspection_query.rb +6 -92
  94. data/lib/graphql/introspection/schema_type.rb +4 -4
  95. data/lib/graphql/introspection/type_type.rb +16 -12
  96. data/lib/graphql/introspection.rb +96 -0
  97. data/lib/graphql/invalid_null_error.rb +18 -0
  98. data/lib/graphql/language/block_string.rb +20 -5
  99. data/lib/graphql/language/cache.rb +37 -0
  100. data/lib/graphql/language/document_from_schema_definition.rb +73 -25
  101. data/lib/graphql/language/lexer.rb +4 -3
  102. data/lib/graphql/language/lexer.rl +3 -3
  103. data/lib/graphql/language/nodes.rb +51 -89
  104. data/lib/graphql/language/parser.rb +552 -530
  105. data/lib/graphql/language/parser.y +114 -99
  106. data/lib/graphql/language/printer.rb +7 -2
  107. data/lib/graphql/language/sanitized_printer.rb +222 -0
  108. data/lib/graphql/language/token.rb +0 -4
  109. data/lib/graphql/language/visitor.rb +2 -2
  110. data/lib/graphql/language.rb +2 -0
  111. data/lib/graphql/name_validator.rb +2 -7
  112. data/lib/graphql/object_type.rb +44 -35
  113. data/lib/graphql/pagination/active_record_relation_connection.rb +14 -1
  114. data/lib/graphql/pagination/array_connection.rb +2 -2
  115. data/lib/graphql/pagination/connection.rb +75 -20
  116. data/lib/graphql/pagination/connections.rb +83 -31
  117. data/lib/graphql/pagination/relation_connection.rb +34 -14
  118. data/lib/graphql/parse_error.rb +0 -1
  119. data/lib/graphql/query/arguments.rb +4 -3
  120. data/lib/graphql/query/arguments_cache.rb +1 -2
  121. data/lib/graphql/query/context.rb +42 -7
  122. data/lib/graphql/query/executor.rb +0 -1
  123. data/lib/graphql/query/fingerprint.rb +26 -0
  124. data/lib/graphql/query/input_validation_result.rb +23 -6
  125. data/lib/graphql/query/literal_input.rb +1 -1
  126. data/lib/graphql/query/null_context.rb +24 -8
  127. data/lib/graphql/query/serial_execution/field_resolution.rb +1 -1
  128. data/lib/graphql/query/serial_execution.rb +1 -0
  129. data/lib/graphql/query/validation_pipeline.rb +5 -2
  130. data/lib/graphql/query/variable_validation_error.rb +1 -1
  131. data/lib/graphql/query/variables.rb +14 -4
  132. data/lib/graphql/query.rb +68 -13
  133. data/lib/graphql/railtie.rb +9 -1
  134. data/lib/graphql/rake_task.rb +12 -9
  135. data/lib/graphql/relay/array_connection.rb +10 -12
  136. data/lib/graphql/relay/base_connection.rb +26 -13
  137. data/lib/graphql/relay/connection_instrumentation.rb +4 -4
  138. data/lib/graphql/relay/connection_type.rb +1 -1
  139. data/lib/graphql/relay/edges_instrumentation.rb +0 -1
  140. data/lib/graphql/relay/mutation.rb +1 -0
  141. data/lib/graphql/relay/node.rb +3 -0
  142. data/lib/graphql/relay/range_add.rb +23 -9
  143. data/lib/graphql/relay/relation_connection.rb +8 -10
  144. data/lib/graphql/relay/type_extensions.rb +2 -0
  145. data/lib/graphql/rubocop/graphql/base_cop.rb +36 -0
  146. data/lib/graphql/rubocop/graphql/default_null_true.rb +43 -0
  147. data/lib/graphql/rubocop/graphql/default_required_true.rb +43 -0
  148. data/lib/graphql/rubocop.rb +4 -0
  149. data/lib/graphql/scalar_type.rb +16 -1
  150. data/lib/graphql/schema/addition.rb +247 -0
  151. data/lib/graphql/schema/argument.rb +210 -12
  152. data/lib/graphql/schema/base_64_encoder.rb +2 -0
  153. data/lib/graphql/schema/build_from_definition/resolve_map.rb +3 -1
  154. data/lib/graphql/schema/build_from_definition.rb +213 -86
  155. data/lib/graphql/schema/default_type_error.rb +2 -0
  156. data/lib/graphql/schema/directive/deprecated.rb +1 -1
  157. data/lib/graphql/schema/directive/feature.rb +1 -1
  158. data/lib/graphql/schema/directive/flagged.rb +57 -0
  159. data/lib/graphql/schema/directive/include.rb +1 -1
  160. data/lib/graphql/schema/directive/skip.rb +1 -1
  161. data/lib/graphql/schema/directive/transform.rb +14 -2
  162. data/lib/graphql/schema/directive.rb +78 -2
  163. data/lib/graphql/schema/enum.rb +80 -9
  164. data/lib/graphql/schema/enum_value.rb +17 -6
  165. data/lib/graphql/schema/field/connection_extension.rb +46 -30
  166. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  167. data/lib/graphql/schema/field.rb +285 -133
  168. data/lib/graphql/schema/find_inherited_value.rb +4 -1
  169. data/lib/graphql/schema/finder.rb +5 -5
  170. data/lib/graphql/schema/input_object.rb +97 -89
  171. data/lib/graphql/schema/interface.rb +24 -19
  172. data/lib/graphql/schema/late_bound_type.rb +2 -2
  173. data/lib/graphql/schema/list.rb +7 -1
  174. data/lib/graphql/schema/loader.rb +137 -103
  175. data/lib/graphql/schema/member/accepts_definition.rb +8 -1
  176. data/lib/graphql/schema/member/base_dsl_methods.rb +15 -19
  177. data/lib/graphql/schema/member/build_type.rb +14 -7
  178. data/lib/graphql/schema/member/has_arguments.rb +205 -12
  179. data/lib/graphql/schema/member/has_ast_node.rb +4 -1
  180. data/lib/graphql/schema/member/has_deprecation_reason.rb +25 -0
  181. data/lib/graphql/schema/member/has_directives.rb +98 -0
  182. data/lib/graphql/schema/member/has_fields.rb +95 -30
  183. data/lib/graphql/schema/member/has_interfaces.rb +90 -0
  184. data/lib/graphql/schema/member/has_unresolved_type_error.rb +15 -0
  185. data/lib/graphql/schema/member/has_validators.rb +31 -0
  186. data/lib/graphql/schema/member/instrumentation.rb +0 -1
  187. data/lib/graphql/schema/member/type_system_helpers.rb +3 -3
  188. data/lib/graphql/schema/member.rb +6 -0
  189. data/lib/graphql/schema/middleware_chain.rb +1 -1
  190. data/lib/graphql/schema/mutation.rb +4 -0
  191. data/lib/graphql/schema/non_null.rb +5 -0
  192. data/lib/graphql/schema/object.rb +47 -46
  193. data/lib/graphql/schema/possible_types.rb +9 -4
  194. data/lib/graphql/schema/printer.rb +16 -34
  195. data/lib/graphql/schema/relay_classic_mutation.rb +32 -4
  196. data/lib/graphql/schema/resolver/has_payload_type.rb +34 -4
  197. data/lib/graphql/schema/resolver.rb +123 -63
  198. data/lib/graphql/schema/scalar.rb +11 -1
  199. data/lib/graphql/schema/subscription.rb +57 -21
  200. data/lib/graphql/schema/timeout.rb +29 -15
  201. data/lib/graphql/schema/timeout_middleware.rb +3 -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 +41 -1
  205. data/lib/graphql/schema/unique_within_type.rb +1 -2
  206. data/lib/graphql/schema/validation.rb +12 -2
  207. data/lib/graphql/schema/validator/allow_blank_validator.rb +29 -0
  208. data/lib/graphql/schema/validator/allow_null_validator.rb +26 -0
  209. data/lib/graphql/schema/validator/exclusion_validator.rb +33 -0
  210. data/lib/graphql/schema/validator/format_validator.rb +48 -0
  211. data/lib/graphql/schema/validator/inclusion_validator.rb +35 -0
  212. data/lib/graphql/schema/validator/length_validator.rb +59 -0
  213. data/lib/graphql/schema/validator/numericality_validator.rb +82 -0
  214. data/lib/graphql/schema/validator/required_validator.rb +68 -0
  215. data/lib/graphql/schema/validator.rb +174 -0
  216. data/lib/graphql/schema/warden.rb +153 -28
  217. data/lib/graphql/schema.rb +364 -330
  218. data/lib/graphql/static_validation/all_rules.rb +1 -0
  219. data/lib/graphql/static_validation/base_visitor.rb +8 -5
  220. data/lib/graphql/static_validation/definition_dependencies.rb +0 -1
  221. data/lib/graphql/static_validation/error.rb +3 -1
  222. data/lib/graphql/static_validation/literal_validator.rb +51 -26
  223. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +44 -87
  224. data/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb +22 -6
  225. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +28 -22
  226. data/lib/graphql/static_validation/rules/arguments_are_defined_error.rb +4 -2
  227. data/lib/graphql/static_validation/rules/directives_are_defined.rb +1 -1
  228. data/lib/graphql/static_validation/rules/fields_will_merge.rb +79 -43
  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/required_arguments_are_present.rb +1 -1
  234. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +6 -7
  235. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +9 -10
  236. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +8 -8
  237. data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +4 -2
  238. data/lib/graphql/static_validation/validation_context.rb +9 -3
  239. data/lib/graphql/static_validation/validation_timeout_error.rb +25 -0
  240. data/lib/graphql/static_validation/validator.rb +42 -8
  241. data/lib/graphql/static_validation.rb +1 -0
  242. data/lib/graphql/string_encoding_error.rb +13 -3
  243. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +118 -19
  244. data/lib/graphql/subscriptions/broadcast_analyzer.rb +81 -0
  245. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +21 -0
  246. data/lib/graphql/subscriptions/event.rb +81 -30
  247. data/lib/graphql/subscriptions/instrumentation.rb +0 -1
  248. data/lib/graphql/subscriptions/serialize.rb +33 -6
  249. data/lib/graphql/subscriptions/subscription_root.rb +15 -4
  250. data/lib/graphql/subscriptions.rb +88 -45
  251. data/lib/graphql/tracing/active_support_notifications_tracing.rb +2 -1
  252. data/lib/graphql/tracing/appoptics_tracing.rb +173 -0
  253. data/lib/graphql/tracing/appsignal_tracing.rb +15 -0
  254. data/lib/graphql/tracing/new_relic_tracing.rb +1 -12
  255. data/lib/graphql/tracing/platform_tracing.rb +43 -17
  256. data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +4 -1
  257. data/lib/graphql/tracing/scout_tracing.rb +11 -0
  258. data/lib/graphql/tracing/skylight_tracing.rb +1 -1
  259. data/lib/graphql/tracing/statsd_tracing.rb +42 -0
  260. data/lib/graphql/tracing.rb +9 -33
  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 +3 -3
  264. data/lib/graphql/types/iso_8601_date_time.rb +25 -10
  265. data/lib/graphql/types/relay/base_connection.rb +6 -90
  266. data/lib/graphql/types/relay/base_edge.rb +2 -34
  267. data/lib/graphql/types/relay/connection_behaviors.rb +156 -0
  268. data/lib/graphql/types/relay/default_relay.rb +27 -0
  269. data/lib/graphql/types/relay/edge_behaviors.rb +53 -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 +2 -20
  275. data/lib/graphql/types/relay/nodes_field.rb +2 -20
  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 +2 -2
  281. data/lib/graphql/union_type.rb +2 -0
  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 +65 -31
  286. data/readme.md +3 -6
  287. metadata +77 -112
  288. data/lib/graphql/execution/interpreter/hash_response.rb +0 -46
  289. data/lib/graphql/literal_validation_error.rb +0 -6
  290. data/lib/graphql/types/relay/base_field.rb +0 -22
  291. data/lib/graphql/types/relay/base_interface.rb +0 -29
  292. data/lib/graphql/types/relay/base_object.rb +0 -26
@@ -1,22 +1,20 @@
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
20
18
 
21
19
  # @return [String] the GraphQL name for this field, camelized unless `camelize: false` is provided
22
20
  attr_reader :name
@@ -24,9 +22,6 @@ module GraphQL
24
22
 
25
23
  attr_writer :description
26
24
 
27
- # @return [String, nil] If present, the field is marked as deprecated with this documentation
28
- attr_accessor :deprecation_reason
29
-
30
25
  # @return [Symbol] Method or hash key on the underlying object to look up
31
26
  attr_reader :method_sym
32
27
 
@@ -36,9 +31,18 @@ module GraphQL
36
31
  # @return [Symbol] The method on the type to look up
37
32
  attr_reader :resolver_method
38
33
 
39
- # @return [Class] The type that this field belongs to
34
+ # @return [Class] The thing this field was defined on (type, mutation, resolver)
40
35
  attr_accessor :owner
41
36
 
37
+ # @return [Class] The GraphQL type this field belongs to. (For fields defined on mutations, it's the payload type)
38
+ def owner_type
39
+ @owner_type ||= if owner < GraphQL::Schema::Mutation
40
+ owner.payload_type
41
+ else
42
+ owner
43
+ end
44
+ end
45
+
42
46
  # @return [Symbol] the original name of the field, passed in by the user
43
47
  attr_reader :original_name
44
48
 
@@ -47,6 +51,15 @@ module GraphQL
47
51
  @resolver_class
48
52
  end
49
53
 
54
+ # @return [Boolean] Is this field a predefined introspection field?
55
+ def introspection?
56
+ @introspection
57
+ end
58
+
59
+ def inspect
60
+ "#<#{self.class} #{path}#{all_argument_definitions.any? ? "(...)" : ""}: #{type.to_type_signature}>"
61
+ end
62
+
50
63
  alias :mutation :resolver
51
64
 
52
65
  # @return [Boolean] Apply tracing to this field? (Default: skip scalars, this is the override value)
@@ -68,11 +81,11 @@ module GraphQL
68
81
  # @see {.initialize} for other options
69
82
  def self.from_options(name = nil, type = nil, desc = nil, resolver: nil, mutation: nil, subscription: nil,**kwargs, &block)
70
83
  if kwargs[:field]
71
- if kwargs[:field] == GraphQL::Relay::Node.field
72
- warn("Legacy-style `GraphQL::Relay::Node.field` is being added to a class-based type. See `GraphQL::Types::Relay::NodeField` for a replacement.")
84
+ if kwargs[:field].is_a?(GraphQL::Field) && kwargs[:field] == GraphQL::Types::Relay::NodeField.graphql_definition
85
+ 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.")
73
86
  return GraphQL::Types::Relay::NodeField
74
- elsif kwargs[:field] == GraphQL::Relay::Node.plural_field
75
- warn("Legacy-style `GraphQL::Relay::Node.plural_field` is being added to a class-based type. See `GraphQL::Types::Relay::NodesField` for a replacement.")
87
+ elsif kwargs[:field].is_a?(GraphQL::Field) && kwargs[:field] == GraphQL::Types::Relay::NodesField.graphql_definition
88
+ 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.")
76
89
  return GraphQL::Types::Relay::NodesField
77
90
  end
78
91
  end
@@ -105,6 +118,9 @@ module GraphQL
105
118
  else
106
119
  kwargs[:type] = type
107
120
  end
121
+ if type.is_a?(Class) && type < GraphQL::Schema::Mutation
122
+ raise ArgumentError, "Use `field #{name.inspect}, mutation: Mutation, ...` to provide a mutation to this field instead"
123
+ end
108
124
  end
109
125
  new(**kwargs, &block)
110
126
  end
@@ -172,7 +188,8 @@ module GraphQL
172
188
  # @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`)
173
189
  # @param resolver_method [Symbol] The method on the type to call to resolve this field (defaults to `name`)
174
190
  # @param connection [Boolean] `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name
175
- # @param max_page_size [Integer] For connections, the maximum number of items to return from this field
191
+ # @param connection_extension [Class] The extension to add, to implement connections. If `nil`, no extension is added.
192
+ # @param max_page_size [Integer, nil] For connections, the maximum number of items to return from this field, or `nil` to allow unlimited results.
176
193
  # @param introspection [Boolean] If true, this field will be marked as `#introspection?` and the name may begin with `__`
177
194
  # @param resolve [<#call(obj, args, ctx)>] **deprecated** for compatibility with <1.8.0
178
195
  # @param field [GraphQL::Field, GraphQL::Schema::Field] **deprecated** for compatibility with <1.8.0
@@ -184,10 +201,14 @@ module GraphQL
184
201
  # @param scope [Boolean] If true, the return type's `.scope_items` method will be called on the return value
185
202
  # @param subscription_scope [Symbol, String] A key in `context` which will be used to scope subscription payloads
186
203
  # @param extensions [Array<Class, Hash<Class => Object>>] Named extensions to apply to this field (see also {#extension})
204
+ # @param directives [Hash{Class => Hash}] Directives to apply to this field
187
205
  # @param trace [Boolean] If true, a {GraphQL::Tracing} tracer will measure this scalar field
206
+ # @param broadcastable [Boolean] Whether or not this field can be distributed in subscription broadcasts
188
207
  # @param ast_node [Language::Nodes::FieldDefinition, nil] If this schema was parsed from definition, this AST node defined the field
189
208
  # @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
190
- 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: nil, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, ast_node: nil, extras: [], extensions: EMPTY_ARRAY, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, arguments: EMPTY_HASH, &definition_block)
209
+ # @param validates [Array<Hash>] Configurations for validating this field
210
+ # @param legacy_edge_class [Class, nil] (DEPRECATED) If present, pass this along to the legacy field definition
211
+ def initialize(type: nil, name: nil, owner: nil, null: true, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, resolver_method: nil, resolve: nil, connection: nil, max_page_size: :not_given, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: nil, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, legacy_edge_class: nil, &definition_block)
191
212
  if name.nil?
192
213
  raise ArgumentError, "missing first `name` argument or keyword `name:`"
193
214
  end
@@ -195,9 +216,6 @@ module GraphQL
195
216
  if type.nil?
196
217
  raise ArgumentError, "missing second `type` argument or keyword `type:`"
197
218
  end
198
- if null.nil?
199
- raise ArgumentError, "missing keyword argument null:"
200
- end
201
219
  end
202
220
  if (field || function || resolve) && extras.any?
203
221
  raise ArgumentError, "keyword `extras:` may only be used with method-based resolve and class-based field such as mutation class, please remove `field:`, `function:` or `resolve:`"
@@ -214,7 +232,7 @@ module GraphQL
214
232
  end
215
233
  @function = function
216
234
  @resolve = resolve
217
- @deprecation_reason = deprecation_reason
235
+ self.deprecation_reason = deprecation_reason
218
236
 
219
237
  if method && hash_key
220
238
  raise ArgumentError, "Provide `method:` _or_ `hash_key:`, not both. (called with: `method: #{method.inspect}, hash_key: #{hash_key.inspect}`)"
@@ -231,19 +249,21 @@ module GraphQL
231
249
  end
232
250
 
233
251
  # TODO: I think non-string/symbol hash keys are wrongly normalized (eg `1` will not work)
234
- method_name = method || hash_key || @underscored_name
235
- resolver_method ||= @underscored_name.to_sym
252
+ method_name = method || hash_key || name_s
253
+ resolver_method ||= name_s.to_sym
236
254
 
237
- @method_str = method_name.to_s
255
+ @method_str = -method_name.to_s
238
256
  @method_sym = method_name.to_sym
239
257
  @resolver_method = resolver_method
240
258
  @complexity = complexity
241
259
  @return_type_expr = type
242
260
  @return_type_null = null
243
261
  @connection = connection
244
- @max_page_size = max_page_size
262
+ @has_max_page_size = max_page_size != :not_given
263
+ @max_page_size = max_page_size == :not_given ? nil : max_page_size
245
264
  @introspection = introspection
246
265
  @extras = extras
266
+ @broadcastable = broadcastable
247
267
  @resolver_class = resolver_class
248
268
  @scope = scope
249
269
  @trace = trace
@@ -251,34 +271,50 @@ module GraphQL
251
271
  @relay_nodes_field = relay_nodes_field
252
272
  @ast_node = ast_node
253
273
  @method_conflict_warning = method_conflict_warning
274
+ @legacy_edge_class = legacy_edge_class
254
275
 
255
276
  arguments.each do |name, arg|
256
- if arg.is_a?(Hash)
277
+ case arg
278
+ when Hash
257
279
  argument(name: name, **arg)
280
+ when GraphQL::Schema::Argument
281
+ add_argument(arg)
282
+ when Array
283
+ arg.each { |a| add_argument(a) }
258
284
  else
259
- own_arguments[name] = arg
285
+ raise ArgumentError, "Unexpected argument config (#{arg.class}): #{arg.inspect}"
260
286
  end
261
287
  end
262
288
 
263
289
  @owner = owner
264
290
  @subscription_scope = subscription_scope
265
291
 
266
- # Do this last so we have as much context as possible when initializing them:
267
- @extensions = []
268
- if extensions.any?
269
- self.extensions(extensions)
270
- end
292
+ @extensions = EMPTY_ARRAY
271
293
  # This should run before connection extension,
272
294
  # but should it run after the definition block?
273
295
  if scoped?
274
296
  self.extension(ScopeExtension)
275
297
  end
298
+
276
299
  # The problem with putting this after the definition_block
277
300
  # is that it would override arguments
278
- if connection?
279
- self.extension(self.class.connection_extension)
301
+ if connection? && connection_extension
302
+ self.extension(connection_extension)
280
303
  end
281
304
 
305
+ # Do this last so we have as much context as possible when initializing them:
306
+ if extensions.any?
307
+ self.extensions(extensions)
308
+ end
309
+
310
+ if directives.any?
311
+ directives.each do |(dir_class, options)|
312
+ self.directive(dir_class, **options)
313
+ end
314
+ end
315
+
316
+ self.validates(validates)
317
+
282
318
  if definition_block
283
319
  if definition_block.arity == 1
284
320
  yield self
@@ -288,6 +324,13 @@ module GraphQL
288
324
  end
289
325
  end
290
326
 
327
+ # If true, subscription updates with this field can be shared between viewers
328
+ # @return [Boolean, nil]
329
+ # @see GraphQL::Subscriptions::BroadcastAnalyzer
330
+ def broadcastable?
331
+ @broadcastable
332
+ end
333
+
291
334
  # @param text [String]
292
335
  # @return [String]
293
336
  def description(text = nil)
@@ -318,6 +361,9 @@ module GraphQL
318
361
  # Read the value
319
362
  @extensions
320
363
  else
364
+ if @extensions.frozen?
365
+ @extensions = @extensions.dup
366
+ end
321
367
  new_extensions.each do |extension|
322
368
  if extension.is_a?(Hash)
323
369
  extension = extension.to_a[0]
@@ -355,11 +401,65 @@ module GraphQL
355
401
  # Read the value
356
402
  @extras
357
403
  else
404
+ if @extras.frozen?
405
+ @extras = @extras.dup
406
+ end
358
407
  # Append to the set of extras on this field
359
408
  @extras.concat(new_extras)
360
409
  end
361
410
  end
362
411
 
412
+ def calculate_complexity(query:, nodes:, child_complexity:)
413
+ if respond_to?(:complexity_for)
414
+ lookahead = GraphQL::Execution::Lookahead.new(query: query, field: self, ast_nodes: nodes, owner_type: owner)
415
+ complexity_for(child_complexity: child_complexity, query: query, lookahead: lookahead)
416
+ elsif connection?
417
+ arguments = query.arguments_for(nodes.first, self)
418
+ max_possible_page_size = nil
419
+ if arguments[:first]
420
+ max_possible_page_size = arguments[:first]
421
+ end
422
+ if arguments[:last] && (max_possible_page_size.nil? || arguments[:last] > max_possible_page_size)
423
+ max_possible_page_size = arguments[:last]
424
+ end
425
+
426
+ if max_possible_page_size.nil?
427
+ max_possible_page_size = max_page_size || query.schema.default_max_page_size
428
+ end
429
+
430
+ if max_possible_page_size.nil?
431
+ raise GraphQL::Error, "Can't calculate complexity for #{path}, no `first:`, `last:`, `max_page_size` or `default_max_page_size`"
432
+ else
433
+ metadata_complexity = 0
434
+ lookahead = GraphQL::Execution::Lookahead.new(query: query, field: self, ast_nodes: nodes, owner_type: owner)
435
+
436
+ if (page_info_lookahead = lookahead.selection(:page_info)).selected?
437
+ metadata_complexity += 1 # pageInfo
438
+ metadata_complexity += page_info_lookahead.selections.size # subfields
439
+ end
440
+
441
+ if lookahead.selects?(:total) || lookahead.selects?(:total_count) || lookahead.selects?(:count)
442
+ metadata_complexity += 1
443
+ end
444
+ # Possible bug: selections on `edges` and `nodes` are _both_ multiplied here. Should they be?
445
+ items_complexity = child_complexity - metadata_complexity
446
+ # Add 1 for _this_ field
447
+ 1 + (max_possible_page_size * items_complexity) + metadata_complexity
448
+ end
449
+ else
450
+ defined_complexity = complexity
451
+ case defined_complexity
452
+ when Proc
453
+ arguments = query.arguments_for(nodes.first, self)
454
+ defined_complexity.call(query.context, arguments.keyword_arguments, child_complexity)
455
+ when Numeric
456
+ defined_complexity + child_complexity
457
+ else
458
+ raise("Invalid complexity: #{defined_complexity.inspect} on #{path} (#{inspect})")
459
+ end
460
+ end
461
+ end
462
+
363
463
  def complexity(new_complexity = nil)
364
464
  case new_complexity
365
465
  when Proc
@@ -380,7 +480,12 @@ module GraphQL
380
480
  end
381
481
  end
382
482
 
383
- # @return [Integer, nil] Applied to connections if present
483
+ # @return [Boolean] True if this field's {#max_page_size} should override the schema default.
484
+ def has_max_page_size?
485
+ @has_max_page_size
486
+ end
487
+
488
+ # @return [Integer, nil] Applied to connections if {#has_max_page_size?}
384
489
  attr_reader :max_page_size
385
490
 
386
491
  # @return [GraphQL::Field]
@@ -402,8 +507,8 @@ module GraphQL
402
507
  field_defn.description = @description
403
508
  end
404
509
 
405
- if @deprecation_reason
406
- field_defn.deprecation_reason = @deprecation_reason
510
+ if self.deprecation_reason
511
+ field_defn.deprecation_reason = self.deprecation_reason
407
512
  end
408
513
 
409
514
  if @resolver_class
@@ -425,6 +530,10 @@ module GraphQL
425
530
  field_defn.relay_nodes_field = @relay_nodes_field
426
531
  end
427
532
 
533
+ if @legacy_edge_class
534
+ field_defn.edge_class = @legacy_edge_class
535
+ end
536
+
428
537
  field_defn.resolve = self.method(:resolve_field)
429
538
  field_defn.connection = connection?
430
539
  field_defn.connection_max_page_size = max_page_size
@@ -433,9 +542,9 @@ module GraphQL
433
542
  field_defn.subscription_scope = @subscription_scope
434
543
  field_defn.ast_node = ast_node
435
544
 
436
- arguments.each do |name, defn|
545
+ all_argument_definitions.each do |defn|
437
546
  arg_graphql = defn.to_graphql
438
- field_defn.arguments[arg_graphql.name] = arg_graphql
547
+ field_defn.arguments[arg_graphql.name] = arg_graphql # rubocop:disable Development/ContextIsPassedCop -- legacy-related
439
548
  end
440
549
 
441
550
  # Support a passed-in proc, one way or another
@@ -453,6 +562,7 @@ module GraphQL
453
562
  field_defn
454
563
  end
455
564
 
565
+ class MissingReturnTypeError < GraphQL::Error; end
456
566
  attr_writer :type
457
567
 
458
568
  def type
@@ -460,14 +570,21 @@ module GraphQL
460
570
  Member::BuildType.parse_type(@function.type, null: false)
461
571
  elsif @field
462
572
  Member::BuildType.parse_type(@field.type, null: false)
573
+ elsif @return_type_expr.nil?
574
+ # Not enough info to determine type
575
+ message = "Can't determine the return type for #{self.path}"
576
+ if @resolver_class
577
+ message += " (it has `resolver: #{@resolver_class}`, consider configuration a `type ...` for that class)"
578
+ end
579
+ raise MissingReturnTypeError, message
463
580
  else
464
581
  Member::BuildType.parse_type(@return_type_expr, null: @return_type_null)
465
582
  end
466
- rescue GraphQL::Schema::InvalidDocumentError => err
583
+ rescue GraphQL::Schema::InvalidDocumentError, MissingReturnTypeError => err
467
584
  # Let this propagate up
468
585
  raise err
469
586
  rescue StandardError => err
470
- raise ArgumentError, "Failed to build return type for #{@owner.graphql_name}.#{name} from #{@return_type_expr.inspect}: (#{err.class}) #{err.message}", err.backtrace
587
+ raise MissingReturnTypeError, "Failed to build return type for #{@owner.graphql_name}.#{name} from #{@return_type_expr.inspect}: (#{err.class}) #{err.message}", err.backtrace
471
588
  end
472
589
 
473
590
  def visible?(context)
@@ -491,10 +608,36 @@ module GraphQL
491
608
  # The resolver will check itself during `resolve()`
492
609
  @resolver_class.authorized?(object, context)
493
610
  else
611
+ if (arg_values = context[:current_arguments])
612
+ # ^^ that's provided by the interpreter at runtime, and includes info about whether the default value was used or not.
613
+ using_arg_values = true
614
+ arg_values = arg_values.argument_values
615
+ else
616
+ arg_values = args
617
+ using_arg_values = false
618
+ end
494
619
  # Faster than `.any?`
495
- arguments.each_value do |arg|
496
- if args.key?(arg.keyword) && !arg.authorized?(object, args[arg.keyword], context)
497
- return false
620
+ arguments(context).each_value do |arg|
621
+ arg_key = arg.keyword
622
+ if arg_values.key?(arg_key)
623
+ arg_value = arg_values[arg_key]
624
+ if using_arg_values
625
+ if arg_value.default_used?
626
+ # pass -- no auth required for default used
627
+ next
628
+ else
629
+ application_arg_value = arg_value.value
630
+ if application_arg_value.is_a?(GraphQL::Execution::Interpreter::Arguments)
631
+ application_arg_value.keyword_arguments
632
+ end
633
+ end
634
+ else
635
+ application_arg_value = arg_value
636
+ end
637
+
638
+ if !arg.authorized?(object, application_arg_value, context)
639
+ return false
640
+ end
498
641
  end
499
642
  end
500
643
  true
@@ -522,7 +665,7 @@ module GraphQL
522
665
  @resolve_proc.call(extended_obj, args, ctx)
523
666
  end
524
667
  else
525
- public_send_field(after_obj, ruby_args, ctx)
668
+ public_send_field(after_obj, ruby_args, query_ctx)
526
669
  end
527
670
  else
528
671
  err = GraphQL::UnauthorizedFieldError.new(object: inner_obj, type: obj.class, context: ctx, field: self)
@@ -544,34 +687,15 @@ module GraphQL
544
687
  begin
545
688
  # Unwrap the GraphQL object to get the application object.
546
689
  application_object = object.object
547
- if self.authorized?(application_object, args, ctx)
548
- # Apply field extensions
549
- with_extensions(object, args, ctx) do |extended_obj, extended_args|
550
- field_receiver = if @resolver_class
551
- resolver_obj = if extended_obj.is_a?(GraphQL::Schema::Object)
552
- extended_obj.object
553
- else
554
- extended_obj
555
- end
556
- @resolver_class.new(object: resolver_obj, context: ctx, field: self)
557
- else
558
- extended_obj
559
- end
560
690
 
561
- if field_receiver.respond_to?(@resolver_method)
562
- # Call the method with kwargs, if there are any
563
- if extended_args.any?
564
- field_receiver.public_send(@resolver_method, **extended_args)
565
- else
566
- field_receiver.public_send(@resolver_method)
567
- end
568
- else
569
- resolve_field_method(field_receiver, extended_args, ctx)
570
- end
691
+ Schema::Validator.validate!(validators, application_object, ctx, args)
692
+
693
+ ctx.schema.after_lazy(self.authorized?(application_object, args, ctx)) do |is_authorized|
694
+ if is_authorized
695
+ public_send_field(object, args, ctx)
696
+ else
697
+ raise GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: ctx, field: self)
571
698
  end
572
- else
573
- err = GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: ctx, field: self)
574
- ctx.schema.unauthorized_field(err)
575
699
  end
576
700
  rescue GraphQL::UnauthorizedFieldError => err
577
701
  err.field ||= self
@@ -583,43 +707,6 @@ module GraphQL
583
707
  err
584
708
  end
585
709
 
586
- # Find a way to resolve this field, checking:
587
- #
588
- # - Hash keys, if the wrapped object is a hash;
589
- # - A method on the wrapped object;
590
- # - Or, raise not implemented.
591
- #
592
- # This can be overridden by defining a method on the object type.
593
- # @param obj [GraphQL::Schema::Object]
594
- # @param ruby_kwargs [Hash<Symbol => Object>]
595
- # @param ctx [GraphQL::Query::Context]
596
- def resolve_field_method(obj, ruby_kwargs, ctx)
597
- if obj.object.is_a?(Hash)
598
- inner_object = obj.object
599
- if inner_object.key?(@method_sym)
600
- inner_object[@method_sym]
601
- else
602
- inner_object[@method_str]
603
- end
604
- elsif obj.object.respond_to?(@method_sym)
605
- if ruby_kwargs.any?
606
- obj.object.public_send(@method_sym, **ruby_kwargs)
607
- else
608
- obj.object.public_send(@method_sym)
609
- end
610
- else
611
- raise <<-ERR
612
- Failed to implement #{@owner.graphql_name}.#{@name}, tried:
613
-
614
- - `#{obj.class}##{@resolver_method}`, which did not exist
615
- - `#{obj.object.class}##{@method_sym}`, which did not exist
616
- - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
617
-
618
- To implement this field, define one of the methods above (and check for typos)
619
- ERR
620
- end
621
- end
622
-
623
710
  # @param ctx [GraphQL::Query::Context::FieldResolutionContext]
624
711
  def fetch_extra(extra_name, ctx)
625
712
  if extra_name != :path && extra_name != :ast_node && respond_to?(extra_name)
@@ -645,11 +732,36 @@ module GraphQL
645
732
  if graphql_args.any? || @extras.any?
646
733
  # Splat the GraphQL::Arguments to Ruby keyword arguments
647
734
  ruby_kwargs = graphql_args.to_kwargs
735
+ maybe_lazies = []
648
736
  # Apply any `prepare` methods. Not great code organization, can this go somewhere better?
649
- arguments.each do |name, arg_defn|
737
+ arguments(field_ctx).each do |name, arg_defn|
650
738
  ruby_kwargs_key = arg_defn.keyword
651
- if ruby_kwargs.key?(ruby_kwargs_key) && arg_defn.prepare
652
- ruby_kwargs[ruby_kwargs_key] = arg_defn.prepare_value(obj, ruby_kwargs[ruby_kwargs_key])
739
+
740
+ if ruby_kwargs.key?(ruby_kwargs_key)
741
+ loads = arg_defn.loads
742
+ value = ruby_kwargs[ruby_kwargs_key]
743
+ loaded_value = if loads && !arg_defn.from_resolver?
744
+ if arg_defn.type.list?
745
+ loaded_values = value.map { |val| load_application_object(arg_defn, loads, val, field_ctx.query.context) }
746
+ field_ctx.schema.after_any_lazies(loaded_values) { |result| result }
747
+ else
748
+ load_application_object(arg_defn, loads, value, field_ctx.query.context)
749
+ end
750
+ elsif arg_defn.type.list? && value.is_a?(Array)
751
+ field_ctx.schema.after_any_lazies(value, &:itself)
752
+ else
753
+ value
754
+ end
755
+
756
+ maybe_lazies << field_ctx.schema.after_lazy(loaded_value) do |loaded_value|
757
+ prepared_value = if arg_defn.prepare
758
+ arg_defn.prepare_value(obj, loaded_value)
759
+ else
760
+ loaded_value
761
+ end
762
+
763
+ ruby_kwargs[ruby_kwargs_key] = prepared_value
764
+ end
653
765
  end
654
766
  end
655
767
 
@@ -657,30 +769,60 @@ module GraphQL
657
769
  ruby_kwargs[extra_arg] = fetch_extra(extra_arg, field_ctx)
658
770
  end
659
771
 
660
- ruby_kwargs
772
+ field_ctx.schema.after_any_lazies(maybe_lazies) do
773
+ ruby_kwargs
774
+ end
661
775
  else
662
776
  NO_ARGS
663
777
  end
664
778
  end
665
779
 
666
- def public_send_field(obj, ruby_kwargs, field_ctx)
667
- query_ctx = field_ctx.query.context
668
- with_extensions(obj, ruby_kwargs, query_ctx) do |extended_obj, extended_args|
780
+ def public_send_field(unextended_obj, unextended_ruby_kwargs, query_ctx)
781
+ with_extensions(unextended_obj, unextended_ruby_kwargs, query_ctx) do |obj, ruby_kwargs|
669
782
  if @resolver_class
670
- if extended_obj.is_a?(GraphQL::Schema::Object)
671
- extended_obj = extended_obj.object
783
+ if obj.is_a?(GraphQL::Schema::Object)
784
+ obj = obj.object
672
785
  end
673
- extended_obj = @resolver_class.new(object: extended_obj, context: query_ctx, field: self)
786
+ obj = @resolver_class.new(object: obj, context: query_ctx, field: self)
674
787
  end
675
788
 
676
- if extended_obj.respond_to?(@resolver_method)
677
- if extended_args.any?
678
- extended_obj.public_send(@resolver_method, **extended_args)
789
+ # Find a way to resolve this field, checking:
790
+ #
791
+ # - A method on the type instance;
792
+ # - Hash keys, if the wrapped object is a hash;
793
+ # - A method on the wrapped object;
794
+ # - Or, raise not implemented.
795
+ #
796
+ if obj.respond_to?(@resolver_method)
797
+ # Call the method with kwargs, if there are any
798
+ if ruby_kwargs.any?
799
+ obj.public_send(@resolver_method, **ruby_kwargs)
679
800
  else
680
- extended_obj.public_send(@resolver_method)
801
+ obj.public_send(@resolver_method)
802
+ end
803
+ elsif obj.object.is_a?(Hash)
804
+ inner_object = obj.object
805
+ if inner_object.key?(@method_sym)
806
+ inner_object[@method_sym]
807
+ else
808
+ inner_object[@method_str]
809
+ end
810
+ elsif obj.object.respond_to?(@method_sym)
811
+ if ruby_kwargs.any?
812
+ obj.object.public_send(@method_sym, **ruby_kwargs)
813
+ else
814
+ obj.object.public_send(@method_sym)
681
815
  end
682
816
  else
683
- resolve_field_method(extended_obj, extended_args, query_ctx)
817
+ raise <<-ERR
818
+ Failed to implement #{@owner.graphql_name}.#{@name}, tried:
819
+
820
+ - `#{obj.class}##{@resolver_method}`, which did not exist
821
+ - `#{obj.object.class}##{@method_sym}`, which did not exist
822
+ - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
823
+
824
+ To implement this field, define one of the methods above (and check for typos)
825
+ ERR
684
826
  end
685
827
  end
686
828
  end
@@ -692,32 +834,42 @@ module GraphQL
692
834
  if @extensions.empty?
693
835
  yield(obj, args)
694
836
  else
695
- # Save these so that the originals can be re-given to `after_resolve` handlers.
696
- original_args = args
697
- original_obj = obj
698
-
699
- memos = []
700
- value = run_extensions_before_resolve(memos, obj, args, ctx) do |extended_obj, extended_args|
701
- yield(extended_obj, extended_args)
837
+ # This is a hack to get the _last_ value for extended obj and args,
838
+ # in case one of the extensions doesn't `yield`.
839
+ # (There's another implementation that uses multiple-return, but I'm wary of the perf cost of the extra arrays)
840
+ extended = { args: args, obj: obj, memos: nil }
841
+ value = run_extensions_before_resolve(obj, args, ctx, extended) do |obj, args|
842
+ yield(obj, args)
702
843
  end
703
844
 
845
+ extended_obj = extended[:obj]
846
+ extended_args = extended[:args]
847
+ memos = extended[:memos] || EMPTY_HASH
848
+
704
849
  ctx.schema.after_lazy(value) do |resolved_value|
705
- @extensions.each_with_index do |ext, idx|
850
+ idx = 0
851
+ @extensions.each do |ext|
706
852
  memo = memos[idx]
707
853
  # TODO after_lazy?
708
- resolved_value = ext.after_resolve(object: original_obj, arguments: original_args, context: ctx, value: resolved_value, memo: memo)
854
+ resolved_value = ext.after_resolve(object: extended_obj, arguments: extended_args, context: ctx, value: resolved_value, memo: memo)
855
+ idx += 1
709
856
  end
710
857
  resolved_value
711
858
  end
712
859
  end
713
860
  end
714
861
 
715
- def run_extensions_before_resolve(memos, obj, args, ctx, idx: 0)
862
+ def run_extensions_before_resolve(obj, args, ctx, extended, idx: 0)
716
863
  extension = @extensions[idx]
717
864
  if extension
718
865
  extension.resolve(object: obj, arguments: args, context: ctx) do |extended_obj, extended_args, memo|
719
- memos << memo
720
- run_extensions_before_resolve(memos, extended_obj, extended_args, ctx, idx: idx + 1) { |o, a| yield(o, a) }
866
+ if memo
867
+ memos = extended[:memos] ||= {}
868
+ memos[idx] = memo
869
+ end
870
+ extended[:obj] = extended_obj
871
+ extended[:args] = extended_args
872
+ run_extensions_before_resolve(extended_obj, extended_args, ctx, extended, idx: idx + 1) { |o, a| yield(o, a) }
721
873
  end
722
874
  else
723
875
  yield(obj, args)