graphql 1.13.24 → 2.5.11

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 (427) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install/mutation_root_generator.rb +2 -2
  3. data/lib/generators/graphql/install/templates/base_mutation.erb +2 -0
  4. data/lib/generators/graphql/install/templates/mutation_type.erb +2 -0
  5. data/lib/generators/graphql/install_generator.rb +50 -1
  6. data/lib/generators/graphql/mutation_delete_generator.rb +1 -1
  7. data/lib/generators/graphql/mutation_update_generator.rb +1 -1
  8. data/lib/generators/graphql/orm_mutations_base.rb +1 -1
  9. data/lib/generators/graphql/relay.rb +21 -18
  10. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  11. data/lib/generators/graphql/templates/base_connection.erb +2 -0
  12. data/lib/generators/graphql/templates/base_edge.erb +2 -0
  13. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  14. data/lib/generators/graphql/templates/base_field.erb +2 -0
  15. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  16. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  17. data/lib/generators/graphql/templates/base_object.erb +2 -0
  18. data/lib/generators/graphql/templates/base_resolver.erb +8 -0
  19. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  20. data/lib/generators/graphql/templates/base_union.erb +2 -0
  21. data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
  22. data/lib/generators/graphql/templates/loader.erb +2 -0
  23. data/lib/generators/graphql/templates/mutation.erb +2 -0
  24. data/lib/generators/graphql/templates/node_type.erb +2 -0
  25. data/lib/generators/graphql/templates/query_type.erb +2 -0
  26. data/lib/generators/graphql/templates/schema.erb +8 -0
  27. data/lib/generators/graphql/type_generator.rb +1 -1
  28. data/lib/graphql/analysis/analyzer.rb +90 -0
  29. data/lib/graphql/analysis/field_usage.rb +65 -28
  30. data/lib/graphql/analysis/max_query_complexity.rb +11 -17
  31. data/lib/graphql/analysis/max_query_depth.rb +13 -19
  32. data/lib/graphql/analysis/query_complexity.rb +236 -61
  33. data/lib/graphql/analysis/query_depth.rb +38 -23
  34. data/lib/graphql/analysis/visitor.rb +280 -0
  35. data/lib/graphql/analysis.rb +93 -6
  36. data/lib/graphql/autoload.rb +38 -0
  37. data/lib/graphql/backtrace/table.rb +118 -73
  38. data/lib/graphql/backtrace.rb +2 -25
  39. data/lib/graphql/coercion_error.rb +1 -9
  40. data/lib/graphql/current.rb +57 -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/limiters.rb +93 -0
  44. data/lib/graphql/dashboard/operation_store.rb +199 -0
  45. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  46. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  47. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  48. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  49. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  50. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  51. data/lib/graphql/dashboard/statics/icon.png +0 -0
  52. data/lib/graphql/dashboard/subscriptions.rb +96 -0
  53. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  54. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  55. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  56. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  57. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +23 -0
  58. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  59. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  60. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  61. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  62. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  63. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  64. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  65. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  66. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  67. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  68. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  69. data/lib/graphql/dashboard.rb +158 -0
  70. data/lib/graphql/dataloader/active_record_association_source.rb +84 -0
  71. data/lib/graphql/dataloader/active_record_source.rb +47 -0
  72. data/lib/graphql/dataloader/async_dataloader.rb +101 -0
  73. data/lib/graphql/dataloader/null_dataloader.rb +11 -2
  74. data/lib/graphql/dataloader/request.rb +5 -0
  75. data/lib/graphql/dataloader/source.rb +103 -47
  76. data/lib/graphql/dataloader.rb +174 -148
  77. data/lib/graphql/dig.rb +3 -2
  78. data/lib/graphql/duration_encoding_error.rb +16 -0
  79. data/lib/graphql/execution/errors.rb +12 -82
  80. data/lib/graphql/execution/interpreter/argument_value.rb +5 -1
  81. data/lib/graphql/execution/interpreter/arguments.rb +1 -1
  82. data/lib/graphql/execution/interpreter/arguments_cache.rb +30 -35
  83. data/lib/graphql/execution/interpreter/resolve.rb +32 -2
  84. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +215 -0
  85. data/lib/graphql/execution/interpreter/runtime.rb +525 -502
  86. data/lib/graphql/execution/interpreter.rb +127 -81
  87. data/lib/graphql/execution/lazy.rb +7 -21
  88. data/lib/graphql/execution/lookahead.rb +133 -55
  89. data/lib/graphql/execution/multiplex.rb +6 -176
  90. data/lib/graphql/execution.rb +11 -4
  91. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  92. data/lib/graphql/introspection/directive_type.rb +1 -1
  93. data/lib/graphql/introspection/dynamic_fields.rb +3 -8
  94. data/lib/graphql/introspection/entry_points.rb +10 -17
  95. data/lib/graphql/introspection/field_type.rb +1 -1
  96. data/lib/graphql/introspection/schema_type.rb +8 -11
  97. data/lib/graphql/introspection/type_type.rb +13 -6
  98. data/lib/graphql/introspection.rb +4 -3
  99. data/lib/graphql/invalid_name_error.rb +1 -1
  100. data/lib/graphql/invalid_null_error.rb +20 -17
  101. data/lib/graphql/language/block_string.rb +34 -18
  102. data/lib/graphql/language/cache.rb +13 -0
  103. data/lib/graphql/language/comment.rb +18 -0
  104. data/lib/graphql/language/definition_slice.rb +1 -1
  105. data/lib/graphql/language/document_from_schema_definition.rb +114 -80
  106. data/lib/graphql/language/lexer.rb +375 -1489
  107. data/lib/graphql/language/nodes.rb +189 -104
  108. data/lib/graphql/language/parser.rb +807 -1941
  109. data/lib/graphql/language/printer.rb +366 -163
  110. data/lib/graphql/language/sanitized_printer.rb +21 -23
  111. data/lib/graphql/language/static_visitor.rb +171 -0
  112. data/lib/graphql/language/visitor.rb +189 -138
  113. data/lib/graphql/language.rb +62 -1
  114. data/lib/graphql/load_application_object_failed_error.rb +5 -1
  115. data/lib/graphql/pagination/active_record_relation_connection.rb +0 -8
  116. data/lib/graphql/pagination/array_connection.rb +8 -6
  117. data/lib/graphql/pagination/connection.rb +61 -7
  118. data/lib/graphql/pagination/connections.rb +3 -28
  119. data/lib/graphql/pagination/mongoid_relation_connection.rb +1 -2
  120. data/lib/graphql/pagination/relation_connection.rb +2 -0
  121. data/lib/graphql/query/context/scoped_context.rb +101 -0
  122. data/lib/graphql/query/context.rb +131 -225
  123. data/lib/graphql/query/input_validation_result.rb +1 -1
  124. data/lib/graphql/query/null_context.rb +11 -33
  125. data/lib/graphql/query/partial.rb +179 -0
  126. data/lib/graphql/query/validation_pipeline.rb +14 -37
  127. data/lib/graphql/query/variable_validation_error.rb +1 -1
  128. data/lib/graphql/query/variables.rb +6 -19
  129. data/lib/graphql/query.rb +162 -98
  130. data/lib/graphql/railtie.rb +15 -109
  131. data/lib/graphql/rake_task/validate.rb +1 -1
  132. data/lib/graphql/rake_task.rb +30 -11
  133. data/lib/graphql/relay/range_add.rb +9 -20
  134. data/lib/graphql/relay.rb +0 -15
  135. data/lib/graphql/rubocop/graphql/base_cop.rb +1 -1
  136. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +144 -0
  137. data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
  138. data/lib/graphql/rubocop.rb +2 -0
  139. data/lib/graphql/schema/addition.rb +70 -33
  140. data/lib/graphql/schema/always_visible.rb +15 -0
  141. data/lib/graphql/schema/argument.rb +104 -59
  142. data/lib/graphql/schema/base_64_encoder.rb +3 -5
  143. data/lib/graphql/schema/build_from_definition.rb +154 -74
  144. data/lib/graphql/schema/directive/flagged.rb +4 -2
  145. data/lib/graphql/schema/directive/one_of.rb +24 -0
  146. data/lib/graphql/schema/directive/specified_by.rb +14 -0
  147. data/lib/graphql/schema/directive/transform.rb +1 -1
  148. data/lib/graphql/schema/directive.rb +47 -24
  149. data/lib/graphql/schema/enum.rb +137 -65
  150. data/lib/graphql/schema/enum_value.rb +11 -26
  151. data/lib/graphql/schema/field/connection_extension.rb +6 -16
  152. data/lib/graphql/schema/field/scope_extension.rb +8 -1
  153. data/lib/graphql/schema/field.rb +399 -404
  154. data/lib/graphql/schema/field_extension.rb +2 -5
  155. data/lib/graphql/schema/find_inherited_value.rb +2 -7
  156. data/lib/graphql/schema/has_single_input_argument.rb +160 -0
  157. data/lib/graphql/schema/input_object.rb +144 -99
  158. data/lib/graphql/schema/interface.rb +34 -51
  159. data/lib/graphql/schema/introspection_system.rb +12 -26
  160. data/lib/graphql/schema/late_bound_type.rb +12 -2
  161. data/lib/graphql/schema/list.rb +3 -9
  162. data/lib/graphql/schema/loader.rb +4 -6
  163. data/lib/graphql/schema/member/base_dsl_methods.rb +32 -18
  164. data/lib/graphql/schema/member/build_type.rb +15 -9
  165. data/lib/graphql/schema/member/has_arguments.rb +192 -96
  166. data/lib/graphql/schema/member/has_ast_node.rb +12 -0
  167. data/lib/graphql/schema/member/has_dataloader.rb +62 -0
  168. data/lib/graphql/schema/member/has_deprecation_reason.rb +18 -4
  169. data/lib/graphql/schema/member/has_directives.rb +81 -61
  170. data/lib/graphql/schema/member/has_fields.rb +119 -39
  171. data/lib/graphql/schema/member/has_interfaces.rb +66 -23
  172. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  173. data/lib/graphql/schema/member/has_validators.rb +32 -6
  174. data/lib/graphql/schema/member/relay_shortcuts.rb +47 -2
  175. data/lib/graphql/schema/member/scoped.rb +19 -0
  176. data/lib/graphql/schema/member/type_system_helpers.rb +32 -2
  177. data/lib/graphql/schema/member/validates_input.rb +4 -4
  178. data/lib/graphql/schema/member.rb +1 -6
  179. data/lib/graphql/schema/mutation.rb +7 -9
  180. data/lib/graphql/schema/non_null.rb +1 -7
  181. data/lib/graphql/schema/object.rb +42 -49
  182. data/lib/graphql/schema/printer.rb +12 -8
  183. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  184. data/lib/graphql/schema/relay_classic_mutation.rb +12 -124
  185. data/lib/graphql/schema/resolver/has_payload_type.rb +20 -10
  186. data/lib/graphql/schema/resolver.rb +96 -81
  187. data/lib/graphql/schema/scalar.rb +10 -30
  188. data/lib/graphql/schema/subscription.rb +60 -14
  189. data/lib/graphql/schema/timeout.rb +44 -31
  190. data/lib/graphql/schema/type_expression.rb +2 -2
  191. data/lib/graphql/schema/type_membership.rb +3 -0
  192. data/lib/graphql/schema/union.rb +12 -19
  193. data/lib/graphql/schema/unique_within_type.rb +1 -1
  194. data/lib/graphql/schema/validator/all_validator.rb +62 -0
  195. data/lib/graphql/schema/validator/required_validator.rb +60 -10
  196. data/lib/graphql/schema/validator.rb +5 -3
  197. data/lib/graphql/schema/visibility/migration.rb +188 -0
  198. data/lib/graphql/schema/visibility/profile.rb +445 -0
  199. data/lib/graphql/schema/visibility/visit.rb +190 -0
  200. data/lib/graphql/schema/visibility.rb +311 -0
  201. data/lib/graphql/schema/warden.rb +318 -94
  202. data/lib/graphql/schema/wrapper.rb +0 -5
  203. data/lib/graphql/schema.rb +1148 -1085
  204. data/lib/graphql/static_validation/all_rules.rb +4 -3
  205. data/lib/graphql/static_validation/base_visitor.rb +11 -27
  206. data/lib/graphql/static_validation/definition_dependencies.rb +7 -1
  207. data/lib/graphql/static_validation/error.rb +2 -2
  208. data/lib/graphql/static_validation/literal_validator.rb +24 -7
  209. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  210. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
  211. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +3 -2
  212. data/lib/graphql/static_validation/rules/directives_are_defined.rb +13 -7
  213. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +14 -12
  214. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +12 -2
  215. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +48 -6
  216. data/lib/graphql/static_validation/rules/fields_will_merge.rb +90 -27
  217. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
  218. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  219. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +12 -2
  220. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  221. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  222. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  223. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  224. data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid.rb +66 -0
  225. data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid_error.rb +29 -0
  226. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  227. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +5 -5
  228. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +5 -5
  229. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  230. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +19 -9
  231. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  232. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  233. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
  234. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +11 -2
  235. data/lib/graphql/static_validation/validation_context.rb +21 -5
  236. data/lib/graphql/static_validation/validator.rb +12 -26
  237. data/lib/graphql/static_validation.rb +0 -3
  238. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +14 -6
  239. data/lib/graphql/subscriptions/broadcast_analyzer.rb +11 -5
  240. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +40 -1
  241. data/lib/graphql/subscriptions/event.rb +24 -12
  242. data/lib/graphql/subscriptions/serialize.rb +3 -1
  243. data/lib/graphql/subscriptions.rb +48 -32
  244. data/lib/graphql/testing/helpers.rb +158 -0
  245. data/lib/graphql/testing.rb +2 -0
  246. data/lib/graphql/tracing/active_support_notifications_trace.rb +27 -0
  247. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  248. data/lib/graphql/tracing/appoptics_trace.rb +259 -0
  249. data/lib/graphql/tracing/appoptics_tracing.rb +9 -2
  250. data/lib/graphql/tracing/appsignal_trace.rb +54 -0
  251. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  252. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  253. data/lib/graphql/tracing/data_dog_trace.rb +71 -0
  254. data/lib/graphql/tracing/data_dog_tracing.rb +3 -0
  255. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  256. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  257. data/lib/graphql/tracing/detailed_trace.rb +93 -0
  258. data/lib/graphql/{execution/instrumentation.rb → tracing/legacy_hooks_trace.rb} +11 -28
  259. data/lib/graphql/tracing/legacy_trace.rb +12 -0
  260. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  261. data/lib/graphql/tracing/new_relic_trace.rb +68 -0
  262. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  263. data/lib/graphql/tracing/notifications_trace.rb +195 -0
  264. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  265. data/lib/graphql/tracing/null_trace.rb +9 -0
  266. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  267. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  268. data/lib/graphql/tracing/perfetto_trace.rb +734 -0
  269. data/lib/graphql/tracing/platform_trace.rb +123 -0
  270. data/lib/graphql/tracing/platform_tracing.rb +28 -41
  271. data/lib/graphql/tracing/{prometheus_tracing → prometheus_trace}/graphql_collector.rb +6 -2
  272. data/lib/graphql/tracing/prometheus_trace.rb +93 -0
  273. data/lib/graphql/tracing/prometheus_tracing.rb +5 -3
  274. data/lib/graphql/tracing/scout_trace.rb +49 -0
  275. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  276. data/lib/graphql/tracing/sentry_trace.rb +80 -0
  277. data/lib/graphql/tracing/statsd_trace.rb +48 -0
  278. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  279. data/lib/graphql/tracing/trace.rb +186 -0
  280. data/lib/graphql/tracing.rb +32 -52
  281. data/lib/graphql/type_kinds.rb +8 -4
  282. data/lib/graphql/types/iso_8601_date.rb +4 -1
  283. data/lib/graphql/types/iso_8601_date_time.rb +4 -0
  284. data/lib/graphql/types/iso_8601_duration.rb +77 -0
  285. data/lib/graphql/types/relay/base_connection.rb +16 -6
  286. data/lib/graphql/types/relay/connection_behaviors.rb +65 -23
  287. data/lib/graphql/types/relay/edge_behaviors.rb +33 -5
  288. data/lib/graphql/types/relay/node_behaviors.rb +12 -2
  289. data/lib/graphql/types/relay/page_info_behaviors.rb +11 -2
  290. data/lib/graphql/types/relay.rb +0 -3
  291. data/lib/graphql/types/string.rb +1 -1
  292. data/lib/graphql/types.rb +18 -10
  293. data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
  294. data/lib/graphql/version.rb +1 -1
  295. data/lib/graphql.rb +76 -123
  296. data/readme.md +13 -3
  297. metadata +225 -142
  298. data/lib/graphql/analysis/analyze_query.rb +0 -98
  299. data/lib/graphql/analysis/ast/analyzer.rb +0 -84
  300. data/lib/graphql/analysis/ast/field_usage.rb +0 -55
  301. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -23
  302. data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
  303. data/lib/graphql/analysis/ast/query_complexity.rb +0 -230
  304. data/lib/graphql/analysis/ast/query_depth.rb +0 -56
  305. data/lib/graphql/analysis/ast/visitor.rb +0 -269
  306. data/lib/graphql/analysis/ast.rb +0 -91
  307. data/lib/graphql/analysis/reducer_state.rb +0 -48
  308. data/lib/graphql/argument.rb +0 -131
  309. data/lib/graphql/authorization.rb +0 -82
  310. data/lib/graphql/backtrace/inspect_result.rb +0 -50
  311. data/lib/graphql/backtrace/legacy_tracer.rb +0 -56
  312. data/lib/graphql/backtrace/tracer.rb +0 -81
  313. data/lib/graphql/backwards_compatibility.rb +0 -61
  314. data/lib/graphql/base_type.rb +0 -232
  315. data/lib/graphql/boolean_type.rb +0 -2
  316. data/lib/graphql/compatibility/execution_specification/counter_schema.rb +0 -53
  317. data/lib/graphql/compatibility/execution_specification/specification_schema.rb +0 -200
  318. data/lib/graphql/compatibility/execution_specification.rb +0 -436
  319. data/lib/graphql/compatibility/lazy_execution_specification/lazy_schema.rb +0 -111
  320. data/lib/graphql/compatibility/lazy_execution_specification.rb +0 -215
  321. data/lib/graphql/compatibility/query_parser_specification/parse_error_specification.rb +0 -87
  322. data/lib/graphql/compatibility/query_parser_specification/query_assertions.rb +0 -79
  323. data/lib/graphql/compatibility/query_parser_specification.rb +0 -266
  324. data/lib/graphql/compatibility/schema_parser_specification.rb +0 -682
  325. data/lib/graphql/compatibility.rb +0 -5
  326. data/lib/graphql/define/assign_argument.rb +0 -12
  327. data/lib/graphql/define/assign_connection.rb +0 -13
  328. data/lib/graphql/define/assign_enum_value.rb +0 -18
  329. data/lib/graphql/define/assign_global_id_field.rb +0 -11
  330. data/lib/graphql/define/assign_mutation_function.rb +0 -34
  331. data/lib/graphql/define/assign_object_field.rb +0 -42
  332. data/lib/graphql/define/defined_object_proxy.rb +0 -53
  333. data/lib/graphql/define/instance_definable.rb +0 -255
  334. data/lib/graphql/define/no_definition_error.rb +0 -7
  335. data/lib/graphql/define/non_null_with_bang.rb +0 -16
  336. data/lib/graphql/define/type_definer.rb +0 -31
  337. data/lib/graphql/define.rb +0 -31
  338. data/lib/graphql/deprecated_dsl.rb +0 -55
  339. data/lib/graphql/deprecation.rb +0 -9
  340. data/lib/graphql/directive/deprecated_directive.rb +0 -2
  341. data/lib/graphql/directive/include_directive.rb +0 -2
  342. data/lib/graphql/directive/skip_directive.rb +0 -2
  343. data/lib/graphql/directive.rb +0 -107
  344. data/lib/graphql/enum_type.rb +0 -133
  345. data/lib/graphql/execution/execute.rb +0 -333
  346. data/lib/graphql/execution/flatten.rb +0 -40
  347. data/lib/graphql/execution/lazy/resolve.rb +0 -91
  348. data/lib/graphql/execution/typecast.rb +0 -50
  349. data/lib/graphql/field/resolve.rb +0 -59
  350. data/lib/graphql/field.rb +0 -226
  351. data/lib/graphql/filter.rb +0 -53
  352. data/lib/graphql/float_type.rb +0 -2
  353. data/lib/graphql/function.rb +0 -128
  354. data/lib/graphql/id_type.rb +0 -2
  355. data/lib/graphql/input_object_type.rb +0 -138
  356. data/lib/graphql/int_type.rb +0 -2
  357. data/lib/graphql/interface_type.rb +0 -72
  358. data/lib/graphql/internal_representation/document.rb +0 -27
  359. data/lib/graphql/internal_representation/node.rb +0 -206
  360. data/lib/graphql/internal_representation/print.rb +0 -51
  361. data/lib/graphql/internal_representation/rewrite.rb +0 -184
  362. data/lib/graphql/internal_representation/scope.rb +0 -88
  363. data/lib/graphql/internal_representation/visit.rb +0 -36
  364. data/lib/graphql/internal_representation.rb +0 -7
  365. data/lib/graphql/language/lexer.rl +0 -260
  366. data/lib/graphql/language/parser.y +0 -550
  367. data/lib/graphql/language/token.rb +0 -34
  368. data/lib/graphql/list_type.rb +0 -80
  369. data/lib/graphql/non_null_type.rb +0 -71
  370. data/lib/graphql/object_type.rb +0 -130
  371. data/lib/graphql/query/arguments.rb +0 -189
  372. data/lib/graphql/query/arguments_cache.rb +0 -24
  373. data/lib/graphql/query/executor.rb +0 -52
  374. data/lib/graphql/query/literal_input.rb +0 -136
  375. data/lib/graphql/query/serial_execution/field_resolution.rb +0 -92
  376. data/lib/graphql/query/serial_execution/operation_resolution.rb +0 -19
  377. data/lib/graphql/query/serial_execution/selection_resolution.rb +0 -23
  378. data/lib/graphql/query/serial_execution/value_resolution.rb +0 -87
  379. data/lib/graphql/query/serial_execution.rb +0 -40
  380. data/lib/graphql/relay/array_connection.rb +0 -83
  381. data/lib/graphql/relay/base_connection.rb +0 -189
  382. data/lib/graphql/relay/connection_instrumentation.rb +0 -54
  383. data/lib/graphql/relay/connection_resolve.rb +0 -43
  384. data/lib/graphql/relay/connection_type.rb +0 -54
  385. data/lib/graphql/relay/edge.rb +0 -27
  386. data/lib/graphql/relay/edge_type.rb +0 -19
  387. data/lib/graphql/relay/edges_instrumentation.rb +0 -39
  388. data/lib/graphql/relay/global_id_resolve.rb +0 -17
  389. data/lib/graphql/relay/mongo_relation_connection.rb +0 -50
  390. data/lib/graphql/relay/mutation/instrumentation.rb +0 -23
  391. data/lib/graphql/relay/mutation/resolve.rb +0 -56
  392. data/lib/graphql/relay/mutation/result.rb +0 -38
  393. data/lib/graphql/relay/mutation.rb +0 -106
  394. data/lib/graphql/relay/node.rb +0 -39
  395. data/lib/graphql/relay/page_info.rb +0 -7
  396. data/lib/graphql/relay/relation_connection.rb +0 -188
  397. data/lib/graphql/relay/type_extensions.rb +0 -32
  398. data/lib/graphql/scalar_type.rb +0 -91
  399. data/lib/graphql/schema/base_64_bp.rb +0 -26
  400. data/lib/graphql/schema/catchall_middleware.rb +0 -35
  401. data/lib/graphql/schema/default_parse_error.rb +0 -10
  402. data/lib/graphql/schema/default_type_error.rb +0 -17
  403. data/lib/graphql/schema/invalid_type_error.rb +0 -7
  404. data/lib/graphql/schema/member/accepts_definition.rb +0 -164
  405. data/lib/graphql/schema/member/cached_graphql_definition.rb +0 -58
  406. data/lib/graphql/schema/member/instrumentation.rb +0 -131
  407. data/lib/graphql/schema/middleware_chain.rb +0 -82
  408. data/lib/graphql/schema/null_mask.rb +0 -11
  409. data/lib/graphql/schema/possible_types.rb +0 -44
  410. data/lib/graphql/schema/rescue_middleware.rb +0 -60
  411. data/lib/graphql/schema/timeout_middleware.rb +0 -88
  412. data/lib/graphql/schema/traversal.rb +0 -228
  413. data/lib/graphql/schema/validation.rb +0 -313
  414. data/lib/graphql/static_validation/default_visitor.rb +0 -15
  415. data/lib/graphql/static_validation/no_validate_visitor.rb +0 -10
  416. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
  417. data/lib/graphql/static_validation/type_stack.rb +0 -216
  418. data/lib/graphql/string_type.rb +0 -2
  419. data/lib/graphql/subscriptions/instrumentation.rb +0 -79
  420. data/lib/graphql/subscriptions/subscription_root.rb +0 -76
  421. data/lib/graphql/tracing/skylight_tracing.rb +0 -70
  422. data/lib/graphql/types/relay/default_relay.rb +0 -31
  423. data/lib/graphql/types/relay/node_field.rb +0 -24
  424. data/lib/graphql/types/relay/nodes_field.rb +0 -43
  425. data/lib/graphql/union_type.rb +0 -115
  426. data/lib/graphql/upgrader/member.rb +0 -937
  427. data/lib/graphql/upgrader/schema.rb +0 -38
