graphql 2.6.12 → 2.6.13

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 +2 -1
  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 +8 -7
  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 +91 -42
  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 +22 -6
  61. data/lib/graphql/execution/finalize.rb +12 -6
  62. data/lib/graphql/execution/input_values.rb +2 -1
  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 +66 -23
  71. data/lib/graphql/execution/interpreter.rb +15 -6
  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 +1 -0
  79. data/lib/graphql/execution/resolve_type_step.rb +2 -1
  80. data/lib/graphql/execution/runner.rb +9 -3
  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 +1 -0
  85. data/lib/graphql/float_encoding_error.rb +7 -2
  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 +5 -0
  110. data/lib/graphql/language/nodes.rb +2094 -419
  111. data/lib/graphql/language/parser.rb +17 -11
  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 +13 -7
  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 +20 -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 +118 -21
  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 -15
  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 +1 -0
  137. data/lib/graphql/query/variables.rb +4 -1
  138. data/lib/graphql/query.rb +193 -49
  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 +18 -6
  153. data/lib/graphql/schema/always_visible.rb +1 -0
  154. data/lib/graphql/schema/argument.rb +265 -42
  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 +94 -27
  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 +12 -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 +621 -109
  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 +16 -5
  186. data/lib/graphql/schema/member/graphql_type_names.rb +10 -6
  187. data/lib/graphql/schema/member/has_arguments.rb +115 -48
  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 +38 -10
  193. data/lib/graphql/schema/member/has_fields.rb +151 -70
  194. data/lib/graphql/schema/member/has_interfaces.rb +10 -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 +33 -12
  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 +20 -17
  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 +1046 -300
  235. data/lib/graphql/static_validation/all_rules.rb +2 -1
  236. data/lib/graphql/static_validation/base_visitor.rb +48 -8
  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 +295 -134
  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 +4 -1
  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 +58 -19
  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 +12 -5
  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 +1 -0
  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 +37 -9
  380. data/readme.md +1 -1
  381. metadata +89 -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
@@ -21,6 +25,7 @@ module GraphQL
21
25
  class Field
22
26
  attr_reader :node, :definition, :owner_type, :parents
23
27
 
28
+ # `parents` is only for the legacy check, see `legacy_mutually_exclusive?`
24
29
  def initialize(node, definition, owner_type, parents)
25
30
  @node = node
26
31
  @definition = definition
@@ -28,6 +33,11 @@ module GraphQL
28
33
  @parents = parents
29
34
  end
30
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
+
31
41
  def return_type
32
42
  @return_type ||= @definition&.type
33
43
  end
@@ -37,7 +47,7 @@ module GraphQL
37
47
  end
38
48
 
39
49
  def comparison_key
40
- @comparison_key ||= [@node, @parents]
50
+ @comparison_key ||= [@node, scope]
41
51
  end
42
52
  end
43
53
 
@@ -52,30 +62,142 @@ module GraphQL
52
62
  @compared_field_groups = {}
53
63
  @field_group_signatures = {}.compare_by_identity
54
64
  @field_selection_signatures = {}.compare_by_identity
55
- # Cache mutually_exclusive? results for type pairs
56
- @mutually_exclusive_cache = {}.compare_by_identity
57
65
  # Cache collect_fields results for sub-selection comparison
58
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
59
83
  end
60
84
 
61
85
  def on_operation_definition(node, _parent)
62
- @conflicts = nil
63
- conflicts_within_selection_set(node, type_definition)
64
- @conflicts&.each_value { |error_type| error_type.each_value { |error| add_error(error) } }
86
+ check_selection_set(node, type_definition)
65
87
  super
66
88
  end
67
89
 
68
90
  def on_field(node, _parent)
69
91
  if !node.selections.empty? && selections_may_conflict?(node.selections)
70
- @conflicts = nil
71
- conflicts_within_selection_set(node, type_definition)
72
- @conflicts&.each_value { |error_type| error_type.each_value { |error| add_error(error) } }
92
+ check_selection_set(node, type_definition)
73
93
  end
74
94
  super
75
95
  end
76
96
 
77
97
  private
78
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
+
79
201
  # Quick check: can the direct children of this selection set possibly conflict?
80
202
  # If all direct selections are Fields with unique names and no aliases,
81
203
  # and there are no fragments, then no response key can have >1 field,
@@ -129,7 +251,7 @@ module GraphQL
129
251
  return if node.selections.empty?
