graphql 2.0.31 → 2.6.1

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 (316) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/detailed_trace_generator.rb +77 -0
  3. data/lib/generators/graphql/install/mutation_root_generator.rb +2 -2
  4. data/lib/generators/graphql/install/templates/base_mutation.erb +2 -0
  5. data/lib/generators/graphql/install/templates/mutation_type.erb +2 -0
  6. data/lib/generators/graphql/install_generator.rb +49 -0
  7. data/lib/generators/graphql/orm_mutations_base.rb +1 -1
  8. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  9. data/lib/generators/graphql/templates/base_connection.erb +2 -0
  10. data/lib/generators/graphql/templates/base_edge.erb +2 -0
  11. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  12. data/lib/generators/graphql/templates/base_field.erb +2 -0
  13. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  14. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  15. data/lib/generators/graphql/templates/base_object.erb +2 -0
  16. data/lib/generators/graphql/templates/base_resolver.erb +8 -0
  17. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  18. data/lib/generators/graphql/templates/base_union.erb +2 -0
  19. data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
  20. data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
  21. data/lib/generators/graphql/templates/loader.erb +2 -0
  22. data/lib/generators/graphql/templates/mutation.erb +2 -0
  23. data/lib/generators/graphql/templates/node_type.erb +2 -0
  24. data/lib/generators/graphql/templates/query_type.erb +2 -0
  25. data/lib/generators/graphql/templates/schema.erb +5 -0
  26. data/lib/generators/graphql/type_generator.rb +1 -1
  27. data/lib/graphql/analysis/analyzer.rb +90 -0
  28. data/lib/graphql/analysis/field_usage.rb +82 -0
  29. data/lib/graphql/analysis/max_query_complexity.rb +20 -0
  30. data/lib/graphql/analysis/max_query_depth.rb +20 -0
  31. data/lib/graphql/analysis/query_complexity.rb +263 -0
  32. data/lib/graphql/analysis/query_depth.rb +58 -0
  33. data/lib/graphql/analysis/visitor.rb +280 -0
  34. data/lib/graphql/analysis.rb +102 -1
  35. data/lib/graphql/autoload.rb +38 -0
  36. data/lib/graphql/backtrace/table.rb +118 -55
  37. data/lib/graphql/backtrace.rb +1 -19
  38. data/lib/graphql/coercion_error.rb +1 -9
  39. data/lib/graphql/current.rb +57 -0
  40. data/lib/graphql/dashboard/application_controller.rb +41 -0
  41. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  42. data/lib/graphql/dashboard/installable.rb +22 -0
  43. data/lib/graphql/dashboard/landings_controller.rb +9 -0
  44. data/lib/graphql/dashboard/limiters.rb +93 -0
  45. data/lib/graphql/dashboard/operation_store.rb +199 -0
  46. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  47. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  48. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  49. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  50. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  51. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  52. data/lib/graphql/dashboard/statics/icon.png +0 -0
  53. data/lib/graphql/dashboard/statics_controller.rb +31 -0
  54. data/lib/graphql/dashboard/subscriptions.rb +97 -0
  55. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  56. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  57. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  58. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  59. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +24 -0
  60. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  61. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  62. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  63. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  64. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  65. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  66. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  67. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  68. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  69. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  70. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  71. data/lib/graphql/dashboard.rb +96 -0
  72. data/lib/graphql/dataloader/active_record_association_source.rb +84 -0
  73. data/lib/graphql/dataloader/active_record_source.rb +47 -0
  74. data/lib/graphql/dataloader/async_dataloader.rb +112 -0
  75. data/lib/graphql/dataloader/null_dataloader.rb +55 -10
  76. data/lib/graphql/dataloader/request.rb +5 -0
  77. data/lib/graphql/dataloader/source.rb +35 -12
  78. data/lib/graphql/dataloader.rb +224 -149
  79. data/lib/graphql/date_encoding_error.rb +1 -1
  80. data/lib/graphql/dig.rb +2 -1
  81. data/lib/graphql/duration_encoding_error.rb +16 -0
  82. data/lib/graphql/execution/field_resolve_step.rb +631 -0
  83. data/lib/graphql/execution/finalize.rb +217 -0
  84. data/lib/graphql/execution/input_values.rb +261 -0
  85. data/lib/graphql/execution/interpreter/argument_value.rb +5 -1
  86. data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
  87. data/lib/graphql/execution/interpreter/handles_raw_value.rb +6 -0
  88. data/lib/graphql/execution/interpreter/resolve.rb +23 -25
  89. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +228 -0
  90. data/lib/graphql/execution/interpreter/runtime.rb +365 -435
  91. data/lib/graphql/execution/interpreter.rb +87 -163
  92. data/lib/graphql/execution/lazy.rb +1 -1
  93. data/lib/graphql/execution/load_argument_step.rb +64 -0
  94. data/lib/graphql/execution/lookahead.rb +105 -31
  95. data/lib/graphql/execution/multiplex.rb +7 -6
  96. data/lib/graphql/execution/next.rb +90 -0
  97. data/lib/graphql/execution/prepare_object_step.rb +128 -0
  98. data/lib/graphql/execution/runner.rb +410 -0
  99. data/lib/graphql/execution/selections_step.rb +91 -0
  100. data/lib/graphql/execution.rb +8 -4
  101. data/lib/graphql/execution_error.rb +17 -10
  102. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  103. data/lib/graphql/introspection/directive_type.rb +7 -3
  104. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  105. data/lib/graphql/introspection/entry_points.rb +20 -6
  106. data/lib/graphql/introspection/enum_value_type.rb +5 -5
  107. data/lib/graphql/introspection/field_type.rb +13 -5
  108. data/lib/graphql/introspection/input_value_type.rb +21 -13
  109. data/lib/graphql/introspection/schema_type.rb +8 -11
  110. data/lib/graphql/introspection/type_type.rb +64 -28
  111. data/lib/graphql/invalid_name_error.rb +1 -1
  112. data/lib/graphql/invalid_null_error.rb +26 -17
  113. data/lib/graphql/language/block_string.rb +34 -18
  114. data/lib/graphql/language/cache.rb +13 -0
  115. data/lib/graphql/language/comment.rb +18 -0
  116. data/lib/graphql/language/definition_slice.rb +1 -1
  117. data/lib/graphql/language/document_from_schema_definition.rb +90 -61
  118. data/lib/graphql/language/lexer.rb +323 -193
  119. data/lib/graphql/language/nodes.rb +139 -77
  120. data/lib/graphql/language/parser.rb +807 -1985
  121. data/lib/graphql/language/printer.rb +324 -151
  122. data/lib/graphql/language/sanitized_printer.rb +21 -23
  123. data/lib/graphql/language/static_visitor.rb +171 -0
  124. data/lib/graphql/language/visitor.rb +62 -119
  125. data/lib/graphql/language.rb +71 -1
  126. data/lib/graphql/load_application_object_failed_error.rb +5 -1
  127. data/lib/graphql/pagination/array_connection.rb +6 -6
  128. data/lib/graphql/pagination/connection.rb +30 -1
  129. data/lib/graphql/pagination/connections.rb +32 -0
  130. data/lib/graphql/pagination/mongoid_relation_connection.rb +1 -2
  131. data/lib/graphql/query/context/scoped_context.rb +101 -0
  132. data/lib/graphql/query/context.rb +88 -144
  133. data/lib/graphql/query/null_context.rb +15 -18
  134. data/lib/graphql/query/partial.rb +194 -0
  135. data/lib/graphql/query/validation_pipeline.rb +4 -4
  136. data/lib/graphql/query/variable_validation_error.rb +1 -1
  137. data/lib/graphql/query/variables.rb +3 -3
  138. data/lib/graphql/query.rb +135 -81
  139. data/lib/graphql/railtie.rb +16 -6
  140. data/lib/graphql/rake_task.rb +3 -12
  141. data/lib/graphql/rubocop/graphql/base_cop.rb +1 -1
  142. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +144 -0
  143. data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
  144. data/lib/graphql/rubocop.rb +2 -0
  145. data/lib/graphql/runtime_error.rb +6 -0
  146. data/lib/graphql/schema/addition.rb +26 -13
  147. data/lib/graphql/schema/always_visible.rb +7 -2
  148. data/lib/graphql/schema/argument.rb +78 -14
  149. data/lib/graphql/schema/base_64_encoder.rb +3 -5
  150. data/lib/graphql/schema/build_from_definition.rb +140 -66
  151. data/lib/graphql/schema/directive/flagged.rb +4 -2
  152. data/lib/graphql/schema/directive/one_of.rb +12 -0
  153. data/lib/graphql/schema/directive/specified_by.rb +14 -0
  154. data/lib/graphql/schema/directive.rb +78 -12
  155. data/lib/graphql/schema/enum.rb +110 -27
  156. data/lib/graphql/schema/enum_value.rb +11 -3
  157. data/lib/graphql/schema/field/connection_extension.rb +4 -51
  158. data/lib/graphql/schema/field/scope_extension.rb +19 -7
  159. data/lib/graphql/schema/field.rb +245 -119
  160. data/lib/graphql/schema/field_extension.rb +12 -9
  161. data/lib/graphql/schema/has_single_input_argument.rb +160 -0
  162. data/lib/graphql/schema/input_object.rb +123 -65
  163. data/lib/graphql/schema/interface.rb +60 -16
  164. data/lib/graphql/schema/introspection_system.rb +8 -17
  165. data/lib/graphql/schema/late_bound_type.rb +4 -0
  166. data/lib/graphql/schema/list.rb +8 -4
  167. data/lib/graphql/schema/loader.rb +3 -4
  168. data/lib/graphql/schema/member/base_dsl_methods.rb +18 -12
  169. data/lib/graphql/schema/member/has_arguments.rb +132 -100
  170. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  171. data/lib/graphql/schema/member/has_dataloader.rb +99 -0
  172. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  173. data/lib/graphql/schema/member/has_directives.rb +5 -5
  174. data/lib/graphql/schema/member/has_fields.rb +121 -17
  175. data/lib/graphql/schema/member/has_interfaces.rb +27 -13
  176. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  177. data/lib/graphql/schema/member/has_validators.rb +1 -1
  178. data/lib/graphql/schema/member/relay_shortcuts.rb +1 -1
  179. data/lib/graphql/schema/member/scoped.rb +19 -0
  180. data/lib/graphql/schema/member/type_system_helpers.rb +18 -5
  181. data/lib/graphql/schema/member/validates_input.rb +3 -3
  182. data/lib/graphql/schema/member.rb +6 -0
  183. data/lib/graphql/schema/mutation.rb +7 -0
  184. data/lib/graphql/schema/non_null.rb +1 -1
  185. data/lib/graphql/schema/object.rb +34 -8
  186. data/lib/graphql/schema/printer.rb +9 -7
  187. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  188. data/lib/graphql/schema/relay_classic_mutation.rb +6 -129
  189. data/lib/graphql/schema/resolver.rb +128 -32
  190. data/lib/graphql/schema/scalar.rb +4 -9
  191. data/lib/graphql/schema/subscription.rb +63 -12
  192. data/lib/graphql/schema/timeout.rb +19 -2
  193. data/lib/graphql/schema/type_expression.rb +2 -2
  194. data/lib/graphql/schema/union.rb +2 -2
  195. data/lib/graphql/schema/unique_within_type.rb +1 -1
  196. data/lib/graphql/schema/validator/all_validator.rb +62 -0
  197. data/lib/graphql/schema/validator/required_validator.rb +92 -11
  198. data/lib/graphql/schema/validator.rb +3 -1
  199. data/lib/graphql/schema/visibility/migration.rb +188 -0
  200. data/lib/graphql/schema/visibility/profile.rb +464 -0
  201. data/lib/graphql/schema/visibility/visit.rb +190 -0
  202. data/lib/graphql/schema/visibility.rb +311 -0
  203. data/lib/graphql/schema/warden.rb +275 -103
  204. data/lib/graphql/schema/wrapper.rb +7 -1
  205. data/lib/graphql/schema.rb +954 -212
  206. data/lib/graphql/static_validation/all_rules.rb +3 -3
  207. data/lib/graphql/static_validation/base_visitor.rb +96 -71
  208. data/lib/graphql/static_validation/literal_validator.rb +6 -7
  209. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +2 -2
  210. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +18 -6
  211. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +6 -2
  212. data/lib/graphql/static_validation/rules/directives_are_defined.rb +6 -3
  213. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
  214. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +14 -3
  215. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +59 -15
  216. data/lib/graphql/static_validation/rules/fields_will_merge.rb +391 -262
  217. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
  218. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +6 -6
  219. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +15 -2
  220. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  221. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  222. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  223. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  224. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  225. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +28 -8
  226. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +5 -5
  227. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  228. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +7 -3
  229. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  230. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  231. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
  232. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +14 -2
  233. data/lib/graphql/static_validation/validation_context.rb +22 -6
  234. data/lib/graphql/static_validation/validator.rb +9 -1
  235. data/lib/graphql/static_validation.rb +0 -1
  236. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +8 -5
  237. data/lib/graphql/subscriptions/broadcast_analyzer.rb +11 -5
  238. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +45 -19
  239. data/lib/graphql/subscriptions/event.rb +22 -4
  240. data/lib/graphql/subscriptions/serialize.rb +3 -1
  241. data/lib/graphql/subscriptions.rb +56 -17
  242. data/lib/graphql/testing/helpers.rb +161 -0
  243. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  244. data/lib/graphql/testing.rb +3 -0
  245. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  246. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  247. data/lib/graphql/tracing/appoptics_trace.rb +11 -3
  248. data/lib/graphql/tracing/appoptics_tracing.rb +9 -2
  249. data/lib/graphql/tracing/appsignal_trace.rb +32 -55
  250. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  251. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  252. data/lib/graphql/tracing/data_dog_trace.rb +46 -158
  253. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  254. data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
  255. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  256. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  257. data/lib/graphql/tracing/detailed_trace.rb +156 -0
  258. data/lib/graphql/tracing/legacy_hooks_trace.rb +75 -0
  259. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  260. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  261. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  262. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  263. data/lib/graphql/tracing/notifications_trace.rb +184 -34
  264. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  265. data/lib/graphql/tracing/null_trace.rb +9 -0
  266. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  267. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  268. data/lib/graphql/tracing/perfetto_trace.rb +864 -0
  269. data/lib/graphql/tracing/platform_trace.rb +5 -0
  270. data/lib/graphql/tracing/platform_tracing.rb +3 -1
  271. data/lib/graphql/tracing/{prometheus_tracing → prometheus_trace}/graphql_collector.rb +5 -1
  272. data/lib/graphql/tracing/prometheus_trace.rb +72 -68
  273. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  274. data/lib/graphql/tracing/scout_trace.rb +32 -55
  275. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  276. data/lib/graphql/tracing/sentry_trace.rb +82 -0
  277. data/lib/graphql/tracing/statsd_trace.rb +33 -41
  278. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  279. data/lib/graphql/tracing/trace.rb +118 -1
  280. data/lib/graphql/tracing.rb +31 -28
  281. data/lib/graphql/type_kinds.rb +2 -1
  282. data/lib/graphql/types/iso_8601_duration.rb +77 -0
  283. data/lib/graphql/types/relay/connection_behaviors.rb +45 -3
  284. data/lib/graphql/types/relay/edge_behaviors.rb +19 -1
  285. data/lib/graphql/types/relay/has_node_field.rb +13 -8
  286. data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
  287. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  288. data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
  289. data/lib/graphql/types.rb +18 -10
  290. data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
  291. data/lib/graphql/unauthorized_error.rb +9 -1
  292. data/lib/graphql/version.rb +1 -1
  293. data/lib/graphql.rb +69 -54
  294. data/readme.md +12 -2
  295. metadata +236 -40
  296. data/lib/graphql/analysis/ast/analyzer.rb +0 -84
  297. data/lib/graphql/analysis/ast/field_usage.rb +0 -57
  298. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -22
  299. data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
  300. data/lib/graphql/analysis/ast/query_complexity.rb +0 -230
  301. data/lib/graphql/analysis/ast/query_depth.rb +0 -55
  302. data/lib/graphql/analysis/ast/visitor.rb +0 -276
  303. data/lib/graphql/analysis/ast.rb +0 -81
  304. data/lib/graphql/backtrace/inspect_result.rb +0 -50
  305. data/lib/graphql/backtrace/trace.rb +0 -96
  306. data/lib/graphql/backtrace/tracer.rb +0 -80
  307. data/lib/graphql/deprecation.rb +0 -9
  308. data/lib/graphql/filter.rb +0 -59
  309. data/lib/graphql/language/parser.y +0 -560
  310. data/lib/graphql/language/token.rb +0 -34
  311. data/lib/graphql/schema/base_64_bp.rb +0 -26
  312. data/lib/graphql/schema/invalid_type_error.rb +0 -7
  313. data/lib/graphql/schema/null_mask.rb +0 -11
  314. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
  315. data/lib/graphql/static_validation/type_stack.rb +0 -216
  316. data/lib/graphql/subscriptions/instrumentation.rb +0 -28
