graphql 1.9.18 → 1.13.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (353) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/core.rb +21 -10
  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/{templates → install/templates}/base_mutation.erb +2 -0
  8. data/lib/generators/graphql/{templates → install/templates}/mutation_type.erb +2 -0
  9. data/lib/generators/graphql/install_generator.rb +44 -7
  10. data/lib/generators/graphql/interface_generator.rb +7 -7
  11. data/lib/generators/graphql/loader_generator.rb +1 -0
  12. data/lib/generators/graphql/mutation_create_generator.rb +22 -0
  13. data/lib/generators/graphql/mutation_delete_generator.rb +22 -0
  14. data/lib/generators/graphql/mutation_generator.rb +6 -30
  15. data/lib/generators/graphql/mutation_update_generator.rb +22 -0
  16. data/lib/generators/graphql/object_generator.rb +28 -12
  17. data/lib/generators/graphql/orm_mutations_base.rb +40 -0
  18. data/lib/generators/graphql/relay.rb +63 -0
  19. data/lib/generators/graphql/relay_generator.rb +21 -0
  20. data/lib/generators/graphql/scalar_generator.rb +4 -2
  21. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  22. data/lib/generators/graphql/templates/base_connection.erb +8 -0
  23. data/lib/generators/graphql/templates/base_edge.erb +8 -0
  24. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  25. data/lib/generators/graphql/templates/base_field.erb +2 -0
  26. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  27. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  28. data/lib/generators/graphql/templates/base_object.erb +2 -0
  29. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  30. data/lib/generators/graphql/templates/base_union.erb +2 -0
  31. data/lib/generators/graphql/templates/enum.erb +7 -1
  32. data/lib/generators/graphql/templates/graphql_controller.erb +16 -12
  33. data/lib/generators/graphql/templates/input.erb +9 -0
  34. data/lib/generators/graphql/templates/interface.erb +6 -2
  35. data/lib/generators/graphql/templates/loader.erb +2 -0
  36. data/lib/generators/graphql/templates/mutation.erb +3 -1
  37. data/lib/generators/graphql/templates/mutation_create.erb +20 -0
  38. data/lib/generators/graphql/templates/mutation_delete.erb +20 -0
  39. data/lib/generators/graphql/templates/mutation_update.erb +21 -0
  40. data/lib/generators/graphql/templates/node_type.erb +9 -0
  41. data/lib/generators/graphql/templates/object.erb +7 -3
  42. data/lib/generators/graphql/templates/query_type.erb +3 -3
  43. data/lib/generators/graphql/templates/scalar.erb +5 -1
  44. data/lib/generators/graphql/templates/schema.erb +22 -27
  45. data/lib/generators/graphql/templates/union.erb +6 -2
  46. data/lib/generators/graphql/type_generator.rb +47 -10
  47. data/lib/generators/graphql/union_generator.rb +5 -5
  48. data/lib/graphql/analysis/analyze_query.rb +7 -0
  49. data/lib/graphql/analysis/ast/field_usage.rb +29 -2
  50. data/lib/graphql/analysis/ast/query_complexity.rb +174 -67
  51. data/lib/graphql/analysis/ast/visitor.rb +16 -7
  52. data/lib/graphql/analysis/ast.rb +21 -11
  53. data/lib/graphql/argument.rb +8 -36
  54. data/lib/graphql/backtrace/inspect_result.rb +0 -1
  55. data/lib/graphql/backtrace/legacy_tracer.rb +56 -0
  56. data/lib/graphql/backtrace/table.rb +44 -5
  57. data/lib/graphql/backtrace/traced_error.rb +0 -1
  58. data/lib/graphql/backtrace/tracer.rb +40 -9
  59. data/lib/graphql/backtrace.rb +28 -19
  60. data/lib/graphql/backwards_compatibility.rb +2 -1
  61. data/lib/graphql/base_type.rb +10 -4
  62. data/lib/graphql/boolean_type.rb +1 -1
  63. data/lib/graphql/compatibility/execution_specification/specification_schema.rb +2 -2
  64. data/lib/graphql/compatibility/execution_specification.rb +1 -0
  65. data/lib/graphql/compatibility/lazy_execution_specification.rb +2 -0
  66. data/lib/graphql/compatibility/query_parser_specification/parse_error_specification.rb +5 -9
  67. data/lib/graphql/compatibility/query_parser_specification.rb +2 -0
  68. data/lib/graphql/compatibility/schema_parser_specification.rb +2 -0
  69. data/lib/graphql/dataloader/null_dataloader.rb +22 -0
  70. data/lib/graphql/dataloader/request.rb +19 -0
  71. data/lib/graphql/dataloader/request_all.rb +19 -0
  72. data/lib/graphql/dataloader/source.rb +155 -0
  73. data/lib/graphql/dataloader.rb +308 -0
  74. data/lib/graphql/date_encoding_error.rb +16 -0
  75. data/lib/graphql/define/assign_enum_value.rb +1 -1
  76. data/lib/graphql/define/assign_global_id_field.rb +2 -2
  77. data/lib/graphql/define/assign_object_field.rb +1 -1
  78. data/lib/graphql/define/defined_object_proxy.rb +5 -8
  79. data/lib/graphql/define/instance_definable.rb +60 -110
  80. data/lib/graphql/define/type_definer.rb +5 -5
  81. data/lib/graphql/deprecated_dsl.rb +18 -5
  82. data/lib/graphql/deprecation.rb +9 -0
  83. data/lib/graphql/directive/deprecated_directive.rb +1 -12
  84. data/lib/graphql/directive/include_directive.rb +1 -1
  85. data/lib/graphql/directive/skip_directive.rb +1 -1
  86. data/lib/graphql/directive.rb +9 -6
  87. data/lib/graphql/enum_type.rb +14 -74
  88. data/lib/graphql/execution/directive_checks.rb +2 -2
  89. data/lib/graphql/execution/errors.rb +110 -8
  90. data/lib/graphql/execution/execute.rb +8 -1
  91. data/lib/graphql/execution/instrumentation.rb +1 -1
  92. data/lib/graphql/execution/interpreter/argument_value.rb +28 -0
  93. data/lib/graphql/execution/interpreter/arguments.rb +88 -0
  94. data/lib/graphql/execution/interpreter/arguments_cache.rb +105 -0
  95. data/lib/graphql/execution/interpreter/handles_raw_value.rb +18 -0
  96. data/lib/graphql/execution/interpreter/resolve.rb +37 -25
  97. data/lib/graphql/execution/interpreter/runtime.rb +721 -386
  98. data/lib/graphql/execution/interpreter.rb +42 -19
  99. data/lib/graphql/execution/lazy/lazy_method_map.rb +4 -0
  100. data/lib/graphql/execution/lazy.rb +5 -1
  101. data/lib/graphql/execution/lookahead.rb +39 -114
  102. data/lib/graphql/execution/multiplex.rb +50 -25
  103. data/lib/graphql/field.rb +15 -119
  104. data/lib/graphql/filter.rb +1 -1
  105. data/lib/graphql/float_type.rb +1 -1
  106. data/lib/graphql/function.rb +5 -30
  107. data/lib/graphql/id_type.rb +1 -1
  108. data/lib/graphql/input_object_type.rb +9 -25
  109. data/lib/graphql/int_type.rb +1 -1
  110. data/lib/graphql/integer_decoding_error.rb +17 -0
  111. data/lib/graphql/integer_encoding_error.rb +18 -2
  112. data/lib/graphql/interface_type.rb +10 -24
  113. data/lib/graphql/internal_representation/document.rb +2 -2
  114. data/lib/graphql/internal_representation/rewrite.rb +1 -1
  115. data/lib/graphql/internal_representation/scope.rb +2 -2
  116. data/lib/graphql/internal_representation/visit.rb +2 -2
  117. data/lib/graphql/introspection/base_object.rb +2 -5
  118. data/lib/graphql/introspection/directive_location_enum.rb +2 -2
  119. data/lib/graphql/introspection/directive_type.rb +12 -6
  120. data/lib/graphql/introspection/entry_points.rb +9 -9
  121. data/lib/graphql/introspection/enum_value_type.rb +2 -2
  122. data/lib/graphql/introspection/field_type.rb +9 -5
  123. data/lib/graphql/introspection/input_value_type.rb +41 -11
  124. data/lib/graphql/introspection/introspection_query.rb +6 -92
  125. data/lib/graphql/introspection/schema_type.rb +12 -12
  126. data/lib/graphql/introspection/type_type.rb +27 -17
  127. data/lib/graphql/introspection.rb +99 -0
  128. data/lib/graphql/invalid_null_error.rb +18 -0
  129. data/lib/graphql/language/block_string.rb +20 -5
  130. data/lib/graphql/language/cache.rb +37 -0
  131. data/lib/graphql/language/definition_slice.rb +21 -10
  132. data/lib/graphql/language/document_from_schema_definition.rb +116 -63
  133. data/lib/graphql/language/lexer.rb +53 -27
  134. data/lib/graphql/language/lexer.rl +5 -3
  135. data/lib/graphql/language/nodes.rb +67 -93
  136. data/lib/graphql/language/parser.rb +929 -896
  137. data/lib/graphql/language/parser.y +125 -102
  138. data/lib/graphql/language/printer.rb +11 -2
  139. data/lib/graphql/language/sanitized_printer.rb +222 -0
  140. data/lib/graphql/language/token.rb +0 -4
  141. data/lib/graphql/language/visitor.rb +2 -2
  142. data/lib/graphql/language.rb +3 -1
  143. data/lib/graphql/name_validator.rb +2 -7
  144. data/lib/graphql/non_null_type.rb +0 -10
  145. data/lib/graphql/object_type.rb +47 -58
  146. data/lib/graphql/pagination/active_record_relation_connection.rb +85 -0
  147. data/lib/graphql/pagination/array_connection.rb +77 -0
  148. data/lib/graphql/pagination/connection.rb +226 -0
  149. data/lib/graphql/pagination/connections.rb +160 -0
  150. data/lib/graphql/pagination/mongoid_relation_connection.rb +25 -0
  151. data/lib/graphql/pagination/relation_connection.rb +226 -0
  152. data/lib/graphql/pagination/sequel_dataset_connection.rb +28 -0
  153. data/lib/graphql/pagination.rb +6 -0
  154. data/lib/graphql/parse_error.rb +0 -1
  155. data/lib/graphql/query/arguments.rb +6 -4
  156. data/lib/graphql/query/arguments_cache.rb +1 -2
  157. data/lib/graphql/query/context.rb +52 -7
  158. data/lib/graphql/query/executor.rb +0 -1
  159. data/lib/graphql/query/fingerprint.rb +26 -0
  160. data/lib/graphql/query/input_validation_result.rb +32 -6
  161. data/lib/graphql/query/literal_input.rb +31 -11
  162. data/lib/graphql/query/null_context.rb +24 -8
  163. data/lib/graphql/query/serial_execution/field_resolution.rb +1 -1
  164. data/lib/graphql/query/serial_execution.rb +1 -0
  165. data/lib/graphql/query/validation_pipeline.rb +6 -4
  166. data/lib/graphql/query/variable_validation_error.rb +3 -3
  167. data/lib/graphql/query/variables.rb +50 -10
  168. data/lib/graphql/query.rb +77 -18
  169. data/lib/graphql/railtie.rb +9 -1
  170. data/lib/graphql/rake_task/validate.rb +3 -0
  171. data/lib/graphql/rake_task.rb +12 -9
  172. data/lib/graphql/relay/array_connection.rb +10 -12
  173. data/lib/graphql/relay/base_connection.rb +30 -13
  174. data/lib/graphql/relay/connection_instrumentation.rb +4 -4
  175. data/lib/graphql/relay/connection_type.rb +18 -4
  176. data/lib/graphql/relay/edge_type.rb +1 -0
  177. data/lib/graphql/relay/edges_instrumentation.rb +1 -2
  178. data/lib/graphql/relay/global_id_resolve.rb +1 -2
  179. data/lib/graphql/relay/mutation.rb +3 -87
  180. data/lib/graphql/relay/node.rb +3 -0
  181. data/lib/graphql/relay/page_info.rb +1 -1
  182. data/lib/graphql/relay/range_add.rb +27 -9
  183. data/lib/graphql/relay/relation_connection.rb +8 -10
  184. data/lib/graphql/relay/type_extensions.rb +2 -0
  185. data/lib/graphql/rubocop/graphql/base_cop.rb +36 -0
  186. data/lib/graphql/rubocop/graphql/default_null_true.rb +43 -0
  187. data/lib/graphql/rubocop/graphql/default_required_true.rb +43 -0
  188. data/lib/graphql/rubocop.rb +4 -0
  189. data/lib/graphql/scalar_type.rb +18 -60
  190. data/lib/graphql/schema/addition.rb +247 -0
  191. data/lib/graphql/schema/argument.rb +274 -18
  192. data/lib/graphql/schema/base_64_encoder.rb +2 -0
  193. data/lib/graphql/schema/build_from_definition/resolve_map/default_resolve.rb +1 -1
  194. data/lib/graphql/schema/build_from_definition/resolve_map.rb +13 -5
  195. data/lib/graphql/schema/build_from_definition.rb +320 -219
  196. data/lib/graphql/schema/built_in_types.rb +5 -5
  197. data/lib/graphql/schema/default_type_error.rb +2 -0
  198. data/lib/graphql/schema/directive/deprecated.rb +18 -0
  199. data/lib/graphql/schema/directive/feature.rb +1 -1
  200. data/lib/graphql/schema/directive/flagged.rb +57 -0
  201. data/lib/graphql/schema/directive/include.rb +2 -2
  202. data/lib/graphql/schema/directive/skip.rb +2 -2
  203. data/lib/graphql/schema/directive/transform.rb +14 -2
  204. data/lib/graphql/schema/directive.rb +130 -6
  205. data/lib/graphql/schema/enum.rb +121 -12
  206. data/lib/graphql/schema/enum_value.rb +24 -7
  207. data/lib/graphql/schema/field/connection_extension.rb +46 -20
  208. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  209. data/lib/graphql/schema/field.rb +465 -181
  210. data/lib/graphql/schema/field_extension.rb +89 -2
  211. data/lib/graphql/schema/find_inherited_value.rb +17 -1
  212. data/lib/graphql/schema/finder.rb +16 -14
  213. data/lib/graphql/schema/input_object.rb +172 -37
  214. data/lib/graphql/schema/interface.rb +39 -25
  215. data/lib/graphql/schema/introspection_system.rb +106 -38
  216. data/lib/graphql/schema/late_bound_type.rb +3 -2
  217. data/lib/graphql/schema/list.rb +65 -1
  218. data/lib/graphql/schema/loader.rb +145 -102
  219. data/lib/graphql/schema/member/accepts_definition.rb +15 -3
  220. data/lib/graphql/schema/member/base_dsl_methods.rb +34 -28
  221. data/lib/graphql/schema/member/build_type.rb +19 -8
  222. data/lib/graphql/schema/member/cached_graphql_definition.rb +34 -2
  223. data/lib/graphql/schema/member/has_arguments.rb +206 -13
  224. data/lib/graphql/schema/member/has_ast_node.rb +20 -0
  225. data/lib/graphql/schema/member/has_deprecation_reason.rb +25 -0
  226. data/lib/graphql/schema/member/has_directives.rb +98 -0
  227. data/lib/graphql/schema/member/has_fields.rb +97 -32
  228. data/lib/graphql/schema/member/has_interfaces.rb +100 -0
  229. data/lib/graphql/schema/member/has_unresolved_type_error.rb +15 -0
  230. data/lib/graphql/schema/member/has_validators.rb +31 -0
  231. data/lib/graphql/schema/member/instrumentation.rb +0 -1
  232. data/lib/graphql/schema/member/type_system_helpers.rb +3 -3
  233. data/lib/graphql/schema/member/validates_input.rb +33 -0
  234. data/lib/graphql/schema/member.rb +11 -0
  235. data/lib/graphql/schema/middleware_chain.rb +1 -1
  236. data/lib/graphql/schema/mutation.rb +4 -0
  237. data/lib/graphql/schema/non_null.rb +37 -1
  238. data/lib/graphql/schema/object.rb +51 -38
  239. data/lib/graphql/schema/possible_types.rb +9 -4
  240. data/lib/graphql/schema/printer.rb +16 -35
  241. data/lib/graphql/schema/relay_classic_mutation.rb +40 -4
  242. data/lib/graphql/schema/resolver/has_payload_type.rb +34 -4
  243. data/lib/graphql/schema/resolver.rb +133 -79
  244. data/lib/graphql/schema/scalar.rb +43 -3
  245. data/lib/graphql/schema/subscription.rb +57 -21
  246. data/lib/graphql/schema/timeout.rb +29 -15
  247. data/lib/graphql/schema/timeout_middleware.rb +3 -1
  248. data/lib/graphql/schema/traversal.rb +2 -2
  249. data/lib/graphql/schema/type_expression.rb +21 -13
  250. data/lib/graphql/schema/type_membership.rb +19 -5
  251. data/lib/graphql/schema/union.rb +44 -3
  252. data/lib/graphql/schema/unique_within_type.rb +1 -2
  253. data/lib/graphql/schema/validation.rb +14 -4
  254. data/lib/graphql/schema/validator/allow_blank_validator.rb +29 -0
  255. data/lib/graphql/schema/validator/allow_null_validator.rb +26 -0
  256. data/lib/graphql/schema/validator/exclusion_validator.rb +33 -0
  257. data/lib/graphql/schema/validator/format_validator.rb +48 -0
  258. data/lib/graphql/schema/validator/inclusion_validator.rb +35 -0
  259. data/lib/graphql/schema/validator/length_validator.rb +59 -0
  260. data/lib/graphql/schema/validator/numericality_validator.rb +82 -0
  261. data/lib/graphql/schema/validator/required_validator.rb +82 -0
  262. data/lib/graphql/schema/validator.rb +171 -0
  263. data/lib/graphql/schema/warden.rb +193 -34
  264. data/lib/graphql/schema.rb +882 -247
  265. data/lib/graphql/static_validation/all_rules.rb +2 -0
  266. data/lib/graphql/static_validation/base_visitor.rb +17 -10
  267. data/lib/graphql/static_validation/definition_dependencies.rb +0 -1
  268. data/lib/graphql/static_validation/error.rb +3 -1
  269. data/lib/graphql/static_validation/literal_validator.rb +51 -26
  270. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +45 -83
  271. data/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb +22 -6
  272. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +35 -26
  273. data/lib/graphql/static_validation/rules/arguments_are_defined_error.rb +4 -2
  274. data/lib/graphql/static_validation/rules/directives_are_defined.rb +1 -1
  275. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -2
  276. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -4
  277. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +5 -5
  278. data/lib/graphql/static_validation/rules/fields_will_merge.rb +94 -51
  279. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +25 -4
  280. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  281. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -2
  282. data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +30 -0
  283. data/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb +30 -0
  284. data/lib/graphql/static_validation/rules/query_root_exists.rb +17 -0
  285. data/lib/graphql/static_validation/rules/query_root_exists_error.rb +26 -0
  286. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +4 -2
  287. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +9 -10
  288. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
  289. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +12 -13
  290. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +19 -14
  291. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
  292. data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +5 -3
  293. data/lib/graphql/static_validation/type_stack.rb +2 -2
  294. data/lib/graphql/static_validation/validation_context.rb +13 -3
  295. data/lib/graphql/static_validation/validation_timeout_error.rb +25 -0
  296. data/lib/graphql/static_validation/validator.rb +43 -9
  297. data/lib/graphql/static_validation.rb +1 -0
  298. data/lib/graphql/string_encoding_error.rb +13 -3
  299. data/lib/graphql/string_type.rb +1 -1
  300. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +123 -22
  301. data/lib/graphql/subscriptions/broadcast_analyzer.rb +81 -0
  302. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +21 -0
  303. data/lib/graphql/subscriptions/event.rb +84 -30
  304. data/lib/graphql/subscriptions/instrumentation.rb +10 -6
  305. data/lib/graphql/subscriptions/serialize.rb +53 -6
  306. data/lib/graphql/subscriptions/subscription_root.rb +15 -5
  307. data/lib/graphql/subscriptions.rb +117 -49
  308. data/lib/graphql/tracing/active_support_notifications_tracing.rb +8 -17
  309. data/lib/graphql/tracing/appoptics_tracing.rb +173 -0
  310. data/lib/graphql/tracing/appsignal_tracing.rb +23 -0
  311. data/lib/graphql/tracing/data_dog_tracing.rb +32 -15
  312. data/lib/graphql/tracing/new_relic_tracing.rb +9 -12
  313. data/lib/graphql/tracing/notifications_tracing.rb +59 -0
  314. data/lib/graphql/tracing/platform_tracing.rb +66 -10
  315. data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +4 -1
  316. data/lib/graphql/tracing/prometheus_tracing.rb +8 -0
  317. data/lib/graphql/tracing/scout_tracing.rb +19 -0
  318. data/lib/graphql/tracing/skylight_tracing.rb +9 -1
  319. data/lib/graphql/tracing/statsd_tracing.rb +42 -0
  320. data/lib/graphql/tracing.rb +15 -35
  321. data/lib/graphql/types/big_int.rb +5 -1
  322. data/lib/graphql/types/int.rb +10 -3
  323. data/lib/graphql/types/iso_8601_date.rb +16 -8
  324. data/lib/graphql/types/iso_8601_date_time.rb +32 -10
  325. data/lib/graphql/types/relay/base_connection.rb +6 -88
  326. data/lib/graphql/types/relay/base_edge.rb +2 -34
  327. data/lib/graphql/types/relay/connection_behaviors.rb +174 -0
  328. data/lib/graphql/types/relay/default_relay.rb +31 -0
  329. data/lib/graphql/types/relay/edge_behaviors.rb +64 -0
  330. data/lib/graphql/types/relay/has_node_field.rb +41 -0
  331. data/lib/graphql/types/relay/has_nodes_field.rb +41 -0
  332. data/lib/graphql/types/relay/node.rb +2 -4
  333. data/lib/graphql/types/relay/node_behaviors.rb +15 -0
  334. data/lib/graphql/types/relay/node_field.rb +3 -22
  335. data/lib/graphql/types/relay/nodes_field.rb +16 -18
  336. data/lib/graphql/types/relay/page_info.rb +2 -14
  337. data/lib/graphql/types/relay/page_info_behaviors.rb +25 -0
  338. data/lib/graphql/types/relay.rb +11 -3
  339. data/lib/graphql/types/string.rb +8 -2
  340. data/lib/graphql/unauthorized_error.rb +2 -2
  341. data/lib/graphql/union_type.rb +5 -25
  342. data/lib/graphql/unresolved_type_error.rb +2 -2
  343. data/lib/graphql/upgrader/member.rb +1 -0
  344. data/lib/graphql/upgrader/schema.rb +1 -0
  345. data/lib/graphql/version.rb +1 -1
  346. data/lib/graphql.rb +87 -31
  347. data/readme.md +3 -6
  348. metadata +126 -124
  349. data/lib/graphql/execution/interpreter/hash_response.rb +0 -46
  350. data/lib/graphql/literal_validation_error.rb +0 -6
  351. data/lib/graphql/types/relay/base_field.rb +0 -22
  352. data/lib/graphql/types/relay/base_interface.rb +0 -29
  353. data/lib/graphql/types/relay/base_object.rb +0 -26
