graphql 2.6.9 → 2.6.14

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 (382) hide show
  1. checksums.yaml +4 -4
  2. data/.rdoc_options +4 -0
  3. data/lib/generators/graphql/core.rb +1 -0
  4. data/lib/generators/graphql/detailed_trace_generator.rb +1 -0
  5. data/lib/generators/graphql/enum_generator.rb +1 -0
  6. data/lib/generators/graphql/field_extractor.rb +1 -0
  7. data/lib/generators/graphql/input_generator.rb +1 -0
  8. data/lib/generators/graphql/install/mutation_root_generator.rb +1 -0
  9. data/lib/generators/graphql/install_generator.rb +1 -0
  10. data/lib/generators/graphql/interface_generator.rb +1 -0
  11. data/lib/generators/graphql/loader_generator.rb +8 -2
  12. data/lib/generators/graphql/mutation_create_generator.rb +8 -2
  13. data/lib/generators/graphql/mutation_delete_generator.rb +8 -2
  14. data/lib/generators/graphql/mutation_generator.rb +8 -2
  15. data/lib/generators/graphql/mutation_update_generator.rb +8 -2
  16. data/lib/generators/graphql/object_generator.rb +1 -0
  17. data/lib/generators/graphql/orm_mutations_base.rb +8 -2
  18. data/lib/generators/graphql/relay.rb +1 -0
  19. data/lib/generators/graphql/relay_generator.rb +1 -0
  20. data/lib/generators/graphql/scalar_generator.rb +1 -0
  21. data/lib/generators/graphql/type_generator.rb +38 -8
  22. data/lib/generators/graphql/union_generator.rb +1 -0
  23. data/lib/graphql/analysis/analyzer.rb +35 -9
  24. data/lib/graphql/analysis/field_usage.rb +1 -0
  25. data/lib/graphql/analysis/max_query_complexity.rb +3 -2
  26. data/lib/graphql/analysis/max_query_depth.rb +1 -0
  27. data/lib/graphql/analysis/query_complexity.rb +57 -20
  28. data/lib/graphql/analysis/query_depth.rb +17 -12
  29. data/lib/graphql/analysis/visitor.rb +79 -16
  30. data/lib/graphql/analysis.rb +23 -6
  31. data/lib/graphql/analysis_error.rb +1 -0
  32. data/lib/graphql/autoload.rb +30 -7
  33. data/lib/graphql/backtrace/table.rb +21 -5
  34. data/lib/graphql/backtrace/traced_error.rb +8 -3
  35. data/lib/graphql/backtrace.rb +14 -9
  36. data/lib/graphql/coercion_error.rb +1 -0
  37. data/lib/graphql/current.rb +44 -19
  38. data/lib/graphql/dashboard/application_controller.rb +1 -0
  39. data/lib/graphql/dashboard/detailed_traces.rb +1 -0
  40. data/lib/graphql/dashboard/installable.rb +1 -0
  41. data/lib/graphql/dashboard/landings_controller.rb +1 -0
  42. data/lib/graphql/dashboard/limiters.rb +1 -0
  43. data/lib/graphql/dashboard/operation_store.rb +1 -0
  44. data/lib/graphql/dashboard/statics_controller.rb +1 -0
  45. data/lib/graphql/dashboard/subscriptions.rb +1 -0
  46. data/lib/graphql/dashboard.rb +36 -22
  47. data/lib/graphql/dataloader/active_record_association_source.rb +1 -0
  48. data/lib/graphql/dataloader/active_record_source.rb +1 -0
  49. data/lib/graphql/dataloader/async_dataloader.rb +45 -16
  50. data/lib/graphql/dataloader/null_dataloader.rb +4 -3
  51. data/lib/graphql/dataloader/request.rb +8 -2
  52. data/lib/graphql/dataloader/request_all.rb +8 -2
  53. data/lib/graphql/dataloader/source.rb +127 -35
  54. data/lib/graphql/dataloader.rb +98 -44
  55. data/lib/graphql/date_encoding_error.rb +2 -1
  56. data/lib/graphql/dig.rb +12 -3
  57. data/lib/graphql/duration_encoding_error.rb +2 -1
  58. data/lib/graphql/execution/directive_checks.rb +8 -3
  59. data/lib/graphql/execution/errors.rb +20 -6
  60. data/lib/graphql/execution/field_resolve_step.rb +288 -168
  61. data/lib/graphql/execution/finalize.rb +12 -6
  62. data/lib/graphql/execution/input_values.rb +5 -2
  63. data/lib/graphql/execution/interpreter/argument_value.rb +17 -5
  64. data/lib/graphql/execution/interpreter/arguments.rb +24 -9
  65. data/lib/graphql/execution/interpreter/arguments_cache.rb +2 -1
  66. data/lib/graphql/execution/interpreter/execution_errors.rb +1 -0
  67. data/lib/graphql/execution/interpreter/handles_raw_value.rb +1 -0
  68. data/lib/graphql/execution/interpreter/resolve.rb +18 -8
  69. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +4 -1
  70. data/lib/graphql/execution/interpreter/runtime.rb +72 -25
  71. data/lib/graphql/execution/interpreter.rb +18 -7
  72. data/lib/graphql/execution/lazy/lazy_method_map.rb +21 -8
  73. data/lib/graphql/execution/lazy.rb +34 -10
  74. data/lib/graphql/execution/load_argument_step.rb +1 -0
  75. data/lib/graphql/execution/lookahead.rb +137 -54
  76. data/lib/graphql/execution/multiplex.rb +4 -4
  77. data/lib/graphql/execution/next.rb +7 -0
  78. data/lib/graphql/execution/prepare_object_step.rb +38 -13
  79. data/lib/graphql/execution/resolve_type_step.rb +2 -1
  80. data/lib/graphql/execution/runner.rb +32 -15
  81. data/lib/graphql/execution/selections_step.rb +3 -2
  82. data/lib/graphql/execution.rb +2 -2
  83. data/lib/graphql/execution_error.rb +19 -10
  84. data/lib/graphql/float_decoding_error.rb +14 -0
  85. data/lib/graphql/float_encoding_error.rb +33 -0
  86. data/lib/graphql/integer_decoding_error.rb +2 -1
  87. data/lib/graphql/integer_encoding_error.rb +8 -3
  88. data/lib/graphql/introspection/base_object.rb +3 -1
  89. data/lib/graphql/introspection/directive_location_enum.rb +1 -0
  90. data/lib/graphql/introspection/directive_type.rb +1 -0
  91. data/lib/graphql/introspection/dynamic_fields.rb +1 -0
  92. data/lib/graphql/introspection/entry_points.rb +1 -0
  93. data/lib/graphql/introspection/enum_value_type.rb +1 -0
  94. data/lib/graphql/introspection/field_type.rb +1 -0
  95. data/lib/graphql/introspection/input_value_type.rb +1 -0
  96. data/lib/graphql/introspection/introspection_query.rb +1 -0
  97. data/lib/graphql/introspection/schema_type.rb +1 -0
  98. data/lib/graphql/introspection/type_kind_enum.rb +1 -0
  99. data/lib/graphql/introspection/type_type.rb +1 -0
  100. data/lib/graphql/introspection.rb +1 -0
  101. data/lib/graphql/invalid_name_error.rb +1 -0
  102. data/lib/graphql/invalid_null_error.rb +19 -6
  103. data/lib/graphql/language/block_string.rb +2 -1
  104. data/lib/graphql/language/cache.rb +10 -4
  105. data/lib/graphql/language/comment.rb +1 -0
  106. data/lib/graphql/language/definition_slice.rb +1 -0
  107. data/lib/graphql/language/document_from_schema_definition.rb +9 -10
  108. data/lib/graphql/language/generation.rb +23 -9
  109. data/lib/graphql/language/lexer.rb +16 -8
  110. data/lib/graphql/language/nodes.rb +2094 -419
  111. data/lib/graphql/language/parser.rb +29 -18
  112. data/lib/graphql/language/printer.rb +31 -16
  113. data/lib/graphql/language/sanitized_printer.rb +16 -5
  114. data/lib/graphql/language/static_visitor.rb +19 -4
  115. data/lib/graphql/language/visitor.rb +51 -29
  116. data/lib/graphql/language.rb +15 -9
  117. data/lib/graphql/load_application_object_failed_error.rb +14 -5
  118. data/lib/graphql/name_validator.rb +1 -0
  119. data/lib/graphql/pagination/active_record_relation_connection.rb +1 -0
  120. data/lib/graphql/pagination/array_connection.rb +24 -6
  121. data/lib/graphql/pagination/connection.rb +178 -48
  122. data/lib/graphql/pagination/connections.rb +21 -14
  123. data/lib/graphql/pagination/mongoid_relation_connection.rb +1 -0
  124. data/lib/graphql/pagination/relation_connection.rb +164 -27
  125. data/lib/graphql/pagination/sequel_dataset_connection.rb +1 -0
  126. data/lib/graphql/pagination.rb +1 -0
  127. data/lib/graphql/parse_error.rb +1 -0
  128. data/lib/graphql/query/context/scoped_context.rb +2 -1
  129. data/lib/graphql/query/context.rb +106 -35
  130. data/lib/graphql/query/fingerprint.rb +15 -7
  131. data/lib/graphql/query/input_validation_result.rb +1 -0
  132. data/lib/graphql/query/null_context.rb +1 -0
  133. data/lib/graphql/query/partial.rb +28 -16
  134. data/lib/graphql/query/result.rb +15 -5
  135. data/lib/graphql/query/validation_pipeline.rb +16 -6
  136. data/lib/graphql/query/variable_validation_error.rb +17 -1
  137. data/lib/graphql/query/variables.rb +9 -2
  138. data/lib/graphql/query.rb +195 -50
  139. data/lib/graphql/railtie.rb +7 -3
  140. data/lib/graphql/rake_task/validate.rb +72 -17
  141. data/lib/graphql/rake_task.rb +55 -23
  142. data/lib/graphql/relay/range_add.rb +32 -21
  143. data/lib/graphql/relay.rb +1 -0
  144. data/lib/graphql/rubocop/graphql/base_cop.rb +1 -0
  145. data/lib/graphql/rubocop/graphql/default_null_true.rb +9 -6
  146. data/lib/graphql/rubocop/graphql/default_required_true.rb +9 -6
  147. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +12 -8
  148. data/lib/graphql/rubocop/graphql/root_types_in_block.rb +9 -5
  149. data/lib/graphql/rubocop.rb +1 -0
  150. data/lib/graphql/runtime_error.rb +1 -0
  151. data/lib/graphql/runtime_type_error.rb +1 -0
  152. data/lib/graphql/schema/addition.rb +69 -50
  153. data/lib/graphql/schema/always_visible.rb +1 -0
  154. data/lib/graphql/schema/argument.rb +279 -45
  155. data/lib/graphql/schema/base_64_encoder.rb +2 -2
  156. data/lib/graphql/schema/build_from_definition/resolve_map/default_resolve.rb +1 -0
  157. data/lib/graphql/schema/build_from_definition/resolve_map.rb +2 -2
  158. data/lib/graphql/schema/build_from_definition.rb +507 -178
  159. data/lib/graphql/schema/built_in_types.rb +1 -0
  160. data/lib/graphql/schema/directive/deprecated.rb +1 -0
  161. data/lib/graphql/schema/directive/feature.rb +39 -22
  162. data/lib/graphql/schema/directive/flagged.rb +6 -1
  163. data/lib/graphql/schema/directive/include.rb +1 -0
  164. data/lib/graphql/schema/directive/one_of.rb +1 -0
  165. data/lib/graphql/schema/directive/skip.rb +1 -0
  166. data/lib/graphql/schema/directive/specified_by.rb +1 -0
  167. data/lib/graphql/schema/directive/transform.rb +17 -8
  168. data/lib/graphql/schema/directive.rb +33 -7
  169. data/lib/graphql/schema/enum.rb +99 -38
  170. data/lib/graphql/schema/enum_value.rb +21 -13
  171. data/lib/graphql/schema/field/connection_extension.rb +10 -2
  172. data/lib/graphql/schema/field/scope_extension.rb +1 -0
  173. data/lib/graphql/schema/field.rb +622 -110
  174. data/lib/graphql/schema/field_extension.rb +89 -34
  175. data/lib/graphql/schema/find_inherited_value.rb +5 -3
  176. data/lib/graphql/schema/finder.rb +22 -8
  177. data/lib/graphql/schema/has_single_input_argument.rb +39 -6
  178. data/lib/graphql/schema/input_object.rb +40 -13
  179. data/lib/graphql/schema/interface.rb +33 -17
  180. data/lib/graphql/schema/introspection_system.rb +7 -3
  181. data/lib/graphql/schema/late_bound_type.rb +2 -2
  182. data/lib/graphql/schema/list.rb +15 -4
  183. data/lib/graphql/schema/loader.rb +23 -12
  184. data/lib/graphql/schema/member/base_dsl_methods.rb +58 -12
  185. data/lib/graphql/schema/member/build_type.rb +21 -7
  186. data/lib/graphql/schema/member/graphql_type_names.rb +10 -6
  187. data/lib/graphql/schema/member/has_arguments.rb +112 -49
  188. data/lib/graphql/schema/member/has_ast_node.rb +1 -0
  189. data/lib/graphql/schema/member/has_authorization.rb +5 -0
  190. data/lib/graphql/schema/member/has_dataloader.rb +91 -34
  191. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -2
  192. data/lib/graphql/schema/member/has_directives.rb +45 -11
  193. data/lib/graphql/schema/member/has_fields.rb +151 -70
  194. data/lib/graphql/schema/member/has_interfaces.rb +8 -2
  195. data/lib/graphql/schema/member/has_path.rb +14 -5
  196. data/lib/graphql/schema/member/has_unresolved_type_error.rb +1 -0
  197. data/lib/graphql/schema/member/has_validators.rb +19 -4
  198. data/lib/graphql/schema/member/relay_shortcuts.rb +10 -2
  199. data/lib/graphql/schema/member/scoped.rb +14 -3
  200. data/lib/graphql/schema/member/type_system_helpers.rb +36 -6
  201. data/lib/graphql/schema/member/validates_input.rb +1 -0
  202. data/lib/graphql/schema/member.rb +9 -2
  203. data/lib/graphql/schema/mutation.rb +50 -46
  204. data/lib/graphql/schema/non_null.rb +21 -5
  205. data/lib/graphql/schema/object.rb +44 -15
  206. data/lib/graphql/schema/printer.rb +52 -31
  207. data/lib/graphql/schema/ractor_shareable.rb +6 -2
  208. data/lib/graphql/schema/relay_classic_mutation.rb +3 -3
  209. data/lib/graphql/schema/resolver/has_payload_type.rb +29 -7
  210. data/lib/graphql/schema/resolver.rb +156 -45
  211. data/lib/graphql/schema/scalar.rb +1 -0
  212. data/lib/graphql/schema/subscription.rb +67 -28
  213. data/lib/graphql/schema/timeout.rb +65 -25
  214. data/lib/graphql/schema/type_expression.rb +14 -5
  215. data/lib/graphql/schema/type_membership.rb +29 -10
  216. data/lib/graphql/schema/union.rb +4 -2
  217. data/lib/graphql/schema/unique_within_type.rb +24 -5
  218. data/lib/graphql/schema/validator/all_validator.rb +17 -9
  219. data/lib/graphql/schema/validator/allow_blank_validator.rb +8 -2
  220. data/lib/graphql/schema/validator/allow_null_validator.rb +8 -2
  221. data/lib/graphql/schema/validator/exclusion_validator.rb +14 -5
  222. data/lib/graphql/schema/validator/format_validator.rb +21 -10
  223. data/lib/graphql/schema/validator/inclusion_validator.rb +15 -6
  224. data/lib/graphql/schema/validator/length_validator.rb +23 -12
  225. data/lib/graphql/schema/validator/numericality_validator.rb +29 -16
  226. data/lib/graphql/schema/validator/required_validator.rb +45 -31
  227. data/lib/graphql/schema/validator.rb +89 -25
  228. data/lib/graphql/schema/visibility/migration.rb +14 -7
  229. data/lib/graphql/schema/visibility/profile.rb +73 -19
  230. data/lib/graphql/schema/visibility/visit.rb +12 -10
  231. data/lib/graphql/schema/visibility.rb +24 -20
  232. data/lib/graphql/schema/warden.rb +117 -31
  233. data/lib/graphql/schema/wrapper.rb +4 -1
  234. data/lib/graphql/schema.rb +1052 -304
  235. data/lib/graphql/static_validation/all_rules.rb +2 -1
  236. data/lib/graphql/static_validation/base_visitor.rb +59 -9
  237. data/lib/graphql/static_validation/definition_dependencies.rb +27 -7
  238. data/lib/graphql/static_validation/error.rb +3 -0
  239. data/lib/graphql/static_validation/interpreter_visitor.rb +1 -0
  240. data/lib/graphql/static_validation/literal_validator.rb +1 -0
  241. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +4 -0
  242. data/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb +1 -0
  243. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +3 -0
  244. data/lib/graphql/static_validation/rules/argument_names_are_unique_error.rb +1 -0
  245. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +3 -0
  246. data/lib/graphql/static_validation/rules/arguments_are_defined_error.rb +1 -0
  247. data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -0
  248. data/lib/graphql/static_validation/rules/directives_are_defined_error.rb +1 -0
  249. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +3 -0
  250. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations_error.rb +1 -0
  251. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +3 -0
  252. data/lib/graphql/static_validation/rules/fields_are_defined_on_type_error.rb +1 -0
  253. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +9 -1
  254. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections_error.rb +1 -0
  255. data/lib/graphql/static_validation/rules/fields_will_merge.rb +355 -157
  256. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +14 -2
  257. data/lib/graphql/static_validation/rules/fragment_names_are_unique.rb +3 -0
  258. data/lib/graphql/static_validation/rules/fragment_names_are_unique_error.rb +1 -0
  259. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -0
  260. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible_error.rb +1 -0
  261. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +3 -0
  262. data/lib/graphql/static_validation/rules/fragment_types_exist_error.rb +1 -0
  263. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +3 -0
  264. data/lib/graphql/static_validation/rules/fragments_are_finite_error.rb +1 -0
  265. data/lib/graphql/static_validation/rules/fragments_are_named.rb +3 -0
  266. data/lib/graphql/static_validation/rules/fragments_are_named_error.rb +1 -0
  267. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +3 -0
  268. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types_error.rb +1 -0
  269. data/lib/graphql/static_validation/rules/fragments_are_used.rb +3 -0
  270. data/lib/graphql/static_validation/rules/fragments_are_used_error.rb +1 -0
  271. data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +3 -0
  272. data/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb +1 -0
  273. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +3 -0
  274. data/lib/graphql/static_validation/rules/mutation_root_exists_error.rb +1 -0
  275. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +3 -0
  276. data/lib/graphql/static_validation/rules/no_definitions_are_present_error.rb +1 -0
  277. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +1 -0
  278. data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid.rb +4 -0
  279. data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid_error.rb +1 -0
  280. data/lib/graphql/static_validation/rules/operation_names_are_valid.rb +3 -0
  281. data/lib/graphql/static_validation/rules/operation_names_are_valid_error.rb +1 -0
  282. data/lib/graphql/static_validation/rules/query_root_exists.rb +3 -0
  283. data/lib/graphql/static_validation/rules/query_root_exists_error.rb +1 -0
  284. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +4 -1
  285. data/lib/graphql/static_validation/rules/required_arguments_are_present_error.rb +1 -0
  286. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +4 -0
  287. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present_error.rb +1 -0
  288. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +43 -5
  289. data/lib/graphql/static_validation/rules/subscription_root_exists_error.rb +1 -0
  290. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +3 -0
  291. data/lib/graphql/static_validation/rules/unique_directives_per_location_error.rb +1 -0
  292. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +3 -0
  293. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed_error.rb +1 -0
  294. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +3 -0
  295. data/lib/graphql/static_validation/rules/variable_names_are_unique_error.rb +1 -0
  296. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +207 -51
  297. data/lib/graphql/static_validation/rules/variable_usages_are_allowed_error.rb +1 -0
  298. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +3 -0
  299. data/lib/graphql/static_validation/rules/variables_are_input_types_error.rb +1 -0
  300. data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +6 -3
  301. data/lib/graphql/static_validation/rules/variables_are_used_and_defined_error.rb +1 -0
  302. data/lib/graphql/static_validation/validation_context.rb +3 -2
  303. data/lib/graphql/static_validation/validation_timeout_error.rb +1 -0
  304. data/lib/graphql/static_validation/validator.rb +41 -16
  305. data/lib/graphql/static_validation.rb +1 -0
  306. data/lib/graphql/string_encoding_error.rb +1 -0
  307. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +85 -65
  308. data/lib/graphql/subscriptions/broadcast_analyzer.rb +10 -4
  309. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +1 -0
  310. data/lib/graphql/subscriptions/event.rb +25 -6
  311. data/lib/graphql/subscriptions/serialize.rb +89 -23
  312. data/lib/graphql/subscriptions.rb +172 -46
  313. data/lib/graphql/testing/helpers.rb +17 -4
  314. data/lib/graphql/testing/mock_action_cable.rb +55 -33
  315. data/lib/graphql/testing.rb +1 -0
  316. data/lib/graphql/tracing/active_support_notifications_trace.rb +17 -9
  317. data/lib/graphql/tracing/active_support_notifications_tracing.rb +2 -1
  318. data/lib/graphql/tracing/appoptics_trace.rb +2 -1
  319. data/lib/graphql/tracing/appoptics_tracing.rb +2 -1
  320. data/lib/graphql/tracing/appsignal_trace.rb +16 -7
  321. data/lib/graphql/tracing/appsignal_tracing.rb +7 -3
  322. data/lib/graphql/tracing/call_legacy_tracers.rb +1 -0
  323. data/lib/graphql/tracing/data_dog_trace.rb +17 -6
  324. data/lib/graphql/tracing/data_dog_tracing.rb +10 -3
  325. data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +1 -0
  326. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +1 -0
  327. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +1 -0
  328. data/lib/graphql/tracing/detailed_trace.rb +105 -42
  329. data/lib/graphql/tracing/legacy_hooks_trace.rb +5 -0
  330. data/lib/graphql/tracing/legacy_trace.rb +1 -0
  331. data/lib/graphql/tracing/monitor_trace.rb +17 -11
  332. data/lib/graphql/tracing/new_relic_trace.rb +17 -8
  333. data/lib/graphql/tracing/new_relic_tracing.rb +7 -3
  334. data/lib/graphql/tracing/notifications_trace.rb +11 -8
  335. data/lib/graphql/tracing/notifications_tracing.rb +23 -7
  336. data/lib/graphql/tracing/null_trace.rb +1 -0
  337. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +1 -0
  338. data/lib/graphql/tracing/perfetto_trace.rb +41 -19
  339. data/lib/graphql/tracing/platform_trace.rb +3 -1
  340. data/lib/graphql/tracing/platform_tracing.rb +16 -7
  341. data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +1 -0
  342. data/lib/graphql/tracing/prometheus_trace.rb +22 -13
  343. data/lib/graphql/tracing/prometheus_tracing.rb +1 -0
  344. data/lib/graphql/tracing/scout_trace.rb +10 -4
  345. data/lib/graphql/tracing/scout_tracing.rb +7 -3
  346. data/lib/graphql/tracing/sentry_trace.rb +25 -6
  347. data/lib/graphql/tracing/statsd_trace.rb +12 -6
  348. data/lib/graphql/tracing/statsd_tracing.rb +7 -1
  349. data/lib/graphql/tracing/trace.rb +231 -58
  350. data/lib/graphql/tracing.rb +25 -9
  351. data/lib/graphql/type_kinds.rb +2 -1
  352. data/lib/graphql/types/big_int.rb +1 -0
  353. data/lib/graphql/types/boolean.rb +1 -0
  354. data/lib/graphql/types/float.rb +19 -4
  355. data/lib/graphql/types/id.rb +1 -0
  356. data/lib/graphql/types/int.rb +2 -1
  357. data/lib/graphql/types/iso_8601_date.rb +21 -4
  358. data/lib/graphql/types/iso_8601_date_time.rb +33 -6
  359. data/lib/graphql/types/iso_8601_duration.rb +42 -9
  360. data/lib/graphql/types/json.rb +1 -0
  361. data/lib/graphql/types/relay/base_connection.rb +38 -32
  362. data/lib/graphql/types/relay/base_edge.rb +15 -9
  363. data/lib/graphql/types/relay/connection_behaviors.rb +19 -3
  364. data/lib/graphql/types/relay/edge_behaviors.rb +17 -4
  365. data/lib/graphql/types/relay/has_node_field.rb +6 -1
  366. data/lib/graphql/types/relay/has_nodes_field.rb +6 -1
  367. data/lib/graphql/types/relay/node.rb +1 -0
  368. data/lib/graphql/types/relay/node_behaviors.rb +5 -1
  369. data/lib/graphql/types/relay/page_info.rb +1 -0
  370. data/lib/graphql/types/relay/page_info_behaviors.rb +1 -0
  371. data/lib/graphql/types/relay.rb +4 -3
  372. data/lib/graphql/types/string.rb +1 -0
  373. data/lib/graphql/types.rb +1 -0
  374. data/lib/graphql/unauthorized_enum_value_error.rb +4 -1
  375. data/lib/graphql/unauthorized_error.rb +11 -4
  376. data/lib/graphql/unauthorized_field_error.rb +4 -1
  377. data/lib/graphql/unresolved_type_error.rb +16 -5
  378. data/lib/graphql/version.rb +2 -1
  379. data/lib/graphql.rb +39 -9
  380. data/readme.md +1 -1
  381. metadata +91 -5
  382. data/.yardopts +0 -5