@@ -2,85 +2,260 @@
2
2
  module GraphQL
3
3
  module Analysis
4
4
  # Calculate the complexity of a query, using {Field#complexity} values.
5
- #
6
- # @example Log the complexity of incoming queries
7
- # MySchema.query_analyzers << GraphQL::Analysis::QueryComplexity.new do |query, complexity|
8
- # Rails.logger.info("Complexity: #{complexity}")
9
- # end
10
- #
11
- class QueryComplexity
12
- # @yield [query, complexity] Called for each query analyzed by the schema, before executing it
13
- # @yieldparam query [GraphQL::Query] The query that was analyzed
14
- # @yieldparam complexity [Numeric] The complexity for this query
15
- def initialize(&block)
16
- @complexity_handler = block
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 = {}
17
12
  end
18
13
 
19
- # State for the query complexity calcuation:
20
- # - `target` is passed to handler
21
- # - `complexities_on_type` holds complexity scores for each type in an IRep node
22
- def initial_value(target)
23
- {
24
- target: target,
25
- complexities_on_type: [TypeComplexity.new],
26
- }
27
- end
28
-
29
- # Implement the query analyzer API
30
- def call(memo, visit_type, irep_node)
31
- if irep_node.ast_node.is_a?(GraphQL::Language::Nodes::Field)
32
- if visit_type == :enter
33
- memo[:complexities_on_type].push(TypeComplexity.new)
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)
34
26
  else