@@ -1,20 +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
11
+ include GraphQL::Schema::Member::HasAstNode
16
12
  include GraphQL::Schema::Member::HasPath
13
+ include GraphQL::Schema::Member::HasValidators
17
14
  extend GraphQL::Schema::FindInheritedValue
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
18
20
 
19
21
  # @return [String] the GraphQL name for this field, camelized unless `camelize: false` is provided
20
22
  attr_reader :name
@@ -22,9 +24,6 @@ module GraphQL
22
24
 
23
25
  attr_writer :description
24
26
 
25
- # @return [String, nil] If present, the field is marked as deprecated with this documentation
26
- attr_accessor :deprecation_reason
27
-
28
27
  # @return [Symbol] Method or hash key on the underlying object to look up
29
28
  attr_reader :method_sym
30
29
 
@@ -34,10 +33,21 @@ module GraphQL
34
33
  # @return [Symbol] The method on the type to look up
35
34
  attr_reader :resolver_method
36
35
 
37
- # @return [Class] The type that this field belongs to
38
- attr_reader :owner
36
+ # @return [Class] The thing this field was defined on (type, mutation, resolver)
37
+ attr_accessor :owner
38
+
39
+ # @return [Class] The GraphQL type this field belongs to. (For fields defined on mutations, it's the payload type)
40
+ def owner_type
41
+ @owner_type ||= if owner.nil?
42
+ raise GraphQL::InvariantError, "Field #{original_name.inspect} (graphql name: #{graphql_name.inspect}) has no owner, but all fields should have an owner. How did this happen?!"
43
+ elsif owner < GraphQL::Schema::Mutation
44
+ owner.payload_type
45
+ else
46
+ owner
47
+ end
48
+ end
39
49
 