@@ -1,8 +1,12 @@
1
1
  # frozen_string_literal: true
2
+ # typed: true
2
3
 
3
4
  # frozen_string_literal: true
4
5
  module GraphQL
5
6
  module StaticValidation
7
+ #--
8
+ # @requires_ancestor: GraphQL::StaticValidation::BaseVisitor
9
+ # @requires_ancestor: GraphQL::StaticValidation::BaseVisitor::ContextMethods
6
10
  module FieldsWillMerge
7
11
  # Validates that a selection set is valid if all fields (including spreading any
8
12
  # fragments) either correspond to distinct response names or can be merged
@@ -15,10 +19,13 @@ module GraphQL
15
19
  # separately (which leads to exponential recursion through nested fragments),
16
20
  # we flatten all fragment spreads into a single field map and compare within it.
17
21
  NO_ARGS = GraphQL::EmptyObjects::EMPTY_HASH
22
+ EXCLUSIVE_COMPARISON = 1
23
+ NONEXCLUSIVE_COMPARISON = 2
18
24
 
19
25
  class Field
20
26
  attr_reader :node, :definition, :owner_type, :parents
21
27
 
28
+ # `parents` is only for the legacy check, see `legacy_mutually_exclusive?`
22
29
  def initialize(node, definition, owner_type, parents)