35
- type_complexities = memo[:complexities_on_type].pop
36
- child_complexity = type_complexities.max_possible_complexity
37
- own_complexity = get_complexity(irep_node, child_complexity)
38
- memo[:complexities_on_type].last.merge(irep_node.owner_type, own_complexity)
27
+ future_complexity
39
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?
40
75
  end
41
- memo
42
76
  end
43
77
 
44
- # Send the query and complexity to the block
45
- # @return [Object, GraphQL::AnalysisError] Whatever the handler returns
46
- def final_value(reduced_value)
47
- total_complexity = reduced_value[:complexities_on_type].last.max_possible_complexity
48
- @complexity_handler.call(reduced_value[:target], total_complexity)
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
49
104
  end
50
105
 
51
106
  private
52
107
 
53
- # Get a complexity value for a field,
54
- # by getting the number or calling its proc
55
- def get_complexity(irep_node, child_complexity)
56
- field_defn = irep_node.definition
57
- defined_complexity = field_defn.complexity
58
- case defined_complexity
59
- when Proc
60
- defined_complexity.call(irep_node.query.context, irep_node.arguments, child_complexity)
61
- when Numeric
62
- defined_complexity + (child_complexity || 0)
63
- else
64
- raise("Invalid complexity: #{defined_complexity.inspect} on #{field_defn.name}")
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)
65
112
  end
