graphql 1.9.17 → 2.0.21

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 (416) 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 +45 -8
  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 +49 -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 +25 -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 +31 -2
  49. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -1
  50. data/lib/graphql/analysis/ast/query_complexity.rb +175 -68
  51. data/lib/graphql/analysis/ast/query_depth.rb +0 -1
  52. data/lib/graphql/analysis/ast/visitor.rb +54 -38
  53. data/lib/graphql/analysis/ast.rb +16 -16
  54. data/lib/graphql/analysis.rb +0 -7
  55. data/lib/graphql/backtrace/inspect_result.rb +0 -1
  56. data/lib/graphql/backtrace/table.rb +37 -16
  57. data/lib/graphql/backtrace/trace.rb +96 -0
  58. data/lib/graphql/backtrace/traced_error.rb +0 -1
  59. data/lib/graphql/backtrace/tracer.rb +39 -9
  60. data/lib/graphql/backtrace.rb +26 -18
  61. data/lib/graphql/dataloader/null_dataloader.rb +24 -0
  62. data/lib/graphql/dataloader/request.rb +19 -0
  63. data/lib/graphql/dataloader/request_all.rb +19 -0
  64. data/lib/graphql/dataloader/source.rb +164 -0
  65. data/lib/graphql/dataloader.rb +311 -0
  66. data/lib/graphql/date_encoding_error.rb +16 -0
  67. data/lib/graphql/deprecation.rb +9 -0
  68. data/lib/graphql/dig.rb +1 -1
  69. data/lib/graphql/execution/directive_checks.rb +2 -2
  70. data/lib/graphql/execution/errors.rb +77 -45
  71. data/lib/graphql/execution/interpreter/argument_value.rb +28 -0
  72. data/lib/graphql/execution/interpreter/arguments.rb +88 -0
  73. data/lib/graphql/execution/interpreter/arguments_cache.rb +104 -0
  74. data/lib/graphql/execution/interpreter/handles_raw_value.rb +18 -0
  75. data/lib/graphql/execution/interpreter/resolve.rb +62 -24
  76. data/lib/graphql/execution/interpreter/runtime.rb +830 -417
  77. data/lib/graphql/execution/interpreter.rb +206 -74
  78. data/lib/graphql/execution/lazy/lazy_method_map.rb +4 -0
  79. data/lib/graphql/execution/lazy.rb +11 -21
  80. data/lib/graphql/execution/lookahead.rb +65 -136
  81. data/lib/graphql/execution/multiplex.rb +6 -152
  82. data/lib/graphql/execution.rb +11 -4
  83. data/lib/graphql/filter.rb +8 -3
  84. data/lib/graphql/integer_decoding_error.rb +17 -0
  85. data/lib/graphql/integer_encoding_error.rb +18 -2
  86. data/lib/graphql/introspection/base_object.rb +2 -5
  87. data/lib/graphql/introspection/directive_location_enum.rb +2 -2
  88. data/lib/graphql/introspection/directive_type.rb +12 -6
  89. data/lib/graphql/introspection/dynamic_fields.rb +3 -8
  90. data/lib/graphql/introspection/entry_points.rb +5 -18
  91. data/lib/graphql/introspection/enum_value_type.rb +2 -2
  92. data/lib/graphql/introspection/field_type.rb +9 -5
  93. data/lib/graphql/introspection/input_value_type.rb +41 -11
  94. data/lib/graphql/introspection/introspection_query.rb +6 -92
  95. data/lib/graphql/introspection/schema_type.rb +12 -12
  96. data/lib/graphql/introspection/type_type.rb +34 -17
  97. data/lib/graphql/introspection.rb +100 -0
  98. data/lib/graphql/invalid_null_error.rb +18 -0
  99. data/lib/graphql/language/block_string.rb +20 -5
  100. data/lib/graphql/language/cache.rb +37 -0
  101. data/lib/graphql/language/definition_slice.rb +21 -10
  102. data/lib/graphql/language/document_from_schema_definition.rb +136 -78
  103. data/lib/graphql/language/lexer.rb +216 -1462
  104. data/lib/graphql/language/nodes.rb +129 -132
  105. data/lib/graphql/language/parser.rb +994 -932
  106. data/lib/graphql/language/parser.y +152 -120
  107. data/lib/graphql/language/printer.rb +48 -23
  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 +192 -84
  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 +77 -0
  114. data/lib/graphql/pagination/array_connection.rb +79 -0
  115. data/lib/graphql/pagination/connection.rb +253 -0
  116. data/lib/graphql/pagination/connections.rb +135 -0
  117. data/lib/graphql/pagination/mongoid_relation_connection.rb +25 -0
  118. data/lib/graphql/pagination/relation_connection.rb +228 -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 +205 -203
  123. data/lib/graphql/query/fingerprint.rb +26 -0
  124. data/lib/graphql/query/input_validation_result.rb +33 -7
  125. data/lib/graphql/query/null_context.rb +22 -9
  126. data/lib/graphql/query/validation_pipeline.rb +16 -38
  127. data/lib/graphql/query/variable_validation_error.rb +3 -3
  128. data/lib/graphql/query/variables.rb +39 -12
  129. data/lib/graphql/query.rb +95 -43
  130. data/lib/graphql/railtie.rb +6 -102
  131. data/lib/graphql/rake_task/validate.rb +4 -1
  132. data/lib/graphql/rake_task.rb +41 -10
  133. data/lib/graphql/relay/range_add.rb +17 -10
  134. data/lib/graphql/relay.rb +0 -15
  135. data/lib/graphql/rubocop/graphql/base_cop.rb +36 -0
  136. data/lib/graphql/rubocop/graphql/default_null_true.rb +43 -0
  137. data/lib/graphql/rubocop/graphql/default_required_true.rb +43 -0
  138. data/lib/graphql/rubocop.rb +4 -0
  139. data/lib/graphql/schema/addition.rb +245 -0
  140. data/lib/graphql/schema/argument.rb +285 -36
  141. data/lib/graphql/schema/base_64_encoder.rb +2 -0
  142. data/lib/graphql/schema/build_from_definition/resolve_map/default_resolve.rb +1 -1
  143. data/lib/graphql/schema/build_from_definition/resolve_map.rb +13 -5
  144. data/lib/graphql/schema/build_from_definition.rb +348 -205
  145. data/lib/graphql/schema/built_in_types.rb +5 -5
  146. data/lib/graphql/schema/directive/deprecated.rb +18 -0
  147. data/lib/graphql/schema/directive/feature.rb +1 -1
  148. data/lib/graphql/schema/directive/flagged.rb +57 -0
  149. data/lib/graphql/schema/directive/include.rb +2 -2
  150. data/lib/graphql/schema/directive/one_of.rb +12 -0
  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 +134 -15
  154. data/lib/graphql/schema/enum.rb +137 -39
  155. data/lib/graphql/schema/enum_value.rb +17 -23
  156. data/lib/graphql/schema/field/connection_extension.rb +50 -20
  157. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  158. data/lib/graphql/schema/field.rb +504 -331
  159. data/lib/graphql/schema/field_extension.rb +86 -2
  160. data/lib/graphql/schema/find_inherited_value.rb +12 -1
  161. data/lib/graphql/schema/finder.rb +16 -14
  162. data/lib/graphql/schema/input_object.rb +182 -60
  163. data/lib/graphql/schema/interface.rb +24 -49
  164. data/lib/graphql/schema/introspection_system.rb +103 -37
  165. data/lib/graphql/schema/late_bound_type.rb +9 -2
  166. data/lib/graphql/schema/list.rb +61 -3
  167. data/lib/graphql/schema/loader.rb +144 -96
  168. data/lib/graphql/schema/member/base_dsl_methods.rb +41 -37
  169. data/lib/graphql/schema/member/build_type.rb +24 -15
  170. data/lib/graphql/schema/member/has_arguments.rb +310 -26
  171. data/lib/graphql/schema/member/has_ast_node.rb +32 -0
  172. data/lib/graphql/schema/member/has_deprecation_reason.rb +24 -0
  173. data/lib/graphql/schema/member/has_directives.rb +118 -0
  174. data/lib/graphql/schema/member/has_fields.rb +166 -44
  175. data/lib/graphql/schema/member/has_interfaces.rb +129 -0
  176. data/lib/graphql/schema/member/has_unresolved_type_error.rb +15 -0
  177. data/lib/graphql/schema/member/has_validators.rb +57 -0
  178. data/lib/graphql/schema/member/relay_shortcuts.rb +47 -2
  179. data/lib/graphql/schema/member/type_system_helpers.rb +20 -3
  180. data/lib/graphql/schema/member/validates_input.rb +33 -0
  181. data/lib/graphql/schema/member.rb +11 -6
  182. data/lib/graphql/schema/mutation.rb +4 -9
  183. data/lib/graphql/schema/non_null.rb +34 -4
  184. data/lib/graphql/schema/object.rb +36 -60
  185. data/lib/graphql/schema/printer.rb +16 -35
  186. data/lib/graphql/schema/relay_classic_mutation.rb +91 -44
  187. data/lib/graphql/schema/resolver/has_payload_type.rb +51 -11
  188. data/lib/graphql/schema/resolver.rb +147 -94
  189. data/lib/graphql/schema/scalar.rb +40 -15
  190. data/lib/graphql/schema/subscription.rb +60 -31
  191. data/lib/graphql/schema/timeout.rb +45 -35
  192. data/lib/graphql/schema/type_expression.rb +21 -13
  193. data/lib/graphql/schema/type_membership.rb +23 -6
  194. data/lib/graphql/schema/union.rb +49 -15
  195. data/lib/graphql/schema/unique_within_type.rb +1 -2
  196. data/lib/graphql/schema/validator/allow_blank_validator.rb +29 -0
  197. data/lib/graphql/schema/validator/allow_null_validator.rb +26 -0
  198. data/lib/graphql/schema/validator/exclusion_validator.rb +33 -0
  199. data/lib/graphql/schema/validator/format_validator.rb +48 -0
  200. data/lib/graphql/schema/validator/inclusion_validator.rb +35 -0
  201. data/lib/graphql/schema/validator/length_validator.rb +59 -0
  202. data/lib/graphql/schema/validator/numericality_validator.rb +82 -0
  203. data/lib/graphql/schema/validator/required_validator.rb +82 -0
  204. data/lib/graphql/schema/validator.rb +171 -0
  205. data/lib/graphql/schema/warden.rb +213 -35
  206. data/lib/graphql/schema/wrapper.rb +0 -5
  207. data/lib/graphql/schema.rb +857 -884
  208. data/lib/graphql/static_validation/all_rules.rb +3 -0
  209. data/lib/graphql/static_validation/base_visitor.rb +21 -31
  210. data/lib/graphql/static_validation/definition_dependencies.rb +7 -2
  211. data/lib/graphql/static_validation/error.rb +3 -1
  212. data/lib/graphql/static_validation/literal_validator.rb +69 -26
  213. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +45 -83
  214. data/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb +22 -6
  215. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +35 -26
  216. data/lib/graphql/static_validation/rules/arguments_are_defined_error.rb +4 -2
  217. data/lib/graphql/static_validation/rules/directives_are_defined.rb +12 -6
  218. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +14 -14
  219. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -4
  220. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +15 -7
  221. data/lib/graphql/static_validation/rules/fields_will_merge.rb +96 -53
  222. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +25 -4
  223. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  224. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -2
  225. data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +30 -0
  226. data/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb +30 -0
  227. data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid.rb +66 -0
  228. data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid_error.rb +29 -0
  229. data/lib/graphql/static_validation/rules/query_root_exists.rb +17 -0
  230. data/lib/graphql/static_validation/rules/query_root_exists_error.rb +26 -0
  231. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +4 -2
  232. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +9 -10
  233. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +13 -7
  234. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +12 -13
  235. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +19 -14
  236. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
  237. data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +5 -3
  238. data/lib/graphql/static_validation/type_stack.rb +2 -2
  239. data/lib/graphql/static_validation/validation_context.rb +13 -3
  240. data/lib/graphql/static_validation/validation_timeout_error.rb +25 -0
  241. data/lib/graphql/static_validation/validator.rb +32 -20
  242. data/lib/graphql/static_validation.rb +1 -2
  243. data/lib/graphql/string_encoding_error.rb +13 -3
  244. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +129 -22
  245. data/lib/graphql/subscriptions/broadcast_analyzer.rb +81 -0
  246. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +58 -0
  247. data/lib/graphql/subscriptions/event.rb +84 -35
  248. data/lib/graphql/subscriptions/instrumentation.rb +0 -47
  249. data/lib/graphql/subscriptions/serialize.rb +53 -6
  250. data/lib/graphql/subscriptions.rb +137 -57
  251. data/lib/graphql/tracing/active_support_notifications_trace.rb +16 -0
  252. data/lib/graphql/tracing/active_support_notifications_tracing.rb +8 -17
  253. data/lib/graphql/tracing/appoptics_trace.rb +231 -0
  254. data/lib/graphql/tracing/appoptics_tracing.rb +173 -0
  255. data/lib/graphql/tracing/appsignal_trace.rb +77 -0
  256. data/lib/graphql/tracing/appsignal_tracing.rb +23 -0
  257. data/lib/graphql/tracing/data_dog_trace.rb +148 -0
  258. data/lib/graphql/tracing/data_dog_tracing.rb +34 -2
  259. data/lib/graphql/tracing/legacy_trace.rb +65 -0
  260. data/lib/graphql/tracing/new_relic_trace.rb +75 -0
  261. data/lib/graphql/tracing/new_relic_tracing.rb +9 -12
  262. data/lib/graphql/tracing/notifications_trace.rb +42 -0
  263. data/lib/graphql/tracing/notifications_tracing.rb +59 -0
  264. data/lib/graphql/tracing/platform_trace.rb +109 -0
  265. data/lib/graphql/tracing/platform_tracing.rb +76 -35
  266. data/lib/graphql/tracing/prometheus_trace.rb +89 -0
  267. data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +5 -2
  268. data/lib/graphql/tracing/prometheus_tracing.rb +11 -3
  269. data/lib/graphql/tracing/scout_trace.rb +72 -0
  270. data/lib/graphql/tracing/scout_tracing.rb +19 -0
  271. data/lib/graphql/tracing/statsd_trace.rb +56 -0
  272. data/lib/graphql/tracing/statsd_tracing.rb +42 -0
  273. data/lib/graphql/tracing/trace.rb +75 -0
  274. data/lib/graphql/tracing.rb +23 -67
  275. data/lib/graphql/type_kinds.rb +6 -3
  276. data/lib/graphql/types/big_int.rb +5 -1
  277. data/lib/graphql/types/int.rb +10 -3
  278. data/lib/graphql/types/iso_8601_date.rb +20 -9
  279. data/lib/graphql/types/iso_8601_date_time.rb +36 -10
  280. data/lib/graphql/types/relay/base_connection.rb +18 -90
  281. data/lib/graphql/types/relay/base_edge.rb +2 -34
  282. data/lib/graphql/types/relay/connection_behaviors.rb +176 -0
  283. data/lib/graphql/types/relay/edge_behaviors.rb +75 -0
  284. data/lib/graphql/types/relay/has_node_field.rb +41 -0
  285. data/lib/graphql/types/relay/has_nodes_field.rb +41 -0
  286. data/lib/graphql/types/relay/node.rb +2 -4
  287. data/lib/graphql/types/relay/node_behaviors.rb +25 -0
  288. data/lib/graphql/types/relay/page_info.rb +2 -14
  289. data/lib/graphql/types/relay/page_info_behaviors.rb +30 -0
  290. data/lib/graphql/types/relay.rb +10 -5
  291. data/lib/graphql/types/string.rb +8 -2
  292. data/lib/graphql/unauthorized_error.rb +2 -2
  293. data/lib/graphql/unresolved_type_error.rb +2 -2
  294. data/lib/graphql/version.rb +1 -1
  295. data/lib/graphql.rb +63 -65
  296. data/readme.md +3 -6
  297. metadata +127 -236
  298. data/lib/graphql/analysis/analyze_query.rb +0 -91
  299. data/lib/graphql/analysis/field_usage.rb +0 -45
  300. data/lib/graphql/analysis/max_query_complexity.rb +0 -26
  301. data/lib/graphql/analysis/max_query_depth.rb +0 -26
  302. data/lib/graphql/analysis/query_complexity.rb +0 -88
  303. data/lib/graphql/analysis/query_depth.rb +0 -43
  304. data/lib/graphql/analysis/reducer_state.rb +0 -48
  305. data/lib/graphql/argument.rb +0 -159
  306. data/lib/graphql/authorization.rb +0 -82
  307. data/lib/graphql/backwards_compatibility.rb +0 -60
  308. data/lib/graphql/base_type.rb +0 -226
  309. data/lib/graphql/boolean_type.rb +0 -2
  310. data/lib/graphql/compatibility/execution_specification/counter_schema.rb +0 -53
  311. data/lib/graphql/compatibility/execution_specification/specification_schema.rb +0 -200
  312. data/lib/graphql/compatibility/execution_specification.rb +0 -435
  313. data/lib/graphql/compatibility/lazy_execution_specification/lazy_schema.rb +0 -111
  314. data/lib/graphql/compatibility/lazy_execution_specification.rb +0 -213
  315. data/lib/graphql/compatibility/query_parser_specification/parse_error_specification.rb +0 -91
  316. data/lib/graphql/compatibility/query_parser_specification/query_assertions.rb +0 -79
  317. data/lib/graphql/compatibility/query_parser_specification.rb +0 -264
  318. data/lib/graphql/compatibility/schema_parser_specification.rb +0 -680
  319. data/lib/graphql/compatibility.rb +0 -5
  320. data/lib/graphql/define/assign_argument.rb +0 -12
  321. data/lib/graphql/define/assign_connection.rb +0 -13
  322. data/lib/graphql/define/assign_enum_value.rb +0 -18
  323. data/lib/graphql/define/assign_global_id_field.rb +0 -11
  324. data/lib/graphql/define/assign_mutation_function.rb +0 -34
  325. data/lib/graphql/define/assign_object_field.rb +0 -42
  326. data/lib/graphql/define/defined_object_proxy.rb +0 -50
  327. data/lib/graphql/define/instance_definable.rb +0 -300
  328. data/lib/graphql/define/no_definition_error.rb +0 -7
  329. data/lib/graphql/define/non_null_with_bang.rb +0 -16
  330. data/lib/graphql/define/type_definer.rb +0 -31
  331. data/lib/graphql/define.rb +0 -31
  332. data/lib/graphql/deprecated_dsl.rb +0 -42
  333. data/lib/graphql/directive/deprecated_directive.rb +0 -13
  334. data/lib/graphql/directive/include_directive.rb +0 -2
  335. data/lib/graphql/directive/skip_directive.rb +0 -2
  336. data/lib/graphql/directive.rb +0 -104
  337. data/lib/graphql/enum_type.rb +0 -193
  338. data/lib/graphql/execution/execute.rb +0 -326
  339. data/lib/graphql/execution/flatten.rb +0 -40
  340. data/lib/graphql/execution/instrumentation.rb +0 -92
  341. data/lib/graphql/execution/interpreter/hash_response.rb +0 -46
  342. data/lib/graphql/execution/lazy/resolve.rb +0 -91
  343. data/lib/graphql/execution/typecast.rb +0 -50
  344. data/lib/graphql/field/resolve.rb +0 -59
  345. data/lib/graphql/field.rb +0 -330
  346. data/lib/graphql/float_type.rb +0 -2
  347. data/lib/graphql/function.rb +0 -153
  348. data/lib/graphql/id_type.rb +0 -2
  349. data/lib/graphql/input_object_type.rb +0 -154
  350. data/lib/graphql/int_type.rb +0 -2
  351. data/lib/graphql/interface_type.rb +0 -86
  352. data/lib/graphql/internal_representation/document.rb +0 -27
  353. data/lib/graphql/internal_representation/node.rb +0 -206
  354. data/lib/graphql/internal_representation/print.rb +0 -51
  355. data/lib/graphql/internal_representation/rewrite.rb +0 -184
  356. data/lib/graphql/internal_representation/scope.rb +0 -88
  357. data/lib/graphql/internal_representation/visit.rb +0 -36
  358. data/lib/graphql/internal_representation.rb +0 -7
  359. data/lib/graphql/language/lexer.rl +0 -258
  360. data/lib/graphql/list_type.rb +0 -80
  361. data/lib/graphql/literal_validation_error.rb +0 -6
  362. data/lib/graphql/non_null_type.rb +0 -81
  363. data/lib/graphql/object_type.rb +0 -141
  364. data/lib/graphql/query/arguments.rb +0 -187
  365. data/lib/graphql/query/arguments_cache.rb +0 -25
  366. data/lib/graphql/query/executor.rb +0 -53
  367. data/lib/graphql/query/literal_input.rb +0 -116
  368. data/lib/graphql/query/serial_execution/field_resolution.rb +0 -92
  369. data/lib/graphql/query/serial_execution/operation_resolution.rb +0 -19
  370. data/lib/graphql/query/serial_execution/selection_resolution.rb +0 -23
  371. data/lib/graphql/query/serial_execution/value_resolution.rb +0 -87
  372. data/lib/graphql/query/serial_execution.rb +0 -39
  373. data/lib/graphql/relay/array_connection.rb +0 -85
  374. data/lib/graphql/relay/base_connection.rb +0 -172
  375. data/lib/graphql/relay/connection_instrumentation.rb +0 -54
  376. data/lib/graphql/relay/connection_resolve.rb +0 -43
  377. data/lib/graphql/relay/connection_type.rb +0 -40
  378. data/lib/graphql/relay/edge.rb +0 -27
  379. data/lib/graphql/relay/edge_type.rb +0 -18
  380. data/lib/graphql/relay/edges_instrumentation.rb +0 -40
  381. data/lib/graphql/relay/global_id_resolve.rb +0 -18
  382. data/lib/graphql/relay/mongo_relation_connection.rb +0 -50
  383. data/lib/graphql/relay/mutation/instrumentation.rb +0 -23
  384. data/lib/graphql/relay/mutation/resolve.rb +0 -56
  385. data/lib/graphql/relay/mutation/result.rb +0 -38
  386. data/lib/graphql/relay/mutation.rb +0 -190
  387. data/lib/graphql/relay/node.rb +0 -36
  388. data/lib/graphql/relay/page_info.rb +0 -7
  389. data/lib/graphql/relay/relation_connection.rb +0 -190
  390. data/lib/graphql/relay/type_extensions.rb +0 -30
  391. data/lib/graphql/scalar_type.rb +0 -133
  392. data/lib/graphql/schema/catchall_middleware.rb +0 -35
  393. data/lib/graphql/schema/default_parse_error.rb +0 -10
  394. data/lib/graphql/schema/default_type_error.rb +0 -15
  395. data/lib/graphql/schema/member/accepts_definition.rb +0 -152
  396. data/lib/graphql/schema/member/cached_graphql_definition.rb +0 -26
  397. data/lib/graphql/schema/member/instrumentation.rb +0 -132
  398. data/lib/graphql/schema/middleware_chain.rb +0 -82
  399. data/lib/graphql/schema/possible_types.rb +0 -39
  400. data/lib/graphql/schema/rescue_middleware.rb +0 -60
  401. data/lib/graphql/schema/timeout_middleware.rb +0 -86
  402. data/lib/graphql/schema/traversal.rb +0 -228
  403. data/lib/graphql/schema/validation.rb +0 -303
  404. data/lib/graphql/static_validation/default_visitor.rb +0 -15
  405. data/lib/graphql/static_validation/no_validate_visitor.rb +0 -10
  406. data/lib/graphql/string_type.rb +0 -2
  407. data/lib/graphql/subscriptions/subscription_root.rb +0 -66
  408. data/lib/graphql/tracing/skylight_tracing.rb +0 -62
  409. data/lib/graphql/types/relay/base_field.rb +0 -22
  410. data/lib/graphql/types/relay/base_interface.rb +0 -29
  411. data/lib/graphql/types/relay/base_object.rb +0 -26
  412. data/lib/graphql/types/relay/node_field.rb +0 -43
  413. data/lib/graphql/types/relay/nodes_field.rb +0 -45
  414. data/lib/graphql/union_type.rb +0 -128
  415. data/lib/graphql/upgrader/member.rb +0 -936
  416. data/lib/graphql/upgrader/schema.rb +0 -37
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
- # test_via: ../subscriptions.rb
3
2
  require "set"