23
30
  @node = node
24
31
  @definition = definition
@@ -26,6 +33,11 @@ module GraphQL
26
33
  @parents = parents
27
34
  end
28
35
 
36
+ # The types which decide if this field is mutually exclusive with other fields
37
+ def scope
38
+ @parents || @owner_type
39
+ end
40
+
29
41
  def return_type
30
42
  @return_type ||= @definition&.type
31
43
  end
@@ -33,6 +45,10 @@ module GraphQL
33
45
  def unwrapped_return_type
34
46
  @unwrapped_return_type ||= return_type&.unwrap
35
47
  end
48
+
49
+ def comparison_key
50
+ @comparison_key ||= [@node, scope]
51
+ end
36
52
  end
37
53
 
38
54
  def initialize(*)
@@ -43,30 +59,145 @@ module GraphQL
43
59
  # Track which sub-selection node pairs have been compared to prevent
44
60
  # infinite recursion with cyclic fragments
45
61
  @compared_sub_selections = {}.compare_by_identity
46
- # Cache mutually_exclusive? results for type pairs
47
- @mutually_exclusive_cache = {}.compare_by_identity
62
+ @compared_field_groups = {}
63
+ @field_group_signatures = {}.compare_by_identity
64
+ @field_selection_signatures = {}.compare_by_identity
48
65
  # Cache collect_fields results for sub-selection comparison