40
- # @return [Symobol] the original name of the field, passed in by the user
50
+ # @return [Symbol] the original name of the field, passed in by the user
41
51
  attr_reader :original_name
42
52
 
43
53
  # @return [Class, nil] The {Schema::Resolver} this field was derived from, if there is one
@@ -45,13 +55,22 @@ module GraphQL
45
55
  @resolver_class
46
56
  end
47
57
 
58
+ # @return [Boolean] Is this field a predefined introspection field?
59
+ def introspection?
60
+ @introspection
61
+ end
62
+
63
+ def inspect
64
+ "#<#{self.class} #{path}#{all_argument_definitions.any? ? "(...)" : ""}: #{type.to_type_signature}>"
65
+ end
66
+
48
67
  alias :mutation :resolver
49
68
 
50
69
  # @return [Boolean] Apply tracing to this field? (Default: skip scalars, this is the override value)
51
70
  attr_reader :trace
52
71
 
53
72
  # @return [String, nil]
54
- attr_reader :subscription_scope
73
+ attr_accessor :subscription_scope
55
74
 
56
75
  # Create a field instance from a list of arguments, keyword arguments, and a block.
57
76
  #
@@ -66,11 +85,11 @@ module GraphQL
66
85
  # @see {.initialize} for other options
67
86
  def self.from_options(name = nil, type = nil, desc = nil, resolver: nil, mutation: nil, subscription: nil,**kwargs, &block)