4
3
  module GraphQL
5
4
  class Subscriptions
@@ -9,6 +8,9 @@ module GraphQL
9
8
  GLOBALID_KEY = "__gid__"
10
9
  SYMBOL_KEY = "__sym__"
11
10
  SYMBOL_KEYS_KEY = "__sym_keys__"
11
+ TIMESTAMP_KEY = "__timestamp__"
12
+ TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S.%N%z" # eg '2020-01-01 23:59:59.123456789+05:00'
13
+ OPEN_STRUCT_KEY = "__ostruct__"
12
14
 
13
15
  module_function
14
16
 
@@ -53,12 +55,40 @@ module GraphQL
53
55
  # @return [Object] An object that load Global::Identification recursive
54
56
  def load_value(value)
55
57
  if value.is_a?(Array)
56
- value.map{|item| load_value(item)}
58
+ is_gids = (v1 = value[0]).is_a?(Hash) && v1.size == 1 && v1[GLOBALID_KEY]
59
+ if is_gids
60
+ # Assume it's an array of global IDs
61
+ ids = value.map { |v| v[GLOBALID_KEY] }
62
+ GlobalID::Locator.locate_many(ids)
63
+ else
64
+ value.map { |item| load_value(item) }
65
+ end
57
66
  elsif value.is_a?(Hash)