49
66
  @sub_fields_cache = {}.compare_by_identity
67
+
68
+ @allow_legacy_invalid_field_conflicts = @schema.allow_legacy_invalid_field_conflicts
69
+ # Unless legacy conflicts are rejected, use the legacy check, which is the same as previous versions.
70
+ # It uses `legacy_mutually_exclusive?` and the caches above. The spec check uses `@spec_caches`.
71
+ @legacy_exclusivity = @allow_legacy_invalid_field_conflicts != false
72
+ @spec_caches = nil
73
+ @mutually_exclusive_cache = nil
74
+ # True when the spec check can find conflicts which the legacy check doesn't find
75
+ @spec_check_needed = false
76
+ # During the legacy check, true when the spec rule treats the parent fields as mutually exclusive
77
+ @spec_inherited_exclusive = false
78
+ # The pairs of nodes which have a conflict in the legacy check
79
+ @legacy_conflict_nodes = nil
80
+ # When this is true, record conflicts in `@spec_conflicts` instead of adding errors
81
+ @find_spec_conflicts = false
82
+ @spec_conflicts = nil
50
83
  end
51
84
 
52
85
  def on_operation_definition(node, _parent)
53
- @conflicts = nil
54
- conflicts_within_selection_set(node, type_definition)
55
- @conflicts&.each_value { |error_type| error_type.each_value { |error| add_error(error) } }
86
+ check_selection_set(node, type_definition)
56
87
  super
