graphql 2.0.32 → 2.5.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (308) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/detailed_trace_generator.rb +77 -0
  3. data/lib/generators/graphql/install/mutation_root_generator.rb +2 -2
  4. data/lib/generators/graphql/install/templates/base_mutation.erb +2 -0
  5. data/lib/generators/graphql/install/templates/mutation_type.erb +2 -0
  6. data/lib/generators/graphql/install_generator.rb +49 -0
  7. data/lib/generators/graphql/orm_mutations_base.rb +1 -1
  8. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  9. data/lib/generators/graphql/templates/base_connection.erb +2 -0
  10. data/lib/generators/graphql/templates/base_edge.erb +2 -0
  11. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  12. data/lib/generators/graphql/templates/base_field.erb +2 -0
  13. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  14. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  15. data/lib/generators/graphql/templates/base_object.erb +2 -0
  16. data/lib/generators/graphql/templates/base_resolver.erb +8 -0
  17. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  18. data/lib/generators/graphql/templates/base_union.erb +2 -0
  19. data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
  20. data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
  21. data/lib/generators/graphql/templates/loader.erb +2 -0
  22. data/lib/generators/graphql/templates/mutation.erb +2 -0
  23. data/lib/generators/graphql/templates/node_type.erb +2 -0
  24. data/lib/generators/graphql/templates/query_type.erb +2 -0
  25. data/lib/generators/graphql/templates/schema.erb +5 -0
  26. data/lib/generators/graphql/type_generator.rb +1 -1
  27. data/lib/graphql/analysis/analyzer.rb +90 -0
  28. data/lib/graphql/analysis/field_usage.rb +82 -0
  29. data/lib/graphql/analysis/max_query_complexity.rb +20 -0
  30. data/lib/graphql/analysis/max_query_depth.rb +20 -0
  31. data/lib/graphql/analysis/query_complexity.rb +263 -0
  32. data/lib/graphql/analysis/query_depth.rb +58 -0
  33. data/lib/graphql/analysis/visitor.rb +280 -0
  34. data/lib/graphql/analysis.rb +95 -1
  35. data/lib/graphql/autoload.rb +38 -0
  36. data/lib/graphql/backtrace/table.rb +118 -55
  37. data/lib/graphql/backtrace.rb +1 -19
  38. data/lib/graphql/coercion_error.rb +1 -9
  39. data/lib/graphql/current.rb +57 -0
  40. data/lib/graphql/dashboard/application_controller.rb +41 -0
  41. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  42. data/lib/graphql/dashboard/installable.rb +22 -0
  43. data/lib/graphql/dashboard/landings_controller.rb +9 -0
  44. data/lib/graphql/dashboard/limiters.rb +93 -0
  45. data/lib/graphql/dashboard/operation_store.rb +199 -0
  46. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  47. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  48. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  49. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  50. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  51. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  52. data/lib/graphql/dashboard/statics/icon.png +0 -0
  53. data/lib/graphql/dashboard/statics_controller.rb +31 -0
  54. data/lib/graphql/dashboard/subscriptions.rb +97 -0
  55. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  56. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  57. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  58. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  59. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +24 -0
  60. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  61. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  62. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  63. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  64. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  65. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  66. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  67. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  68. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  69. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  70. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  71. data/lib/graphql/dashboard.rb +96 -0
  72. data/lib/graphql/dataloader/active_record_association_source.rb +84 -0
  73. data/lib/graphql/dataloader/active_record_source.rb +47 -0
  74. data/lib/graphql/dataloader/async_dataloader.rb +112 -0
  75. data/lib/graphql/dataloader/null_dataloader.rb +55 -10
  76. data/lib/graphql/dataloader/request.rb +5 -0
  77. data/lib/graphql/dataloader/source.rb +35 -12
  78. data/lib/graphql/dataloader.rb +224 -149
  79. data/lib/graphql/date_encoding_error.rb +1 -1
  80. data/lib/graphql/dig.rb +2 -1
  81. data/lib/graphql/duration_encoding_error.rb +16 -0
  82. data/lib/graphql/execution/interpreter/argument_value.rb +5 -1
  83. data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
  84. data/lib/graphql/execution/interpreter/resolve.rb +23 -25
  85. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +228 -0
  86. data/lib/graphql/execution/interpreter/runtime.rb +363 -434
  87. data/lib/graphql/execution/interpreter.rb +91 -164
  88. data/lib/graphql/execution/lookahead.rb +105 -31
  89. data/lib/graphql/execution/multiplex.rb +7 -6
  90. data/lib/graphql/execution/next/field_resolve_step.rb +711 -0
  91. data/lib/graphql/execution/next/load_argument_step.rb +60 -0
  92. data/lib/graphql/execution/next/prepare_object_step.rb +129 -0
  93. data/lib/graphql/execution/next/runner.rb +389 -0
  94. data/lib/graphql/execution/next/selections_step.rb +37 -0
  95. data/lib/graphql/execution/next.rb +70 -0
  96. data/lib/graphql/execution.rb +1 -0
  97. data/lib/graphql/execution_error.rb +13 -10
  98. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  99. data/lib/graphql/introspection/directive_type.rb +7 -3
  100. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  101. data/lib/graphql/introspection/entry_points.rb +20 -6
  102. data/lib/graphql/introspection/enum_value_type.rb +5 -5
  103. data/lib/graphql/introspection/field_type.rb +13 -5
  104. data/lib/graphql/introspection/input_value_type.rb +21 -13
  105. data/lib/graphql/introspection/schema_type.rb +8 -11
  106. data/lib/graphql/introspection/type_type.rb +64 -28
  107. data/lib/graphql/invalid_name_error.rb +1 -1
  108. data/lib/graphql/invalid_null_error.rb +26 -17
  109. data/lib/graphql/language/block_string.rb +34 -18
  110. data/lib/graphql/language/cache.rb +13 -0
  111. data/lib/graphql/language/comment.rb +18 -0
  112. data/lib/graphql/language/definition_slice.rb +1 -1
  113. data/lib/graphql/language/document_from_schema_definition.rb +90 -61
  114. data/lib/graphql/language/lexer.rb +319 -193
  115. data/lib/graphql/language/nodes.rb +136 -77
  116. data/lib/graphql/language/parser.rb +807 -1985
  117. data/lib/graphql/language/printer.rb +324 -151
  118. data/lib/graphql/language/sanitized_printer.rb +21 -23
  119. data/lib/graphql/language/static_visitor.rb +171 -0
  120. data/lib/graphql/language/visitor.rb +23 -83
  121. data/lib/graphql/language.rb +71 -1
  122. data/lib/graphql/load_application_object_failed_error.rb +5 -1
  123. data/lib/graphql/pagination/array_connection.rb +6 -6
  124. data/lib/graphql/pagination/connection.rb +30 -1
  125. data/lib/graphql/pagination/connections.rb +32 -0
  126. data/lib/graphql/pagination/mongoid_relation_connection.rb +1 -2
  127. data/lib/graphql/query/context/scoped_context.rb +101 -0
  128. data/lib/graphql/query/context.rb +82 -144
  129. data/lib/graphql/query/null_context.rb +15 -18
  130. data/lib/graphql/query/partial.rb +179 -0
  131. data/lib/graphql/query/validation_pipeline.rb +4 -4
  132. data/lib/graphql/query/variable_validation_error.rb +1 -1
  133. data/lib/graphql/query/variables.rb +3 -3
  134. data/lib/graphql/query.rb +126 -81
  135. data/lib/graphql/railtie.rb +16 -6
  136. data/lib/graphql/rake_task.rb +3 -12
  137. data/lib/graphql/rubocop/graphql/base_cop.rb +1 -1
  138. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +144 -0
  139. data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
  140. data/lib/graphql/rubocop.rb +2 -0
  141. data/lib/graphql/schema/addition.rb +26 -13
  142. data/lib/graphql/schema/always_visible.rb +7 -2
  143. data/lib/graphql/schema/argument.rb +75 -9
  144. data/lib/graphql/schema/base_64_encoder.rb +3 -5
  145. data/lib/graphql/schema/build_from_definition.rb +123 -60
  146. data/lib/graphql/schema/directive/flagged.rb +4 -2
  147. data/lib/graphql/schema/directive/one_of.rb +12 -0
  148. data/lib/graphql/schema/directive/specified_by.rb +14 -0
  149. data/lib/graphql/schema/directive.rb +54 -2
  150. data/lib/graphql/schema/enum.rb +110 -27
  151. data/lib/graphql/schema/enum_value.rb +10 -2
  152. data/lib/graphql/schema/field/connection_extension.rb +15 -49
  153. data/lib/graphql/schema/field/scope_extension.rb +23 -7
  154. data/lib/graphql/schema/field.rb +245 -118
  155. data/lib/graphql/schema/field_extension.rb +34 -1
  156. data/lib/graphql/schema/has_single_input_argument.rb +160 -0
  157. data/lib/graphql/schema/input_object.rb +116 -60
  158. data/lib/graphql/schema/interface.rb +34 -16
  159. data/lib/graphql/schema/introspection_system.rb +8 -17
  160. data/lib/graphql/schema/late_bound_type.rb +4 -0
  161. data/lib/graphql/schema/list.rb +3 -3
  162. data/lib/graphql/schema/loader.rb +3 -4
  163. data/lib/graphql/schema/member/base_dsl_methods.rb +18 -2
  164. data/lib/graphql/schema/member/has_arguments.rb +132 -100
  165. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  166. data/lib/graphql/schema/member/has_dataloader.rb +99 -0
  167. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  168. data/lib/graphql/schema/member/has_directives.rb +4 -4
  169. data/lib/graphql/schema/member/has_fields.rb +115 -15
  170. data/lib/graphql/schema/member/has_interfaces.rb +26 -12
  171. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  172. data/lib/graphql/schema/member/has_validators.rb +1 -1
  173. data/lib/graphql/schema/member/relay_shortcuts.rb +1 -1
  174. data/lib/graphql/schema/member/scoped.rb +19 -0
  175. data/lib/graphql/schema/member/type_system_helpers.rb +17 -4
  176. data/lib/graphql/schema/member/validates_input.rb +3 -3
  177. data/lib/graphql/schema/member.rb +6 -0
  178. data/lib/graphql/schema/mutation.rb +7 -0
  179. data/lib/graphql/schema/object.rb +34 -8
  180. data/lib/graphql/schema/printer.rb +9 -7
  181. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  182. data/lib/graphql/schema/relay_classic_mutation.rb +6 -129
  183. data/lib/graphql/schema/resolver.rb +90 -32
  184. data/lib/graphql/schema/scalar.rb +4 -9
  185. data/lib/graphql/schema/subscription.rb +63 -10
  186. data/lib/graphql/schema/timeout.rb +19 -2
  187. data/lib/graphql/schema/type_expression.rb +2 -2
  188. data/lib/graphql/schema/union.rb +2 -2
  189. data/lib/graphql/schema/unique_within_type.rb +1 -1
  190. data/lib/graphql/schema/validator/all_validator.rb +62 -0
  191. data/lib/graphql/schema/validator/required_validator.rb +92 -11
  192. data/lib/graphql/schema/validator.rb +3 -1
  193. data/lib/graphql/schema/visibility/migration.rb +188 -0
  194. data/lib/graphql/schema/visibility/profile.rb +445 -0
  195. data/lib/graphql/schema/visibility/visit.rb +190 -0
  196. data/lib/graphql/schema/visibility.rb +311 -0
  197. data/lib/graphql/schema/warden.rb +275 -103
  198. data/lib/graphql/schema.rb +950 -210
  199. data/lib/graphql/static_validation/all_rules.rb +3 -3
  200. data/lib/graphql/static_validation/base_visitor.rb +7 -6
  201. data/lib/graphql/static_validation/literal_validator.rb +6 -7
  202. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  203. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
  204. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +3 -2
  205. data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
  206. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
  207. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +12 -2
  208. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +47 -13
  209. data/lib/graphql/static_validation/rules/fields_will_merge.rb +88 -25
  210. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
  211. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  212. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +12 -2
  213. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  214. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  215. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  216. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  217. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  218. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +5 -5
  219. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +5 -5
  220. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  221. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +7 -3
  222. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  223. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  224. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
  225. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +11 -2
  226. data/lib/graphql/static_validation/validation_context.rb +21 -5
  227. data/lib/graphql/static_validation/validator.rb +9 -1
  228. data/lib/graphql/static_validation.rb +0 -1
  229. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +8 -5
  230. data/lib/graphql/subscriptions/broadcast_analyzer.rb +11 -5
  231. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  232. data/lib/graphql/subscriptions/event.rb +21 -4
  233. data/lib/graphql/subscriptions/serialize.rb +3 -1
  234. data/lib/graphql/subscriptions.rb +21 -17
  235. data/lib/graphql/testing/helpers.rb +161 -0
  236. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  237. data/lib/graphql/testing.rb +3 -0
  238. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  239. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  240. data/lib/graphql/tracing/appoptics_trace.rb +7 -3
  241. data/lib/graphql/tracing/appoptics_tracing.rb +9 -2
  242. data/lib/graphql/tracing/appsignal_trace.rb +32 -59
  243. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  244. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  245. data/lib/graphql/tracing/data_dog_trace.rb +46 -162
  246. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  247. data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
  248. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  249. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  250. data/lib/graphql/tracing/detailed_trace.rb +156 -0
  251. data/lib/graphql/tracing/legacy_hooks_trace.rb +75 -0
  252. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  253. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  254. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  255. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  256. data/lib/graphql/tracing/notifications_trace.rb +183 -37
  257. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  258. data/lib/graphql/tracing/null_trace.rb +9 -0
  259. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  260. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  261. data/lib/graphql/tracing/perfetto_trace.rb +864 -0
  262. data/lib/graphql/tracing/platform_tracing.rb +3 -1
  263. data/lib/graphql/tracing/{prometheus_tracing → prometheus_trace}/graphql_collector.rb +5 -1
  264. data/lib/graphql/tracing/prometheus_trace.rb +73 -73
  265. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  266. data/lib/graphql/tracing/scout_trace.rb +32 -58
  267. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  268. data/lib/graphql/tracing/sentry_trace.rb +82 -0
  269. data/lib/graphql/tracing/statsd_trace.rb +33 -45
  270. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  271. data/lib/graphql/tracing/trace.rb +112 -1
  272. data/lib/graphql/tracing.rb +31 -28
  273. data/lib/graphql/type_kinds.rb +2 -1
  274. data/lib/graphql/types/iso_8601_duration.rb +77 -0
  275. data/lib/graphql/types/relay/connection_behaviors.rb +44 -2
  276. data/lib/graphql/types/relay/edge_behaviors.rb +18 -0
  277. data/lib/graphql/types/relay/has_node_field.rb +13 -8
  278. data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
  279. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  280. data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
  281. data/lib/graphql/types.rb +18 -10
  282. data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
  283. data/lib/graphql/unauthorized_error.rb +5 -1
  284. data/lib/graphql/version.rb +1 -1
  285. data/lib/graphql.rb +71 -54
  286. data/readme.md +12 -2
  287. metadata +233 -37
  288. data/lib/graphql/analysis/ast/analyzer.rb +0 -84
  289. data/lib/graphql/analysis/ast/field_usage.rb +0 -57
  290. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -22
  291. data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
  292. data/lib/graphql/analysis/ast/query_complexity.rb +0 -230
  293. data/lib/graphql/analysis/ast/query_depth.rb +0 -55
  294. data/lib/graphql/analysis/ast/visitor.rb +0 -276
  295. data/lib/graphql/analysis/ast.rb +0 -81
  296. data/lib/graphql/backtrace/inspect_result.rb +0 -50
  297. data/lib/graphql/backtrace/trace.rb +0 -96
  298. data/lib/graphql/backtrace/tracer.rb +0 -80
  299. data/lib/graphql/deprecation.rb +0 -9
  300. data/lib/graphql/filter.rb +0 -59
  301. data/lib/graphql/language/parser.y +0 -560
  302. data/lib/graphql/language/token.rb +0 -34
  303. data/lib/graphql/schema/base_64_bp.rb +0 -26
  304. data/lib/graphql/schema/invalid_type_error.rb +0 -7
  305. data/lib/graphql/schema/null_mask.rb +0 -11
  306. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
  307. data/lib/graphql/static_validation/type_stack.rb +0 -216
  308. data/lib/graphql/subscriptions/instrumentation.rb +0 -28