68
87
  if kwargs[:field]
69
- if kwargs[:field] == GraphQL::Relay::Node.field
70
- 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.")
71
90
  return GraphQL::Types::Relay::NodeField
72
- elsif kwargs[:field] == GraphQL::Relay::Node.plural_field
73
- 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.")
74
93
  return GraphQL::Types::Relay::NodesField
75
94
  end
76
95
  end
@@ -103,6 +122,9 @@ module GraphQL
103
122
  else
104
123
  kwargs[:type] = type
105
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
106
128
  end
107
129
  new(**kwargs, &block)
108
130
  end
@@ -152,6 +174,16 @@ module GraphQL
152
174
  end
153
175
  end
154
176
 
177
+ # @return Boolean
178
+ attr_reader :relay_node_field
179
+ # @return Boolean
180
+ attr_reader :relay_nodes_field
181
+
182
+ # @return [Boolean] Should we warn if this field's name conflicts with a built-in method?
183
+ def method_conflict_warning?
184
+ @method_conflict_warning
185
+ end
186
+
155
187
  # @param name [Symbol] The underscore-cased version of this field name (will be camelized for the GraphQL API)
156
188
  # @param type [Class, GraphQL::BaseType, Array] The return type of this field
157
189
  # @param owner [Class] The type that this field belongs to
@@ -160,9 +192,11 @@ module GraphQL
160
192
  # @param deprecation_reason [String] If present, the field is marked "deprecated" with this message
161
193
  # @param method [Symbol] The method to call on the underlying object to resolve this field (defaults to `name`)
162
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
163
196
  # @param resolver_method [Symbol] The method on the type to call to resolve this field (defaults to `name`)
164
197
  # @param connection [Boolean] `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name
165
- # @param max_page_size [Integer] For connections, the maximum number of items to return from this field
198
+ # @param connection_extension [Class] The extension to add, to implement connections. If `nil`, no extension is added.
199
+ # @param max_page_size [Integer, nil] For connections, the maximum number of items to return from this field, or `nil` to allow unlimited results.
166
200
  # @param introspection [Boolean] If true, this field will be marked as `#introspection?` and the name may begin with `__`
167
201
  # @param resolve [<#call(obj, args, ctx)>] **deprecated** for compatibility with <1.8.0
168
202
  # @param field [GraphQL::Field, GraphQL::Schema::Field] **deprecated** for compatibility with <1.8.0
@@ -174,8 +208,14 @@ module GraphQL
174
208
  # @param scope [Boolean] If true, the return type's `.scope_items` method will be called on the return value
175
209
  # @param subscription_scope [Symbol, String] A key in `context` which will be used to scope subscription payloads
176
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
177
212
  # @param trace [Boolean] If true, a {GraphQL::Tracing} tracer will measure this scalar field
178
- 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, extras: [], extensions: [], resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, arguments: {}, &definition_block)
213
+ # @param broadcastable [Boolean] Whether or not this field can be distributed in subscription broadcasts
214
+ # @param ast_node [Language::Nodes::FieldDefinition, nil] If this schema was parsed from definition, this AST node defined the field
215
+ # @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
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)
179
219
  if name.nil?
180
220
  raise ArgumentError, "missing first `name` argument or keyword `name:`"
181
221
  end
@@ -183,16 +223,16 @@ module GraphQL
183
223
  if type.nil?
184
224
  raise ArgumentError, "missing second `type` argument or keyword `type:`"
185
225
  end
186
- if null.nil?
187
- raise ArgumentError, "missing keyword argument null:"
188
- end
189
226
  end
190
227
  if (field || function || resolve) && extras.any?
191
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:`"
192
229
  end
193
230
  @original_name = name
194
- @underscored_name = -Member::BuildType.underscore(name.to_s)
195
- @name = -(camelize ? Member::BuildType.camelize(name.to_s) : name.to_s)
231
+ name_s = -name.to_s
232
+
233
+ @underscored_name = -Member::BuildType.underscore(name_s)
234
+ @name = -(camelize ? Member::BuildType.camelize(name_s) : name_s)
235
+ NameValidator.validate!(@name)
196
236
  @description = description
197
237
  if field.is_a?(GraphQL::Schema::Field)
198
238
  raise ArgumentError, "Instead of passing a field as `field:`, use `add_field(field)` to add an already-defined field."
@@ -201,10 +241,10 @@ module GraphQL
201
241
  end
202
242
  @function = function
203
243
  @resolve = resolve
204
- @deprecation_reason = deprecation_reason
244
+ self.deprecation_reason = deprecation_reason
205
245
 
206
- if method && hash_key
207
- raise ArgumentError, "Provide `method:` _or_ `hash_key:`, not both. (called with: `method: #{method.inspect}, hash_key: #{hash_key.inspect}`)"
246
+ if method && hash_key && dig
247
+ raise ArgumentError, "Provide `method:`, `hash_key:` _or_ `dig:`, not multiple. (called with: `method: #{method.inspect}, hash_key: #{hash_key.inspect}, dig: #{dig.inspect}`)"
208
248
  end
209
249
 
210
250
  if resolver_method
@@ -212,60 +252,84 @@ module GraphQL
212
252
  raise ArgumentError, "Provide `method:` _or_ `resolver_method:`, not both. (called with: `method: #{method.inspect}, resolver_method: #{resolver_method.inspect}`)"
213
253
  end
214
254
 
215
- if hash_key
216
- raise ArgumentError, "Provide `hash_key:` _or_ `resolver_method:`, not both. (called with: `hash_key: #{hash_key.inspect}, resolver_method: #{resolver_method.inspect}`)"
255
+ if hash_key || dig
256
+ raise ArgumentError, "Provide `hash_key:`, `dig:`, _or_ `resolver_method:`, not multiple. (called with: `hash_key: #{hash_key.inspect}, dig: #{dig.inspect}, resolver_method: #{resolver_method.inspect}`)"
217
257
  end
