graphql 1.9.21 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

Files changed (397) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/core.rb +21 -10
  3. data/lib/generators/graphql/enum_generator.rb +4 -10
  4. data/lib/generators/graphql/field_extractor.rb +31 -0
  5. data/lib/generators/graphql/input_generator.rb +50 -0
  6. data/lib/generators/graphql/install/mutation_root_generator.rb +34 -0
  7. data/lib/generators/graphql/{templates → install/templates}/base_mutation.erb +2 -0
  8. data/lib/generators/graphql/{templates → install/templates}/mutation_type.erb +2 -0
  9. data/lib/generators/graphql/install_generator.rb +44 -7
  10. data/lib/generators/graphql/interface_generator.rb +7 -7
  11. data/lib/generators/graphql/loader_generator.rb +1 -0
  12. data/lib/generators/graphql/mutation_create_generator.rb +22 -0
  13. data/lib/generators/graphql/mutation_delete_generator.rb +22 -0
  14. data/lib/generators/graphql/mutation_generator.rb +6 -30
  15. data/lib/generators/graphql/mutation_update_generator.rb +22 -0
  16. data/lib/generators/graphql/object_generator.rb +28 -12
  17. data/lib/generators/graphql/orm_mutations_base.rb +40 -0
  18. data/lib/generators/graphql/relay.rb +63 -0
  19. data/lib/generators/graphql/relay_generator.rb +21 -0
  20. data/lib/generators/graphql/scalar_generator.rb +4 -2
  21. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  22. data/lib/generators/graphql/templates/base_connection.erb +8 -0
  23. data/lib/generators/graphql/templates/base_edge.erb +8 -0
  24. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  25. data/lib/generators/graphql/templates/base_field.erb +2 -0
  26. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  27. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  28. data/lib/generators/graphql/templates/base_object.erb +2 -0
  29. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  30. data/lib/generators/graphql/templates/base_union.erb +2 -0
  31. data/lib/generators/graphql/templates/enum.erb +7 -1
  32. data/lib/generators/graphql/templates/graphql_controller.erb +16 -12
  33. data/lib/generators/graphql/templates/input.erb +9 -0
  34. data/lib/generators/graphql/templates/interface.erb +6 -2
  35. data/lib/generators/graphql/templates/loader.erb +2 -0
  36. data/lib/generators/graphql/templates/mutation.erb +3 -1
  37. data/lib/generators/graphql/templates/mutation_create.erb +20 -0
  38. data/lib/generators/graphql/templates/mutation_delete.erb +20 -0
  39. data/lib/generators/graphql/templates/mutation_update.erb +21 -0
  40. data/lib/generators/graphql/templates/node_type.erb +9 -0
  41. data/lib/generators/graphql/templates/object.erb +7 -3
  42. data/lib/generators/graphql/templates/query_type.erb +3 -3
  43. data/lib/generators/graphql/templates/scalar.erb +5 -1
  44. data/lib/generators/graphql/templates/schema.erb +22 -27
  45. data/lib/generators/graphql/templates/union.erb +6 -2
  46. data/lib/generators/graphql/type_generator.rb +47 -10
  47. data/lib/generators/graphql/union_generator.rb +5 -5
  48. data/lib/graphql/analysis/ast/field_usage.rb +29 -2
  49. data/lib/graphql/analysis/ast/query_complexity.rb +175 -68
  50. data/lib/graphql/analysis/ast/query_depth.rb +0 -1
  51. data/lib/graphql/analysis/ast/visitor.rb +17 -8
  52. data/lib/graphql/analysis/ast.rb +14 -14
  53. data/lib/graphql/analysis.rb +0 -7
  54. data/lib/graphql/backtrace/inspect_result.rb +0 -1
  55. data/lib/graphql/backtrace/table.rb +37 -16
  56. data/lib/graphql/backtrace/traced_error.rb +0 -1
  57. data/lib/graphql/backtrace/tracer.rb +39 -9
  58. data/lib/graphql/backtrace.rb +20 -17
  59. data/lib/graphql/dataloader/null_dataloader.rb +22 -0
  60. data/lib/graphql/dataloader/request.rb +19 -0
  61. data/lib/graphql/dataloader/request_all.rb +19 -0
  62. data/lib/graphql/dataloader/source.rb +155 -0
  63. data/lib/graphql/dataloader.rb +308 -0
  64. data/lib/graphql/date_encoding_error.rb +16 -0
  65. data/lib/graphql/deprecation.rb +9 -0
  66. data/lib/graphql/dig.rb +1 -1
  67. data/lib/graphql/execution/directive_checks.rb +2 -2
  68. data/lib/graphql/execution/errors.rb +108 -14
  69. data/lib/graphql/execution/instrumentation.rb +1 -1
  70. data/lib/graphql/execution/interpreter/argument_value.rb +28 -0
  71. data/lib/graphql/execution/interpreter/arguments.rb +88 -0
  72. data/lib/graphql/execution/interpreter/arguments_cache.rb +105 -0
  73. data/lib/graphql/execution/interpreter/handles_raw_value.rb +18 -0
  74. data/lib/graphql/execution/interpreter/resolve.rb +37 -25
  75. data/lib/graphql/execution/interpreter/runtime.rb +715 -387
  76. data/lib/graphql/execution/interpreter.rb +32 -31
  77. data/lib/graphql/execution/lazy/lazy_method_map.rb +4 -0
  78. data/lib/graphql/execution/lazy.rb +5 -1
  79. data/lib/graphql/execution/lookahead.rb +32 -114
  80. data/lib/graphql/execution/multiplex.rb +60 -92
  81. data/lib/graphql/execution.rb +11 -3
  82. data/lib/graphql/filter.rb +1 -1
  83. data/lib/graphql/integer_decoding_error.rb +17 -0
  84. data/lib/graphql/integer_encoding_error.rb +18 -2
  85. data/lib/graphql/introspection/base_object.rb +2 -5
  86. data/lib/graphql/introspection/directive_location_enum.rb +2 -2
  87. data/lib/graphql/introspection/directive_type.rb +11 -5
  88. data/lib/graphql/introspection/dynamic_fields.rb +3 -8
  89. data/lib/graphql/introspection/entry_points.rb +5 -18
  90. data/lib/graphql/introspection/enum_value_type.rb +2 -2
  91. data/lib/graphql/introspection/field_type.rb +9 -5
  92. data/lib/graphql/introspection/input_value_type.rb +41 -11
  93. data/lib/graphql/introspection/introspection_query.rb +6 -92
  94. data/lib/graphql/introspection/schema_type.rb +10 -10
  95. data/lib/graphql/introspection/type_type.rb +27 -17
  96. data/lib/graphql/introspection.rb +99 -0
  97. data/lib/graphql/invalid_null_error.rb +18 -0
  98. data/lib/graphql/language/block_string.rb +20 -5
  99. data/lib/graphql/language/cache.rb +37 -0
  100. data/lib/graphql/language/definition_slice.rb +21 -10
  101. data/lib/graphql/language/document_from_schema_definition.rb +99 -63
  102. data/lib/graphql/language/lexer.rb +53 -27
  103. data/lib/graphql/language/lexer.rl +5 -3
  104. data/lib/graphql/language/nodes.rb +64 -93
  105. data/lib/graphql/language/parser.rb +929 -896
  106. data/lib/graphql/language/parser.y +125 -102
  107. data/lib/graphql/language/printer.rb +11 -2
  108. data/lib/graphql/language/sanitized_printer.rb +222 -0
  109. data/lib/graphql/language/token.rb +0 -4
  110. data/lib/graphql/language/visitor.rb +2 -2
  111. data/lib/graphql/language.rb +3 -1
  112. data/lib/graphql/name_validator.rb +2 -7
  113. data/lib/graphql/pagination/active_record_relation_connection.rb +85 -0
  114. data/lib/graphql/pagination/array_connection.rb +77 -0
  115. data/lib/graphql/pagination/connection.rb +226 -0
  116. data/lib/graphql/pagination/connections.rb +134 -0
  117. data/lib/graphql/pagination/mongoid_relation_connection.rb +25 -0
  118. data/lib/graphql/pagination/relation_connection.rb +226 -0
  119. data/lib/graphql/pagination/sequel_dataset_connection.rb +28 -0
  120. data/lib/graphql/pagination.rb +6 -0
  121. data/lib/graphql/parse_error.rb +0 -1
  122. data/lib/graphql/query/context.rb +51 -190
  123. data/lib/graphql/query/fingerprint.rb +26 -0
  124. data/lib/graphql/query/input_validation_result.rb +23 -6
  125. data/lib/graphql/query/literal_input.rb +26 -11
  126. data/lib/graphql/query/null_context.rb +24 -8
  127. data/lib/graphql/query/validation_pipeline.rb +12 -38
  128. data/lib/graphql/query/variable_validation_error.rb +2 -2
  129. data/lib/graphql/query/variables.rb +26 -9
  130. data/lib/graphql/query.rb +62 -32
  131. data/lib/graphql/railtie.rb +6 -102
  132. data/lib/graphql/rake_task/validate.rb +3 -0
  133. data/lib/graphql/rake_task.rb +12 -9
  134. data/lib/graphql/relay/range_add.rb +23 -9
  135. data/lib/graphql/relay.rb +0 -15
  136. data/lib/graphql/rubocop/graphql/base_cop.rb +36 -0
  137. data/lib/graphql/rubocop/graphql/default_null_true.rb +43 -0
  138. data/lib/graphql/rubocop/graphql/default_required_true.rb +43 -0
  139. data/lib/graphql/rubocop.rb +4 -0
  140. data/lib/graphql/schema/addition.rb +240 -0
  141. data/lib/graphql/schema/argument.rb +262 -28
  142. data/lib/graphql/schema/base_64_encoder.rb +2 -0
  143. data/lib/graphql/schema/build_from_definition/resolve_map/default_resolve.rb +1 -1
  144. data/lib/graphql/schema/build_from_definition/resolve_map.rb +13 -5
  145. data/lib/graphql/schema/build_from_definition.rb +319 -220
  146. data/lib/graphql/schema/built_in_types.rb +5 -5
  147. data/lib/graphql/schema/directive/deprecated.rb +18 -0
  148. data/lib/graphql/schema/directive/feature.rb +1 -1
  149. data/lib/graphql/schema/directive/flagged.rb +57 -0
  150. data/lib/graphql/schema/directive/include.rb +2 -2
  151. data/lib/graphql/schema/directive/skip.rb +2 -2
  152. data/lib/graphql/schema/directive/transform.rb +14 -2
  153. data/lib/graphql/schema/directive.rb +117 -14
  154. data/lib/graphql/schema/enum.rb +115 -22
  155. data/lib/graphql/schema/enum_value.rb +16 -21
  156. data/lib/graphql/schema/field/connection_extension.rb +46 -20
  157. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  158. data/lib/graphql/schema/field.rb +376 -291
  159. data/lib/graphql/schema/field_extension.rb +89 -2
  160. data/lib/graphql/schema/find_inherited_value.rb +17 -1
  161. data/lib/graphql/schema/finder.rb +16 -14
  162. data/lib/graphql/schema/input_object.rb +147 -60
  163. data/lib/graphql/schema/interface.rb +28 -43
  164. data/lib/graphql/schema/introspection_system.rb +101 -38
  165. data/lib/graphql/schema/late_bound_type.rb +3 -2
  166. data/lib/graphql/schema/list.rb +46 -3
  167. data/lib/graphql/schema/loader.rb +144 -102
  168. data/lib/graphql/schema/member/base_dsl_methods.rb +33 -32
  169. data/lib/graphql/schema/member/build_type.rb +23 -14
  170. data/lib/graphql/schema/member/has_arguments.rb +212 -23
  171. data/lib/graphql/schema/member/has_ast_node.rb +20 -0
  172. data/lib/graphql/schema/member/has_deprecation_reason.rb +25 -0
  173. data/lib/graphql/schema/member/has_directives.rb +98 -0
  174. data/lib/graphql/schema/member/has_fields.rb +99 -34
  175. data/lib/graphql/schema/member/has_interfaces.rb +88 -0
  176. data/lib/graphql/schema/member/has_unresolved_type_error.rb +15 -0
  177. data/lib/graphql/schema/member/has_validators.rb +31 -0
  178. data/lib/graphql/schema/member/type_system_helpers.rb +3 -3
  179. data/lib/graphql/schema/member/validates_input.rb +33 -0
  180. data/lib/graphql/schema/member.rb +11 -6
  181. data/lib/graphql/schema/mutation.rb +4 -9
  182. data/lib/graphql/schema/non_null.rb +34 -4
  183. data/lib/graphql/schema/object.rb +38 -65
  184. data/lib/graphql/schema/printer.rb +16 -35
  185. data/lib/graphql/schema/relay_classic_mutation.rb +57 -32
  186. data/lib/graphql/schema/resolver/has_payload_type.rb +34 -4
  187. data/lib/graphql/schema/resolver.rb +133 -79
  188. data/lib/graphql/schema/scalar.rb +40 -15
  189. data/lib/graphql/schema/subscription.rb +57 -21
  190. data/lib/graphql/schema/timeout.rb +29 -15
  191. data/lib/graphql/schema/type_expression.rb +21 -13
  192. data/lib/graphql/schema/type_membership.rb +19 -5
  193. data/lib/graphql/schema/union.rb +39 -14
  194. data/lib/graphql/schema/unique_within_type.rb +1 -2
  195. data/lib/graphql/schema/validator/allow_blank_validator.rb +29 -0
  196. data/lib/graphql/schema/validator/allow_null_validator.rb +26 -0
  197. data/lib/graphql/schema/validator/exclusion_validator.rb +33 -0
  198. data/lib/graphql/schema/validator/format_validator.rb +48 -0
  199. data/lib/graphql/schema/validator/inclusion_validator.rb +35 -0
  200. data/lib/graphql/schema/validator/length_validator.rb +59 -0
  201. data/lib/graphql/schema/validator/numericality_validator.rb +82 -0
  202. data/lib/graphql/schema/validator/required_validator.rb +82 -0
  203. data/lib/graphql/schema/validator.rb +171 -0
  204. data/lib/graphql/schema/warden.rb +182 -32
  205. data/lib/graphql/schema/wrapper.rb +0 -5
  206. data/lib/graphql/schema.rb +687 -891
  207. data/lib/graphql/static_validation/all_rules.rb +2 -0
  208. data/lib/graphql/static_validation/base_visitor.rb +21 -31
  209. data/lib/graphql/static_validation/definition_dependencies.rb +0 -1
  210. data/lib/graphql/static_validation/error.rb +3 -1
  211. data/lib/graphql/static_validation/literal_validator.rb +51 -26
  212. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +45 -83
  213. data/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb +22 -6
  214. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +35 -26
  215. data/lib/graphql/static_validation/rules/arguments_are_defined_error.rb +4 -2
  216. data/lib/graphql/static_validation/rules/directives_are_defined.rb +1 -1
  217. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +14 -14
  218. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -4
  219. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +5 -5
  220. data/lib/graphql/static_validation/rules/fields_will_merge.rb +94 -51
  221. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +25 -4
  222. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  223. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -2
  224. data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +30 -0
  225. data/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb +30 -0
  226. data/lib/graphql/static_validation/rules/query_root_exists.rb +17 -0
  227. data/lib/graphql/static_validation/rules/query_root_exists_error.rb +26 -0
  228. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +4 -2
  229. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +9 -10
  230. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
  231. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +12 -13
  232. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +19 -14
  233. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
  234. data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +5 -3
  235. data/lib/graphql/static_validation/type_stack.rb +2 -2
  236. data/lib/graphql/static_validation/validation_context.rb +13 -3
  237. data/lib/graphql/static_validation/validation_timeout_error.rb +25 -0
  238. data/lib/graphql/static_validation/validator.rb +31 -19
  239. data/lib/graphql/static_validation.rb +1 -2
  240. data/lib/graphql/string_encoding_error.rb +13 -3
  241. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +123 -22
  242. data/lib/graphql/subscriptions/broadcast_analyzer.rb +81 -0
  243. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +58 -0
  244. data/lib/graphql/subscriptions/event.rb +85 -31
  245. data/lib/graphql/subscriptions/instrumentation.rb +0 -47
  246. data/lib/graphql/subscriptions/serialize.rb +53 -6
  247. data/lib/graphql/subscriptions.rb +111 -52
  248. data/lib/graphql/tracing/active_support_notifications_tracing.rb +8 -17
  249. data/lib/graphql/tracing/appoptics_tracing.rb +173 -0
  250. data/lib/graphql/tracing/appsignal_tracing.rb +23 -0
  251. data/lib/graphql/tracing/data_dog_tracing.rb +14 -1
  252. data/lib/graphql/tracing/new_relic_tracing.rb +9 -12
  253. data/lib/graphql/tracing/notifications_tracing.rb +59 -0
  254. data/lib/graphql/tracing/platform_tracing.rb +57 -29
  255. data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +4 -1
  256. data/lib/graphql/tracing/prometheus_tracing.rb +8 -0
  257. data/lib/graphql/tracing/scout_tracing.rb +19 -0
  258. data/lib/graphql/tracing/statsd_tracing.rb +42 -0
  259. data/lib/graphql/tracing.rb +15 -36
  260. data/lib/graphql/types/big_int.rb +5 -1
  261. data/lib/graphql/types/int.rb +10 -3
  262. data/lib/graphql/types/iso_8601_date.rb +16 -8
  263. data/lib/graphql/types/iso_8601_date_time.rb +32 -10
  264. data/lib/graphql/types/relay/base_connection.rb +6 -88
  265. data/lib/graphql/types/relay/base_edge.rb +2 -34
  266. data/lib/graphql/types/relay/connection_behaviors.rb +170 -0
  267. data/lib/graphql/types/relay/default_relay.rb +21 -0
  268. data/lib/graphql/types/relay/edge_behaviors.rb +64 -0
  269. data/lib/graphql/types/relay/has_node_field.rb +41 -0
  270. data/lib/graphql/types/relay/has_nodes_field.rb +41 -0
  271. data/lib/graphql/types/relay/node.rb +2 -4
  272. data/lib/graphql/types/relay/node_behaviors.rb +19 -0
  273. data/lib/graphql/types/relay/page_info.rb +2 -14
  274. data/lib/graphql/types/relay/page_info_behaviors.rb +25 -0
  275. data/lib/graphql/types/relay.rb +11 -5
  276. data/lib/graphql/types/string.rb +8 -2
  277. data/lib/graphql/unauthorized_error.rb +2 -2
  278. data/lib/graphql/unresolved_type_error.rb +2 -2
  279. data/lib/graphql/version.rb +1 -1
  280. data/lib/graphql.rb +41 -58
  281. data/readme.md +3 -6
  282. metadata +97 -231
  283. data/lib/graphql/analysis/analyze_query.rb +0 -91
  284. data/lib/graphql/analysis/field_usage.rb +0 -45
  285. data/lib/graphql/analysis/max_query_complexity.rb +0 -26
  286. data/lib/graphql/analysis/max_query_depth.rb +0 -26
  287. data/lib/graphql/analysis/query_complexity.rb +0 -88
  288. data/lib/graphql/analysis/query_depth.rb +0 -43
  289. data/lib/graphql/analysis/reducer_state.rb +0 -48
  290. data/lib/graphql/argument.rb +0 -159
  291. data/lib/graphql/authorization.rb +0 -82
  292. data/lib/graphql/backwards_compatibility.rb +0 -60
  293. data/lib/graphql/base_type.rb +0 -226
  294. data/lib/graphql/boolean_type.rb +0 -2
  295. data/lib/graphql/compatibility/execution_specification/counter_schema.rb +0 -53
  296. data/lib/graphql/compatibility/execution_specification/specification_schema.rb +0 -200
  297. data/lib/graphql/compatibility/execution_specification.rb +0 -435
  298. data/lib/graphql/compatibility/lazy_execution_specification/lazy_schema.rb +0 -111
  299. data/lib/graphql/compatibility/lazy_execution_specification.rb +0 -213
  300. data/lib/graphql/compatibility/query_parser_specification/parse_error_specification.rb +0 -91
  301. data/lib/graphql/compatibility/query_parser_specification/query_assertions.rb +0 -79
  302. data/lib/graphql/compatibility/query_parser_specification.rb +0 -264
  303. data/lib/graphql/compatibility/schema_parser_specification.rb +0 -680
  304. data/lib/graphql/compatibility.rb +0 -5
  305. data/lib/graphql/define/assign_argument.rb +0 -12
  306. data/lib/graphql/define/assign_connection.rb +0 -13
  307. data/lib/graphql/define/assign_enum_value.rb +0 -18
  308. data/lib/graphql/define/assign_global_id_field.rb +0 -11
  309. data/lib/graphql/define/assign_mutation_function.rb +0 -34
  310. data/lib/graphql/define/assign_object_field.rb +0 -42
  311. data/lib/graphql/define/defined_object_proxy.rb +0 -53
  312. data/lib/graphql/define/instance_definable.rb +0 -311
  313. data/lib/graphql/define/no_definition_error.rb +0 -7
  314. data/lib/graphql/define/non_null_with_bang.rb +0 -16
  315. data/lib/graphql/define/type_definer.rb +0 -31
  316. data/lib/graphql/define.rb +0 -31
  317. data/lib/graphql/deprecated_dsl.rb +0 -42
  318. data/lib/graphql/directive/deprecated_directive.rb +0 -13
  319. data/lib/graphql/directive/include_directive.rb +0 -2
  320. data/lib/graphql/directive/skip_directive.rb +0 -2
  321. data/lib/graphql/directive.rb +0 -104
  322. data/lib/graphql/enum_type.rb +0 -193
  323. data/lib/graphql/execution/execute.rb +0 -326
  324. data/lib/graphql/execution/flatten.rb +0 -40
  325. data/lib/graphql/execution/interpreter/hash_response.rb +0 -46
  326. data/lib/graphql/execution/typecast.rb +0 -50
  327. data/lib/graphql/field/resolve.rb +0 -59
  328. data/lib/graphql/field.rb +0 -330
  329. data/lib/graphql/float_type.rb +0 -2
  330. data/lib/graphql/function.rb +0 -153
  331. data/lib/graphql/id_type.rb +0 -2
  332. data/lib/graphql/input_object_type.rb +0 -154
  333. data/lib/graphql/int_type.rb +0 -2
  334. data/lib/graphql/interface_type.rb +0 -86
  335. data/lib/graphql/internal_representation/document.rb +0 -27
  336. data/lib/graphql/internal_representation/node.rb +0 -206
  337. data/lib/graphql/internal_representation/print.rb +0 -51
  338. data/lib/graphql/internal_representation/rewrite.rb +0 -184
  339. data/lib/graphql/internal_representation/scope.rb +0 -88
  340. data/lib/graphql/internal_representation/visit.rb +0 -36
  341. data/lib/graphql/internal_representation.rb +0 -7
  342. data/lib/graphql/list_type.rb +0 -80
  343. data/lib/graphql/literal_validation_error.rb +0 -6
  344. data/lib/graphql/non_null_type.rb +0 -81
  345. data/lib/graphql/object_type.rb +0 -141
  346. data/lib/graphql/query/arguments.rb +0 -187
  347. data/lib/graphql/query/arguments_cache.rb +0 -25
  348. data/lib/graphql/query/executor.rb +0 -53
  349. data/lib/graphql/query/serial_execution/field_resolution.rb +0 -92
  350. data/lib/graphql/query/serial_execution/operation_resolution.rb +0 -19
  351. data/lib/graphql/query/serial_execution/selection_resolution.rb +0 -23
  352. data/lib/graphql/query/serial_execution/value_resolution.rb +0 -87
  353. data/lib/graphql/query/serial_execution.rb +0 -39
  354. data/lib/graphql/relay/array_connection.rb +0 -85
  355. data/lib/graphql/relay/base_connection.rb +0 -172
  356. data/lib/graphql/relay/connection_instrumentation.rb +0 -54
  357. data/lib/graphql/relay/connection_resolve.rb +0 -43
  358. data/lib/graphql/relay/connection_type.rb +0 -40
  359. data/lib/graphql/relay/edge.rb +0 -27
  360. data/lib/graphql/relay/edge_type.rb +0 -18
  361. data/lib/graphql/relay/edges_instrumentation.rb +0 -40
  362. data/lib/graphql/relay/global_id_resolve.rb +0 -18
  363. data/lib/graphql/relay/mongo_relation_connection.rb +0 -50
  364. data/lib/graphql/relay/mutation/instrumentation.rb +0 -23
  365. data/lib/graphql/relay/mutation/resolve.rb +0 -56
  366. data/lib/graphql/relay/mutation/result.rb +0 -38
  367. data/lib/graphql/relay/mutation.rb +0 -190
  368. data/lib/graphql/relay/node.rb +0 -36
  369. data/lib/graphql/relay/page_info.rb +0 -7
  370. data/lib/graphql/relay/relation_connection.rb +0 -190
  371. data/lib/graphql/relay/type_extensions.rb +0 -30
  372. data/lib/graphql/scalar_type.rb +0 -133
  373. data/lib/graphql/schema/catchall_middleware.rb +0 -35
  374. data/lib/graphql/schema/default_parse_error.rb +0 -10
  375. data/lib/graphql/schema/default_type_error.rb +0 -15
  376. data/lib/graphql/schema/member/accepts_definition.rb +0 -152
  377. data/lib/graphql/schema/member/cached_graphql_definition.rb +0 -26
  378. data/lib/graphql/schema/member/instrumentation.rb +0 -132
  379. data/lib/graphql/schema/middleware_chain.rb +0 -82
  380. data/lib/graphql/schema/possible_types.rb +0 -39
  381. data/lib/graphql/schema/rescue_middleware.rb +0 -60
  382. data/lib/graphql/schema/timeout_middleware.rb +0 -86
  383. data/lib/graphql/schema/traversal.rb +0 -228
  384. data/lib/graphql/schema/validation.rb +0 -303
  385. data/lib/graphql/static_validation/default_visitor.rb +0 -15
  386. data/lib/graphql/static_validation/no_validate_visitor.rb +0 -10
  387. data/lib/graphql/string_type.rb +0 -2
  388. data/lib/graphql/subscriptions/subscription_root.rb +0 -74
  389. data/lib/graphql/tracing/skylight_tracing.rb +0 -62
  390. data/lib/graphql/types/relay/base_field.rb +0 -22
  391. data/lib/graphql/types/relay/base_interface.rb +0 -29
  392. data/lib/graphql/types/relay/base_object.rb +0 -26
  393. data/lib/graphql/types/relay/node_field.rb +0 -43
  394. data/lib/graphql/types/relay/nodes_field.rb +0 -45
  395. data/lib/graphql/union_type.rb +0 -135
  396. data/lib/graphql/upgrader/member.rb +0 -936
  397. data/lib/graphql/upgrader/schema.rb +0 -37