57
88
  end
58
89
 
59
90
  def on_field(node, _parent)
60
91
  if !node.selections.empty? && selections_may_conflict?(node.selections)
61
- @conflicts = nil
62
- conflicts_within_selection_set(node, type_definition)
63
- @conflicts&.each_value { |error_type| error_type.each_value { |error| add_error(error) } }
92
+ check_selection_set(node, type_definition)
64
93
  end
65
94
  super
66
95
  end
67
96
 
68
97
  private
69
98
 
99
+ #: (untyped node, untyped parent_type) -> void
100
+ def check_selection_set(node, parent_type)
101
+ conflicts_within_selection_set(node, parent_type)
102
+
103
+ if @spec_check_needed
104
+ # Find the conflicts with the spec check, without errors or warnings. The new ones are legacy conflicts.
105
+ legacy_conflicts = @conflicts
106
+ conflict_count = @conflict_count
107
+ @conflicts = nil
108
+ with_spec_check { conflicts_within_selection_set(node, parent_type) }
109
+ @conflicts = legacy_conflicts
110
+ @conflict_count = conflict_count
111
+ legacy_conflict_nodes = (@legacy_conflict_nodes ||= {}.compare_by_identity)
112
+ @spec_conflicts&.each do |(response_key, node1, node2, different_names, different_arguments, type1, type2)|
113
+ next if legacy_conflict_nodes[node1]&.key?(node2)
114
+ add_legacy_invalid_conflict(response_key, node1, node2, different_names, different_arguments, type1, type2)
115
+ add_legacy_conflict_nodes(node1, node2)
116
+ end
117
+ @spec_conflicts = nil
118
+ @spec_check_needed = false
119
+ end
120
+
121
+ @conflicts&.each_value { |error_type| error_type.each_value { |error| add_error(error) } }
122
+ @conflicts = nil
123
+ end
124
+
125
+ # Use the spec rule for mutually exclusive fields, with its own caches, and record conflicts in `@spec_conflicts`
126
+ #: () { () -> void } -> void
127
+ def with_spec_check(&block)
128
+ legacy_caches = [@compared_sub_selections, @compared_field_groups, @field_group_signatures, @sub_fields_cache]
129
+ @compared_sub_selections, @compared_field_groups, @field_group_signatures, @sub_fields_cache =
130
+ (@spec_caches ||= [{}.compare_by_identity, {}, {}.compare_by_identity, {}.compare_by_identity])
131
+ @legacy_exclusivity = false
132
+ @find_spec_conflicts = true
133
+ yield
134
+ ensure
135
+ @legacy_exclusivity = true
136
+ @find_spec_conflicts = false
137
+ @compared_sub_selections, @compared_field_groups, @field_group_signatures, @sub_fields_cache = legacy_caches
138
+ end
139
+
140
+ #: (untyped response_key, GraphQL::Language::Nodes::Field node1, GraphQL::Language::Nodes::Field node2, bool different_names, bool different_arguments, untyped type1, untyped type2) -> void
141
+ def add_legacy_invalid_conflict(response_key, node1, node2, different_names, different_arguments, type1, type2)
142
+ message_override = nil
143
+ if @allow_legacy_invalid_field_conflicts == true
144
+ legacy_handling = @schema.legacy_invalid_field_conflicts(@context.query, node1, node2)
145
+ case legacy_handling
146
+ when nil
147
+ return
148
+ when :return_validation_error
149
+ # Add the error below
150
+ when String
151
+ message_override = legacy_handling
152
+ else
153
+ raise GraphQL::Error, "#{@schema}.legacy_invalid_field_conflicts returned unexpected value: #{legacy_handling.inspect}. Expected `nil`, String, or `:return_validation_error`."
154
+ end
155
+ else
156
+ @context.query.logger.warn <<~WARN
157
+ GraphQL-Ruby encountered fields which can't be merged in this query: `#{node1.name}` (at #{node1.line}:#{node1.col}) vs. `#{node2.name}` (at #{node2.line}:#{node2.col}).
158
+ This will return an error in future GraphQL-Ruby versions, as per the GraphQL specification
159
+ Learn about migrating here: https://graphql-ruby.org/api-doc/#{GraphQL::VERSION}/GraphQL/Schema.html#allow_legacy_invalid_field_conflicts-class_method
160
+ WARN
161
+ return
162
+ end
163
+
164
+ if type1
165
+ conflict = conflicts[:return_type][response_key]
166
+ conflict.message = message_override if message_override
167
+ conflict.add_conflict(node1, "`#{type1.to_type_signature}`")
168
+ conflict.add_conflict(node2, "`#{type2.to_type_signature}`")
169
+ @conflict_count += 1
170
+ else
171
+ add_field_conflict(response_key, node1, node2, different_names, different_arguments, message_override)
172
+ end
173
+ end
174
+
175
+ #: (GraphQL::Language::Nodes::Field node1, GraphQL::Language::Nodes::Field node2) -> void
176
+ def add_legacy_conflict_nodes(node1, node2)
177
+ legacy_conflict_nodes = (@legacy_conflict_nodes ||= {}.compare_by_identity)
178
+ (legacy_conflict_nodes[node1] ||= {}.compare_by_identity)[node2] = true
179
+ (legacy_conflict_nodes[node2] ||= {}.compare_by_identity)[node1] = true
180
+ end
181
+
182
+ #: (untyped response_key, GraphQL::Language::Nodes::Field node1, GraphQL::Language::Nodes::Field node2, bool different_names, bool different_arguments, String? message_override) -> void
183
+ def add_field_conflict(response_key, node1, node2, different_names, different_arguments, message_override)
184
+ if different_names
185
+ conflict = conflicts[:field][response_key]
186
+ conflict.message = message_override if message_override
187
+ conflict.add_conflict(node1, node1.name)
188
+ conflict.add_conflict(node2, node2.name)
189
+ @conflict_count += 1
190
+ end
191
+
192
+ if different_arguments
193
+ conflict = conflicts[:argument][response_key]
194
+ conflict.message = message_override if message_override
195
+ conflict.add_conflict(node1, GraphQL::Language.serialize(serialize_field_args(node1)))
196
+ conflict.add_conflict(node2, GraphQL::Language.serialize(serialize_field_args(node2)))
197
+ @conflict_count += 1
198
+ end
199
+ end
200
+
70
201
  # Quick check: can the direct children of this selection set possibly conflict?
71
202
  # If all direct selections are Fields with unique names and no aliases,
72
203
  # and there are no fragments, then no response key can have >1 field,
@@ -120,7 +251,7 @@ module GraphQL
120
251
  return if node.selections.empty?
121
252
 