58
- if value.size == 1 && value.key?(GLOBALID_KEY)
59
- GlobalID::Locator.locate(value[GLOBALID_KEY])
60
- elsif value.size == 1 && value.key?(SYMBOL_KEY)
61
- value[SYMBOL_KEY].to_sym
67
+ if value.size == 1
68
+ case value.keys.first # there's only 1 key
69
+ when GLOBALID_KEY
70
+ GlobalID::Locator.locate(value[GLOBALID_KEY])
71
+ when SYMBOL_KEY
72
+ value[SYMBOL_KEY].to_sym
73
+ when TIMESTAMP_KEY
74
+ timestamp_class_name, *timestamp_args = value[TIMESTAMP_KEY]
75
+ timestamp_class = Object.const_get(timestamp_class_name)
76
+ if defined?(ActiveSupport::TimeWithZone) && timestamp_class <= ActiveSupport::TimeWithZone
77
+ zone_name, timestamp_s = timestamp_args
78
+ zone = ActiveSupport::TimeZone[zone_name]
79
+ raise "Zone #{zone_name} not found, unable to deserialize" unless zone
80
+ zone.strptime(timestamp_s, TIMESTAMP_FORMAT)
81
+ else
82
+ timestamp_s = timestamp_args.first
83
+ timestamp_class.strptime(timestamp_s, TIMESTAMP_FORMAT)
84
+ end
85
+ when OPEN_STRUCT_KEY
86
+ ostruct_values = load_value(value[OPEN_STRUCT_KEY])
87
+ OpenStruct.new(ostruct_values)
88
+ else
89
+ key = value.keys.first
90
+ { key => load_value(value[key]) }
91
+ end
62
92
  else