130
252
 
131
253
  # Collect all fields from this selection set, expanding fragments transitively
132
- 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)
133
255
 
134
256
  # Find conflicts within each response key group
135
257
  find_conflicts_within(response_keys)
@@ -144,7 +266,7 @@ module GraphQL
144
266
  end
145
267
 
146
268
  def collect_fields_inner(selections, owner_type:, parents:, response_keys:, visited_fragments:)
147
- deferred_spreads = nil
269
+ deferred_spreads = nil #: Array[untyped]?
148
270
  sel_idx = 0
149
271
  sel_len = selections.size
150
272
 
@@ -166,11 +288,10 @@ module GraphQL
166
288
  existing << field
167
289
  end
168
290
  when GraphQL::Language::Nodes::InlineFragment
169
- 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
170
292
 
171
293
  if frag_type
172
- new_parents = parents.dup
173
- new_parents << frag_type
294
+ new_parents = parents && (parents.dup << frag_type)
174
295
  collect_fields_inner(sel.selections, owner_type: frag_type, parents: new_parents, response_keys: response_keys, visited_fragments: visited_fragments)
175
296
  end
176
297
  when GraphQL::Language::Nodes::FragmentSpread
@@ -197,8 +318,7 @@ module GraphQL
197
318
  frag_type = @types.type(frag.type.name)
198
319
  next unless frag_type
199
320
 
200
- new_parents = parents.dup
201
- new_parents << frag_type
321
+ new_parents = parents && (parents.dup << frag_type)
202
322
  collect_fields_inner(frag.selections, owner_type: frag_type, parents: new_parents, response_keys: response_keys, visited_fragments: visited_fragments)
203
323
  end
204
324
  end
@@ -208,12 +328,13 @@ module GraphQL
208
328
  response_keys.each do |key, fields|
209
329
  next unless fields.is_a?(Array)
210
330
 
211
- # Optimization: group fields by signature (name + definition + arguments).
212
- # Fields with the same signature can only conflict on sub-selections,
213
- # so we only need to compare one pair within each group.
214
- 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
215
336
  f0 = fields[0]
216
- all_same = true
337
+ all_same = true #: bool
217
338
  i = 1
218
339
  while i < fields.size
219
340
  unless fields_same_signature?(f0, fields[i])
@@ -224,27 +345,23 @@ module GraphQL
224
345
  end
225
346
 
226
347
  if all_same
348
+ # These fields can only conflict on sub-selections.
227
349
  find_conflicts_between_selection_groups(key, fields)
228
350
  else
229
- groups = fields.group_by { |f| field_signature(f) }
230
- unique_groups = groups.values
231
-
232
- # Compare representatives across different groups
233
- gi = 0
234
- while gi < unique_groups.size
235
- gj = gi + 1
236
- while gj < unique_groups.size
237
- find_conflict(key, unique_groups[gi][0], unique_groups[gj][0])
238
- gj += 1
239
- end
240
-
241
- # Within the same group, fields can only conflict on sub-selections.
242
- group = unique_groups[gi]
243
- if group.size >= 2
244
- find_conflicts_between_selection_groups(key, group)
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
+
357
+ i = 0
358
+ while i < representatives.size
359
+ j = i + 1
360
+ while j < representatives.size
361
+ find_conflict(key, representatives[i], representatives[j])
362
+ j += 1
245
363
  end
246
-
247
- gi += 1
364
+ i += 1
248
365
  end
249
366
  end
250
367
  else
@@ -262,6 +379,42 @@ module GraphQL
262
379
  end
263
380
  end
264
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
+
265
418
  def find_conflicts_between_selection_groups(response_key, fields)
266
419
  fields_by_selection = {}
267
420
  fields.each do |field|
@@ -290,6 +443,7 @@ module GraphQL
290
443
  n2 = f2.node
291
444
 
292
445
  f1.definition.equal?(f2.definition) &&
446
+ f1.scope == f2.scope &&
293
447
  n1.name == n2.name &&
294
448
  same_arguments?(n1, n2)
295
449
  end
@@ -313,25 +467,28 @@ module GraphQL
313
467
  node1 = field1.node
314
468
  node2 = field2.node
315
469
 
316
- are_mutually_exclusive = mutually_exclusive || mutually_exclusive?(field1.parents, field2.parents)
470
+ are_mutually_exclusive = mutually_exclusive || mutually_exclusive?(field1, field2)
317
471
 