@@ -0,0 +1,263 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Analysis
4
+ # Calculate the complexity of a query, using {Field#complexity} values.
5
+ class QueryComplexity < Analyzer
6
+ # State for the query complexity calculation:
7
+ # - `complexities_on_type` holds complexity scores for each type
8
+ def initialize(query)
9
+ super
10
+ @skip_introspection_fields = !query.schema.max_complexity_count_introspection_fields
11
+ @complexities_on_type_by_query = {}
12
+ end
13
+
14
+ # Override this method to use the complexity result
15
+ def result
16
+ case subject.schema.complexity_cost_calculation_mode_for(subject.context)
17
+ when :future
18
+ max_possible_complexity
19
+ when :legacy
20
+ max_possible_complexity(mode: :legacy)
21
+ when :compare
22
+ future_complexity = max_possible_complexity
23
+ legacy_complexity = max_possible_complexity(mode: :legacy)
24
+ if future_complexity != legacy_complexity
25
+ subject.schema.legacy_complexity_cost_calculation_mismatch(subject, future_complexity, legacy_complexity)
26
+ else
27
+ future_complexity
28
+ end
29
+ when nil
30
+ subject.logger.warn <<~GRAPHQL
31
+ GraphQL-Ruby's complexity cost system is getting some "breaking fixes" in a future version. See the migration notes at https://graphql-ruby.org/api-doc/#{GraphQL::VERSION}/GraphQL/Schema.html#complexity_cost_calculation_mode_for-class_method
32
+
33
+ To opt into the future behavior, configure your schema (#{subject.schema.name ? subject.schema.name : subject.schema.ancestors}) with:
34
+
35
+ complexity_cost_calculation_mode(:future) # or `:legacy`, `:compare`
36
+
37
+ GRAPHQL
38
+ max_possible_complexity(mode: :legacy)
39
+ else
40
+ raise ArgumentError, "Expected `:future`, `:legacy`, `:compare`, or `nil` from `#{query.schema}.complexity_cost_calculation_mode_for` but got: #{query.schema.complexity_cost_calculation_mode.inspect}"
41
+ end
42
+ end
43
+
44
+ # ScopedTypeComplexity models a tree of GraphQL types mapped to inner selections, ie:
45
+ # Hash<GraphQL::BaseType, Hash<String, ScopedTypeComplexity>>
46
+ class ScopedTypeComplexity < Hash
47
+ # A proc for defaulting empty namespace requests as a new scope hash.
48
+ DEFAULT_PROC = ->(h, k) { h[k] = {} }
49
+
50
+ attr_reader :field_definition, :response_path, :query
51
+
52
+ # @param parent_type [Class] The owner of `field_definition`
53
+ # @param field_definition [GraphQL::Field, GraphQL::Schema::Field] Used for getting the `.complexity` configuration
54
+ # @param query [GraphQL::Query] Used for `query.possible_types`
55
+ # @param response_path [Array<String>] The path to the response key for the field
56
+ # @return [Hash<GraphQL::BaseType, Hash<String, ScopedTypeComplexity>>]
57
+ def initialize(parent_type, field_definition, query, response_path)
58
+ super(&DEFAULT_PROC)
59
+ @parent_type = parent_type
60
+ @field_definition = field_definition
61
+ @query = query
62
+ @response_path = response_path
63
+ @nodes = []
64
+ end
65
+
66
+ # @return [Array<GraphQL::Language::Nodes::Field>]
67
+ attr_reader :nodes
68
+
69
+ def own_complexity(child_complexity)
70
+ @field_definition.calculate_complexity(query: @query, nodes: @nodes, child_complexity: child_complexity)
71
+ end
72
+
73
+ def composite?
74
+ !empty?
75
+ end
76
+ end
77
+
78
+ def on_enter_field(node, parent, visitor)
79
+ # We don't want to visit fragment definitions,
80
+ # we'll visit them when we hit the spreads instead
81
+ return if visitor.visiting_fragment_definition?
82
+ return if visitor.skipping?
83
+ return if @skip_introspection_fields && visitor.field_definition.introspection?
84
+ parent_type = visitor.parent_type_definition
85
+ field_key = node.alias || node.name
86
+
87
+ # Find or create a complexity scope stack for this query.
88
+ scopes_stack = @complexities_on_type_by_query[visitor.query] ||= [ScopedTypeComplexity.new(nil, nil, query, visitor.response_path)]
89
+
90
+ # Find or create the complexity costing node for this field.
91
+ scope = scopes_stack.last[parent_type][field_key] ||= ScopedTypeComplexity.new(parent_type, visitor.field_definition, visitor.query, visitor.response_path)
92
+ scope.nodes.push(node)
93
+ scopes_stack.push(scope)
94
+ end
95
+
96
+ def on_leave_field(node, parent, visitor)
97
+ # We don't want to visit fragment definitions,
98
+ # we'll visit them when we hit the spreads instead
99
+ return if visitor.visiting_fragment_definition?
100
+ return if visitor.skipping?
101
+ return if @skip_introspection_fields && visitor.field_definition.introspection?
102
+ scopes_stack = @complexities_on_type_by_query[visitor.query]
103
+ scopes_stack.pop
104
+ end
105
+
106
+ private
107
+
108
+ # @return [Integer]
109
+ def max_possible_complexity(mode: :future)
110
+ @complexities_on_type_by_query.reduce(0) do |total, (query, scopes_stack)|
111
+ total + merged_max_complexity_for_scopes(query, [scopes_stack.first], mode)
112
+ end
113
+ end
114
+
115
+ # @param query [GraphQL::Query] Used for `query.possible_types`
116
+ # @param scopes [Array<ScopedTypeComplexity>] Array of scoped type complexities
117
+ # @param mode [:future, :legacy]
118
+ # @return [Integer]
119
+ def merged_max_complexity_for_scopes(query, scopes, mode)
120
+ # Aggregate a set of all possible scope types encountered (scope keys).
121
+ # Use a hash, but ignore the values; it's just a fast way to work with the keys.
122
+ possible_scope_types = scopes.each_with_object({}) do |scope, memo|
123
+ memo.merge!(scope)
124
+ end
125
+
126
+ # Expand abstract scope types into their concrete implementations;
127
+ # overlapping abstracts coalesce through their intersecting types.
128
+ possible_scope_types.keys.each do |possible_scope_type|
129
+ next unless possible_scope_type.kind.abstract?
130
+
131
+ query.types.possible_types(possible_scope_type).each do |impl_type|
132
+ possible_scope_types[impl_type] ||= true
133
+ end
134
+ possible_scope_types.delete(possible_scope_type)
135
+ end
136
+
137
+ # Aggregate the lexical selections that may apply to each possible type,
138
+ # and then return the maximum cost among possible typed selections.
139
+ possible_scope_types.each_key.reduce(0) do |max, possible_scope_type|
140
+ # Collect inner selections from all scopes that intersect with this possible type.
141
+ all_inner_selections = scopes.each_with_object([]) do |scope, memo|
142
+ scope.each do |scope_type, inner_selections|
143
+ memo << inner_selections if types_intersect?(query, scope_type, possible_scope_type)
144
+ end
145
+ end
146
+
147
+ # Find the maximum complexity for the scope type among possible lexical branches.
148
+ complexity = case mode
149
+ when :legacy
150
+ legacy_merged_max_complexity(query, all_inner_selections)
151
+ when :future
152
+ merged_max_complexity(query, all_inner_selections)
153
+ else
154
+ raise ArgumentError, "Expected :legacy or :future, not: #{mode.inspect}"
155
+ end
156
+ complexity > max ? complexity : max
157
+ end
158
+ end
159
+
160
+ def types_intersect?(query, a, b)
161
+ return true if a == b
162
+ a_types = query.types.possible_types(a)
163
+ query.types.possible_types(b).any? { |t| a_types.include?(t) }
164
+ end
165
+
166
+ # A hook which is called whenever a field's max complexity is calculated.
167
+ # Override this method to capture individual field complexity details.
168
+ #
169
+ # @param scoped_type_complexity [ScopedTypeComplexity]
170
+ # @param max_complexity [Numeric] Field's maximum complexity including child complexity
171
+ # @param child_complexity [Numeric, nil] Field's child complexity
172
+ def field_complexity(scoped_type_complexity, max_complexity:, child_complexity: nil)
173
+ end
174
+
175
+ # @param inner_selections [Array<Hash<String, ScopedTypeComplexity>>] Field selections for a scope
176
+ # @return [Integer] Total complexity value for all these selections in the parent scope
177
+ def merged_max_complexity(query, inner_selections)
178
+ # Aggregate a set of all unique field selection keys across all scopes.
179
+ # Use a hash, but ignore the values; it's just a fast way to work with the keys.
180
+ unique_field_keys = inner_selections.each_with_object({}) do |inner_selection, memo|
181
+ memo.merge!(inner_selection)
182
+ end
183
+
184
+ # Add up the total cost for each unique field name's coalesced selections
185
+ unique_field_keys.each_key.reduce(0) do |total, field_key|
186
+ # Collect all child scopes for this field key;
187
+ # all keys come with at least one scope.
188
+ child_scopes = inner_selections.filter_map { _1[field_key] }
189
+
190
+ # Compute maximum possible cost of child selections;
191
+ # composites merge their maximums, while leaf scopes are always zero.
192
+ # FieldsWillMerge validation assures all scopes are uniformly composite or leaf.
193
+ maximum_children_cost = if child_scopes.any?(&:composite?)
194
+ merged_max_complexity_for_scopes(query, child_scopes, :future)
195
+ else
196
+ 0
197
+ end
198
+
199
+ # Identify the maximum cost and scope among possibilities
200
+ maximum_cost = 0
201
+ maximum_scope = child_scopes.reduce(child_scopes.last) do |max_scope, possible_scope|
202
+ scope_cost = possible_scope.own_complexity(maximum_children_cost)
203
+ if scope_cost > maximum_cost
204
+ maximum_cost = scope_cost
205
+ possible_scope
206
+ else
207
+ max_scope
208
+ end
209
+ end
210
+
211
+ field_complexity(
212
+ maximum_scope,
213
+ max_complexity: maximum_cost,
214
+ child_complexity: maximum_children_cost,
215
+ )
216
+
217
+ total + maximum_cost
218
+ end
219
+ end
220
+
221
+ def legacy_merged_max_complexity(query, inner_selections)
222
+ # Aggregate a set of all unique field selection keys across all scopes.
223
+ # Use a hash, but ignore the values; it's just a fast way to work with the keys.
224
+ unique_field_keys = inner_selections.each_with_object({}) do |inner_selection, memo|
225
+ memo.merge!(inner_selection)
226
+ end
227
+
228
+ # Add up the total cost for each unique field name's coalesced selections
229
+ unique_field_keys.each_key.reduce(0) do |total, field_key|
230
+ composite_scopes = nil
231
+ field_cost = 0
232
+
233
+ # Collect composite selection scopes for further aggregation,
234
+ # leaf selections report their costs directly.
235
+ inner_selections.each do |inner_selection|
236
+ child_scope = inner_selection[field_key]
237
+ next unless child_scope
238
+
239
+ # Empty child scopes are leaf nodes with zero child complexity.
240
+ if child_scope.empty?
241
+ field_cost = child_scope.own_complexity(0)
242
+ field_complexity(child_scope, max_complexity: field_cost, child_complexity: nil)
243
+ else
244
+ composite_scopes ||= []
245
+ composite_scopes << child_scope
246
+ end
247
+ end
248
+
249
+ if composite_scopes
250
+ child_complexity = merged_max_complexity_for_scopes(query, composite_scopes, :legacy)
251
+
252
+ # This is the last composite scope visited; assume it's representative (for backwards compatibility).
253
+ # Note: it would be more correct to score each composite scope and use the maximum possibility.
254
+ field_cost = composite_scopes.last.own_complexity(child_complexity)
255
+ field_complexity(composite_scopes.last, max_complexity: field_cost, child_complexity: child_complexity)
256
+ end
257
+
258
+ total + field_cost
259
+ end
260
+ end
261
+ end
262
+ end
263
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Analysis
4
+ # A query reducer for measuring the depth of a given query.
5
+ #
6
+ # See https://graphql-ruby.org/queries/ast_analysis.html for more examples.
7
+ #
8
+ # @example Logging the depth of a query
9
+ # class LogQueryDepth < GraphQL::Analysis::QueryDepth
10
+ # def result
11
+ # log("GraphQL query depth: #{@max_depth}")
12
+ # end
13
+ # end
14
+ #
15
+ # # In your Schema file:
16
+ #
17
+ # class MySchema < GraphQL::Schema
18
+ # query_analyzer LogQueryDepth
19
+ # end
20
+ #
21
+ # # When you run the query, the depth will get logged:
22
+ #
23
+ # Schema.execute(query_str)
24
+ # # GraphQL query depth: 8
25
+ #
26
+ class QueryDepth < Analyzer
27
+ def initialize(query)
28
+ @max_depth = 0
29
+ @current_depth = 0
30
+ @count_introspection_fields = query.schema.count_introspection_fields
31
+ super
32
+ end
33
+
34
+ def on_enter_field(node, parent, visitor)
35
+ return if visitor.skipping? ||
36
+ visitor.visiting_fragment_definition? ||
37
+ (@count_introspection_fields == false && visitor.field_definition.introspection?)
38
+
39
+ @current_depth += 1
40
+ end
41
+
42
+ def on_leave_field(node, parent, visitor)
43
+ return if visitor.skipping? ||
44
+ visitor.visiting_fragment_definition? ||
45
+ (@count_introspection_fields == false && visitor.field_definition.introspection?)
46
+
47
+ if @max_depth < @current_depth
48
+ @max_depth = @current_depth
49
+ end
50
+ @current_depth -= 1
51
+ end
52
+
53
+ def result
54
+ @max_depth
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,280 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Analysis
4
+ # Depth first traversal through a query AST, calling AST analyzers
5
+ # along the way.
6
+ #
7
+ # The visitor is a special case of GraphQL::Language::StaticVisitor, visiting
8
+ # only the selected operation, providing helpers for common use cases such
9
+ # as skipped fields and visiting fragment spreads.
10
+ #
11
+ # @see {GraphQL::Analysis::Analyzer} AST Analyzers for queries
12
+ class Visitor < GraphQL::Language::StaticVisitor
13
+ def initialize(query:, analyzers:, timeout:)
14
+ @analyzers = analyzers
15
+ @path = []
16
+ @object_types = []
17
+ @directives = []
18
+ @field_definitions = []
19
+ @argument_definitions = []
20
+ @directive_definitions = []
21
+ @rescued_errors = []
22
+ @query = query
23
+ @schema = query.schema
24
+ @types = query.types
25
+ @response_path = []
26
+ @skip_stack = [false]
27
+ @timeout_time = if timeout
28
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second) + timeout
29
+ else
30
+ Float::INFINITY
31
+ end
32
+ super(query.selected_operation)
33
+ end
34
+
35
+ # @return [GraphQL::Query] the query being visited
36
+ attr_reader :query
37
+
38
+ # @return [Array<GraphQL::ObjectType>] Types whose scope we've entered
39
+ attr_reader :object_types
40
+
41
+ # @return [Array<GraphQL::AnalysisError]
42
+ attr_reader :rescued_errors
43
+
44
+ def visit
45
+ return unless @document
46
+ super
47
+ end
48
+
49
+ # Visit Helpers
50
+
51
+ # @return [GraphQL::Execution::Interpreter::Arguments] Arguments for this node, merging default values, literal values and query variables
52
+ # @see {GraphQL::Query#arguments_for}
53
+ def arguments_for(ast_node, field_definition)
54
+ @query.arguments_for(ast_node, field_definition)
55
+ end
56
+
57
+ # @return [Boolean] If the visitor is currently inside a fragment definition
58
+ def visiting_fragment_definition?
59
+ @in_fragment_def
60
+ end
61
+
62
+ # @return [Boolean] If the current node should be skipped because of a skip or include directive
63
+ def skipping?
64
+ @skipping
65
+ end
66
+
67
+ # @return [Array<String>] The path to the response key for the current field
68
+ def response_path
69
+ @response_path.dup
70
+ end
71
+
72
+ # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
73
+ # Visitor Hooks
74
+ [
75
+ :operation_definition, :fragment_definition,
76
+ :inline_fragment, :field, :directive, :argument, :fragment_spread
77
+ ].each do |node_type|
78
+ module_eval <<-RUBY, __FILE__, __LINE__
79
+ def call_on_enter_#{node_type}(node, parent)
80
+ @analyzers.each do |a|
81
+ a.on_enter_#{node_type}(node, parent, self)
82
+ rescue AnalysisError => err
83
+ @rescued_errors << err
84
+ end
85
+ end
86
+
87
+ def call_on_leave_#{node_type}(node, parent)
88
+ @analyzers.each do |a|
89
+ a.on_leave_#{node_type}(node, parent, self)
90
+ rescue AnalysisError => err
91
+ @rescued_errors << err
92
+ end
93
+ end
94
+
95
+ RUBY
96
+ end
97
+ # rubocop:enable Development/NoEvalCop
98
+
99
+ def on_operation_definition(node, parent)
100
+ check_timeout
101
+ object_type = @schema.root_type_for_operation(node.operation_type)
102
+ @object_types.push(object_type)
103
+ @path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}")
104
+ call_on_enter_operation_definition(node, parent)
105
+ super
106
+ call_on_leave_operation_definition(node, parent)
107
+ @object_types.pop
108
+ @path.pop
109
+ end
110
+
111
+ def on_inline_fragment(node, parent)
112
+ check_timeout
113
+ object_type = if node.type
114
+ @types.type(node.type.name)
115
+ else
116
+ @object_types.last
117
+ end
118
+ @object_types.push(object_type)
119
+ @path.push("...#{node.type ? " on #{node.type.name}" : ""}")
120
+ @skipping = @skip_stack.last || skip?(node)
121
+ @skip_stack << @skipping
122
+ call_on_enter_inline_fragment(node, parent)
123
+ super
124
+ @skipping = @skip_stack.pop
125
+ call_on_leave_inline_fragment(node, parent)
126
+ @object_types.pop
127
+ @path.pop
128
+ end
129
+
130
+ def on_field(node, parent)
131
+ check_timeout
132
+ @response_path.push(node.alias || node.name)
133
+ parent_type = @object_types.last
134
+ # This could be nil if the previous field wasn't found:
135
+ field_definition = parent_type && @types.field(parent_type, node.name)
136
+ @field_definitions.push(field_definition)
137
+ if !field_definition.nil?
138
+ next_object_type = field_definition.type.unwrap
139
+ @object_types.push(next_object_type)
140
+ else
141
+ @object_types.push(nil)
142
+ end
143
+ @path.push(node.alias || node.name)
144
+
145
+ @skipping = @skip_stack.last || skip?(node)
146
+ @skip_stack << @skipping
147
+
148
+ call_on_enter_field(node, parent)
149
+ super
150
+ @skipping = @skip_stack.pop
151
+ call_on_leave_field(node, parent)
152
+ @response_path.pop
153
+ @field_definitions.pop
154
+ @object_types.pop
155
+ @path.pop
156
+ end
157
+
158
+ def on_directive(node, parent)
159
+ check_timeout
160
+ directive_defn = @schema.directives[node.name]
161
+ @directive_definitions.push(directive_defn)
162
+ call_on_enter_directive(node, parent)
163
+ super
164
+ call_on_leave_directive(node, parent)
165
+ @directive_definitions.pop
166
+ end
167
+
168
+ def on_argument(node, parent)
169
+ check_timeout
170
+ argument_defn = if (arg = @argument_definitions.last)
171
+ arg_type = arg.type.unwrap
172
+ if arg_type.kind.input_object?
173
+ @types.argument(arg_type, node.name)
174
+ else
175
+ nil
176
+ end
177
+ elsif (directive_defn = @directive_definitions.last)
178
+ @types.argument(directive_defn, node.name)
179
+ elsif (field_defn = @field_definitions.last)
180
+ @types.argument(field_defn, node.name)
181
+ else
182
+ nil
183
+ end
184
+
185
+ @argument_definitions.push(argument_defn)
186
+ @path.push(node.name)
187
+ call_on_enter_argument(node, parent)
188
+ super
189
+ call_on_leave_argument(node, parent)
190
+ @argument_definitions.pop
191
+ @path.pop
192
+ end
193
+
194
+ def on_fragment_spread(node, parent)
195
+ check_timeout
196
+ @path.push("... #{node.name}")
197
+ @skipping = @skip_stack.last || skip?(node)
198
+ @skip_stack << @skipping
199
+
200
+ call_on_enter_fragment_spread(node, parent)
201
+ enter_fragment_spread_inline(node)
202
+ super
203
+ @skipping = @skip_stack.pop
204
+ leave_fragment_spread_inline(node)
205
+ call_on_leave_fragment_spread(node, parent)
206
+ @path.pop
207
+ end
208
+
209
+ # @return [GraphQL::BaseType] The current object type
210
+ def type_definition
211
+ @object_types.last
212
+ end
213
+
214
+ # @return [GraphQL::BaseType] The type which the current type came from
215
+ def parent_type_definition
216
+ @object_types[-2]
217
+ end
218
+
219
+ # @return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one
220
+ def field_definition
221
+ @field_definitions.last
222
+ end
223
+
224
+ # @return [GraphQL::Field, nil] The GraphQL field which returned the object that the current field belongs to
225
+ def previous_field_definition
226
+ @field_definitions[-2]
227
+ end
228
+
229
+ # @return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one
230
+ def directive_definition
231
+ @directive_definitions.last
232
+ end
233
+
234
+ # @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one
235
+ def argument_definition
236
+ @argument_definitions.last
237
+ end
238
+
239
+ # @return [GraphQL::Argument, nil] The previous GraphQL argument
240
+ def previous_argument_definition
241
+ @argument_definitions[-2]
242
+ end
243
+
244
+ private
245
+
246
+ # Visit a fragment spread inline instead of visiting the definition
247
+ # by itself.
248
+ def enter_fragment_spread_inline(fragment_spread)
249
+ fragment_def = query.fragments[fragment_spread.name]
250
+
251
+ object_type = if fragment_def.type
252
+ @types.type(fragment_def.type.name)
253
+ else
254
+ object_types.last
255
+ end
256
+
257
+ object_types << object_type
258
+
259
+ on_fragment_definition_children(fragment_def)
260
+ end
261
+
262
+ # Visit a fragment spread inline instead of visiting the definition
263
+ # by itself.
264
+ def leave_fragment_spread_inline(_fragment_spread)
265
+ object_types.pop
266
+ end
267
+
268
+ def skip?(ast_node)
269
+ dir = ast_node.directives
270
+ !dir.empty? && !GraphQL::Execution::DirectiveChecks.include?(dir, query)
271
+ end
272
+
273
+ def check_timeout
274
+ if Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second) > @timeout_time
275
+ raise GraphQL::Analysis::TimeoutError
276
+ end
277
+ end
278
+ end
279
+ end
280
+ end
@@ -1,2 +1,96 @@
1
1
  # frozen_string_literal: true