218
258
  end
219
259
 
220
260
  # TODO: I think non-string/symbol hash keys are wrongly normalized (eg `1` will not work)
221
- method_name = method || hash_key || @underscored_name
222
- resolver_method ||= @underscored_name.to_sym
261
+ method_name = method || hash_key || name_s
262
+ @dig_keys = dig
263
+ if hash_key
264
+ @hash_key = hash_key
265
+ end
223
266
 
224
- @method_str = method_name.to_s
267
+ resolver_method ||= name_s.to_sym
268
+
269
+ @method_str = -method_name.to_s
225
270
  @method_sym = method_name.to_sym
226
271
  @resolver_method = resolver_method
227
272
  @complexity = complexity
228
273
  @return_type_expr = type
229
274
  @return_type_null = null
230
275
  @connection = connection
231
- @max_page_size = max_page_size
276
+ @has_max_page_size = max_page_size != :not_given
277
+ @max_page_size = max_page_size == :not_given ? nil : max_page_size
232
278
  @introspection = introspection
233
279
  @extras = extras
280
+ @broadcastable = broadcastable
234
281
  @resolver_class = resolver_class
235
282
  @scope = scope
236
283
  @trace = trace
237
284
  @relay_node_field = relay_node_field
238
285
  @relay_nodes_field = relay_nodes_field
286
+ @ast_node = ast_node
287
+ @method_conflict_warning = method_conflict_warning
288
+ @legacy_edge_class = legacy_edge_class
239
289
 
240
- # Override the default from HasArguments
241
- @own_arguments = {}
242
290
  arguments.each do |name, arg|
243
- if arg.is_a?(Hash)
291
+ case arg
292
+ when Hash
244
293
  argument(name: name, **arg)
294
+ when GraphQL::Schema::Argument
295
+ add_argument(arg)
296
+ when Array
297
+ arg.each { |a| add_argument(a) }
245
298
  else
246
- @own_arguments[name] = arg
299
+ raise ArgumentError, "Unexpected argument config (#{arg.class}): #{arg.inspect}"
247
300
  end
248
301
  end
249
302
 
250
303
  @owner = owner
251
304
  @subscription_scope = subscription_scope
252
305
 
253
- # Do this last so we have as much context as possible when initializing them:
254
- @extensions = []
255
- if extensions.any?
256
- self.extensions(extensions)
257
- end
306
+ @extensions = EMPTY_ARRAY
307
+ @call_after_define = false
258
308
  # This should run before connection extension,
259
309
  # but should it run after the definition block?
260
310
  if scoped?
261
311
  self.extension(ScopeExtension)
262
312
  end
313
+
263
314
  # The problem with putting this after the definition_block
264
315
  # is that it would override arguments
265
- if connection?
266
- self.extension(self.class.connection_extension)
316
+ if connection? && connection_extension
317
+ self.extension(connection_extension)
318
+ end
319
+
320
+ # Do this last so we have as much context as possible when initializing them:
321
+ if extensions.any?
322
+ self.extensions(extensions)
323
+ end
324
+
325
+ if directives.any?
326
+ directives.each do |(dir_class, options)|
327
+ self.directive(dir_class, **options)
328
+ end
267
329
  end
268
330
 
331
+ self.validates(validates)
332
+
269
333
  if definition_block
270
334
  if definition_block.arity == 1
271
335
  yield self
@@ -273,6 +337,16 @@ module GraphQL
273
337
  instance_eval(&definition_block)
274
338
  end
275
339
  end
340
+
341
+ self.extensions.each(&:after_define_apply)
342
+ @call_after_define = true
343
+ end
344
+
345
+ # If true, subscription updates with this field can be shared between viewers
346
+ # @return [Boolean, nil]
347
+ # @see GraphQL::Subscriptions::BroadcastAnalyzer
348
+ def broadcastable?
349
+ @broadcastable
276
350
  end
277
351
 
278
352
  # @param text [String]
@@ -298,24 +372,20 @@ module GraphQL
298
372
  # @example adding an extension with options
299
373
  # extensions([MyExtensionClass, { AnotherExtensionClass => { filter: true } }])
300
374
  #
301
- # @param extensions [Array<Class, Hash<Class => Object>>] Add extensions to this field. For hash elements, only the first key/value is used.
375
+ # @param extensions [Array<Class, Hash<Class => Hash>>] Add extensions to this field. For hash elements, only the first key/value is used.
302
376
  # @return [Array<GraphQL::Schema::FieldExtension>] extensions to apply to this field
303
377
  def extensions(new_extensions = nil)
304
- if new_extensions.nil?
305
- # Read the value
306
- @extensions
307
- else
308
- new_extensions.each do |extension|
309
- if extension.is_a?(Hash)
310
- extension = extension.to_a[0]
311
- extension_class, options = *extension
312
- @extensions << extension_class.new(field: self, options: options)
378
+ if new_extensions
379
+ new_extensions.each do |extension_config|
380
+ if extension_config.is_a?(Hash)
381
+ extension_class, options = *extension_config.to_a[0]
382
+ self.extension(extension_class, options)
313
383
  else
314
- extension_class = extension
315
- @extensions << extension_class.new(field: self, options: nil)
384
+ self.extension(extension_config)
316
385
  end
317
386
  end
318
387
  end
388
+ @extensions
319
389
  end
320
390
 
321
391
  # Add `extension` to this field, initialized with `options` if provided.
@@ -326,10 +396,19 @@ module GraphQL
326
396
  # @example adding an extension with options
327
397
  # extension(MyExtensionClass, filter: true)
328
398
  #
329
- # @param extension [Class] subclass of {Schema::Fieldextension}
330
- # @param options [Object] if provided, given as `options:` when initializing `extension`.
331
- def extension(extension, options = nil)
332
- extensions([{extension => options}])
399
+ # @param extension_class [Class] subclass of {Schema::FieldExtension}
400
+ # @param options [Hash] if provided, given as `options:` when initializing `extension`.
401
+ # @return [void]
402
+ def extension(extension_class, options = nil)
403
+ extension_inst = extension_class.new(field: self, options: options)
404
+ if @extensions.frozen?
405
+ @extensions = @extensions.dup
406
+ end
407
+ if @call_after_define
408
+ extension_inst.after_define_apply
409
+ end
410
+ @extensions << extension_inst
411
+ nil
333
412
  end
334
413
 
335
414
  # Read extras (as symbols) from this field,
@@ -342,12 +421,80 @@ module GraphQL
342
421
  # Read the value
343
422
  @extras
344
423
  else
424
+ if @extras.frozen?
425
+ @extras = @extras.dup
426
+ end
345
427
  # Append to the set of extras on this field
346
428
  @extras.concat(new_extras)
347
429
  end
348
430
  end
349
431
 