@@ -14,9 +14,11 @@ module GraphQL
14
14
  end
15
15
 
16
16
  def message
17
- "Field '#{field_name}' has #{kind == :argument ? 'an' : 'a'} #{kind} conflict: #{conflicts}?"
17
+ @message || "Field '#{field_name}' has #{kind == :argument ? 'an' : 'a'} #{kind} conflict: #{conflicts}?"
18
18
  end
19
19
 
20
+ attr_writer :message
21
+
20
22
  def path
21
23
  []
22
24
  end
@@ -26,7 +28,13 @@ module GraphQL
26
28
  end
27
29
 
28
30
  def add_conflict(node, conflict_str)
29
- return if nodes.include?(node)
31
+ # Check if we already have an error for this exact node.
32
+ # Use object identity first (fast path), then fall back to
33
+ # 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) }
35
+ # already have an error for this node
36
+ return
37
+ end
30
38
 
31
39
  @nodes << node
32
40
  @conflicts << conflict_str
@@ -8,8 +8,8 @@ module GraphQL
8
8
  end
9
9
 
10
10
  def on_inline_fragment(node, parent)
11
- fragment_parent = context.object_types[-2]
12
- fragment_child = context.object_types.last
11
+ fragment_parent = @parent_object_type
12
+ fragment_child = @current_object_type
13
13
  if fragment_child