63
93
  loaded_h = {}
64
94
  sym_keys = value.fetch(SYMBOL_KEYS_KEY, [])
@@ -101,6 +131,23 @@ module GraphQL
101
131
  { SYMBOL_KEY => obj.to_s }
102
132
  elsif obj.respond_to?(:to_gid_param)
103
133
  {GLOBALID_KEY => obj.to_gid_param}
134
+ elsif defined?(ActiveSupport::TimeWithZone) && obj.is_a?(ActiveSupport::TimeWithZone) && obj.class.name != Time.name
135
+ # This handles a case where Rails prior to 7 would
136
+ # make the class ActiveSupport::TimeWithZone return "Time" for
137
+ # its name. In Rails 7, it will now return "ActiveSupport::TimeWithZone",
138
+ # which happens to be incompatible with expectations we have
139
+ # with what a Time class supports ( notably, strptime in `load_value` ).
140
+ #
141
+ # This now passes along the name of the zone, such that a future deserialization
142
+ # of this string will use the correct time zone from the ActiveSupport TimeZone
143
+ # list to produce the time.
144
+ #
145
+ { TIMESTAMP_KEY => [obj.class.name, obj.time_zone.name, obj.strftime(TIMESTAMP_FORMAT)] }
146
+ elsif obj.is_a?(Date) || obj.is_a?(Time)
147
+ # DateTime extends Date; for TimeWithZone, call `.utc` first.
148
+ { TIMESTAMP_KEY => [obj.class.name, obj.strftime(TIMESTAMP_FORMAT)] }
149
+ elsif obj.is_a?(OpenStruct)
150
+ { OPEN_STRUCT_KEY => dump_value(obj.to_h) }
104
151
  else