350
- def complexity(new_complexity)
432
+ def calculate_complexity(query:, nodes:, child_complexity:)
433
+ if respond_to?(:complexity_for)
434
+ lookahead = GraphQL::Execution::Lookahead.new(query: query, field: self, ast_nodes: nodes, owner_type: owner)
435
+ complexity_for(child_complexity: child_complexity, query: query, lookahead: lookahead)
436
+ elsif connection?
437
+ arguments = query.arguments_for(nodes.first, self)
438
+ max_possible_page_size = nil
439
+ if arguments.respond_to?(:[]) # It might have been an error
440
+ if arguments[:first]
441
+ max_possible_page_size = arguments[:first]
442
+ end
443
+
444
+ if arguments[:last] && (max_possible_page_size.nil? || arguments[:last] > max_possible_page_size)
445
+ max_possible_page_size = arguments[:last]
446
+ end
447
+ end
448
+
449
+ if max_possible_page_size.nil?
450
+ max_possible_page_size = max_page_size || query.schema.default_max_page_size
451
+ end
452
+
453
+ if max_possible_page_size.nil?
454
+ raise GraphQL::Error, "Can't calculate complexity for #{path}, no `first:`, `last:`, `max_page_size` or `default_max_page_size`"
455
+ else
456
+ metadata_complexity = 0
457
+ lookahead = GraphQL::Execution::Lookahead.new(query: query, field: self, ast_nodes: nodes, owner_type: owner)
458
+
459
+ if (page_info_lookahead = lookahead.selection(:page_info)).selected?
460
+ metadata_complexity += 1 # pageInfo
461
+ metadata_complexity += page_info_lookahead.selections.size # subfields
462
+ end
463
+
464
+ if lookahead.selects?(:total) || lookahead.selects?(:total_count) || lookahead.selects?(:count)
465
+ metadata_complexity += 1
466
+ end
467
+
468
+ nodes_edges_complexity = 0
469
+ nodes_edges_complexity += 1 if lookahead.selects?(:edges)
470
+ nodes_edges_complexity += 1 if lookahead.selects?(:nodes)
471
+
472
+ # Possible bug: selections on `edges` and `nodes` are _both_ multiplied here. Should they be?
473
+ items_complexity = child_complexity - metadata_complexity - nodes_edges_complexity
474
+ # Add 1 for _this_ field
475
+ 1 + (max_possible_page_size * items_complexity) + metadata_complexity + nodes_edges_complexity
476
+ end
477
+ else
478
+ defined_complexity = complexity
479
+ case defined_complexity
480
+ when Proc
481
+ arguments = query.arguments_for(nodes.first, self)
482
+ if arguments.is_a?(GraphQL::ExecutionError)
483
+ return child_complexity
484
+ elsif arguments.respond_to?(:keyword_arguments)
485
+ arguments = arguments.keyword_arguments
486
+ end
487
+
488
+ defined_complexity.call(query.context, arguments, child_complexity)
489
+ when Numeric
490
+ defined_complexity + child_complexity
491
+ else
492
+ raise("Invalid complexity: #{defined_complexity.inspect} on #{path} (#{inspect})")
493
+ end
494
+ end
495
+ end
496
+
497
+ def complexity(new_complexity = nil)
351
498
  case new_complexity
352
499
  when Proc
353
500
  if new_complexity.parameters.size != 3
@@ -360,14 +507,23 @@ module GraphQL
360
507
  end
361
508
  when Numeric
362
509
  @complexity = new_complexity
510
+ when nil
511
+ @complexity
363
512
  else
364
513
  raise("Invalid complexity: #{new_complexity.inspect} on #{@name}")
365
514
  end
366
515
  end
367
516
 
368
- # @return [Integer, nil] Applied to connections if present
517
+ # @return [Boolean] True if this field's {#max_page_size} should override the schema default.
518
+ def has_max_page_size?
519
+ @has_max_page_size
520
+ end
521
+
522
+ # @return [Integer, nil] Applied to connections if {#has_max_page_size?}
369
523
  attr_reader :max_page_size
370
524
 
525
+ prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
526
+
371
527
  # @return [GraphQL::Field]
372
528
  def to_graphql
373
529
  field_defn = if @field
@@ -387,8 +543,8 @@ module GraphQL
387
543
  field_defn.description = @description
388
544
  end
389
545
 
390
- if @deprecation_reason
391
- field_defn.deprecation_reason = @deprecation_reason
546
+ if self.deprecation_reason
547
+ field_defn.deprecation_reason = self.deprecation_reason
392
548
  end
393
549
 
394
550
  if @resolver_class
@@ -410,16 +566,21 @@ module GraphQL
410
566
  field_defn.relay_nodes_field = @relay_nodes_field
411
567
  end
412
568
 
569
+ if @legacy_edge_class
570
+ field_defn.edge_class = @legacy_edge_class
571
+ end
572
+
413
573
  field_defn.resolve = self.method(:resolve_field)
414
574
  field_defn.connection = connection?
415
575
  field_defn.connection_max_page_size = max_page_size
416
576
  field_defn.introspection = @introspection
417
577
  field_defn.complexity = @complexity
418
578
  field_defn.subscription_scope = @subscription_scope
579
+ field_defn.ast_node = ast_node
419
580
 
420
- arguments.each do |name, defn|
421
- arg_graphql = defn.to_graphql
422
- field_defn.arguments[arg_graphql.name] = arg_graphql
581
+ all_argument_definitions.each do |defn|
582
+ arg_graphql = defn.deprecated_to_graphql
583
+ field_defn.arguments[arg_graphql.name] = arg_graphql # rubocop:disable Development/ContextIsPassedCop -- legacy-related
423
584
  end
424
585
 
425
586
  # Support a passed-in proc, one way or another
@@ -433,14 +594,33 @@ module GraphQL
433
594
 
434
595
  # Ok, `self` isn't a class, but this is for consistency with the classes
435
596
  field_defn.metadata[:type_class] = self
436
-
597
+ field_defn.arguments_class = GraphQL::Query::Arguments.construct_arguments_class(field_defn)
437
598
  field_defn
438
599
  end
439
600
 
601
+ class MissingReturnTypeError < GraphQL::Error; end
602
+ attr_writer :type
603
+
440
604
  def type
441
- @type ||= Member::BuildType.parse_type(@return_type_expr, null: @return_type_null)
442
- rescue
443
- raise ArgumentError, "Failed to build return type for #{@owner.graphql_name}.#{name} from #{@return_type_expr.inspect}: #{$!.message}", $!.backtrace
605
+ @type ||= if @function
606
+ Member::BuildType.parse_type(@function.type, null: false)
607
+ elsif @field
608
+ Member::BuildType.parse_type(@field.type, null: false)
609
+ elsif @return_type_expr.nil?
610
+ # Not enough info to determine type
611
+ message = "Can't determine the return type for #{self.path}"
612
+ if @resolver_class
613
+ message += " (it has `resolver: #{@resolver_class}`, consider configuration a `type ...` for that class)"
614
+ end
615
+ raise MissingReturnTypeError, message
616
+ else
617
+ Member::BuildType.parse_type(@return_type_expr, null: @return_type_null)
618
+ end
619
+ rescue GraphQL::Schema::InvalidDocumentError, MissingReturnTypeError => err
620
+ # Let this propagate up
621
+ raise err
622
+ rescue StandardError => err
623
+ raise MissingReturnTypeError, "Failed to build return type for #{@owner.graphql_name}.#{name} from #{@return_type_expr.inspect}: (#{err.class}) #{err.message}", err.backtrace
444
624
  end
445
625
 
446
626
  def visible?(context)
@@ -459,15 +639,43 @@ module GraphQL
459
639
  end
460
640
  end
461
641
 
462
- def authorized?(object, context)
642
+ def authorized?(object, args, context)
463
643
  if @resolver_class
464
- # The resolver will check itself during `resolve()`
644
+ # The resolver _instance_ will check itself during `resolve()`
465
645
  @resolver_class.authorized?(object, context)
466
646
  else
467
- # Faster than `.any?`
468
- arguments.each_value do |arg|
469
- if !arg.authorized?(object, context)
470
- return false
647
+ if (arg_values = context[:current_arguments])
648
+ # ^^ that's provided by the interpreter at runtime, and includes info about whether the default value was used or not.
649
+ using_arg_values = true
650
+ arg_values = arg_values.argument_values
651
+ else
652
+ arg_values = args
653
+ using_arg_values = false
654
+ end
655
+ if args.size > 0
656
+ args = context.warden.arguments(self)
657
+ args.each do |arg|
658
+ arg_key = arg.keyword
659
+ if arg_values.key?(arg_key)
660
+ arg_value = arg_values[arg_key]
661
+ if using_arg_values
662
+ if arg_value.default_used?
663
+ # pass -- no auth required for default used
664
+ next
665
+ else
666
+ application_arg_value = arg_value.value
667
+ if application_arg_value.is_a?(GraphQL::Execution::Interpreter::Arguments)
668
+ application_arg_value.keyword_arguments
669
+ end
670
+ end
671
+ else
672
+ application_arg_value = arg_value
673
+ end
674
+
675
+ if !arg.authorized?(object, application_arg_value, context)
676
+ return false
677
+ end
678
+ end
471
679
  end
472
680
  end
473
681
  true
@@ -485,21 +693,22 @@ module GraphQL
485
693
  # Some legacy fields can have `nil` here, not exactly sure why.
486
694
  # @see https://github.com/rmosolgo/graphql-ruby/issues/1990 before removing