14
14
  validate_fragment_in_scope(fragment_parent, fragment_child, node, context, context.path)
15
15
  end
@@ -17,7 +17,7 @@ module GraphQL
17
17
  end
18
18
 
19
19
  def on_fragment_spread(node, parent)
20
- fragment_parent = context.object_types.last
20
+ fragment_parent = @current_object_type
21
21
  @spreads_to_validate << FragmentSpread.new(node: node, parent_type: fragment_parent, path: context.path)
22
22
  super
23
23
  end
@@ -28,7 +28,7 @@ module GraphQL
28
28
  frag_node = context.fragments[frag_spread.node.name]
29
29
  if frag_node
30
30
  fragment_child_name = frag_node.type.name
31
- fragment_child = context.warden.get_type(fragment_child_name)
31
+ fragment_child = @types.type(fragment_child_name)
32
32
  # Might be non-existent type name
33
33
  if fragment_child
34
34
  validate_fragment_in_scope(frag_spread.parent_type, fragment_child, frag_spread.node, context, frag_spread.path)
@@ -44,8 +44,8 @@ module GraphQL
44
44
  # It's not a valid fragment type, this error was handled someplace else
45
45
  return
46
46
  end
47
- parent_types = context.warden.possible_types(parent_type.unwrap)
48
- child_types = context.warden.possible_types(child_type.unwrap)
47
+ parent_types = @types.possible_types(parent_type.unwrap)
48
+ child_types = @types.possible_types(child_type.unwrap)
49
49
 