318
472
  if !are_mutually_exclusive
319
- if node1.name != node2.name
320
- conflict = conflicts[:field][response_key]
321
-
322
- conflict.add_conflict(node1, node1.name)
323
- 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
324
483
 
325
- @conflict_count += 1
484
+ # Like graphql-js, stop here: the document is already invalid, and comparing sub-selections can be slow.
485
+ return
326
486
  end
327
-
328
- if !same_arguments?(node1, node2)
329
- conflict = conflicts[:argument][response_key]
330
-
331
- conflict.add_conflict(node1, GraphQL::Language.serialize(serialize_field_args(node1)))
332
- conflict.add_conflict(node2, GraphQL::Language.serialize(serialize_field_args(node2)))
333
-
334
- @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
335
492
  end
336
493
  end
337
494
 
@@ -340,74 +497,77 @@ module GraphQL
340
497
  (t1 = field1.return_type) &&
341
498
  (t2 = field2.return_type) &&
342
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
507
+
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
343
521
 
344
- return_error = nil
345
- message_override = nil
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)
346
527
 
347
- case @schema.allow_legacy_invalid_return_type_conflicts
348
- when false
528
+ case legacy_handling
529
+ when nil
530
+ return_error = false
531
+ when :return_validation_error
349
532
  return_error = true
350
- when true
351
- legacy_handling = @schema.legacy_invalid_return_type_conflicts(@context.query, t1, t2, node1, node2)
352
-
353
- case legacy_handling
354
- when nil
355
- return_error = false
356
- when :return_validation_error
357
- return_error = true
358
- when String
359
- return_error = true
360
- message_override = legacy_handling
361
- else
362
- raise GraphQL::Error, "#{@schema}.legacy_invalid_scalar_conflicts returned unexpected value: #{legacy_handling.inspect}. Expected `nil`, String, or `:return_validation_error`."
363
- end
533
+ when String
534
+ return_error = true
535
+ message_override = legacy_handling
364
536
  else
365
- return_error = false
366
- @context.query.logger.warn <<~WARN
367
- 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}).
368
- This will return an error in future GraphQL-Ruby versions, as per the GraphQL specification
369
- Learn about migrating here: https://graphql-ruby.org/api-doc/#{GraphQL::VERSION}/GraphQL/Schema.html#allow_legacy_invalid_return_type_conflicts-class_method
370
- WARN
537
+ raise GraphQL::Error, "#{@schema}.legacy_invalid_scalar_conflicts returned unexpected value: #{legacy_handling.inspect}. Expected `nil`, String, or `:return_validation_error`."
371
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
372
547
 
373
- if return_error
374
- conflict = conflicts[:return_type][response_key]
375
-
376
- if message_override
377
- conflict.message = message_override
378
- end
548
+ if return_error
549
+ conflict = conflicts[:return_type][response_key]
379
550
 
380
- conflict.add_conflict(node1, "`#{t1.to_type_signature}`")
381
- conflict.add_conflict(node2, "`#{t2.to_type_signature}`")
382
- @conflict_count += 1
551
+ if message_override
552
+ conflict.message = message_override
383
553
  end
384
- end
385
554
 
386
- find_conflicts_between_sub_selection_sets(
387
- field1,
388
- field2,
389
- mutually_exclusive: are_mutually_exclusive,
390
- )
555
+ conflict.add_conflict(node1, "`#{t1.to_type_signature}`")
556
+ conflict.add_conflict(node2, "`#{t2.to_type_signature}`")
557
+ @conflict_count += 1
558
+ end
391
559
  end
392
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
393
563
  def return_types_conflict?(type1, type2)
394
- if type1.list?
395
- if type2.list?
396
- return_types_conflict?(type1.of_type, type2.of_type)
397
- else
398
- true
399
- end
400
- elsif type2.list?
401
- true
402
- elsif type1.non_null?
403
- if type2.non_null?
404
- return_types_conflict?(type1.of_type, type2.of_type)
405
- else
406
- true
407
- end
408
- elsif type2.non_null?
409
- true
410
- 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?
411
571
  type1 != type2
412
572
  else
413
573
  false
@@ -452,7 +612,7 @@ module GraphQL
452
612
  if inner && inner.key?(return_type)
453
613
  inner[return_type]
454
614
  else