@@ -22,15 +22,15 @@ module GraphQL
22
22
  node_values = node_values.select { |value| value.is_a? GraphQL::Language::Nodes::VariableIdentifier }
23
23
 
24
24
  if node_values.any?
25
- arguments = case parent
25
+ argument_owner = case parent
26
26
  when GraphQL::Language::Nodes::Field
27
- context.field_definition.arguments
27
+ context.field_definition
28
28
  when GraphQL::Language::Nodes::Directive
29
- context.directive_definition.arguments
29
+ context.directive_definition
30
30
  when GraphQL::Language::Nodes::InputObject
31
31
  arg_type = context.argument_definition.type.unwrap
32
- if arg_type.is_a?(GraphQL::InputObjectType)
33
- arguments = arg_type.input_fields
32
+ if arg_type.kind.input_object?
33
+ arg_type
34
34
  else
35
35
  # This is some kind of error
36
36
  nil
@@ -43,7 +43,7 @@ module GraphQL
43
43
  var_defn_ast = @declared_variables[node_value.name]
44
44
  # Might be undefined :(
45
45
  # VariablesAreUsedAndDefined can't finalize its search until the end of the document.
46
- var_defn_ast && arguments && validate_usage(arguments, node, var_defn_ast)
46
+ var_defn_ast && argument_owner && validate_usage(argument_owner, node, var_defn_ast)
47
47
  end