66
113
  end
67
114
 
68
- # Selections on an object may apply differently depending on what is _actually_ returned by the resolve function.
69
- # Find the maximum possible complexity among those combinations.
70
- class TypeComplexity
71
- def initialize
72
- @types = Hash.new(0)
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)
73
124
  end
74
125
 
75
- # Return the max possible complexity for types in this selection
76
- def max_possible_complexity
77
- @types.each_value.max || 0
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)
78
135
  end
79
136
 
80
- # Store the complexity for the branch on `type_defn`.
81
- # Later we will see if this is the max complexity among branches.
82
- def merge(type_defn, complexity)
83
- @types[type_defn] += complexity
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
84
259
  end
85
260
  end
86
261
  end
@@ -3,40 +3,55 @@ module GraphQL
3
3
  module Analysis
4
4
  # A query reducer for measuring the depth of a given query.
5
5
  #
6
+ # See https://graphql-ruby.org/queries/ast_analysis.html for more examples.
7
+ #
6
8
  # @example Logging the depth of a query
7
- # Schema.query_analyzers << GraphQL::Analysis::QueryDepth.new { |query, depth| puts "GraphQL query depth: #{depth}" }
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
+ #
8
23
  # Schema.execute(query_str)
9
24
  # # GraphQL query depth: 8
10
25
  #
11
- class QueryDepth
12
- def initialize(&block)
13
- @depth_handler = block
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
14
32
  end
15
33
 
16
- def initial_value(query)
17
- {
18
- max_depth: 0,
19
- current_depth: 0,
20
- query: query,
21
- }
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
22
40
  end
23
41
 
24
- def call(memo, visit_type, irep_node)
25
- if irep_node.ast_node.is_a?(GraphQL::Language::Nodes::Field)
26
- if visit_type == :enter
27
- memo[:current_depth] += 1
28
- else
29
- if memo[:max_depth] < memo[:current_depth]
30
- memo[:max_depth] = memo[:current_depth]
31
- end
32
- memo[:current_depth] -= 1
33
- end
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
34
49
  end
35
- memo
50
+ @current_depth -= 1
36
51
  end
37
52
 
38
- def final_value(memo)
39
- @depth_handler.call(memo[:query], memo[:max_depth])
53
+ def result
54
+ @max_depth
40
55
  end
41
56
  end
42
57
  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