455
- 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)
456
616
  inner ||= {}.compare_by_identity
457
617
  inner[return_type] = result
458
618
  @sub_fields_cache[node] = inner
@@ -545,47 +705,48 @@ module GraphQL
545
705
  serialized_args
546
706
  end
547
707
 
548
- # Given two list of parents, find out if they are mutually exclusive
549
- 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)
550
726
  if parents1.empty? || parents2.empty?
551
727
  false
552
728
  elsif parents1.length == parents2.length
729
+ cache = (@mutually_exclusive_cache ||= {}.compare_by_identity)
553
730
  i = 0
554
731
  len = parents1.length
555
-
556
732
  while i < len
557
733
  type1 = parents1[i - 1]
558
734
  type2 = parents2[i - 1]
559
735
  unless type1.equal?(type2)
560
- inner = @mutually_exclusive_cache[type1]
561
- if inner
562
- cached = inner[type2]
563
- if cached.nil?
564
- cached = types_mutually_exclusive?(type1, type2)
565
- inner[type2] = cached
566
- end
567
- else
568
- cached = types_mutually_exclusive?(type1, type2)
569
- inner = {}.compare_by_identity
570
- inner[type2] = cached
571
- @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?
572
740
  end
573
- return true if cached
741
+ return true if exclusive
574
742
  end
575
743
  i += 1
576
744
  end
577
-
578
745
  false
579
746
  else
580
747
  true
581
748
  end
582
749
  end
583
-
584
- def types_mutually_exclusive?(type1, type2)
585
- possible_right_types = @types.possible_types(type1)
586
- possible_left_types = @types.possible_types(type2)
587
- (possible_right_types & possible_left_types).empty?
588
- end
589
750
  end
590
751
  end
591
752
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ # typed: true
2
3
  module GraphQL
3
4
  module StaticValidation
4
5
  class FieldsWillMergeError < StaticValidation::Error
@@ -11,6 +12,9 @@ module GraphQL
11
12
  @field_name = field_name
12
13
  @kind = kind
13
14
  @conflicts = []
15
+ # Indexes of `@nodes`, so that `add_conflict` doesn't compare `node` with each one
16
+ @node_ids = {}.compare_by_identity #: Hash[GraphQL::Language::Nodes::AbstractNode, bool]
17
+ @nodes_by_location = nil #: Hash[Integer?, Hash[Integer?, Array[GraphQL::Language::Nodes::AbstractNode]]]?
14
18
  end
15
19
 
16
20
  def message
@@ -31,11 +35,19 @@ module GraphQL
31
35
  # Check if we already have an error for this exact node.
32
36
  # Use object identity first (fast path), then fall back to
33
37
  # value + location comparison for duplicate AST nodes.
34
- if nodes.any? { |n| n.equal?(node) || (n.line == node.line && n.col == node.col && n == node) }
38
+ return if @node_ids.key?(node)
39
+
40
+ nodes_at_line = (@nodes_by_location ||= {})[node.line] ||= {}
41
+ nodes_at_location = nodes_at_line[node.col]
42
+ if nodes_at_location
35
43
  # already have an error for this node
36
- return
44
+ return if nodes_at_location.any? { |n| n == node }
45
+ nodes_at_location << node
46
+ else
47
+ nodes_at_line[node.col] = [node]
37
48
  end
38
49
 
50
+ @node_ids[node] = true
39
51
  @nodes << node
40
52
  @conflicts << conflict_str
41
53
  end
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
+ # typed: true
2
3
  module GraphQL
3
4
  module StaticValidation
5
+ #--
6
+ # @requires_ancestor: GraphQL::StaticValidation::BaseVisitor
4
7
  module FragmentNamesAreUnique
5
8
 
6
9
  def initialize(*)
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ # typed: true
2
3
  module GraphQL
3
4
  module StaticValidation
4
5
  class FragmentNamesAreUniqueError < StaticValidation::Error
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
+ # typed: true
2
3
  module GraphQL
3
4
  module StaticValidation
5
+ #--
6
+ # @requires_ancestor: GraphQL::StaticValidation::BaseVisitor
4
7
  module FragmentSpreadsArePossible
5
8
  def initialize(*)
6
9
  super
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ # typed: true
2
3
  module GraphQL
3
4
  module StaticValidation
4
5
  class FragmentSpreadsArePossibleError < StaticValidation::Error