105
152
  obj
106
153
  end
@@ -1,12 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
  require "securerandom"
3
+ require "graphql/subscriptions/broadcast_analyzer"
3
4
  require "graphql/subscriptions/event"
4
5
  require "graphql/subscriptions/instrumentation"
5
6
  require "graphql/subscriptions/serialize"
6
- if defined?(ActionCable)
7
- require "graphql/subscriptions/action_cable_subscriptions"
8
- end
9
- require "graphql/subscriptions/subscription_root"
7
+ require "graphql/subscriptions/action_cable_subscriptions"
8
+ require "graphql/subscriptions/default_subscription_resolve_extension"
10
9
 
11
10
  module GraphQL
12
11
  class Subscriptions
@@ -16,39 +15,64 @@ module GraphQL
16
15
  class InvalidTriggerError < GraphQL::Error
17
16
  end
18
17
 
18
+ # Raised when either:
19
+ # - An initial subscription didn't have a value for `context[subscription_scope]`
20
+ # - Or, an update didn't pass `.trigger(..., scope:)`
21
+ # When raised, the initial subscription or update fails completely.
22
+ class SubscriptionScopeMissingError < GraphQL::Error
23
+ end
24
+
19
25
  # @see {Subscriptions#initialize} for options, concrete implementations may add options.
20
26
  def self.use(defn, options = {})
21
- schema = defn.target
22
- options[:schema] = schema
23
- schema.subscriptions = self.new(options)
27
+ schema = defn.is_a?(Class) ? defn : defn.target
28
+
29
+ if schema.subscriptions(inherited: false)
30
+ raise ArgumentError, "Can't reinstall subscriptions. #{schema} is using #{schema.subscriptions}, can't also add #{self}"
31
+ end
32
+
24
33
  instrumentation = Subscriptions::Instrumentation.new(schema: schema)
25
- defn.instrument(:field, instrumentation)
26
34
  defn.instrument(:query, instrumentation)
35
+ options[:schema] = schema
36
+ schema.subscriptions = self.new(**options)
37
+ schema.add_subscription_extension_if_necessary
27
38
  nil
28
39
  end
29
40
 
30
41
  # @param schema [Class] the GraphQL schema this manager belongs to
31
- def initialize(schema:, **rest)
42
+ # @param validate_update [Boolean] If false, then validation is skipped when executing updates
43
+ def initialize(schema:, validate_update: true, broadcast: false, default_broadcastable: false, **rest)
44
+ if broadcast
45
+ schema.query_analyzer(Subscriptions::BroadcastAnalyzer)
46
+ end
47
+ @default_broadcastable = default_broadcastable
32
48
  @schema = schema
49
+ @validate_update = validate_update
33
50
  end
34
51
 
52
+ # @return [Boolean] Used when fields don't have `broadcastable:` explicitly set
53
+ attr_reader :default_broadcastable
54
+
35
55
  # Fetch subscriptions matching this field + arguments pair
36
56
  # And pass them off to the queue.
37
57
  # @param event_name [String]
38
58
  # @param args [Hash<String, Symbol => Object]
39
59
  # @param object [Object]
40
60
  # @param scope [Symbol, String]
61
+ # @param context [Hash]
41
62
  # @return [void]
42
- def trigger(event_name, args, object, scope: nil)
63
+ def trigger(event_name, args, object, scope: nil, context: {})
64
+ # Make something as context-like as possible, even though there isn't a current query:
65
+ dummy_query = GraphQL::Query.new(@schema, "", validate: false, context: context)
66
+ context = dummy_query.context
43
67
  event_name = event_name.to_s
44
68
 
45
69
  # Try with the verbatim input first:
46
- field = @schema.get_field(@schema.subscription, event_name)
70
+ field = @schema.get_field(@schema.subscription, event_name, context)
47
71
 
48
72
  if field.nil?
49
73
  # And if it wasn't found, normalize it:
50
74
  normalized_event_name = normalize_name(event_name)
51
- field = @schema.get_field(@schema.subscription, normalized_event_name)
75
+ field = @schema.get_field(@schema.subscription, normalized_event_name, context)
52
76
  if field.nil?
53
77
  raise InvalidTriggerError, "No subscription matching trigger: #{event_name} (looked for #{@schema.subscription.graphql_name}.#{normalized_event_name})"
54
78
  end
@@ -58,13 +82,15 @@ module GraphQL
58
82
  end
59
83
 