50
50
  if child_types.none? { |c| parent_types.include?(c) }
51
51
  name = node.respond_to?(:name) ? " #{node.name}" : ""
@@ -21,10 +21,23 @@ module GraphQL
21
21
  true
22
22
  else
23
23
  type_name = fragment_node.type.name
24
- type = context.warden.get_type(type_name)
24
+ type = @types.type(type_name)
25
25
  if type.nil?
26
+ suggestion = if @schema.did_you_mean
27
+ @all_possible_fragment_type_names ||= begin
28
+ names = []
29
+ context.types.all_types.each do |type|
30
+ if type.kind.fields?
31
+ names << type.graphql_name
32
+ end
33
+ end
34
+ names
35
+ end
36
+ context.did_you_mean_suggestion(type_name, @all_possible_fragment_type_names)
37
+ end
38
+
26
39
  add_error(GraphQL::StaticValidation::FragmentTypesExistError.new(
27
- "No such type #{type_name}, so it can't be a fragment condition",
40
+ "No such type #{type_name}, so it can't be a fragment condition#{suggestion}",
28
41
  nodes: fragment_node,
29
42
  type: type_name
30
43
  ))
@@ -19,7 +19,7 @@ module GraphQL
19
19
  true
20
20
  else
21
21
  type_name = node_type.to_query_string
22
- type_def = context.warden.get_type(type_name)
22
+ type_def = @types.type(type_name)
23
23
  if type_def.nil? || !type_def.kind.composite?