487
695
  inner_obj = after_obj && after_obj.object
488
- if authorized?(inner_obj, query_ctx)
489
- ruby_args = to_ruby_args(after_obj, args, ctx)
490
- # Then if it passed, resolve the field
491
- if @resolve_proc
492
- # Might be nil, still want to call the func in that case
493
- with_extensions(inner_obj, ruby_args, query_ctx) do |extended_obj, extended_args|
494
- # Pass the GraphQL args here for compatibility:
495
- @resolve_proc.call(extended_obj, args, ctx)
696
+ ctx.schema.after_lazy(to_ruby_args(after_obj, args, ctx)) do |ruby_args|
697
+ if authorized?(inner_obj, ruby_args, query_ctx)
698
+ # Then if it passed, resolve the field
699
+ if @resolve_proc
700
+ # Might be nil, still want to call the func in that case
701
+ with_extensions(inner_obj, ruby_args, query_ctx) do |extended_obj, extended_args|
702
+ # Pass the GraphQL args here for compatibility:
703
+ @resolve_proc.call(extended_obj, args, ctx)
704
+ end
705
+ else
706
+ public_send_field(after_obj, ruby_args, query_ctx)
496
707
  end
497
708
  else
498
- public_send_field(after_obj, ruby_args, ctx)
709
+ err = GraphQL::UnauthorizedFieldError.new(object: inner_obj, type: obj.class, context: ctx, field: self)
710
+ query_ctx.schema.unauthorized_field(err)
499
711
  end
500
- else
501
- err = GraphQL::UnauthorizedFieldError.new(object: inner_obj, type: obj.class, context: ctx, field: self)
502
- query_ctx.schema.unauthorized_field(err)
503
712
  end
504
713
  end
505
714
  end
@@ -516,34 +725,15 @@ module GraphQL
516
725
  begin
517
726
  # Unwrap the GraphQL object to get the application object.
518
727
  application_object = object.object
519
- if self.authorized?(application_object, ctx)
520
- # Apply field extensions
521
- with_extensions(object, args, ctx) do |extended_obj, extended_args|
522
- field_receiver = if @resolver_class
523
- resolver_obj = if extended_obj.is_a?(GraphQL::Schema::Object)
524
- extended_obj.object
525
- else
526
- extended_obj
527
- end
528
- @resolver_class.new(object: resolver_obj, context: ctx, field: self)
529
- else
530
- extended_obj
531
- end
532
728
 
533
- if field_receiver.respond_to?(@resolver_method)
534
- # Call the method with kwargs, if there are any
535
- if extended_args.any?
536
- field_receiver.public_send(@resolver_method, **extended_args)
537
- else
538
- field_receiver.public_send(@resolver_method)
539
- end
540
- else
541
- resolve_field_method(field_receiver, extended_args, ctx)
542
- end
729
+ Schema::Validator.validate!(validators, application_object, ctx, args)
730
+
731
+ ctx.schema.after_lazy(self.authorized?(application_object, args, ctx)) do |is_authorized|
732
+ if is_authorized
733
+ public_send_field(object, args, ctx)
734
+ else
735
+ raise GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: ctx, field: self)
543
736
  end
544
- else
545
- err = GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: ctx, field: self)
546
- ctx.schema.unauthorized_field(err)
547
737
  end
548
738
  rescue GraphQL::UnauthorizedFieldError => err
549
739
  err.field ||= self
@@ -555,46 +745,9 @@ module GraphQL
555
745
  err
556
746
  end
557
747
 
558
- # Find a way to resolve this field, checking:
559
- #
560
- # - Hash keys, if the wrapped object is a hash;
561
- # - A method on the wrapped object;
562
- # - Or, raise not implemented.
563
- #
564
- # This can be overridden by defining a method on the object type.
565
- # @param obj [GraphQL::Schema::Object]
566
- # @param ruby_kwargs [Hash<Symbol => Object>]
567
- # @param ctx [GraphQL::Query::Context]
568
- def resolve_field_method(obj, ruby_kwargs, ctx)
569
- if obj.object.is_a?(Hash)
570
- inner_object = obj.object
571
- if inner_object.key?(@method_sym)
572
- inner_object[@method_sym]
573
- else
574
- inner_object[@method_str]
575
- end
576
- elsif obj.object.respond_to?(@method_sym)
577
- if ruby_kwargs.any?
578
- obj.object.public_send(@method_sym, **ruby_kwargs)
579
- else
580
- obj.object.public_send(@method_sym)
581
- end
582
- else
583
- raise <<-ERR
584
- Failed to implement #{@owner.graphql_name}.#{@name}, tried:
585
-
586
- - `#{obj.class}##{@resolver_method}`, which did not exist
587
- - `#{obj.object.class}##{@method_sym}`, which did not exist
588
- - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
589
-
590
- To implement this field, define one of the methods above (and check for typos)
591
- ERR
592
- end
593
- end
594
-
595
748
  # @param ctx [GraphQL::Query::Context::FieldResolutionContext]
596
749
  def fetch_extra(extra_name, ctx)
597
- if extra_name != :path && respond_to?(extra_name)
750
+ if extra_name != :path && extra_name != :ast_node && respond_to?(extra_name)
598
751
  self.public_send(extra_name)
599
752
  elsif ctx.respond_to?(extra_name)
600
753
  ctx.public_send(extra_name)
@@ -617,11 +770,36 @@ module GraphQL
617
770
  if graphql_args.any? || @extras.any?
618
771
  # Splat the GraphQL::Arguments to Ruby keyword arguments
619
772
  ruby_kwargs = graphql_args.to_kwargs
773
+ maybe_lazies = []
620
774
  # Apply any `prepare` methods. Not great code organization, can this go somewhere better?
621
- arguments.each do |name, arg_defn|
775
+ arguments(field_ctx).each do |name, arg_defn|
622
776
  ruby_kwargs_key = arg_defn.keyword
623
- if ruby_kwargs.key?(ruby_kwargs_key) && arg_defn.prepare
624
- ruby_kwargs[ruby_kwargs_key] = arg_defn.prepare_value(obj, ruby_kwargs[ruby_kwargs_key])
777
+
778
+ if ruby_kwargs.key?(ruby_kwargs_key)
779
+ loads = arg_defn.loads
780
+ value = ruby_kwargs[ruby_kwargs_key]
781
+ loaded_value = if loads && !arg_defn.from_resolver?
782
+ if arg_defn.type.list?
783
+ loaded_values = value.map { |val| load_application_object(arg_defn, loads, val, field_ctx.query.context) }
784
+ field_ctx.schema.after_any_lazies(loaded_values) { |result| result }
785
+ else
786
+ load_application_object(arg_defn, loads, value, field_ctx.query.context)
787
+ end
788
+ elsif arg_defn.type.list? && value.is_a?(Array)
789
+ field_ctx.schema.after_any_lazies(value, &:itself)
790
+ else
791
+ value
792
+ end
793
+
794
+ maybe_lazies << field_ctx.schema.after_lazy(loaded_value) do |loaded_value|
795
+ prepared_value = if arg_defn.prepare
796
+ arg_defn.prepare_value(obj, loaded_value)
797
+ else
798
+ loaded_value
799
+ end
800
+
801
+ ruby_kwargs[ruby_kwargs_key] = prepared_value
802
+ end
625
803
  end
626
804
  end
627
805
 
@@ -629,32 +807,118 @@ module GraphQL
629
807
  ruby_kwargs[extra_arg] = fetch_extra(extra_arg, field_ctx)
630
808
  end
631
809
 
632
- ruby_kwargs
810
+ field_ctx.schema.after_any_lazies(maybe_lazies) do
811
+ ruby_kwargs
812
+ end
633
813
  else
634
814
  NO_ARGS
635
815
  end
636
816
  end
637
817
 