60
84
  # Normalize symbol-keyed args to strings, try camelizing them
61
- normalized_args = normalize_arguments(normalized_event_name, field, args)
85
+ # Should this accept a real context somehow?
86
+ normalized_args = normalize_arguments(normalized_event_name, field, args, GraphQL::Query::NullContext)
62
87
 
63
88
  event = Subscriptions::Event.new(
64
89
  name: normalized_event_name,
65
90
  arguments: normalized_args,
66
91
  field: field,
67
92
  scope: scope,
93
+ context: context,
68
94
  )
69
95
  execute_all(event, object)
70
96
  end
@@ -72,40 +98,73 @@ module GraphQL
72
98
  # `event` was triggered on `object`, and `subscription_id` was subscribed,
73
99
  # so it should be updated.
74
100
  #
75
- # Load `subscription_id`'s GraphQL data, re-evaluate the query, and deliver the result.
76
- #
77
- # This is where a queue may be inserted to push updates in the background.
101
+ # Load `subscription_id`'s GraphQL data, re-evaluate the query and return the result.
78
102
  #
79
103
  # @param subscription_id [String]
80
104
  # @param event [GraphQL::Subscriptions::Event] The event which was triggered
81
105
  # @param object [Object] The value for the subscription field
82
- # @return [void]
83
- def execute(subscription_id, event, object)
106
+ # @return [GraphQL::Query::Result]
107
+ def execute_update(subscription_id, event, object)
84
108
  # Lookup the saved data for this subscription
85
109
  query_data = read_subscription(subscription_id)
110
+ if query_data.nil?
111
+ delete_subscription(subscription_id)
112
+ return nil
113
+ end
114
+
86
115
  # Fetch the required keys from the saved data
87
116
  query_string = query_data.fetch(:query_string)
88
117
  variables = query_data.fetch(:variables)
89
118
  context = query_data.fetch(:context)
90
119
  operation_name = query_data.fetch(:operation_name)
91
- # Re-evaluate the saved query
92
- result = @schema.execute(
93
- {
94
- query: query_string,
95
- context: context,
96
- subscription_topic: event.topic,
97
- operation_name: operation_name,
98
- variables: variables,
99
- root_value: object,
100
- }
101
- )
102
- deliver(subscription_id, result)
103
- rescue GraphQL::Schema::Subscription::NoUpdateError
104
- # This update was skipped in user code; do nothing.
105
- rescue GraphQL::Schema::Subscription::UnsubscribedError
106
- # `unsubscribe` was called, clean up on our side
107
- # TODO also send `{more: false}` to client?
108
- delete_subscription(subscription_id)
120
+ execute_options = {
121
+ query: query_string,
122
+ context: context,
123
+ subscription_topic: event.topic,
124
+ operation_name: operation_name,
125
+ variables: variables,
126
+ root_value: object,
127
+ }
128
+
129
+ # merge event's and query's context together
130
+ context.merge!(event.context) unless event.context.nil? || context.nil?
131
+
132
+ execute_options[:validate] = validate_update?(**execute_options)
133
+ result = @schema.execute(**execute_options)
134
+ subscriptions_context = result.context.namespace(:subscriptions)
135
+ if subscriptions_context[:no_update]
136
+ result = nil
137
+ end
138
+
139
+ unsubscribed = subscriptions_context[:unsubscribed]
140
+
141
+ if unsubscribed
142
+ # `unsubscribe` was called, clean up on our side
143
+ # TODO also send `{more: false}` to client?
144
+ delete_subscription(subscription_id)
145
+ result = nil
146
+ end
147
+
148
+ result
149
+ end
150
+
151
+ # Define this method to customize whether to validate
152
+ # this subscription when executing an update.
153
+ #
154
+ # @return [Boolean] defaults to `true`, or false if `validate: false` is provided.
155
+ def validate_update?(query:, context:, root_value:, subscription_topic:, operation_name:, variables:)
156
+ @validate_update
157
+ end
158
+
159
+ # Run the update query for this subscription and deliver it
160
+ # @see {#execute_update}
161
+ # @see {#deliver}
162
+ # @return [void]
163
+ def execute(subscription_id, event, object)
164
+ res = execute_update(subscription_id, event, object)
165
+ if !res.nil?
166
+ deliver(subscription_id, res)
167
+ end
109
168
  end
110
169
 
111
170
  # Event `event` occurred on `object`,
@@ -114,16 +173,6 @@ module GraphQL
114
173
  # @param object [Object]
115
174
  # @return [void]
116
175
  def execute_all(event, object)
117
- each_subscription_id(event) do |subscription_id|
118
- execute(subscription_id, event, object)
119
- end
120
- end
121
-
122
- # Get each `subscription_id` subscribed to `event.topic` and yield them
123
- # @param event [GraphQL::Subscriptions::Event]
124
- # @yieldparam subscription_id [String]
125
- # @return [void]
126
- def each_subscription_id(event)
127
176
  raise GraphQL::RequiredImplementationMissingError
128
177
  end
129
178
 
@@ -178,6 +227,16 @@ module GraphQL
178
227
  Schema::Member::BuildType.camelize(event_or_arg_name.to_s)
179
228
  end
180
229
 
230
+ # @return [Boolean] if true, then a query like this one would be broadcasted
231
+ def broadcastable?(query_str, **query_options)
232
+ query = GraphQL::Query.new(@schema, query_str, **query_options)
233
+ if !query.valid?
234
+ raise "Invalid query: #{query.validation_errors.map(&:to_h).inspect}"
235
+ end
236
+ GraphQL::Analysis::AST.analyze_query(query, @schema.query_analyzers)
237
+ query.context.namespace(:subscriptions)[:subscription_broadcastable]
238
+ end
239
+
181
240
  private
182
241
 
183
242
  # Recursively normalize `args` as belonging to `arg_owner`:
@@ -186,32 +245,53 @@ module GraphQL
186
245
  # @param arg_owner [GraphQL::Field, GraphQL::BaseType]
187
246
  # @param args [Hash, Array, Any] some GraphQL input value to coerce as `arg_owner`
188
247
  # @return [Any] normalized arguments value
189
- def normalize_arguments(event_name, arg_owner, args)
248
+ def normalize_arguments(event_name, arg_owner, args, context)
190
249
  case arg_owner
191
- when GraphQL::Field, GraphQL::InputObjectType
250
+ when GraphQL::Schema::Field, Class
251
+ if arg_owner.is_a?(Class) && !arg_owner.kind.input_object?
252
+ # it's a type, but not an input object
253
+ return args
254
+ end
192
255
  normalized_args = {}
193
256
  missing_arg_names = []
194
257
  args.each do |k, v|
195
258
  arg_name = k.to_s
196
- arg_defn = arg_owner.arguments[arg_name]
259
+ arg_defn = arg_owner.get_argument(arg_name, context)
197
260
  if arg_defn