24
24
  add_error(GraphQL::StaticValidation::FragmentsAreOnCompositeTypesError.new(
25
25
  "Invalid fragment on type #{type_name} (must be Union, Interface or Object)",
@@ -3,7 +3,7 @@ module GraphQL
3
3
  module StaticValidation
4
4
  module MutationRootExists
5
5
  def on_operation_definition(node, _parent)
6
- if node.operation_type == 'mutation' && context.warden.root_type_for_operation("mutation").nil?
6
+ if node.operation_type == 'mutation' && context.query.types.mutation_root.nil?
7
7
  add_error(GraphQL::StaticValidation::MutationRootExistsError.new(
8
8
  'Schema is not configured for mutations',
9
9
  nodes: node
@@ -32,7 +32,7 @@ module GraphQL
32
32
 
33
33
  def on_document(node, parent)
34
34
  super
35
- if @schema_definition_nodes.any?
35
+ if !@schema_definition_nodes.empty?
36
36
  add_error(GraphQL::StaticValidation::NoDefinitionsArePresentError.new(%|Query cannot contain schema definitions|, nodes: @schema_definition_nodes))
37
37
  end
38
38
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module StaticValidation
4
+ class NotSingleSubscriptionError < StaticValidation::Error
5
+ def initialize(message, path: nil, nodes: [])
6
+ super(message, path: path, nodes: nodes)
7
+ end
8
+
9
+ # A hash representation of this Message
10
+ def to_h
11
+ extensions = {
12
+ "code" => code,
13
+ }
14
+
15
+ super.merge({
16
+ "extensions" => extensions
17
+ })
18
+ end
19
+
20
+ def code
21
+ "notSingleSubscription"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -3,7 +3,7 @@ module GraphQL
3
3
  module StaticValidation
4
4
  module QueryRootExists
5
5
  def on_operation_definition(node, _parent)
6
- if (node.operation_type == 'query' || node.operation_type.nil?) && context.warden.root_type_for_operation("query").nil?
6
+ if (node.operation_type == 'query' || node.operation_type.nil?) && context.query.types.query_root.nil?
7
7
  add_error(GraphQL::StaticValidation::QueryRootExistsError.new(
8
8
  'Schema is not configured for queries',
9
9
  nodes: node
@@ -2,8 +2,13 @@
2
2
  module GraphQL
3
3
  module StaticValidation
4
4
  module RequiredArgumentsArePresent
5
+ def initialize(*)
6
+ super
7
+ @required_args_cache = {}.compare_by_identity
8
+ end
9
+
5
10
  def on_field(node, _parent)
6
- assert_required_args(node, field_definition)
11
+ assert_required_args(node, @current_field_definition)
7
12
  super
8
13
  end
9
14
 
@@ -16,15 +21,30 @@ module GraphQL
16
21
  private
17
22
 
18
23
  def assert_required_args(ast_node, defn)
19
- args = defn.arguments(context.query.context)
20
- return if args.empty?
21
- present_argument_names = ast_node.arguments.map(&:name)
22
- required_argument_names = context.warden.arguments(defn)
23
- .select { |a| a.type.kind.non_null? && !a.default_value? && context.warden.get_argument(defn, a.name) }
24
- .map(&:name)
24
+ return unless defn
25
25
 
26
+ # Cache required argument names per definition to avoid re-iterating
27
+ # arguments for the same definition across field instances
28
+ if @required_args_cache.key?(defn)
29
+ required_argument_names = @required_args_cache[defn]
30
+ else
31
+ args = @types.arguments(defn)
32
+ required_argument_names = nil
33
+ if !args.empty?
34
+ args.each do |a|
35
+ if a.type.kind.non_null? && !a.default_value? && @types.argument(defn, a.name)
36
+ (required_argument_names ||= []) << a.graphql_name
37
+ end
38
+ end
39
+ end
40
+ @required_args_cache[defn] = required_argument_names
41
+ end
42
+
43
+ return if required_argument_names.nil?
44
+
45
+ present_argument_names = ast_node.arguments.map(&:name)
26
46
  missing_names = required_argument_names - present_argument_names
27
- if missing_names.any?
47
+ if !missing_names.empty?
28
48
  add_error(GraphQL::StaticValidation::RequiredArgumentsArePresentError.new(
29
49
  "#{ast_node.class.name.split("::").last} '#{ast_node.name}' is missing required arguments: #{missing_names.join(", ")}",
30
50
  nodes: ast_node,
@@ -26,7 +26,7 @@ module GraphQL
26
26
  context.directive_definition || context.field_definition
27
27
  end
28
28
 
29
- parent_type = context.warden.get_argument(defn, parent_name(parent, defn))
29
+ parent_type = context.types.argument(defn, parent_name(parent, defn))
30
30
  parent_type ? parent_type.type.unwrap : nil
31
31
  end
32
32
 
@@ -34,16 +34,16 @@ module GraphQL
34
34
  parent_type = get_parent_type(context, parent)
35
35
  return unless parent_type && parent_type.kind.input_object?
36
36
 
37
- required_fields = context.warden.arguments(parent_type)
38
- .select{|arg| arg.type.kind.non_null?}
39
- .map(&:graphql_name)
37
+ required_fields = context.types.arguments(parent_type)
38
+ .select{ |arg| arg.type.kind.non_null? && !arg.default_value? }
39
+ .map!(&:graphql_name)
40
40
 
41
41
  present_fields = ast_node.arguments.map(&:name)
42
42
  missing_fields = required_fields - present_fields
43
43
 
44
44
  missing_fields.each do |missing_field|
45
45
  path = [*context.path, missing_field]
46
- missing_field_type = context.warden.get_argument(parent_type, missing_field).type
46
+ missing_field_type = context.types.argument(parent_type, missing_field).type
47
47
  add_error(RequiredInputObjectAttributesArePresentError.new(
48
48
  "Argument '#{missing_field}' on InputObject '#{parent_type.to_type_signature}' is required. Expected type #{missing_field_type.to_type_signature}",
49
49
  argument_name: missing_field,
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module StaticValidation
4
+ module SubscriptionRootExistsAndSingleSubscriptionSelection
5
+ def on_operation_definition(node, parent)
6
+ if node.operation_type == "subscription"
7
+ if context.types.subscription_root.nil?
8
+ add_error(GraphQL::StaticValidation::SubscriptionRootExistsError.new(
9
+ 'Schema is not configured for subscriptions',
10
+ nodes: node
11
+ ))
12
+ elsif node.selections.size != 1
13
+ add_error(GraphQL::StaticValidation::NotSingleSubscriptionError.new(
14
+ 'A subscription operation may only have one selection',
15
+ nodes: node,
16
+ ))
17
+ else
18
+ super
19
+ end
20
+ else
21
+ super
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -19,13 +19,17 @@ module GraphQL
19
19
  :on_field,
20
20
  ]
21
21
 
22
- DIRECTIVE_NODE_HOOKS.each do |method_name|
23
- define_method(method_name) do |node, parent|
24
- if node.directives.any?
22
+ VALIDATE_DIRECTIVE_LOCATION_ON_NODE = <<~RUBY
23
+ def %{method_name}(node, parent)
24
+ if !node.directives.empty?
25
25
  validate_directive_location(node)
26
26
  end
27
27
  super(node, parent)
28
28
  end
29
+ RUBY
30
+ DIRECTIVE_NODE_HOOKS.each do |method_name|
31
+ # Can't use `define_method {...}` here because the proc can't be isolated for use in non-main Ractors
32
+ module_eval(VALIDATE_DIRECTIVE_LOCATION_ON_NODE % { method_name: method_name }) # rubocop:disable Development/NoEvalCop
29
33
  end
30
34
 
31
35
  private
@@ -5,36 +5,27 @@ module GraphQL
5
5
  def on_variable_definition(node, parent)
6
6
  if !node.default_value.nil?
7
7
  value = node.default_value
8
- if node.type.is_a?(GraphQL::Language::Nodes::NonNullType)
9
- add_error(GraphQL::StaticValidation::VariableDefaultValuesAreCorrectlyTypedError.new(
10
- "Non-null variable $#{node.name} can't have a default value",
11
- nodes: node,
12
- name: node.name,
13
- error_type: VariableDefaultValuesAreCorrectlyTypedError::VIOLATIONS[:INVALID_ON_NON_NULL]
14
- ))
8
+ type = context.schema.type_from_ast(node.type, context: context)
9
+ if type.nil?
10
+ # This is handled by another validator
15
11
  else
16
- type = context.schema.type_from_ast(node.type, context: context)
17
- if type.nil?
18
- # This is handled by another validator
19
- else
20
- validation_result = context.validate_literal(value, type)
12
+ validation_result = context.validate_literal(value, type)
21
13
 
22
- if !validation_result.valid?
23
- problems = validation_result.problems
24
- first_problem = problems && problems.first
25
- if first_problem
26
- error_message = first_problem["explanation"]
27
- end
28
-
29
- error_message ||= "Default value for $#{node.name} doesn't match type #{type.to_type_signature}"
30
- add_error(GraphQL::StaticValidation::VariableDefaultValuesAreCorrectlyTypedError.new(
31
- error_message,
32
- nodes: node,
33
- name: node.name,
34
- type: type.to_type_signature,
35
- error_type: VariableDefaultValuesAreCorrectlyTypedError::VIOLATIONS[:INVALID_TYPE],
36
- ))
14
+ if !validation_result.valid?
15
+ problems = validation_result.problems
16
+ first_problem = problems && problems.first
17
+ if first_problem
18
+ error_message = first_problem["explanation"]
37
19
  end
20
+
21
+ error_message ||= "Default value for $#{node.name} doesn't match type #{type.to_type_signature}"
22
+ add_error(GraphQL::StaticValidation::VariableDefaultValuesAreCorrectlyTypedError.new(
23
+ error_message,
24
+ nodes: node,
25
+ name: node.name,
26
+ type: type.to_type_signature,
27
+ error_type: VariableDefaultValuesAreCorrectlyTypedError::VIOLATIONS[:INVALID_TYPE],
28
+ ))
38
29
  end
39
30
  end
40
31
  end
@@ -4,7 +4,7 @@ module GraphQL
4
4
  module VariableNamesAreUnique
5
5
  def on_operation_definition(node, parent)
6
6
  var_defns = node.variables
7
- if var_defns.any?
7
+ if !var_defns.empty?
8
8
  vars_by_name = Hash.new { |h, k| h[k] = [] }
9
9
  var_defns.each { |v| vars_by_name[v.name] << v }
10
10
  vars_by_name.each do |name, defns|
@@ -21,7 +21,7 @@ module GraphQL
21
21
  end
22
22
  node_values = node_values.select { |value| value.is_a? GraphQL::Language::Nodes::VariableIdentifier }
23
23
 
24
- if node_values.any?
24
+ if !node_values.empty?
25
25
  argument_owner = case parent
26
26
  when GraphQL::Language::Nodes::Field
27
27
  context.field_definition
@@ -65,7 +65,7 @@ module GraphQL
65
65
  end
66
66
  end
67
67
 
68
- arg_defn = context.warden.get_argument(argument_owner, arg_node.name)
68
+ arg_defn = @types.argument(argument_owner, arg_node.name)
69
69
  arg_defn_type = arg_defn.type
70
70
 
71
71
  # If the argument is non-null, but it was given a default value,
@@ -4,11 +4,23 @@ module GraphQL
4
4
  module VariablesAreInputTypes
5
5
  def on_variable_definition(node, parent)
6
6
  type_name = get_type_name(node.type)
7
- type = context.warden.get_type(type_name)
7
+ type = context.query.types.type(type_name)
8
8
 
9
9
  if type.nil?
10
+ suggestion = if @schema.did_you_mean
11
+ @all_possible_input_type_names ||= begin
12
+ names = []
13
+ context.types.all_types.each { |(t)|
14
+ if t.kind.input?
15
+ names << t.graphql_name
16
+ end
17
+ }
18
+ names
19
+ end
20
+ context.did_you_mean_suggestion(type_name, @all_possible_input_type_names)
21
+ end
10
22
  add_error(GraphQL::StaticValidation::VariablesAreInputTypesError.new(
11
- "#{type_name} isn't a defined input type (on $#{node.name})",
23
+ "#{type_name} isn't a defined input type (on $#{node.name})#{suggestion}",
12
24
  nodes: node,
13
25
  name: node.name,
14
26
  type: type_name
@@ -8,20 +8,20 @@ module GraphQL
8
8
  # It provides access to the schema & fragments which validators may read from.
9
9
  #
10
10
  # It holds a list of errors which each validator may add to.
11
- #
12
- # It also provides limited access to the {TypeStack} instance,
13
- # which tracks state as you climb in and out of different fields.
14
11
  class ValidationContext
15
12
  extend Forwardable
16
13
 
17
14
  attr_reader :query, :errors, :visitor,
18
15
  :on_dependency_resolve_handlers,
19
- :max_errors
16
+ :max_errors, :types, :schema
17
+
20
18
 
21
- def_delegators :@query, :schema, :document, :fragments, :operations, :warden
19
+ def_delegators :@query, :document, :fragments, :operations
22
20
 
23
21
  def initialize(query, visitor_class, max_errors)
24
22
  @query = query
23
+ @types = query.types # TODO update migrated callers to use this accessor
24
+ @schema = query.schema
25
25
  @literal_validator = LiteralValidator.new(context: query.context)
26
26
  @errors = []
27
27
  @max_errors = max_errors || Float::INFINITY
@@ -29,9 +29,10 @@ module GraphQL
29
29
  @visitor = visitor_class.new(document, self)
30
30
  end
31
31
 
32
+ # TODO stop using def_delegators because of Array allocations
32
33
  def_delegators :@visitor,
33
34
  :path, :type_definition, :field_definition, :argument_definition,
34
- :parent_type_definition, :directive_definition, :object_types, :dependencies
35
+ :parent_type_definition, :directive_definition, :dependencies
35
36
 
36
37
  def on_dependency_resolve(&handler)
37
38
  @on_dependency_resolve_handlers << handler
@@ -48,6 +49,21 @@ module GraphQL
48
49
  def schema_directives
49
50
  @schema_directives ||= schema.directives
50
51
  end
52
+
53
+ def did_you_mean_suggestion(name, options)
54
+ if did_you_mean = schema.did_you_mean
55
+ suggestions = did_you_mean::SpellChecker.new(dictionary: options).correct(name)
56
+ case suggestions.size
57
+ when 0
58
+ ""
59
+ when 1
60
+ " (Did you mean `#{suggestions.first}`?)"
61
+ else
62
+ last_sugg = suggestions.pop
63
+ " (Did you mean #{suggestions.map {|s| "`#{s}`"}.join(", ")} or `#{last_sugg}`?)"
64
+ end
65
+ end
66
+ end
51
67
  end
52
68
  end
53
69
  end
@@ -27,7 +27,10 @@ module GraphQL
27
27
  # @param max_errors [Integer] Maximum number of errors before aborting validation. Any positive number will limit the number of errors. Defaults to nil for no limit.
28
28
  # @return [Array<Hash>]
29
29
  def validate(query, validate: true, timeout: nil, max_errors: nil)
30
+ errors = nil
31
+ query.current_trace.begin_validate(query, validate)
30
32
  query.current_trace.validate(validate: validate, query: query) do
33
+ begin_t = Time.now
31
34
  errors = if validate == false
32
35
  []
33
36
  else
@@ -52,13 +55,18 @@ module GraphQL
52
55
  end
53
56
 
54
57
  {
58
+ remaining_timeout: timeout ? (timeout - (Time.now - begin_t)) : nil,
55
59
  errors: errors,
56
60
  }
57
61
  end
58
62
  rescue GraphQL::ExecutionError => e
63
+ errors = [e]
59
64
  {
60
- errors: [e],
65
+ remaining_timeout: nil,
66
+ errors: errors,
61
67
  }
68
+ ensure
69
+ query.current_trace.end_validate(query, validate, errors)
62
70
  end
63
71
 
64
72
  # Invoked when static validation times out.
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require "graphql/static_validation/error"
3
3
  require "graphql/static_validation/definition_dependencies"
4
- require "graphql/static_validation/type_stack"
5
4
  require "graphql/static_validation/validator"
6
5
  require "graphql/static_validation/validation_context"
7
6
  require "graphql/static_validation/validation_timeout_error"
@@ -35,7 +35,7 @@ module GraphQL
35
35
  # }
36
36
  #
37
37
  # result = MySchema.execute(
38
- # query: query,
38
+ # query,
39
39
  # context: context,
40
40
  # variables: variables,
41
41
  # operation_name: operation_name
@@ -81,6 +81,7 @@ module GraphQL
81
81
  # end
82
82
  # end
83
83
  #
84
+ # @see GraphQL::Testing::MockActionCable for test helpers
84
85
  class ActionCableSubscriptions < GraphQL::Subscriptions
85
86
  SUBSCRIPTION_PREFIX = "graphql-subscription:"
86
87
  EVENT_PREFIX = "graphql-event:"
@@ -107,7 +108,7 @@ module GraphQL
107
108
  when 2
108
109
  true
109
110
  else
110
- raise ArgumentError, "#{@serializer} must repond to `.load` accepting one or two arguments"
111
+ raise ArgumentError, "#{@serializer} must respond to `.load` accepting one or two arguments"
111
112
  end
112
113
  @transmit_ns = namespace
113
114
  super
@@ -124,7 +125,8 @@ module GraphQL
124
125
  # This subscription was re-evaluated.
125
126
  # Send it to the specific stream where this client was waiting.
126
127
  def deliver(subscription_id, result)
127
- payload = { result: result.to_h, more: true }
128
+ has_more = !result.context.namespace(:subscriptions)[:final_update]
129
+ payload = { result: result.to_h, more: has_more }
128
130
  @action_cable.server.broadcast(stream_subscription_name(subscription_id), payload)
129
131
  end
130
132
 
@@ -165,11 +167,12 @@ module GraphQL
165
167
  #
166
168
  def setup_stream(channel, initial_event)
167
169
  topic = initial_event.topic
168
- channel.stream_from(stream_event_name(initial_event), coder: @action_cable_coder) do |message|
170
+ event_stream = stream_event_name(initial_event)
171
+ channel.stream_from(event_stream, coder: @action_cable_coder) do |message|
169
172
  events_by_fingerprint = @events[topic]
170
173
  object = nil
171
174
  events_by_fingerprint.each do |_fingerprint, events|
172
- if events.any? && events.first == initial_event
175
+ if !events.empty? && events.first == initial_event
173
176
  # The fingerprint has told us that this response should be shared by all subscribers,
174
177
  # so just run it once, then deliver the result to every subscriber
175
178
  first_event = events.first
@@ -9,7 +9,7 @@ module GraphQL
9
9
  # Assign the result to `context.namespace(:subscriptions)[:subscription_broadcastable]`
10
10
  # @api private
11
11
  # @see Subscriptions#broadcastable? for a public API
12
- class BroadcastAnalyzer < GraphQL::Analysis::AST::Analyzer
12
+ class BroadcastAnalyzer < GraphQL::Analysis::Analyzer
13
13
  def initialize(subject)
14
14
  super
15
15
  @default_broadcastable = subject.schema.subscriptions.default_broadcastable
@@ -28,9 +28,8 @@ module GraphQL
28
28
  end
29
29
 
30
30
  current_field = visitor.field_definition
31
- apply_broadcastable(current_field)
32
-
33
31
  current_type = visitor.parent_type_definition
32
+ apply_broadcastable(current_type, current_field)
34
33
  if current_type.kind.interface?
35
34
  pt = @query.possible_types(current_type)
36
35
  pt.each do |object_type|
@@ -38,7 +37,7 @@ module GraphQL
38
37
  # Inherited fields would be exactly the same object;
39
38
  # only check fields that are overrides of the inherited one
40
39
  if ot_field && ot_field != current_field
41
- apply_broadcastable(ot_field)
40
+ apply_broadcastable(object_type, ot_field)
42
41
  end
43
42
  end
44
43
  end
@@ -55,10 +54,16 @@ module GraphQL
55
54
  private
56
55
 
57
56
  # Modify `@subscription_broadcastable` based on `field_defn`'s configuration (and/or the default value)
58
- def apply_broadcastable(field_defn)
57
+ def apply_broadcastable(owner_type, field_defn)
59
58
  current_field_broadcastable = field_defn.introspection? || field_defn.broadcastable?
59
+
60
+ if current_field_broadcastable.nil? && owner_type.respond_to?(:default_broadcastable?)
61
+ current_field_broadcastable = owner_type.default_broadcastable?
62
+ end
63
+
60
64
  case current_field_broadcastable
61
65
  when nil
66
+ query.logger.debug { "`broadcastable: nil` for field: #{field_defn.path}" }
62
67
  # If the value wasn't set, mix in the default value:
63
68
  # - If the default is false and the current value is true, make it false
64
69
  # - If the default is true and the current value is true, it stays true
@@ -66,6 +71,7 @@ module GraphQL
66
71
  # - If the default is true and the current value is false, keep it false
67
72
  @subscription_broadcastable = @subscription_broadcastable && @default_broadcastable
68
73
  when false
74
+ query.logger.debug { "`broadcastable: false` for field: #{field_defn.path}" }
69
75
  # One non-broadcastable field is enough to make the whole subscription non-broadcastable
70
76
  @subscription_broadcastable = false
71
77
  when true