48
48
  end
49
49
  super
@@ -51,24 +51,29 @@ module GraphQL
51
51
 
52
52
  private
53
53
 
54
- def validate_usage(arguments, arg_node, ast_var)
55
- var_type = context.schema.type_from_ast(ast_var.type)
54
+ def validate_usage(argument_owner, arg_node, ast_var)
55
+ var_type = context.schema.type_from_ast(ast_var.type, context: context)
56
56
  if var_type.nil?
57
57
  return
58
58
  end
59
59
  if !ast_var.default_value.nil?
60
- unless var_type.is_a?(GraphQL::NonNullType)
60
+ unless var_type.kind.non_null?
61
61
  # If the value is required, but the argument is not,
62
62
  # and yet there's a non-nil default, then we impliclty
63
63
  # make the argument also a required type.
64
-
65
- var_type = GraphQL::NonNullType.new(of_type: var_type)
64
+ var_type = var_type.to_non_null_type
66
65
  end
67
66
  end
68
67
 
69
- arg_defn = arguments[arg_node.name]
68
+ arg_defn = context.warden.get_argument(argument_owner, arg_node.name)
70
69
  arg_defn_type = arg_defn.type
71
70
 
71
+ # If the argument is non-null, but it was given a default value,
72
+ # then treat it as nullable in practice, see https://github.com/rmosolgo/graphql-ruby/issues/3793
73
+ if arg_defn_type.non_null? && arg_defn.default_value?
74
+ arg_defn_type = arg_defn_type.of_type
75
+ end
76
+
72
77
  var_inner_type = var_type.unwrap