2
- require "graphql/analysis/ast"
2
+ require "graphql/analysis/visitor"
3
+ require "graphql/analysis/analyzer"
4
+ require "graphql/analysis/field_usage"
5
+ require "graphql/analysis/query_complexity"
6
+ require "graphql/analysis/max_query_complexity"
7
+ require "graphql/analysis/query_depth"
8
+ require "graphql/analysis/max_query_depth"
9
+ module GraphQL
10
+ module Analysis
11
+ AST = self
12
+
13
+ class TimeoutError < AnalysisError
14
+ def initialize(...)
15
+ super("Timeout on validation of query")
16
+ end
17
+ end
18
+
19
+ module_function
20
+ # Analyze a multiplex, and all queries within.
21
+ # Multiplex analyzers are ran for all queries, keeping state.
22
+ # Query analyzers are ran per query, without carrying state between queries.
23
+ #
24
+ # @param multiplex [GraphQL::Execution::Multiplex]
25
+ # @param analyzers [Array<GraphQL::Analysis::Analyzer>]
26
+ # @return [Array<Any>] Results from multiplex analyzers
27
+ def analyze_multiplex(multiplex, analyzers)
28
+ multiplex_analyzers = analyzers.map { |analyzer| analyzer.new(multiplex) }
29
+
30
+ multiplex.current_trace.analyze_multiplex(multiplex: multiplex) do
31
+ query_results = multiplex.queries.map do |query|
32
+ if query.valid?
33
+ analyze_query(
34
+ query,
35
+ query.analyzers,
36
+ multiplex_analyzers: multiplex_analyzers
37
+ )
38
+ else
39
+ []
40
+ end
41
+ end
42
+
43
+ multiplex_results = multiplex_analyzers.map(&:result)
44
+ multiplex_errors = analysis_errors(multiplex_results)
45
+
46
+ multiplex.queries.each_with_index do |query, idx|
47
+ query.analysis_errors = multiplex_errors + analysis_errors(query_results[idx])
48
+ end
49
+ multiplex_results
50
+ end
51
+ end
52
+
53
+ # @param query [GraphQL::Query]
54
+ # @param analyzers [Array<GraphQL::Analysis::Analyzer>]
55
+ # @return [Array<Any>] Results from those analyzers
56
+ def analyze_query(query, analyzers, multiplex_analyzers: [])
57
+ query.current_trace.analyze_query(query: query) do
58
+ query_analyzers = analyzers
59
+ .map { |analyzer| analyzer.new(query) }
60
+ .tap { _1.select!(&:analyze?) }
61
+
62
+ analyzers_to_run = query_analyzers + multiplex_analyzers
63
+ if !analyzers_to_run.empty?
64
+
65
+ analyzers_to_run.select!(&:visit?)
66
+ if !analyzers_to_run.empty?
67
+ visitor = GraphQL::Analysis::Visitor.new(
68
+ query: query,
69
+ analyzers: analyzers_to_run,
70
+ timeout: query.validate_timeout_remaining,
71
+ )
72
+
73
+ visitor.visit
74
+
75
+ if !visitor.rescued_errors.empty?
76
+ return visitor.rescued_errors
77
+ end
78
+ end
79
+
80
+ query_analyzers.map(&:result)
81
+ else
82
+ []
83
+ end
84
+ end
85
+ rescue TimeoutError => err
86
+ [err]
87
+ rescue GraphQL::UnauthorizedError, GraphQL::ExecutionError
88
+ # This error was raised during analysis and will be returned the client before execution
89
+ []
90
+ end
91
+
92
+ def analysis_errors(results)
93
+ results.flatten.tap { _1.select! { |r| r.is_a?(GraphQL::AnalysisError) } }
94
+ end
95
+ end
96
+ end