122
253
  # Collect all fields from this selection set, expanding fragments transitively
123
- response_keys = collect_fields(node.selections, owner_type: parent_type, parents: [])
254
+ response_keys = collect_fields(node.selections, owner_type: parent_type, parents: @legacy_exclusivity ? [] : nil)
124
255
 
125
256
  # Find conflicts within each response key group
126
257
  find_conflicts_within(response_keys)
@@ -135,7 +266,7 @@ module GraphQL
135
266
  end
136
267
 
137
268
  def collect_fields_inner(selections, owner_type:, parents:, response_keys:, visited_fragments:)
138
- deferred_spreads = nil
269
+ deferred_spreads = nil #: Array[untyped]?
139
270
  sel_idx = 0
140
271
  sel_len = selections.size
141
272
 
@@ -157,11 +288,10 @@ module GraphQL
157
288
  existing << field
158
289
  end
159
290
  when GraphQL::Language::Nodes::InlineFragment
160
- frag_type = sel.type ? @types.type(sel.type.name) : owner_type
291
+ frag_type = (sel_type = sel.type) ? @types.type(sel_type.name) : owner_type
161
292
 
162
293
  if frag_type
163
- new_parents = parents.dup
164
- new_parents << frag_type
294
+ new_parents = parents && (parents.dup << frag_type)
165
295
  collect_fields_inner(sel.selections, owner_type: frag_type, parents: new_parents, response_keys: response_keys, visited_fragments: visited_fragments)
166
296
  end
167
297
  when GraphQL::Language::Nodes::FragmentSpread
@@ -188,8 +318,7 @@ module GraphQL
188
318
  frag_type = @types.type(frag.type.name)
189
319
  next unless frag_type
190
320
 
191
- new_parents = parents.dup
192
- new_parents << frag_type
321
+ new_parents = parents && (parents.dup << frag_type)
193
322
  collect_fields_inner(frag.selections, owner_type: frag_type, parents: new_parents, response_keys: response_keys, visited_fragments: visited_fragments)
194
323
  end
195
324
  end
@@ -199,12 +328,13 @@ module GraphQL
199
328
  response_keys.each do |key, fields|
200
329
  next unless fields.is_a?(Array)
201
330
 
202
- # Optimization: group fields by signature (name + definition + arguments).
203
- # Fields with the same signature can only conflict on sub-selections,
204
- # so we only need to compare one pair within each group.
205
- if fields.size > 4
331
+ # Optimization: compare only one of the fields which have the same signature
332
+ # (name + definition + arguments), parent type and sub-selections.
333
+ if fields.size > 4 && @legacy_exclusivity
334
+ legacy_find_conflicts_within(key, fields)
335
+ elsif fields.size > 4
206
336
  f0 = fields[0]
207
- all_same = true
337
+ all_same = true #: bool
208
338
  i = 1
209
339
  while i < fields.size
210
340
  unless fields_same_signature?(f0, fields[i])
@@ -215,54 +345,24 @@ module GraphQL
215
345
  end
216
346
 
217
347
  if all_same
218
- # All fields share a signature, so they can only conflict on
219
- # sub-selections. Deduplicate by AST node identity — fields from
220
- # the same node always have identical sub-selections.
221
- unique_nodes = fields.uniq { |f| f.node.object_id }
348
+ # These fields can only conflict on sub-selections.
349
+ find_conflicts_between_selection_groups(key, fields)
350
+ else
351
+ representatives = {}
352
+ fields.each do |field|
353
+ representatives[[field_signature(field), field.scope, field_selection_signature(field)]] ||= field
354
+ end
355
+ representatives = representatives.values
356
+
222
357
  i = 0
223
- while i < unique_nodes.size
358
+ while i < representatives.size
224
359
  j = i + 1
225
- while j < unique_nodes.size
226
- if unique_nodes[i].node.selections.size > 0 || unique_nodes[j].node.selections.size > 0
227
- find_conflict(key, unique_nodes[i], unique_nodes[j])
228
- end
360
+ while j < representatives.size
361
+ find_conflict(key, representatives[i], representatives[j])
229
362
  j += 1
230
363
  end
231
364
  i += 1
232
365
  end
233
- else
234
- groups = fields.group_by { |f| field_signature(f) }
235
- unique_groups = groups.values
236
-
237
- # Compare representatives across different groups
238
- gi = 0
239
- while gi < unique_groups.size
240
- gj = gi + 1
241
- while gj < unique_groups.size
242
- find_conflict(key, unique_groups[gi][0], unique_groups[gj][0])
243
- gj += 1
244
- end
245
-
246
- # Within same group, deduplicate by AST node and compare all
247
- # pairs for sub-selection conflicts
248
- group = unique_groups[gi]
249
- if group.size >= 2
250
- unique_in_group = group.uniq { |f| f.node.object_id }
251
- ui = 0
252
- while ui < unique_in_group.size
253
- uj = ui + 1
254
- while uj < unique_in_group.size
255
- if unique_in_group[ui].node.selections.size > 0 || unique_in_group[uj].node.selections.size > 0
256
- find_conflict(key, unique_in_group[ui], unique_in_group[uj])
257
- end
258
- uj += 1
259
- end
260
- ui += 1
261
- end
262
- end
263
-
264
- gi += 1
265
- end
266
366
  end
267
367
  else
268
368
  # Small number of fields — original O(n²) is fine
@@ -279,11 +379,71 @@ module GraphQL
279
379
  end
280
380
  end
281
381
 
382
+ # Previous versions compared only the first field of each group of fields with the same signature,
383
+ # and one field for each sub-selection in a group. For the spec check, that's the same as comparing each field,
384
+ # unless the fields in a group have different parent types or sub-selections. Then the spec check can find more conflicts.
385
+ #: (untyped response_key, Array[Field] fields) -> void
386
+ def legacy_find_conflicts_within(response_key, fields)
387
+ f0 = fields[0] #: as !nil
388
+ n0 = f0.node
389
+ if fields.all? { |f| f.definition.equal?(f0.definition) && f.node.name == n0.name && same_arguments?(f.node, n0) }
390
+ find_conflicts_between_selection_groups(response_key, fields)
391
+ if !@spec_check_needed && !fields.all? { |f| f.node.selections.empty? || f.owner_type.equal?(f0.owner_type) }
392
+ @spec_check_needed = true
393
+ end
394
+ else
395
+ groups = fields.group_by { |f| field_signature(f) }.values
396
+ gi = 0
397
+ while gi < groups.size
398
+ group = groups[gi] #: as !nil
399
+ gj = gi + 1
400
+ while gj < groups.size
401
+ other_group = groups[gj] #: as !nil
402
+ find_conflict(response_key, group[0], other_group[0])
403
+ gj += 1
404
+ end
405
+ if group.size >= 2
406
+ find_conflicts_between_selection_groups(response_key, group)
407
+ if !@spec_check_needed
408
+ g0 = group[0] #: as !nil
409
+ g0_selections = field_selection_signature(g0)
410
+ @spec_check_needed = !group.all? { |f| f.owner_type.equal?(g0.owner_type) && field_selection_signature(f) == g0_selections }
411
+ end
412
+ end
413
+ gi += 1
414
+ end
415
+ end
416
+ end
417
+
418
+ def find_conflicts_between_selection_groups(response_key, fields)
419
+ fields_by_selection = {}
420
+ fields.each do |field|
421
+ fields_by_selection[field_selection_signature(field)] ||= field
422
+ end
423
+
424
+ representatives = fields_by_selection.values
425
+ i = 0
426
+ while i < representatives.size
427
+ j = i + 1
428
+ while j < representatives.size
429
+ find_conflict(response_key, representatives[i], representatives[j])
430
+ j += 1
431
+ end
432
+ i += 1
433
+ end
434
+ end
435
+
436
+ def field_selection_signature(field)
437
+ node = field.node
438
+ @field_selection_signatures[node] ||= node.selections.map(&:to_query_string)
439
+ end
440
+
282
441
  def fields_same_signature?(f1, f2)