73
78
  arg_inner_type = arg_defn_type.unwrap
74
79
 
@@ -85,10 +90,10 @@ module GraphQL
85
90
 
86
91
  def create_error(error_message, var_type, ast_var, arg_defn, arg_node)
87
92
  add_error(GraphQL::StaticValidation::VariableUsagesAreAllowedError.new(
88
- "#{error_message} on variable $#{ast_var.name} and argument #{arg_node.name} (#{var_type.to_s} / #{arg_defn.type.to_s})",
93
+ "#{error_message} on variable $#{ast_var.name} and argument #{arg_node.name} (#{var_type.to_type_signature} / #{arg_defn.type.to_type_signature})",
89
94
  nodes: arg_node,
90
95
  name: ast_var.name,
91
- type: var_type.to_s,
96
+ type: var_type.to_type_signature,
92
97
  argument: arg_node.name,
93
98
  error: error_message
94
99
  ))
@@ -15,7 +15,7 @@ module GraphQL
15
15
  ))
16
16
  elsif !type.kind.input?
17
17
  add_error(GraphQL::StaticValidation::VariablesAreInputTypesError.new(
18
- "#{type.name} isn't a valid input type (on $#{node.name})",
18
+ "#{type.graphql_name} isn't a valid input type (on $#{node.name})",
19
19
  nodes: node,
20
20
  name: node.name,
21
21
  type: type_name
@@ -2,7 +2,7 @@
2
2
  module GraphQL
3
3
  module StaticValidation
4
4
  # The problem is
5
- # - Variable usage must be determined at the OperationDefinition level
5
+ # - Variable $usage must be determined at the OperationDefinition level
6
6
  # - You can't tell how fragments use variables until you visit FragmentDefinitions (which may be at the end of the document)
7
7
  #
8
8
  # So, this validator includes some crazy logic to follow fragment spreads recursively, while avoiding infinite loops.
@@ -126,8 +126,9 @@ module GraphQL
126
126
  node_variables
127
127
  .select { |name, usage| usage.declared? && !usage.used? }
128
128
  .each { |var_name, usage|
129
+ declared_by_error_name = usage.declared_by.name || "anonymous #{usage.declared_by.operation_type}"
129
130
  add_error(GraphQL::StaticValidation::VariablesAreUsedAndDefinedError.new(
130
- "Variable $#{var_name} is declared by #{usage.declared_by.name} but not used",
131
+ "Variable $#{var_name} is declared by #{declared_by_error_name} but not used",
131
132
  nodes: usage.declared_by,
132
133
  path: usage.path,
133
134
  name: var_name,
@@ -139,8 +140,9 @@ module GraphQL
139
140
  node_variables
140
141
  .select { |name, usage| usage.used? && !usage.declared? }
141
142
  .each { |var_name, usage|
143
+ used_by_error_name = usage.used_by.name || "anonymous #{usage.used_by.operation_type}"
142
144
  add_error(GraphQL::StaticValidation::VariablesAreUsedAndDefinedError.new(
143
- "Variable $#{var_name} is used by #{usage.used_by.name} but not declared",
145
+ "Variable $#{var_name} is used by #{used_by_error_name} but not declared",
144
146
  nodes: usage.ast_node,
145
147
  path: usage.path,
146
148
  name: var_name,
@@ -55,7 +55,7 @@ module GraphQL
55
55
  module FragmentWithTypeStrategy
56
56
  def push(stack, node)
57
57
  object_type = if node.type
58
- stack.schema.types.fetch(node.type.name, nil)
58
+ stack.schema.get_type(node.type.name)
59
59
  else
60
60
  stack.object_types.last
61
61
  end
@@ -148,7 +148,7 @@ module GraphQL
148
148
  if stack.argument_definitions.last
149
149
  arg_type = stack.argument_definitions.last.type.unwrap
150
150
  if arg_type.kind.input_object?
151
- argument_defn = arg_type.input_fields[node.name]
151
+ argument_defn = arg_type.arguments[node.name]
152
152
  else
153
153
  argument_defn = nil
154
154
  end
@@ -15,14 +15,16 @@ module GraphQL
15
15
  extend Forwardable
16
16
 
17
17
  attr_reader :query, :errors, :visitor,
18
- :on_dependency_resolve_handlers
18
+ :on_dependency_resolve_handlers,
19
+ :max_errors
19
20
 
20
21
  def_delegators :@query, :schema, :document, :fragments, :operations, :warden
21
22
 
22
- def initialize(query, visitor_class)
23
+ def initialize(query, visitor_class, max_errors)
23
24
  @query = query
24
25
  @literal_validator = LiteralValidator.new(context: query.context)
25
26
  @errors = []
27
+ @max_errors = max_errors || Float::INFINITY
26
28
  @on_dependency_resolve_handlers = []
27
29
  @visitor = visitor_class.new(document, self)
28
30
  end
@@ -35,9 +37,17 @@ module GraphQL
35
37
  @on_dependency_resolve_handlers << handler
36
38
  end
37
39
 
38
- def valid_literal?(ast_value, type)
40
+ def validate_literal(ast_value, type)
39
41
  @literal_validator.validate(ast_value, type)
40
42
  end
43
+
44
+ def too_many_errors?
45
+ @errors.length >= @max_errors
46
+ end
47
+
48
+ def schema_directives
49
+ @schema_directives ||= schema.directives
50
+ end
41
51
  end
42
52
  end
43
53
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module StaticValidation
4
+ class ValidationTimeoutError < 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
+ "validationTimeout"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,4 +1,6 @@
1
1
  # frozen_string_literal: true
2
+ require "timeout"
3
+
2
4
  module GraphQL
3
5
  module StaticValidation
4
6
  # Initialized with a {GraphQL::Schema}, then it can validate {GraphQL::Language::Nodes::Documents}s based on that schema.
@@ -20,42 +22,52 @@ module GraphQL
20
22
 
21
23
  # Validate `query` against the schema. Returns an array of message hashes.
22
24
  # @param query [GraphQL::Query]
25
+ # @param validate [Boolean]
26
+ # @param timeout [Float] Number of seconds to wait before aborting validation. Any positive number may be used, including Floats to specify fractional seconds.
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.
23
28
  # @return [Array<Hash>]
24
- def validate(query, validate: true)
29
+ def validate(query, validate: true, timeout: nil, max_errors: nil)
25
30
  query.trace("validate", { validate: validate, query: query }) do
26
- can_skip_rewrite = query.context.interpreter? && query.schema.using_ast_analysis?
27
- errors = if validate == false && can_skip_rewrite
31
+ errors = if validate == false
28
32
  []
29
33
  else
30
34
  rules_to_use = validate ? @rules : []
31
- visitor_class = BaseVisitor.including_rules(rules_to_use, rewrite: !can_skip_rewrite)
35
+ visitor_class = BaseVisitor.including_rules(rules_to_use)
32
36
 
33
- context = GraphQL::StaticValidation::ValidationContext.new(query, visitor_class)
37
+ context = GraphQL::StaticValidation::ValidationContext.new(query, visitor_class, max_errors)
34
38
 
35
- # Attach legacy-style rules
36
- rules_to_use.each do |rule_class_or_module|
37
- if rule_class_or_module.method_defined?(:validate)
38
- rule_class_or_module.new.validate(context)
39
+ begin
40
+ # CAUTION: Usage of the timeout module makes the assumption that validation rules are stateless Ruby code that requires no cleanup if process was interrupted. This means no blocking IO calls, native gems, locks, or `rescue` clauses that must be reached.
41
+ # A timeout value of 0 or nil will execute the block without any timeout.
42
+ Timeout::timeout(timeout) do
43
+ catch(:too_many_validation_errors) do
44
+ context.visitor.visit
45
+ end
39
46
  end
47
+ rescue Timeout::Error
48
+ handle_timeout(query, context)
40
49
  end
41
50
 
42
- context.visitor.visit
43
51
  context.errors
44
52
  end
45
53
 
46
-
47
- irep = if errors.empty? && context
48
- # Only return this if there are no errors and validation was actually run
49
- context.visitor.rewrite_document
50
- else
51
- nil
52
- end
53
-
54
54
  {
55
55
  errors: errors,
56
- irep: irep,
57
56
  }
58
57
  end
58
+ rescue GraphQL::ExecutionError => e
59
+ {
60
+ errors: [e],
61
+ }
62
+ end
63
+
64
+ # Invoked when static validation times out.
65
+ # @param query [GraphQL::Query]
66
+ # @param context [GraphQL::StaticValidation::ValidationContext]
67
+ def handle_timeout(query, context)
68
+ context.errors << GraphQL::StaticValidation::ValidationTimeoutError.new(
69
+ "Timeout on validation of query"
70
+ )
59
71
  end
60
72
  end
61
73
  end
@@ -4,9 +4,9 @@ require "graphql/static_validation/definition_dependencies"
4
4
  require "graphql/static_validation/type_stack"
5
5
  require "graphql/static_validation/validator"
6
6
  require "graphql/static_validation/validation_context"
7
+ require "graphql/static_validation/validation_timeout_error"
7
8
  require "graphql/static_validation/literal_validator"
8
9
  require "graphql/static_validation/base_visitor"
9
- require "graphql/static_validation/no_validate_visitor"
10
10
 
11
11
  rules_glob = File.expand_path("../static_validation/rules/*.rb", __FILE__)
12
12
  Dir.glob(rules_glob).each do |file|
@@ -14,5 +14,4 @@ Dir.glob(rules_glob).each do |file|
14
14
  end
15
15
 
16
16
  require "graphql/static_validation/all_rules"
17
- require "graphql/static_validation/default_visitor"
18
17
  require "graphql/static_validation/interpreter_visitor"
@@ -1,10 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
3
  class StringEncodingError < GraphQL::RuntimeTypeError
4
- attr_reader :string
5
- def initialize(str)
4
+ attr_reader :string, :field, :path
5
+ def initialize(str, context:)
6
6
  @string = str
7
- super("String \"#{str}\" was encoded as #{str.encoding}! GraphQL requires an encoding compatible with UTF-8.")
7
+ @field = context[:current_field]
8
+ @path = context[:current_path]
9
+ message = "String #{str.inspect} was encoded as #{str.encoding}".dup
10
+ if @path
11
+ message << " @ #{@path.join(".")}"
12
+ end
13
+ if @field
14
+ message << " (#{@field.path})"
15
+ end
16
+ message << ". GraphQL requires an encoding compatible with UTF-8."
17
+ super(message)
8
18
  end
9
19
  end
10
20
  end
@@ -4,13 +4,14 @@ module GraphQL
4
4
  # A subscriptions implementation that sends data
5
5
  # as ActionCable broadcastings.
6
6
  #
7
- # Experimental, some things to keep in mind:
7
+ # Some things to keep in mind:
8
8
  #
9
9
  # - No queueing system; ActiveJob should be added
10
10
  # - Take care to reload context when re-delivering the subscription. (see {Query#subscription_update?})
11
+ # - Avoid the async ActionCable adapter and use the redis or PostgreSQL adapters instead. Otherwise calling #trigger won't work from background jobs or the Rails console.
11
12
  #
12
13
  # @example Adding ActionCableSubscriptions to your schema
13
- # MySchema = GraphQL::Schema.define do
14
+ # class MySchema < GraphQL::Schema
14
15
  # # ...
15
16
  # use GraphQL::Subscriptions::ActionCableSubscriptions
16
17
  # end
@@ -26,22 +27,22 @@ module GraphQL
26
27
  # variables = ensure_hash(data["variables"])
27
28
  # operation_name = data["operationName"]
28
29
  # context = {
29
- # # Re-implement whatever context methods you need
30
+ # # Re-implement whatever context methods you need
30
31
  # # in this channel or ApplicationCable::Channel
31
32
  # # current_user: current_user,
32
33
  # # Make sure the channel is in the context
33
34
  # channel: self,
34
35
  # }
35
36
  #
36
- # result = MySchema.execute({
37
+ # result = MySchema.execute(
37
38
  # query: query,
38
39
  # context: context,
39
40
  # variables: variables,
40
41
  # operation_name: operation_name
41
- # })
42
+ # )
42
43
  #
43
44
  # payload = {
44
- # result: result.subscription? ? { data: nil } : result.to_h,
45
+ # result: result.to_h,
45
46
  # more: result.subscription?,
46
47
  # }
47
48
  #
@@ -85,27 +86,40 @@ module GraphQL
85
86
  EVENT_PREFIX = "graphql-event:"
86
87
 
87
88
  # @param serializer [<#dump(obj), #load(string)] Used for serializing messages before handing them to `.broadcast(msg)`
88
- def initialize(serializer: Serialize, **rest)
89
+ # @param namespace [string] Used to namespace events and subscriptions (default: '')
90
+ def initialize(serializer: Serialize, namespace: '', action_cable: ActionCable, action_cable_coder: ActiveSupport::JSON, **rest)
89
91
  # A per-process map of subscriptions to deliver.
90
92
  # This is provided by Rails, so let's use it
91
93
  @subscriptions = Concurrent::Map.new
94
+ @events = Concurrent::Map.new { |h, k| h[k] = Concurrent::Map.new { |h2, k2| h2[k2] = Concurrent::Array.new } }
95
+ @action_cable = action_cable
96
+ @action_cable_coder = action_cable_coder
92
97
  @serializer = serializer
98
+ @serialize_with_context = case @serializer.method(:load).arity
99
+ when 1
100
+ false
101
+ when 2
102
+ true
103
+ else
104
+ raise ArgumentError, "#{@serializer} must repond to `.load` accepting one or two arguments"
105
+ end
106
+ @transmit_ns = namespace
93
107
  super
94
108
  end
95
109
 
96
110
  # An event was triggered; Push the data over ActionCable.
97
111
  # Subscribers will re-evaluate locally.
98
112
  def execute_all(event, object)
99
- stream = EVENT_PREFIX + event.topic
113
+ stream = stream_event_name(event)
100
114
  message = @serializer.dump(object)
101
- ActionCable.server.broadcast(stream, message)
115
+ @action_cable.server.broadcast(stream, message)
102
116
  end
103
117
 
104
118
  # This subscription was re-evaluated.
105
119
  # Send it to the specific stream where this client was waiting.
106
120
  def deliver(subscription_id, result)
107
121
  payload = { result: result.to_h, more: true }
108
- ActionCable.server.broadcast(SUBSCRIPTION_PREFIX + subscription_id, payload)
122
+ @action_cable.server.broadcast(stream_subscription_name(subscription_id), payload)
109
123
  end
110
124
 
111
125
  # A query was run where these events were subscribed to.
@@ -113,33 +127,120 @@ module GraphQL
113
127
  # It will receive notifications when events come in
114
128
  # and re-evaluate the query locally.
115
129
  def write_subscription(query, events)
116
- channel = query.context.fetch(:channel)
130
+ unless (channel = query.context[:channel])
131
+ raise GraphQL::Error, "This GraphQL Subscription client does not support the transport protocol expected"\
132
+ "by the backend Subscription Server implementation (graphql-ruby ActionCableSubscriptions in this case)."\
133
+ "Some official client implementation including Apollo (https://graphql-ruby.org/javascript_client/apollo_subscriptions.html), "\
134
+ "Relay Modern (https://graphql-ruby.org/javascript_client/relay_subscriptions.html#actioncable)."\
135
+ "GraphiQL via `graphiql-rails` may not work out of box (#1051)."
136
+ end
117
137
  subscription_id = query.context[:subscription_id] ||= build_id
118
- stream = query.context[:action_cable_stream] ||= SUBSCRIPTION_PREFIX + subscription_id
138
+ stream = stream_subscription_name(subscription_id)
119
139
  channel.stream_from(stream)
120
140
  @subscriptions[subscription_id] = query
121
141
  events.each do |event|
122
- channel.stream_from(EVENT_PREFIX + event.topic, coder: ActiveSupport::JSON) do |message|
123
- execute(subscription_id, event, @serializer.load(message))
124
- nil
142
+ # Setup a new listener to run all events with this topic in this process
143
+ setup_stream(channel, event)
144
+ # Add this event to the list of events to be updated
145
+ @events[event.topic][event.fingerprint] << event
146
+ end
147
+ end
148
+
149
+ # Every subscribing channel is listening here, but only one of them takes any action.
150
+ # This is so we can reuse payloads when possible, and make one payload to send to
151
+ # all subscribers.
152
+ #
153
+ # But the problem is, any channel could close at any time, so each channel has to
154
+ # be ready to take over the primary position.
155
+ #
156
+ # To make sure there's always one-and-only-one channel building payloads,
157
+ # let the listener belonging to the first event on the list be
158
+ # the one to build and publish payloads.
159
+ #
160
+ def setup_stream(channel, initial_event)
161
+ topic = initial_event.topic
162
+ channel.stream_from(stream_event_name(initial_event), coder: @action_cable_coder) do |message|
163
+ events_by_fingerprint = @events[topic]
164
+ object = nil
165
+ events_by_fingerprint.each do |_fingerprint, events|
166
+ if events.any? && events.first == initial_event
167
+ # The fingerprint has told us that this response should be shared by all subscribers,
168
+ # so just run it once, then deliver the result to every subscriber
169
+ first_event = events.first
170
+ first_subscription_id = first_event.context.fetch(:subscription_id)
171
+ object ||= load_action_cable_message(message, first_event.context)
172
+ result = execute_update(first_subscription_id, first_event, object)
173
+ if !result.nil?
174
+ # Having calculated the result _once_, send the same payload to all subscribers
175
+ events.each do |event|
176
+ subscription_id = event.context.fetch(:subscription_id)
177
+ deliver(subscription_id, result)
178
+ end
179
+ end
180
+ end
125
181
  end
182
+ nil
183
+ end
184
+ end
185
+
186
+ # This is called to turn an ActionCable-broadcasted string (JSON)
187
+ # into a query-ready application object.
188
+ # @param message [String] n ActionCable-broadcasted string (JSON)
189
+ # @param context [GraphQL::Query::Context] the context of the first event for a given subscription fingerprint
190
+ def load_action_cable_message(message, context)
191
+ if @serialize_with_context
192
+ @serializer.load(message, context)
193
+ else
194
+ @serializer.load(message)
126
195
  end
127
196
  end
128
197
 
129
198
  # Return the query from "storage" (in memory)
130
199
  def read_subscription(subscription_id)
131
200
  query = @subscriptions[subscription_id]
132
- {
133
- query_string: query.query_string,
134
- variables: query.provided_variables,
135
- context: query.context.to_h,
136
- operation_name: query.operation_name,
137
- }
201
+ if query.nil?
202
+ # This can happen when a subscription is triggered from an unsubscribed channel,
203
+ # see https://github.com/rmosolgo/graphql-ruby/issues/2478.
204
+ # (This `nil` is handled by `#execute_update`)
205
+ nil
206
+ else
207
+ {
208
+ query_string: query.query_string,
209
+ variables: query.provided_variables,
210
+ context: query.context.to_h,
211
+ operation_name: query.operation_name,
212
+ }
213
+ end
138
214
  end
139
215
 
140
216
  # The channel was closed, forget about it.
141
217
  def delete_subscription(subscription_id)
142
- @subscriptions.delete(subscription_id)
218
+ query = @subscriptions.delete(subscription_id)
219
+ # In case this came from the server, tell the client to unsubscribe:
220
+ @action_cable.server.broadcast(stream_subscription_name(subscription_id), { more: false })
221
+ # This can be `nil` when `.trigger` happens inside an unsubscribed ActionCable channel,
222
+ # see https://github.com/rmosolgo/graphql-ruby/issues/2478
223
+ if query
224
+ events = query.context.namespace(:subscriptions)[:events]
225
+ events.each do |event|
226
+ ev_by_fingerprint = @events[event.topic]
227
+ ev_for_fingerprint = ev_by_fingerprint[event.fingerprint]
228
+ ev_for_fingerprint.delete(event)
229
+ if ev_for_fingerprint.empty?
230
+ ev_by_fingerprint.delete(event.fingerprint)
231
+ end
232
+ end
233
+ end
234
+ end
235
+
236
+ private
237
+
238
+ def stream_subscription_name(subscription_id)
239
+ [SUBSCRIPTION_PREFIX, @transmit_ns, subscription_id].join
240
+ end
241
+
242
+ def stream_event_name(event)
243
+ [EVENT_PREFIX, @transmit_ns, event.topic].join
143
244
  end
144
245
  end
145
246
  end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Subscriptions
5
+ # Detect whether the current operation:
6
+ # - Is a subscription operation
7
+ # - Is completely broadcastable
8
+ #
9
+ # Assign the result to `context.namespace(:subscriptions)[:subscription_broadcastable]`
10
+ # @api private
11
+ # @see Subscriptions#broadcastable? for a public API
12
+ class BroadcastAnalyzer < GraphQL::Analysis::AST::Analyzer
13
+ def initialize(subject)
14
+ super
15
+ @default_broadcastable = subject.schema.subscriptions.default_broadcastable
16
+ # Maybe this will get set to false while analyzing
17
+ @subscription_broadcastable = true
18
+ end
19
+
20
+ # Only analyze subscription operations
21
+ def analyze?
22
+ @query.subscription?
23
+ end
24
+
25
+ def on_enter_field(node, parent, visitor)
26
+ if (@subscription_broadcastable == false) || visitor.skipping?
27
+ return
28
+ end
29
+
30
+ current_field = visitor.field_definition
31
+ apply_broadcastable(current_field)
32
+
33
+ current_type = visitor.parent_type_definition
34
+ if current_type.kind.interface?
35
+ pt = @query.possible_types(current_type)
36
+ pt.each do |object_type|
37
+ ot_field = @query.get_field(object_type, current_field.graphql_name)
38
+ # Inherited fields would be exactly the same object;
39
+ # only check fields that are overrides of the inherited one
40
+ if ot_field && ot_field != current_field
41
+ apply_broadcastable(ot_field)
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ # Assign the result to context.
48
+ # (This method is allowed to return an error, but we don't need to)
49
+ # @return [void]
50
+ def result
51
+ query.context.namespace(:subscriptions)[:subscription_broadcastable] = @subscription_broadcastable
52
+ nil
53
+ end
54
+
55
+ private
56
+
57
+ # Modify `@subscription_broadcastable` based on `field_defn`'s configuration (and/or the default value)
58
+ def apply_broadcastable(field_defn)
59
+ current_field_broadcastable = field_defn.introspection? || field_defn.broadcastable?
60
+ case current_field_broadcastable
61
+ when nil
62
+ # If the value wasn't set, mix in the default value:
63
+ # - If the default is false and the current value is true, make it false
64
+ # - If the default is true and the current value is true, it stays true
65
+ # - If the default is false and the current value is false, keep it false
66
+ # - If the default is true and the current value is false, keep it false
67
+ @subscription_broadcastable = @subscription_broadcastable && @default_broadcastable
68
+ when false
69
+ # One non-broadcastable field is enough to make the whole subscription non-broadcastable
70
+ @subscription_broadcastable = false
71
+ when true
72
+ # Leave `@broadcastable_query` true if it's already true,
73
+ # but don't _set_ it to true if it was set to false by something else.
74
+ # Actually, just leave it!
75
+ else
76
+ raise ArgumentError, "Unexpected `.broadcastable?` value for #{field_defn.path}: #{current_field_broadcastable}"
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end