638
- def public_send_field(obj, ruby_kwargs, field_ctx)
639
- query_ctx = field_ctx.query.context
640
- with_extensions(obj, ruby_kwargs, query_ctx) do |extended_obj, extended_args|
641
- if @resolver_class
642
- if extended_obj.is_a?(GraphQL::Schema::Object)
643
- extended_obj = extended_obj.object
818
+ def public_send_field(unextended_obj, unextended_ruby_kwargs, query_ctx)
819
+ with_extensions(unextended_obj, unextended_ruby_kwargs, query_ctx) do |obj, ruby_kwargs|
820
+ begin
821
+ method_receiver = nil
822
+ method_to_call = nil
823
+ if @resolver_class
824
+ if obj.is_a?(GraphQL::Schema::Object)
825
+ obj = obj.object
826
+ end
827
+ obj = @resolver_class.new(object: obj, context: query_ctx, field: self)
644
828
  end
645
- extended_obj = @resolver_class.new(object: extended_obj, context: query_ctx, field: self)
829
+
830
+ # Find a way to resolve this field, checking:
831
+ #
832
+ # - A method on the type instance;
833
+ # - Hash keys, if the wrapped object is a hash or responds to `#[]`
834
+ # - A method on the wrapped object;
835
+ # - Or, raise not implemented.
836
+ #
837
+ if obj.respond_to?(@resolver_method)
838
+ method_to_call = @resolver_method
839
+ method_receiver = obj
840
+ # Call the method with kwargs, if there are any
841
+ if ruby_kwargs.any?
842
+ obj.public_send(@resolver_method, **ruby_kwargs)
843
+ else
844
+ obj.public_send(@resolver_method)
845
+ end
846
+ elsif obj.object.is_a?(Hash)
847
+ inner_object = obj.object
848
+ if @dig_keys
849
+ inner_object.dig(*@dig_keys)
850
+ elsif inner_object.key?(@method_sym)
851
+ inner_object[@method_sym]
852
+ else
853
+ inner_object[@method_str]
854
+ end
855
+ elsif defined?(@hash_key) && obj.object.respond_to?(:[])
856
+ obj.object[@hash_key]
857
+ elsif obj.object.respond_to?(@method_sym)
858
+ method_to_call = @method_sym
859
+ method_receiver = obj.object
860
+ if ruby_kwargs.any?
861
+ obj.object.public_send(@method_sym, **ruby_kwargs)
862
+ else
863
+ obj.object.public_send(@method_sym)
864
+ end
865
+ else
866
+ raise <<-ERR
867
+ Failed to implement #{@owner.graphql_name}.#{@name}, tried:
868
+
869
+ - `#{obj.class}##{@resolver_method}`, which did not exist
870
+ - `#{obj.object.class}##{@method_sym}`, which did not exist
871
+ - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
872
+
873
+ To implement this field, define one of the methods above (and check for typos)
874
+ ERR
875
+ end
876
+ rescue ArgumentError
877
+ assert_satisfactory_implementation(method_receiver, method_to_call, ruby_kwargs)
878
+ # if the line above doesn't raise, re-raise
879
+ raise
646
880
  end
881
+ end
882
+ end
647
883
 
648
- if extended_obj.respond_to?(@resolver_method)
649
- if extended_args.any?
650
- extended_obj.public_send(@resolver_method, **extended_args)
884
+ def assert_satisfactory_implementation(receiver, method_name, ruby_kwargs)
885
+ method_defn = receiver.method(method_name)
886
+ unsatisfied_ruby_kwargs = ruby_kwargs.dup
887
+ unsatisfied_method_params = []
888
+ encountered_keyrest = false
889
+ method_defn.parameters.each do |(param_type, param_name)|
890
+ case param_type
891
+ when :key
892
+ unsatisfied_ruby_kwargs.delete(param_name)
893
+ when :keyreq
894
+ if unsatisfied_ruby_kwargs.key?(param_name)
895
+ unsatisfied_ruby_kwargs.delete(param_name)
651
896
  else
652
- extended_obj.public_send(@resolver_method)
897
+ unsatisfied_method_params << "- `#{param_name}:` is required by Ruby, but not by GraphQL. Consider `#{param_name}: nil` instead, or making this argument required in GraphQL."
653
898
  end
654
- else
655
- resolve_field_method(extended_obj, extended_args, query_ctx)
899
+ when :keyrest
900
+ encountered_keyrest = true
901
+ when :req
902
+ unsatisfied_method_params << "- `#{param_name}` is required by Ruby, but GraphQL doesn't pass positional arguments. If it's meant to be a GraphQL argument, use `#{param_name}:` instead. Otherwise, remove it."
903
+ when :opt, :rest
904
+ # This is fine, although it will never be present
656
905
  end
657
906
  end
907
+
908
+ if encountered_keyrest
909
+ unsatisfied_ruby_kwargs.clear
910
+ end
911
+
912
+ if unsatisfied_ruby_kwargs.any? || unsatisfied_method_params.any?
913
+ raise FieldImplementationFailed.new, <<-ERR
914
+ Failed to call #{method_name} on #{receiver.inspect} because the Ruby method params were incompatible with the GraphQL arguments:
915
+
916
+ #{ unsatisfied_ruby_kwargs
917
+ .map { |key, value| "- `#{key}: #{value}` was given by GraphQL but not defined in the Ruby method. Add `#{key}:` to the method parameters." }
918
+ .concat(unsatisfied_method_params)
919
+ .join("\n") }
920
+ ERR
921
+ end
658
922
  end
659
923
 
660
924
  # Wrap execution with hooks.
@@ -664,32 +928,52 @@ module GraphQL
664
928
  if @extensions.empty?
665
929
  yield(obj, args)
666
930
  else
667
- # Save these so that the originals can be re-given to `after_resolve` handlers.
668
- original_args = args
669
- original_obj = obj
670
-
671
- memos = []
672
- value = run_extensions_before_resolve(memos, obj, args, ctx) do |extended_obj, extended_args|
673
- yield(extended_obj, extended_args)
931
+ # This is a hack to get the _last_ value for extended obj and args,
932
+ # in case one of the extensions doesn't `yield`.
933
+ # (There's another implementation that uses multiple-return, but I'm wary of the perf cost of the extra arrays)
934
+ extended = { args: args, obj: obj, memos: nil, added_extras: nil }
935
+ value = run_extensions_before_resolve(obj, args, ctx, extended) do |obj, args|
936
+ if (added_extras = extended[:added_extras])
937
+ args = args.dup
938
+ added_extras.each { |e| args.delete(e) }
939
+ end
940
+ yield(obj, args)
674
941
  end
675
942
 
943
+ extended_obj = extended[:obj]
944
+ extended_args = extended[:args]
945
+ memos = extended[:memos] || EMPTY_HASH
946
+
676
947
  ctx.schema.after_lazy(value) do |resolved_value|
677
- @extensions.each_with_index do |ext, idx|
948
+ idx = 0
949
+ @extensions.each do |ext|
678
950
  memo = memos[idx]
679
951
  # TODO after_lazy?
680
- resolved_value = ext.after_resolve(object: original_obj, arguments: original_args, context: ctx, value: resolved_value, memo: memo)
952
+ resolved_value = ext.after_resolve(object: extended_obj, arguments: extended_args, context: ctx, value: resolved_value, memo: memo)
953
+ idx += 1
681
954
  end
682
955
  resolved_value
683
956
  end
684
957
  end
685
958
  end
686
959
 
687
- def run_extensions_before_resolve(memos, obj, args, ctx, idx: 0)
960
+ def run_extensions_before_resolve(obj, args, ctx, extended, idx: 0)
688
961
  extension = @extensions[idx]
689
962
  if extension
690
963
  extension.resolve(object: obj, arguments: args, context: ctx) do |extended_obj, extended_args, memo|
691
- memos << memo
692
- run_extensions_before_resolve(memos, extended_obj, extended_args, ctx, idx: idx + 1) { |o, a| yield(o, a) }
964
+ if memo
965
+ memos = extended[:memos] ||= {}
966
+ memos[idx] = memo
967
+ end
968
+
969
+ if (extras = extension.added_extras)
970
+ ae = extended[:added_extras] ||= []
971
+ ae.concat(extras)
972
+ end
973
+
974
+ extended[:obj] = extended_obj
975
+ extended[:args] = extended_args
976
+ run_extensions_before_resolve(extended_obj, extended_args, ctx, extended, idx: idx + 1) { |o, a| yield(o, a) }
693
977
  end
694
978
  else
695
979
  yield(obj, args)