283
442
  n1 = f1.node
284
443
  n2 = f2.node
285
444
 
286
445
  f1.definition.equal?(f2.definition) &&
446
+ f1.scope == f2.scope &&
287
447
  n1.name == n2.name &&
288
448
  same_arguments?(n1, n2)
289
449
  end
@@ -307,25 +467,28 @@ module GraphQL
307
467
  node1 = field1.node
308
468
  node2 = field2.node
309
469
 
310
- are_mutually_exclusive = mutually_exclusive || mutually_exclusive?(field1.parents, field2.parents)
470
+ are_mutually_exclusive = mutually_exclusive || mutually_exclusive?(field1, field2)
311
471
 
312
472
  if !are_mutually_exclusive
313
- if node1.name != node2.name
314
- conflict = conflicts[:field][response_key]
315
-
316
- conflict.add_conflict(node1, node1.name)
317
- conflict.add_conflict(node2, node2.name)
473
+ different_names = node1.name != node2.name
474
+ different_arguments = !same_arguments?(node1, node2)
475
+ if different_names || different_arguments
476
+ if @find_spec_conflicts
477
+ (@spec_conflicts ||= []) << [response_key, node1, node2, different_names, different_arguments, nil, nil]
478
+ @conflict_count += 1
479
+ else
480
+ add_field_conflict(response_key, node1, node2, different_names, different_arguments, nil)
481
+ add_legacy_conflict_nodes(node1, node2) if @legacy_exclusivity
482
+ end
318
483
 
319
- @conflict_count += 1
484
+ # Like graphql-js, stop here: the document is already invalid, and comparing sub-selections can be slow.
485
+ return
320
486
  end
321
-
322
- if !same_arguments?(node1, node2)
323
- conflict = conflicts[:argument][response_key]
324
-
325
- conflict.add_conflict(node1, GraphQL::Language.serialize(serialize_field_args(node1)))
326
- conflict.add_conflict(node2, GraphQL::Language.serialize(serialize_field_args(node2)))
327
-
328
- @conflict_count += 1
487
+ elsif @legacy_exclusivity
488
+ # If the spec rule doesn't treat these fields as mutually exclusive, it can find a conflict
489
+ spec_exclusive = @spec_inherited_exclusive || spec_mutually_exclusive?(field1, field2)
490
+ if !spec_exclusive && !@spec_check_needed && (node1.name != node2.name || !same_arguments?(node1, node2))
491
+ @spec_check_needed = true
329
492
  end
330
493
  end
331
494
 
@@ -334,74 +497,77 @@ module GraphQL
334
497
  (t1 = field1.return_type) &&
335
498
  (t2 = field2.return_type) &&
336
499
  return_types_conflict?(t1, t2)
500
+ if @find_spec_conflicts
501
+ (@spec_conflicts ||= []) << [response_key, node1, node2, false, false, t1, t2]
502
+ else
503
+ add_return_type_conflict(response_key, t1, t2, node1, node2)
504
+ add_legacy_conflict_nodes(node1, node2) if @legacy_exclusivity
505
+ end
506
+ end
337
507
 
338
- return_error = nil
339
- message_override = nil
508
+ if spec_exclusive && !@spec_inherited_exclusive
509
+ @spec_inherited_exclusive = true
510
+ find_conflicts_between_sub_selection_sets(field1, field2, mutually_exclusive: are_mutually_exclusive)
511
+ @spec_inherited_exclusive = false
512
+ else
513
+ find_conflicts_between_sub_selection_sets(field1, field2, mutually_exclusive: are_mutually_exclusive)
514
+ end
515
+ end
516
+
517
+ #: (untyped response_key, untyped t1, untyped t2, GraphQL::Language::Nodes::Field node1, GraphQL::Language::Nodes::Field node2) -> void
518
+ def add_return_type_conflict(response_key, t1, t2, node1, node2)
519
+ return_error = nil
520
+ message_override = nil
340
521
 
341
- case @schema.allow_legacy_invalid_return_type_conflicts
342
- when false
522
+ case @schema.allow_legacy_invalid_return_type_conflicts
523
+ when false
524
+ return_error = true
525
+ when true
526
+ legacy_handling = @schema.legacy_invalid_return_type_conflicts(@context.query, t1, t2, node1, node2)
527
+
528
+ case legacy_handling
529
+ when nil
530
+ return_error = false
531
+ when :return_validation_error
343
532
  return_error = true
344
- when true
345
- legacy_handling = @schema.legacy_invalid_return_type_conflicts(@context.query, t1, t2, node1, node2)
346
-
347
- case legacy_handling
348
- when nil
349
- return_error = false
350
- when :return_validation_error
351
- return_error = true
352
- when String
353
- return_error = true
354
- message_override = legacy_handling
355
- else
356
- raise GraphQL::Error, "#{@schema}.legacy_invalid_scalar_conflicts returned unexpected value: #{legacy_handling.inspect}. Expected `nil`, String, or `:return_validation_error`."
357
- end
533
+ when String
534
+ return_error = true
535
+ message_override = legacy_handling
358
536
  else