198
261
  normalized_arg_name = arg_name
199
262
  else
200
263
  normalized_arg_name = normalize_name(arg_name)
201
- arg_defn = arg_owner.arguments[normalized_arg_name]
264
+ arg_defn = arg_owner.get_argument(normalized_arg_name, context)
202
265
  end
203
266
 
204
267
  if arg_defn
205
- normalized_args[normalized_arg_name] = normalize_arguments(event_name, arg_defn.type, v)
268
+ if arg_defn.loads
269
+ normalized_arg_name = arg_defn.keyword.to_s
270
+ end
271
+ normalized = normalize_arguments(event_name, arg_defn.type, v, context)
272
+ normalized_args[normalized_arg_name] = normalized
206
273
  else
207
274
  # Couldn't find a matching argument definition
208
275
  missing_arg_names << arg_name
209
276
  end
210
277
  end
211
278
 
279
+ # Backfill default values so that trigger arguments
280
+ # match query arguments.
281
+ arg_owner.arguments(context).each do |_name, arg_defn|
282
+ if arg_defn.default_value? && !normalized_args.key?(arg_defn.name)
283
+ default_value = arg_defn.default_value
284
+ # We don't have an underlying "object" here, so it can't call methods.
285
+ # This is broken.
286
+ normalized_args[arg_defn.name] = arg_defn.prepare_value(nil, default_value, context: context)
287
+ end
288
+ end
289
+
212
290
  if missing_arg_names.any?
213
- arg_owner_name = if arg_owner.is_a?(GraphQL::Field)
214
- "Subscription.#{arg_owner.name}"
291
+ arg_owner_name = if arg_owner.is_a?(GraphQL::Schema::Field)
292
+ arg_owner.path
293
+ elsif arg_owner.is_a?(Class)
294
+ arg_owner.graphql_name
215
295
  else
216
296
  arg_owner.to_s
217
297
  end
@@ -219,10 +299,10 @@ module GraphQL
219
299
  end
220
300
 
221
301
  normalized_args
222
- when GraphQL::ListType
223
- args.map { |a| normalize_arguments(event_name, arg_owner.of_type, a) }
224
- when GraphQL::NonNullType
225
- normalize_arguments(event_name, arg_owner.of_type, args)
302
+ when GraphQL::Schema::List
303
+ args.map { |a| normalize_arguments(event_name, arg_owner.of_type, a, context) }
304
+ when GraphQL::Schema::NonNull
305
+ normalize_arguments(event_name, arg_owner.of_type, args, context)
226
306
  else
227
307
  args
228
308
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql/tracing/notifications_trace'
4
+
5
+ module GraphQL
6
+ module Tracing
7
+ # This implementation forwards events to ActiveSupport::Notifications
8
+ # with a `graphql` suffix.
9
+ module ActiveSupportNotificationsTrace
10
+ include NotificationsTrace
11
+ def initialize(engine: ActiveSupport::Notifications, **rest)
12
+ super
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,29 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'graphql/tracing/notifications_tracing'
4
+
3
5
  module GraphQL
4
6
  module Tracing
5
7
  # This implementation forwards events to ActiveSupport::Notifications
6
- # with a `graphql.` prefix.
8
+ # with a `graphql` suffix.
7
9
  #
10
+ # @see KEYS for event names
8
11
  module ActiveSupportNotificationsTracing
9
12
  # A cache of frequently-used keys to avoid needless string allocations
10
- KEYS = {
11
- "lex" => "graphql.lex",
12
- "parse" => "graphql.parse",
13
- "validate" => "graphql.validate",
14
- "analyze_multiplex" => "graphql.analyze_multiplex",
15
- "analyze_query" => "graphql.analyze_query",
16
- "execute_query" => "graphql.execute_query",
17
- "execute_query_lazy" => "graphql.execute_query_lazy",
18
- "execute_field" => "graphql.execute_field",
19
- "execute_field_lazy" => "graphql.execute_field_lazy",
20
- }
13
+ KEYS = NotificationsTracing::KEYS
14
+ NOTIFICATIONS_ENGINE = NotificationsTracing.new(ActiveSupport::Notifications) if defined?(ActiveSupport::Notifications)
21
15
 
22
- def self.trace(key, metadata)
23
- prefixed_key = KEYS[key] || "graphql.#{key}"
24
- ActiveSupport::Notifications.instrument(prefixed_key, metadata) do
25
- yield
26
- end
16
+ def self.trace(key, metadata, &blk)
17
+ NOTIFICATIONS_ENGINE.trace(key, metadata, &blk)
27
18
  end
28
19
  end
29
20
  end