359
- return_error = false
360
- @context.query.logger.warn <<~WARN
361
- GraphQL-Ruby encountered mismatched types in this query: `#{t1.to_type_signature}` (at #{node1.line}:#{node1.col}) vs. `#{t2.to_type_signature}` (at #{node2.line}:#{node2.col}).
362
- This will return an error in future GraphQL-Ruby versions, as per the GraphQL specification
363
- Learn about migrating here: https://graphql-ruby.org/api-doc/#{GraphQL::VERSION}/GraphQL/Schema.html#allow_legacy_invalid_return_type_conflicts-class_method
364
- WARN
537
+ raise GraphQL::Error, "#{@schema}.legacy_invalid_scalar_conflicts returned unexpected value: #{legacy_handling.inspect}. Expected `nil`, String, or `:return_validation_error`."
365
538
  end
539
+ else
540
+ return_error = false
541
+ @context.query.logger.warn <<~WARN
542
+ GraphQL-Ruby encountered mismatched types in this query: `#{t1.to_type_signature}` (at #{node1.line}:#{node1.col}) vs. `#{t2.to_type_signature}` (at #{node2.line}:#{node2.col}).
543
+ This will return an error in future GraphQL-Ruby versions, as per the GraphQL specification
544
+ Learn about migrating here: https://graphql-ruby.org/api-doc/#{GraphQL::VERSION}/GraphQL/Schema.html#allow_legacy_invalid_return_type_conflicts-class_method
545
+ WARN
546
+ end
366
547
 
367
- if return_error
368
- conflict = conflicts[:return_type][response_key]
369
-
370
- if message_override
371
- conflict.message = message_override
372
- end
548
+ if return_error
549
+ conflict = conflicts[:return_type][response_key]
373
550
 
374
- conflict.add_conflict(node1, "`#{t1.to_type_signature}`")
375
- conflict.add_conflict(node2, "`#{t2.to_type_signature}`")
376
- @conflict_count += 1
551
+ if message_override
552
+ conflict.message = message_override
377
553
  end
378
- end
379
554
 
380
- find_conflicts_between_sub_selection_sets(
381
- field1,
382
- field2,
383
- mutually_exclusive: are_mutually_exclusive,
384
- )
555
+ conflict.add_conflict(node1, "`#{t1.to_type_signature}`")
556
+ conflict.add_conflict(node2, "`#{t2.to_type_signature}`")
557
+ @conflict_count += 1
558
+ end
385
559
  end
386
560
 
561
+ # SameResponseShape in the spec. Compare one wrapper at a time: `list?` is also true for a non-null list.
562
+ #: (untyped type1, untyped type2) -> bool
387
563
  def return_types_conflict?(type1, type2)
388
- if type1.list?
389
- if type2.list?
390
- return_types_conflict?(type1.of_type, type2.of_type)
391
- else
392
- true
393
- end
394
- elsif type2.list?
395
- true
396
- elsif type1.non_null?
397
- if type2.non_null?
398
- return_types_conflict?(type1.of_type, type2.of_type)
399
- else
400
- true
401
- end
402
- elsif type2.non_null?
403
- true
404
- elsif type1.kind.leaf? && type2.kind.leaf?
564
+ kind1 = type1.kind
565
+ kind2 = type2.kind
566
+ if kind1.non_null? || kind2.non_null?
567
+ !(kind1.non_null? && kind2.non_null?) || return_types_conflict?(type1.of_type, type2.of_type)
568
+ elsif kind1.list? || kind2.list?
569
+ !(kind1.list? && kind2.list?) || return_types_conflict?(type1.of_type, type2.of_type)
570
+ elsif kind1.leaf? || kind2.leaf?
405
571
  type1 != type2
406
572
  else
407
573
  false
@@ -446,7 +612,7 @@ module GraphQL
446
612
  if inner && inner.key?(return_type)
447
613
  inner[return_type]
448
614
  else
449
- result = collect_fields(node.selections, owner_type: return_type, parents: [return_type])
615
+ result = collect_fields(node.selections, owner_type: return_type, parents: @legacy_exclusivity ? [return_type] : nil)
450
616
  inner ||= {}.compare_by_identity
451
617
  inner[return_type] = result
452
618
  @sub_fields_cache[node] = inner
@@ -458,6 +624,7 @@ module GraphQL
458
624
  response_keys.each do |key, fields|
459
625
  fields2 = response_keys2[key]
460
626
  next unless fields2
627
+ next if field_groups_already_compared?(fields, fields2, mutually_exclusive)
461
628
 
462
629
  fields_arr = fields.is_a?(Field) ? [fields] : fields
463
630
  fields2_arr = fields2.is_a?(Field) ? [fields2] : fields2
@@ -475,6 +642,36 @@ module GraphQL
475
642
  end
476
643
  end
477
644
 
645
+ def field_groups_already_compared?(fields, fields2, mutually_exclusive)
646
+ signature1 = field_group_signature(fields)
647
+ signature2 = field_group_signature(fields2)
648
+ previous_comparisons = @compared_field_groups[signature1]
649
+ comparison_state = previous_comparisons && previous_comparisons[signature2]
650
+
651
+ if mutually_exclusive
652
+ return true if comparison_state
653
+ new_state = EXCLUSIVE_COMPARISON
654
+ else
655
+ return true if comparison_state == NONEXCLUSIVE_COMPARISON
656
+ new_state = NONEXCLUSIVE_COMPARISON
657
+ end
658
+
659
+ previous_comparisons ||= (@compared_field_groups[signature1] = {})
660
+ previous_comparisons[signature2] = new_state
661
+
662
+ reverse_comparisons = @compared_field_groups[signature2] ||= {}
663
+ reverse_comparisons[signature1] = new_state
664
+ false
665
+ end
666
+
667
+ def field_group_signature(fields)
668
+ if fields.is_a?(Field)
669
+ fields.comparison_key
670
+ else
671
+ @field_group_signatures[fields] ||= fields.map(&:comparison_key)
672
+ end
673
+ end
674
+
478
675
  def same_arguments?(field1, field2)
479
676
  arguments1 = field1.arguments
480
677
  arguments2 = field2.arguments
@@ -508,47 +705,48 @@ module GraphQL
508
705
  serialized_args
509
706
  end
510
707
 
511
- # Given two list of parents, find out if they are mutually exclusive
512
- def mutually_exclusive?(parents1, parents2)
708
+ #: (Field field1, Field field2) -> bool
709
+ def mutually_exclusive?(field1, field2)
710
+ @legacy_exclusivity ? legacy_mutually_exclusive?(field1.parents, field2.parents) : spec_mutually_exclusive?(field1, field2)
711
+ end
712
+
713
+ # As in the spec (FieldsInSetCanMerge), only fields on different object types are mutually exclusive,
714
+ # even if no runtime object can be selected by both of them.
715
+ #: (Field field1, Field field2) -> bool
716
+ def spec_mutually_exclusive?(field1, field2)
717
+ owner_type1 = field1.owner_type
718
+ owner_type2 = field2.owner_type
719
+ !owner_type1.equal?(owner_type2) && owner_type1.kind.object? && owner_type2.kind.object?
720
+ end
721
+
722
+ # Previous versions compared the lists of type conditions above each field, position by position,
723
+ # and treated lists with different lengths as mutually exclusive.
724
+ #: (Array[untyped] parents1, Array[untyped] parents2) -> bool
725
+ def legacy_mutually_exclusive?(parents1, parents2)
513
726
  if parents1.empty? || parents2.empty?
514
727
  false
515
728
  elsif parents1.length == parents2.length
729
+ cache = (@mutually_exclusive_cache ||= {}.compare_by_identity)
516
730
  i = 0
517
731
  len = parents1.length
518
-
519
732
  while i < len
520
733
  type1 = parents1[i - 1]
521
734
  type2 = parents2[i - 1]
522
735
  unless type1.equal?(type2)
523
- inner = @mutually_exclusive_cache[type1]
524
- if inner
525
- cached = inner[type2]
526
- if cached.nil?
527
- cached = types_mutually_exclusive?(type1, type2)
528
- inner[type2] = cached
529
- end
530
- else
531
- cached = types_mutually_exclusive?(type1, type2)
532
- inner = {}.compare_by_identity
533
- inner[type2] = cached
534
- @mutually_exclusive_cache[type1] = inner
736
+ inner = (cache[type1] ||= {}.compare_by_identity)
737
+ exclusive = inner[type2]
738
+ if exclusive.nil?
739
+ exclusive = inner[type2] = (@types.possible_types(type1) & @types.possible_types(type2)).empty?
535
740
  end
536
- return true if cached
741
+ return true if exclusive
537
742
  end
538
743
  i += 1
539
744
  end
540
-
541
745
  false
542
746
  else
543
747
  true
544
748
  end
545
749
  end
546
-
547
- def types_mutually_exclusive?(type1, type2)
548
- possible_right_types = @types.possible_types(type1)
549
- possible_left_types = @types.possible_types(type2)
550
- (possible_right_types & possible_left_types).empty?
551
- end
552
750
  end
553
751
  end
554
752
  end