@@ -0,0 +1,231 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Tracing
5
+
6
+ # This class uses the AppopticsAPM SDK from the appoptics_apm gem to create
7
+ # traces for GraphQL.
8
+ #
9
+ # There are 4 configurations available. They can be set in the
10
+ # appoptics_apm config file or in code. Please see:
11
+ # {https://docs.appoptics.com/kb/apm_tracing/ruby/configure}
12
+ #
13
+ # AppOpticsAPM::Config[:graphql][:enabled] = true|false
14
+ # AppOpticsAPM::Config[:graphql][:transaction_name] = true|false
15
+ # AppOpticsAPM::Config[:graphql][:sanitize_query] = true|false
16
+ # AppOpticsAPM::Config[:graphql][:remove_comments] = true|false
17
+ module AppOpticsTrace
18
+ # These GraphQL events will show up as 'graphql.prep' spans
19
+ PREP_KEYS = ['lex', 'parse', 'validate', 'analyze_query', 'analyze_multiplex'].freeze
20
+ # These GraphQL events will show up as 'graphql.execute' spans
21
+ EXEC_KEYS = ['execute_multiplex', 'execute_query', 'execute_query_lazy'].freeze
22
+
23
+ # During auto-instrumentation this version of AppOpticsTracing is compared
24
+ # with the version provided in the appoptics_apm gem, so that the newer
25
+ # version of the class can be used
26
+
27
+ def self.version
28
+ Gem::Version.new('1.0.0')
29
+ end
30
+
31
+ [
32
+ 'lex',
33
+ 'parse',
34
+ 'validate',
35
+ 'analyze_query',
36
+ 'analyze_multiplex',
37
+ 'execute_multiplex',
38
+ 'execute_query',
39
+ 'execute_query_lazy',
40
+ ].each do |trace_method|
41
+ module_eval <<-RUBY, __FILE__, __LINE__
42
+ def #{trace_method}(**data)
43
+ return super if !defined?(AppOpticsAPM) || gql_config[:enabled] == false
44
+ layer = span_name("#{trace_method}")
45
+ kvs = metadata(data, layer)
46
+ kvs[:Key] = "#{trace_method}" if (PREP_KEYS + EXEC_KEYS).include?("#{trace_method}")
47
+
48
+ transaction_name(kvs[:InboundQuery]) if kvs[:InboundQuery] && layer == 'graphql.execute'
49
+
50
+ ::AppOpticsAPM::SDK.trace(layer, kvs) do
51
+ kvs.clear # we don't have to send them twice
52
+ super
53
+ end
54
+ end
55
+ RUBY
56
+ end
57
+
58
+ def platform_execute_field(platform_key, data)
59
+ return super if !defined?(AppOpticsAPM) || gql_config[:enabled] == false
60
+ layer = platform_key
61
+ kvs = metadata(data, layer)
62
+
63
+ ::AppOpticsAPM::SDK.trace(layer, kvs) do
64
+ kvs.clear # we don't have to send them twice
65
+ yield
66
+ end
67
+ end
68
+
69
+ def authorized(**data)
70
+ return super if !defined?(AppOpticsAPM) || gql_config[:enabled] == false
71
+ layer = @platform_authorized_key_cache[data[:type]]
72
+ kvs = metadata(data, layer)
73
+
74
+ ::AppOpticsAPM::SDK.trace(layer, kvs) do
75
+ kvs.clear # we don't have to send them twice
76
+ super
77
+ end
78
+ end
79
+
80
+ def authorized_lazy(**data)
81
+ return super if !defined?(AppOpticsAPM) || gql_config[:enabled] == false
82
+ layer = @platform_authorized_key_cache[data[:type]]
83
+ kvs = metadata(data, layer)
84
+
85
+ ::AppOpticsAPM::SDK.trace(layer, kvs) do
86
+ kvs.clear # we don't have to send them twice
87
+ super
88
+ end
89
+ end
90
+
91
+ def resolve_type(**data)
92
+ return super if !defined?(AppOpticsAPM) || gql_config[:enabled] == false
93
+ layer = @platform_resolve_type_key_cache[data[:type]]
94
+ kvs = metadata(data, layer)
95
+
96
+ ::AppOpticsAPM::SDK.trace(layer, kvs) do
97
+ kvs.clear # we don't have to send them twice
98
+ super
99
+ end
100
+ end
101
+
102
+ def resolve_type_lazy(**data)
103
+ return super if !defined?(AppOpticsAPM) || gql_config[:enabled] == false
104
+ layer = @platform_resolve_type_key_cache[data[:type]]
105
+ kvs = metadata(data, layer)
106
+
107
+ ::AppOpticsAPM::SDK.trace(layer, kvs) do
108
+ kvs.clear # we don't have to send them twice
109
+ super
110
+ end
111
+ end
112
+
113
+ include PlatformTrace
114
+
115
+ def platform_field_key(field)
116
+ "graphql.#{field.owner.graphql_name}.#{field.graphql_name}"
117
+ end
118
+
119
+ def platform_authorized_key(type)
120
+ "graphql.authorized.#{type.graphql_name}"
121
+ end
122
+
123
+ def platform_resolve_type_key(type)
124
+ "graphql.resolve_type.#{type.graphql_name}"
125
+ end
126
+
127
+ private
128
+
129
+ def gql_config
130
+ ::AppOpticsAPM::Config[:graphql] ||= {}
131
+ end
132
+
133
+ def transaction_name(query)
134
+ return if gql_config[:transaction_name] == false ||
135
+ ::AppOpticsAPM::SDK.get_transaction_name
136
+
137
+ split_query = query.strip.split(/\W+/, 3)
138
+ split_query[0] = 'query' if split_query[0].empty?
139
+ name = "graphql.#{split_query[0..1].join('.')}"
140
+
141
+ ::AppOpticsAPM::SDK.set_transaction_name(name)
142
+ end
143
+
144
+ def multiplex_transaction_name(names)
145
+ return if gql_config[:transaction_name] == false ||
146
+ ::AppOpticsAPM::SDK.get_transaction_name
147
+
148
+ name = "graphql.multiplex.#{names.join('.')}"
149
+ name = "#{name[0..251]}..." if name.length > 254
150
+
151
+ ::AppOpticsAPM::SDK.set_transaction_name(name)
152
+ end
153
+
154
+ def span_name(key)
155
+ return 'graphql.prep' if PREP_KEYS.include?(key)
156
+ return 'graphql.execute' if EXEC_KEYS.include?(key)
157
+
158
+ key[/^graphql\./] ? key : "graphql.#{key}"
159
+ end
160
+
161
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
162
+ def metadata(data, layer)
163
+ data.keys.map do |key|
164
+ case key
165
+ when :context
166
+ graphql_context(data[key], layer)
167
+ when :query
168
+ graphql_query(data[key])
169
+ when :query_string
170
+ graphql_query_string(data[key])
171
+ when :multiplex
172
+ graphql_multiplex(data[key])
173
+ when :path
174
+ [key, data[key].join(".")]
175
+ else
176
+ [key, data[key]]
177
+ end
178
+ end.flatten(2).each_slice(2).to_h.merge(Spec: 'graphql')
179
+ end
180
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
181
+
182
+ def graphql_context(context, layer)
183
+ context.errors && context.errors.each do |err|
184
+ AppOpticsAPM::API.log_exception(layer, err)
185
+ end
186
+
187
+ [[:Path, context.path.join('.')]]
188
+ end
189
+
190
+ def graphql_query(query)
191
+ return [] unless query
192
+
193
+ query_string = query.query_string
194
+ query_string = remove_comments(query_string) if gql_config[:remove_comments] != false
195
+ query_string = sanitize(query_string) if gql_config[:sanitize_query] != false
196
+
197
+ [[:InboundQuery, query_string],
198
+ [:Operation, query.selected_operation_name]]
199
+ end
200
+
201
+ def graphql_query_string(query_string)
202
+ query_string = remove_comments(query_string) if gql_config[:remove_comments] != false
203
+ query_string = sanitize(query_string) if gql_config[:sanitize_query] != false
204
+
205
+ [:InboundQuery, query_string]
206
+ end
207
+
208
+ def graphql_multiplex(data)
209
+ names = data.queries.map(&:operations).map(&:keys).flatten.compact
210
+ multiplex_transaction_name(names) if names.size > 1
211
+
212
+ [:Operations, names.join(', ')]
213
+ end
214
+
215
+ def sanitize(query)
216
+ return unless query
217
+
218
+ # remove arguments
219
+ query.gsub(/"[^"]*"/, '"?"') # strings
220
+ .gsub(/-?[0-9]*\.?[0-9]+e?[0-9]*/, '?') # ints + floats
221
+ .gsub(/\[[^\]]*\]/, '[?]') # arrays
222
+ end
223
+
224
+ def remove_comments(query)
225
+ return unless query
226
+
227
+ query.gsub(/#[^\n\r]*/, '')
228
+ end
229
+ end
230
+ end
231
+ end